Build or maintain a static site with JavaScript enhancements using $ARGUMENTS. Apply all rules below strictly.
Composition
This skill is /web-static plus a bounded amount of JavaScript. Everything in /web-static applies
unchanged — CSS-only interactive patterns, CSS reset, page transitions, performance rules, project
structure, and the verification loop — except its no-JavaScript hard constraint, which this skill
replaces with the Enhancement Contract below.
/web-conventions — semantic HTML, accessibility, modern CSS, design tokens, Baseline policy
/javascript-conventions — modules, syntax, collections, asynchrony, errors, JSDoc types, and the
Baseline lookup for JavaScript features
/web-static — everything above the JavaScript line
/web-latest may be composed on top for experiments and PoCs
Rules in this skill override all of them.
Enhancement Contract
- every page is complete and usable with zero JavaScript — content readable, navigation
traversable, forms submittable and validated by the browser
- JavaScript only adds behavior to markup that already exists; it never produces the content
that is the page's purpose
- links keep real
hrefs and forms keep action/method — navigation and submission never depend
on a script
- a module that fails — throws, 404s, hits an unsupported API — degrades the page to its no-script
baseline, never to a broken page
- enhancements are independent: one module failing must not disable the others
- nothing renders late — no content flashes in, moves, or is replaced after load
The Cut Line
If HTML or CSS can express it, a script is a defect, not a choice. Before writing one, ask what the
page looks like with it removed; if the answer is "broken", the design is wrong, not the script.
| Behavior |
Verdict |
| Accordion, disclosure |
<details>/<summary> — no script |
| Tabs, hamburger, dropdown |
:checked + siblings, :focus-within — no script |
| Modal |
:target — no script; <dialog> plus a script only when focus trapping and Esc matter |
| Form validation |
input types and constraint attributes — a script only for rules markup cannot express |
| Page transitions, smooth scroll, carousels |
@view-transition, scroll-behavior, scroll snap — no script |
| Theme toggle that persists |
script — CSS cannot read localStorage |
| Clipboard |
script — navigator.clipboard |
| Filter, sort, or search over content already in the DOM |
script |
| Locale or relative time formatting |
script — Intl over a <time datetime> already in the HTML |
| Loading supplementary data |
script — but content the page exists to show stays in the HTML |
Modules
one module per behavior, named after the behavior: theme-toggle.js, copy-button.js
each module exports a single enhance() — no work at import time (per /javascript-conventions)
one entry point, js/enhance.js, imports each module and calls it inside its own try/catch,
so a failing enhancement cannot take down the rest:
import { enhance as themeToggle } from "./theme-toggle.js";
import { enhance as copyButton } from "./copy-button.js";
for (const enhancement of [themeToggle, copyButton]) {
try {
enhancement();
} catch (cause) {
console.error(new Error(`enhancement failed: ${enhancement.name}`, { cause }));
}
}
load it once per page: <script type="module" src="/js/enhance.js"></script> — modules defer by
default, so it belongs in <head>
a module finds its targets through a dedicated data- attribute ([data-copy]), never through a
class used for styling — the hook is explicit, greppable, and its absence means no enhancement
delegate events on a container rather than binding per element
feature-detect every Newly Available API with a fallback, per the /javascript-conventions
Baseline lookup — a missing API degrades to the baseline, it does not throw
zero runtime dependencies — no lit-html, no CDN, no npm, no bundler, no TypeScript. A sprinkle
that needs a templating library is not a sprinkle
no store, no client-side router, no shadow DOM
a custom element is permitted only as a wrapper that enhances its existing light-DOM children;
one that renders its own content belongs to /web-components
Budget
Keep the JavaScript smaller than the CSS. A module past ~100 lines, or more than a handful of
modules on a page, means the site has become an application — escalate rather than grow.
Project Structure
/web-static's structure, with all enhancement modules under a single js/ directory:
project/
├── index.html
├── css/style.css
├── js/
│ ├── enhance.js # entry point: imports and calls each enhance()
│ ├── theme-toggle.js
│ └── copy-button.js
├── checks.md
└── images/
One directory is a rule, not a convention — the no-JavaScript pass below removes it in a single step.
Verification Loop
/web-static's loop runs unchanged — serve with java zws <site-root>, then console, structure,
responsive, preferences, and Lighthouse checks on every page, plus checks.md and the Baseline
check. Never author with zws --live during a run; the injected reload script contaminates both
passes. Three amendments:
Console. An empty console no longer proves the JavaScript policy — it proves the modules run
clean. Errors and failed requests are red as before.
Baseline check. It now covers the JavaScript features the modules use as well as the CSS,
resolved through the same web-conventions snapshot.
No-JavaScript pass — required, every page. The Chrome DevTools MCP cannot disable JavaScript, so
remove it at the source: copy the site root to a scratch directory, delete its js/ directory,
serve the copy with a second zws instance, and re-run the structure check plus every
checks.md line marked Baseline. The only console output permitted in this pass is the failed
request for the missing entry point — that failure is the evidence the scripts were absent; anything
else is red. If the copy cannot be served, report the pass as not runnable rather than self-certify.
checks.md
Two sections, so every line declares which pass it belongs to. Labels stay stable identifiers and
remain the requirement id under /sbce.
# Checks
## Baseline — holds with js/ removed
- [R1.2] /catalog/ at 1280px: every workshop is an <article> containing a <time>
- [nav-mobile] / at 375px: snapshot contains button "Menu"; nav links hidden until checked
## Enhanced — holds with the modules loaded
- [copy-btn] /snippets/ at 1280px: clicking button "Copy" changes its label to "Copied"
- [theme] / at 1280px: clicking button "Dark" sets data-theme="dark" on <html>; reload keeps it
Every enhancement needs a Baseline line stating what stands in for it without JavaScript.
Green = /web-static green, plus the no-JavaScript pass green, plus every Enhanced check.
Report per label with the observed evidence. The enhanced pass alone never establishes green.
Escalate to /web-components
Stop and switch stacks at the first of these — converting is cheap early and expensive late:
- state shared between two enhancements, or state that outlives a page view
- views selected by URL, or any client-side routing
- content that exists only after a
fetch
- a need for templating, or for components that render themselves
- the budget above exceeded
What NOT to Do
In addition to the /web-static and /web-conventions prohibitions:
- do not use inline
<script> blocks or onclick-style handlers
- do not render content that the page exists to show
- do not script an interaction the cut line assigns to CSS
- do not intercept a link or form submission that works without JavaScript
- do not add a dependency, a build step, or a polyfill
- do not use a Newly Available API without a detection and a fallback
- do not declare a page green without the no-JavaScript pass
1---2name: web-sprinkles3description: Build static multipage websites enhanced with small, self-contained ES modules — progressive enhancement, where every page is complete and usable with zero JavaScript and scripts only add behavior to markup that already exists. Composes with `web-static` (CSS-only patterns, structure, verification loop), `web-conventions` (HTML/CSS/Baseline), and `javascript-conventions` (language rules); owns the enhancement contract, the CSS-versus-script cut line, and a no-JavaScript verification pass. Use when a static site needs a theme toggle, a copy button, a client-side filter, `Intl` formatting, or a small `fetch`. Triggers on "sprinkles of JavaScript", "a bit of JavaScript", "small amount of JS", "progressive enhancement", "static site with JavaScript", "vanilla JS page", "enhance this static page", "no framework, just a script". Not for pure HTML/CSS sites — use `web-static`; not for applications with client-side state, routing, or templating — use `web-components`.4---56Build or maintain a static site with JavaScript enhancements using $ARGUMENTS. Apply all rules below strictly.78## Composition910This skill is `/web-static` plus a bounded amount of JavaScript. Everything in `/web-static` applies11unchanged — CSS-only interactive patterns, CSS reset, page transitions, performance rules, project12structure, and the verification loop — except its no-JavaScript hard constraint, which this skill13replaces with the Enhancement Contract below.1415- `/web-conventions` — semantic HTML, accessibility, modern CSS, design tokens, Baseline policy16- `/javascript-conventions` — modules, syntax, collections, asynchrony, errors, JSDoc types, and the17 Baseline lookup for JavaScript features18- `/web-static` — everything above the JavaScript line19- `/web-latest` may be composed on top for experiments and PoCs2021Rules in this skill override all of them.2223## Enhancement Contract2425- every page is **complete and usable with zero JavaScript** — content readable, navigation26 traversable, forms submittable and validated by the browser27- JavaScript only **adds behavior to markup that already exists**; it never produces the content28 that is the page's purpose29- links keep real `href`s and forms keep `action`/`method` — navigation and submission never depend30 on a script31- a module that fails — throws, 404s, hits an unsupported API — degrades the page to its no-script32 baseline, never to a broken page33- enhancements are independent: one module failing must not disable the others34- nothing renders late — no content flashes in, moves, or is replaced after load3536## The Cut Line3738If HTML or CSS can express it, a script is a defect, not a choice. Before writing one, ask what the39page looks like with it removed; if the answer is "broken", the design is wrong, not the script.4041| Behavior | Verdict |42|---|---|43| Accordion, disclosure | `<details>`/`<summary>` — no script |44| Tabs, hamburger, dropdown | `:checked` + siblings, `:focus-within` — no script |45| Modal | `:target` — no script; `<dialog>` plus a script only when focus trapping and `Esc` matter |46| Form validation | input types and constraint attributes — a script only for rules markup cannot express |47| Page transitions, smooth scroll, carousels | `@view-transition`, `scroll-behavior`, scroll snap — no script |48| Theme toggle that persists | script — CSS cannot read `localStorage` |49| Clipboard | script — `navigator.clipboard` |50| Filter, sort, or search over content already in the DOM | script |51| Locale or relative time formatting | script — `Intl` over a `<time datetime>` already in the HTML |52| Loading supplementary data | script — but content the page exists to show stays in the HTML |5354## Modules5556- one module per behavior, named after the behavior: `theme-toggle.js`, `copy-button.js`57- each module exports a single `enhance()` — no work at import time (per `/javascript-conventions`)58- one entry point, `js/enhance.js`, imports each module and calls it inside its own `try`/`catch`,59 so a failing enhancement cannot take down the rest:6061 ```javascript62 import { enhance as themeToggle } from "./theme-toggle.js";63 import { enhance as copyButton } from "./copy-button.js";6465 for (const enhancement of [themeToggle, copyButton]) {66 try {67 enhancement();68 } catch (cause) {69 console.error(new Error(`enhancement failed: ${enhancement.name}`, { cause }));70 }71 }72 ```7374- load it once per page: `<script type="module" src="/js/enhance.js"></script>` — modules defer by75 default, so it belongs in `<head>`76- a module finds its targets through a dedicated `data-` attribute (`[data-copy]`), never through a77 class used for styling — the hook is explicit, greppable, and its absence means no enhancement78- delegate events on a container rather than binding per element79- feature-detect every Newly Available API with a fallback, per the `/javascript-conventions`80 Baseline lookup — a missing API degrades to the baseline, it does not throw81- **zero runtime dependencies** — no lit-html, no CDN, no npm, no bundler, no TypeScript. A sprinkle82 that needs a templating library is not a sprinkle83- no store, no client-side router, no shadow DOM84- a custom element is permitted only as a wrapper that enhances its existing light-DOM children;85 one that renders its own content belongs to `/web-components`8687## Budget8889Keep the JavaScript smaller than the CSS. A module past ~100 lines, or more than a handful of90modules on a page, means the site has become an application — escalate rather than grow.9192## Project Structure9394`/web-static`'s structure, with all enhancement modules under a single `js/` directory:9596```97project/98├── index.html99├── css/style.css100├── js/101│ ├── enhance.js # entry point: imports and calls each enhance()102│ ├── theme-toggle.js103│ └── copy-button.js104├── checks.md105└── images/106```107108One directory is a rule, not a convention — the no-JavaScript pass below removes it in a single step.109110## Verification Loop111112`/web-static`'s loop runs unchanged — serve with `java zws <site-root>`, then console, structure,113responsive, preferences, and Lighthouse checks on every page, plus `checks.md` and the Baseline114check. Never author with `zws --live` during a run; the injected reload script contaminates both115passes. Three amendments:116117**Console.** An empty console no longer proves the JavaScript policy — it proves the modules run118clean. Errors and failed requests are red as before.119120**Baseline check.** It now covers the JavaScript features the modules use as well as the CSS,121resolved through the same `web-conventions` snapshot.122123**No-JavaScript pass — required, every page.** The Chrome DevTools MCP cannot disable JavaScript, so124remove it at the source: copy the site root to a scratch directory, delete its `js/` directory,125serve the copy with a second `zws` instance, and re-run the **structure** check plus every126`checks.md` line marked Baseline. The only console output permitted in this pass is the failed127request for the missing entry point — that failure is the evidence the scripts were absent; anything128else is red. If the copy cannot be served, report the pass as not runnable rather than self-certify.129130### checks.md131132Two sections, so every line declares which pass it belongs to. Labels stay stable identifiers and133remain the requirement id under `/sbce`.134135```markdown136# Checks137138## Baseline — holds with js/ removed139- [R1.2] /catalog/ at 1280px: every workshop is an <article> containing a <time>140- [nav-mobile] / at 375px: snapshot contains button "Menu"; nav links hidden until checked141142## Enhanced — holds with the modules loaded143- [copy-btn] /snippets/ at 1280px: clicking button "Copy" changes its label to "Copied"144- [theme] / at 1280px: clicking button "Dark" sets data-theme="dark" on <html>; reload keeps it145```146147Every enhancement needs a Baseline line stating what stands in for it without JavaScript.148149**Green** = `/web-static` green, plus the no-JavaScript pass green, plus every Enhanced check.150Report per label with the observed evidence. The enhanced pass alone never establishes green.151152## Escalate to /web-components153154Stop and switch stacks at the first of these — converting is cheap early and expensive late:155156- state shared between two enhancements, or state that outlives a page view157- views selected by URL, or any client-side routing158- content that exists only after a `fetch`159- a need for templating, or for components that render themselves160- the budget above exceeded161162## What NOT to Do163164In addition to the `/web-static` and `/web-conventions` prohibitions:165166- do not use inline `<script>` blocks or `onclick`-style handlers167- do not render content that the page exists to show168- do not script an interaction the cut line assigns to CSS169- do not intercept a link or form submission that works without JavaScript170- do not add a dependency, a build step, or a polyfill171- do not use a Newly Available API without a detection and a fallback172- do not declare a page green without the no-JavaScript pass