Yodacon Ship Pack · Tutorial Note 9 · October 2026
Spinning Without Script: How the CSS-Only Example Uses the Pack
Abstract—The CSS-only example animates pack ships with no JavaScript: two steps(6) animations, one per background axis, walk the 6 × 6 sheet in frame order, and mod(), round() and :has() hold any heading chosen by a radio button. This note derives the frame sequence and follows the page's stylesheet.
Index Terms—CSS animation, steps(), CSS math functions, :has(), sprite sheets.
I. Introduction
The CSS example has no script at all. Eleven ships spin in a hangar, and a row of radio buttons stops them
all at one heading. Everything is in the page's stylesheet, examples/css/index.html, and in one
style attribute per ship. It is the form to use where JavaScript is unwelcome: an e-mail-safe
page, a README preview, a loading screen.
II. Assets Used
| Asset | Where | Used for |
|---|---|---|
sheet-alpha.png ×11 | index.html:104 | Each ship's background |
ship.json (copied) | index.html:104 | Frame size, as --w and --h |
With no script the page cannot read ship.json, so each ship's frame size is copied into its
style attribute with the sheet URL and a whole-number scale --s
(--sheet, --w, --h, --s). Those four properties are the
only per-ship data; the rest of the page is shared rules.
III. The Sprite
As in the DOM example [2], an element one frame in size shows the sheet, scaled by s, as its
background (Listing 1). Two derived properties, --cell-w and --cell-h, hold the
scaled frame size so the later rules can stay short.
index.html:27–3427.ship {28 --cell-w: calc(var(--w) * var(--s) * 1px);29 --cell-h: calc(var(--h) * var(--s) * 1px);30 display: block;31 width: var(--cell-w);32 height: var(--cell-h);33 background: var(--sheet) no-repeat 0 0 / calc(var(--cell-w) * 6) auto;34 image-rendering: pixelated; /* keep the 1997 pixels square */
IV. Spinning With Two Step Animations
The frames run 0 to 35 across six rows of six. A single steps(36) animation would need the
frames in one strip; the pack's sheets are a grid, so the page runs two animations at once (Listing 2). One
moves background-position-x through six columns in T/6, repeating; the other moves
background-position-y through six rows in T. At time t the column is
⌊36t/T⌋ mod 6 and the row ⌊6t/T⌋, so the frame shown is
f = 6 · row + col = ⌊36 t / T⌋ mod 36(1)
which is the clockwise spin, one frame every T/36 = 0.1 s. The two properties are animated
separately, so neither animation disturbs the other. Hovering pauses both with
animation-play-state, and prefers-reduced-motion turns them off
(index.html:68).
index.html:40–4640 --turn: 3.6s; /* one full turn; 0.1 s a frame */41 animation: cols calc(var(--turn) / 6) steps(6) infinite,42 rows var(--turn) steps(6) infinite;43}44@keyframes cols { to { background-position-x: calc(var(--cell-w) * -6); } }45@keyframes rows { to { background-position-y: calc(var(--cell-h) * -6); } }46.ship:hover { animation-play-state: paused; }
V. Holding a Heading
A fixed frame f needs its column and row, and CSS can now compute both: mod() and
round(down, …) (Listing 3). The heading itself comes from the radio buttons: for each one a rule
body:has(#h9:checked) .ship { --f: 9; } sets --f on every ship, so the form
state reaches the ships without a line of script. Choosing “Spin” removes the override and the animations
resume.
index.html:49–5549body:not(:has(#spin:checked)) .ship {50 animation: none;51 background-position:52 calc(mod(var(--f), 6) * var(--cell-w) * -1)53 calc(round(down, var(--f) / 6) * var(--cell-h) * -1);54}55body:has(#h0:checked) .ship { --f: 0; }
VI. Using It in Your Project
- Copy the
.shiprules and the two@keyframes. - For each ship, write its sheet URL and its
frame_widthandframe_heightfromship.jsoninto astyleattribute. - Set
--turnfor speed, or--f(0–35) withanimation: nonefor a fixed heading.
Browsers without mod() and round() still spin; only the held heading needs
them.
VII. Conclusion
Two steps(6) animations on the two background axes reproduce the pack's frame order exactly,
and CSS math picks any single frame, so a pack ship can spin or hold a heading with no JavaScript.
References
- Yodacon Ship Pack, “From sprite sheet to moving ship,” Tutorial Note 1, examples/, 2026. examples/#tutorial
- Yodacon Ship Pack, “A ship in plain HTML and CSS,” Tutorial Note 2, examples/dom/.
- W3C, CSS Values and Units Module Level 4, §10 “Stepped value functions,” Working Draft, 2024.
- W3C, CSS Easing Functions Level 1, §3 “Step easing functions,” Candidate Recommendation, 2023.