The Toybox
Widgets are the decorations and toys, the cat, the snow, the counters. They are JavaScript, individually loadable, and strictly optional: a retrostrap page with zero widgets is still a retrostrap page. Load the core script and the toybox, then declare what you want on any element:
<script defer src="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/retrostrap.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/retrostrap@0.1.0/dist/retrostrap-toybox.min.js"></script>
<body data-rs-widgets="snowfall neko"
data-rs-neko-behavior="patrol" data-rs-neko-target="#masthead">
Every widget honors the ten-point Toybox contract: budgeted, pausable when the tab is
hidden, invisible to assistive tech, and silent under prefers-reduced-motion
if it's decorative. There are no live specimens on this page, a page full of snow and
cats would bury the documentation. Load the toybox and try them on a page of your own.
clock: Clock
The JavaScript clock tucked into the corner of the 'enhanced' homepage.
An LED clock, refreshed once a second.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
format |
12 · 24 | 24 |
|
seconds |
bool | true |
|
label |
string | - | a caption under the digits |
Accessibility: The visible digits are aria-hidden; a screen reader reads an sr-only <time>, updated once a minute, not every tick. A changing digit is content, not motion, so reduced motion changes nothing.
<span data-rs-widgets="clock" data-rs-clock-format="12" data-rs-clock-label="server time"></span>
countdown: Countdown
"Only 47 days until the year 2000!" on a million homepages.
An LED countdown to a target date and time.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
to |
datetime | - | ISO datetime; required |
done |
string | - | message shown at zero |
Events: rs:countdown:done
Accessibility: The digits are aria-hidden; a screen reader hears an sr-only remaining-time line updated once a minute. A changing digit is content, so reduced motion keeps the count.
<span data-rs-widgets="countdown" data-rs-countdown-to="2000-01-01T00:00:00Z"></span>
crt: CRT overlay
The look of a page seen through a tube monitor at 2 a.m.
Scanlines and a vignette, with an optional (off-by-default) flicker.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
vignette |
bool | true |
|
flicker |
bool | false |
capped at 2 changes/sec, tiny opacity delta; force-disabled under reduced motion |
Accessibility: Decoration: aria-hidden, pointer-events:none. Flicker stays far below the photosensitivity threshold and never runs under prefers-reduced-motion.
<body data-rs-widgets="crt">
cursor-trail: Cursor trail
The DHTML trail scripts and Comet Cursor of the late-90s 'enhanced' homepage.
A little pool of sprites that chase your pointer.
decorative pointer only
| Option | Type | Default | Notes |
|---|---|---|---|
variant |
dots · stars · sparkle · hearts · bubbles · image | stars |
|
count |
int | 8 |
max 16 |
size |
px | 12 |
max 24 |
color |
color | - | legal palette; default theme accent |
image |
url | - | for variant=image |
Accessibility: Decoration: the layer is aria-hidden and pointer-events:none. Never spawns under prefers-reduced-motion, and no-ops on touch.
<!-- add the toybox script, then declare the widget on any element --> <body data-rs-widgets="cursor-trail" data-rs-cursor-trail-variant="stars" data-rs-cursor-trail-count="10">
dvd: Bouncing logo
The DVD screensaver logo that hypnotized a generation.
A logo that bounces around the viewport, waiting for the corner hit.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
target |
selector | - | an element to bounce; otherwise a default chip |
text |
string | - | text for the default chip |
speed |
slow · normal · fast | normal |
Events: rs:dvd:corner
Accessibility: Decoration: aria-hidden, pointer-events:none. Under prefers-reduced-motion it parks in a corner and holds still.
<body data-rs-widgets="dvd">
fortune: Fortune floppy
The fortune cookie of the command line, pinned to a homepage.
Shows an old-web fortune; click for another.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
list |
json-array | - | custom fortunes via an inline application/json block, not a data attribute |
Events: rs:fortune:next
Accessibility: Plain text and a real button. The pick is seeded off the clock, so no Math.random.
<div class="rs-panel" data-rs-widgets="fortune"></div>
guestbook: Guestbook
The comment section's gentle ancestor: a form, a list, and social obligation.
Renders guestbook entries and wires the sign form.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
src |
url | - | endpoint or JSON file; or an inline <script type=application/json> |
Events: rs:content
Accessibility: Every entry renders as text nodes only, injected markup shows as literal text, never runs. A missing endpoint shows a friendly note. Honeypot and minimum-time traps guard an existing form.
<section data-rs-widgets="guestbook" data-rs-guestbook-src="guestbook.json"> <form action="mailto:me@example.com" method="post">…the rs- form fields…</form> </section>
hit-counter: Hit counter
The CGI counter that proved your page had visitors, in a pre-analytics world.
The odometer visitor counter: static, local, or api-backed.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
mode |
static · local · api | static |
|
value |
int | 1337 |
for static mode |
src |
url | - | for api mode; POST returns { count } |
digits |
int | 6 |
zero-pad width; the label line is your own rs-counter__label markup |
Accessibility: Announced once to a screen reader as ordinary text, never on a tick. The api contract counts hits, never people, no identifiers stored (Decency Law).
You are visitor <span data-rs-widgets="hit-counter" data-rs-hit-counter-mode="local"></span>
jukebox: Jukebox
The MIDI player tucked in the corner, but this one waits for your click.
A playlist audio player in window chrome. Never autoplays.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
tracks |
json-array | - | each { title, src }; attribute JSON or an inline JSON block |
loop |
bool | false |
|
src |
url | - | a fallback audio URL: plays when a track has no src, or alone as a one-track player; never fetched as a playlist |
Events: rs:jukebox:track
Accessibility: NEVER autoplays, there is no autoplay option (Decency Law). Fully keyboard-operable; the fake EQ is aria-hidden and holds still under reduced motion.
<div data-rs-widgets="jukebox">
<script type="application/json">{"tracks":[{"title":"Hangar Bay","src":"audio/hangar.ogg"}]}</script>
</div>
konami: Konami code
Up up down down left right left right B A: the most-typed secret on Earth.
The cheat code, and a 30-second party mode.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
href |
url | - | navigate to a secret page on the code, instead of party mode |
words |
string | - | comma-separated secret words that also trigger the egg when typed |
Accessibility: Loading the widget adds Retrostrap.konami.on(seq, fn) and .trigger(). Party mode composes only non-flashing widgets and stays in budget; under reduced motion it declines with a small note. A ten-tap gesture is the touch alternative.
<body data-rs-widgets="konami"> <!-- or give it a secret page: data-rs-konami-href="secret.html" -->
kugeln: Kugeln
The spinning-ball DHTML cursor toys of around 2000, coloured orbs that chased the mouse across a homepage.
Shiny 3D spheres that trail or orbit your pointer, in a rainbow of colours.
decorative pointer only
| Option | Type | Default | Notes |
|---|---|---|---|
colors |
rainbow · red · gold · green · cyan · blue · purple · pink · silver | rainbow |
|
count |
int | 7 |
max 12 |
size |
px | 20 |
8-32 |
mode |
trail · orbit | trail |
Accessibility: Decoration: the layer is aria-hidden and pointer-events:none. Never spawns under prefers-reduced-motion, and no-ops on touch.
<!-- add the toybox script, then declare the widget on any element --> <!-- colors: rainbow (default) or one of red gold green cyan blue purple pink silver --> <body data-rs-widgets="kugeln" data-rs-kugeln-colors="rainbow" data-rs-kugeln-count="7" data-rs-kugeln-mode="trail">
last-updated: Last updated
"Last updated: 04/17/1998", the proudest line on any homepage.
Prints the document's last-modified date, era-style.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
prefix |
string | Last updated: |
|
date |
string | - | pin this date verbatim instead of the file's last-modified time |
format |
long · short | long |
|
locale |
string | - | defaults to the page lang |
Accessibility: Static text; nothing to rule on. Hides itself if the browser reports a garbage date.
<p class="rs-footer__updated" data-rs-widgets="last-updated"></p>
neko: Neko (Gif the cat)
The desktop-cat lineage (Neko/oneko), on the web since the late 90s. Gif is our own, drawn from a blank grid.
A pixel cat that chases your cursor, patrols an element edge, or dozes.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
skin |
gif · calico · void | gif |
|
behavior |
chase · patrol · sleepy | chase |
|
target |
selector | - | the element to patrol the top edge of |
speed |
slow · normal · fast | normal |
|
src |
string | - | override the sprite sheet URL (defaults to the skin's) |
Events: rs:neko:sleep, rs:neko:wake
Accessibility: Decoration: aria-hidden, pointer-events:none. Under prefers-reduced-motion the cat stays, sitting still. Chase falls back to patrol on touch. Two cats per page, maximum.
<!-- Gif patrols the top edge of the masthead -->
<body data-rs-widgets="neko" data-rs-neko-skin="gif"
data-rs-neko-behavior="patrol" data-rs-neko-target="#masthead">
pixel-pet: Pixel pet
The virtual pets of the early-2000s web, a creature that lived in the corner of your page.
A little desktop pet in the corner that you feed, and that remembers you between visits.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
name |
string | Blobby |
|
color |
green · blue · pink · gold | green |
|
pace |
int | - | seconds until fully hungry; default 300 |
Events: rs:pixel-pet:fed, rs:pixel-pet:gold
Accessibility: The pet is a labelled button you can feed with the keyboard; its mood is shown as text. State persists in localStorage. Appears (static) under prefers-reduced-motion.
<!-- add the toybox script, then declare the pet anywhere; it lives in a corner --> <!-- color: green (default) · blue · pink · gold --> <body data-rs-widgets="pixel-pet" data-rs-pixel-pet-name="Blobby" data-rs-pixel-pet-color="green">
smilies: Smilies
The forum smiley pack, type the code, see the face, running since the phpBB age.
Turns :) and friends into pixel emoticons.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
scope |
selector | - | the subtree to parse; defaults to the host |
src |
string | - | override the smilies pack image URL (same-origin) |
Accessibility: Each image's alt is the original typed code, so copy-paste and screen readers round-trip. The parser skips code, kbd, inputs, and any [data-rs-no-smilies] region.
<!-- codes in this element become pixel faces: :) ;) :D --> <div data-rs-widgets="smilies">Welcome!! :) Sign my guestbook ;)</div>
snowfall: Snowfall
The JavaScript snow script every winter homepage installed in December.
Falling snow (or hearts, or confetti) on a canvas.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
density |
light · normal · blizzard | normal |
or a number, capped at 100 |
glyph |
star · heart · leaf · confetti | - | default is a pixel flake |
speed |
slow · normal · fast | normal |
|
wind |
number | 0 |
-2 to 2 |
melt |
bool | true |
Accessibility: Decoration: canvas is aria-hidden, pointer-events:none. Never mounts under prefers-reduced-motion; the shared loop pauses when the tab is hidden.
<body data-rs-widgets="snowfall" data-rs-snowfall-density="light">
sparkle: Sparkle
The glitter-graphics and sparkle-trail scene of the kawaii and shrine pages.
Twinkles at the pointer, or ambient glitter on a zone.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
mode |
pointer · ambient · both | pointer |
|
trigger |
move · click | move |
|
palette |
gold · ice · rainbow · theme | theme |
|
color |
color | - | one legal color; overrides palette |
zone |
selector | - | ambient area; defaults to the host |
Accessibility: Decoration: aria-hidden, pointer-events:none, silent under prefers-reduced-motion.
<body data-rs-widgets="sparkle" data-rs-sparkle-mode="both" data-rs-sparkle-palette="gold">
starfield: Starfield
The flying-through-space screensaver, reborn as a page background.
Warp-speed stars on a canvas.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
density |
sparse · normal · dense | normal |
or a number, capped at 200 |
speed |
slow · normal · fast | normal |
|
layers |
int | 2 |
1 to 3 parallax layers |
Accessibility: Decoration: aria-hidden, pointer-events:none. Under prefers-reduced-motion it becomes a still sprinkle of stars, the look without the flight.
<body data-rs-widgets="starfield">
ticker: Ticker
The window.status scrollers that welcomed you to a thousand homepages.
A scrolling status-bar text ticker.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
messages |
json-array | - | the lines to scroll |
source |
selector | - | read <li>s from this element instead |
speed |
slow · normal · fast | normal |
|
dock |
bottom · none | none |
|
separator |
string | +++ |
Accessibility: Informative: the messages are ordinary readable text (never aria-live). Pauses on hover and focus. Under prefers-reduced-motion it swaps the message every 5s instead of scrolling.
<p class="rs-statusbar" data-rs-widgets="ticker" data-rs-ticker-messages='["Welcome to my page!!","Sign my guestbook"]'></p>
transitions: Page transitions
The PowerPoint-meets-Internet-Explorer scene change, tamed.
Page-to-page wipes via the View Transitions API.
decorative
| Option | Type | Default | Notes |
|---|---|---|---|
style |
wipe · box-in · box-out · dissolve · checker | wipe |
Accessibility: Pure progressive enhancement: where the API is unsupported, navigation is instant and unbroken. The API honors reduced motion on its own; the widget also skips under it.
<body data-rs-widgets="transitions">
webring: Webring
Sites holding hands in a circle, curated by a ringmaster.
Fills an rs-webring-bar from a ring's JSON, computing prev/random/next.
informative
| Option | Type | Default | Notes |
|---|---|---|---|
src |
url | - | the ring JSON; or an inline <script type=application/json> |
Accessibility: Plain links, fully functional as static HTML. Works from an inline ring with no service at all.
<div class="rs-webring-bar" data-rs-widgets="webring" data-rs-webring-src="ring.json"></div>
windows: Window manager
The draggable fake-window scripts of the DHTML era.
Makes rs-window elements draggable, with click-to-front (or dispatch rs:window:raise) and a five-window cap.
informative
Events: rs:window:focus
Accessibility: Drag by the titlebar with mouse, touch, or the keyboard (focus the titlebar, arrow to move, Shift for fine steps, Home to reset). Dragging is disabled below the svga breakpoint, where windows render in flow. Five windows maximum.
<body data-rs-widgets="windows"> <div class="rs-window" data-rs-window="floating">…titlebar + body…</div>