Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Wrap any HTML artifact with a side panel of live, parameterized controls — accent color, type scale, density, motion, theme — that rewrite CSS custom properties in real time and persist to localStorage. Lets the user explore variants of a design without re-prompting the agent. Use when the brief asks for "variants", "side-by-side options", "tweak this", "let me adjust", "live knobs", or "实时调参".
.claude/skills/nexu-io-tweaks/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 35% | 0% |
Wrap any HTML artifact with a side panel of live controls that rewrite CSS custom properties in real time and persist to localStorage. Inspired by the huashu-design tweak pattern.
A single self-contained HTML file with two layers:
re-keyed so all visual decisions read from CSS custom properties: --accent, --scale, --density, --mode, --motion.
form controls bound to those custom properties via a tiny vanilla-JS bridge. Persists every change to localStorage keyed by the artifact identifier.
The user can:
preferences (or sensible defaults).
watch the stage update instantly — no rerender.
reset to defaults.
in the last 20% themselves.
feel the variants live (instead of you re-running the agent).
want viewers to play.
parameters don't help).
with carefully balanced data viz — knobs would degrade it).
> Pick a subset that suits the artifact. Don't ship all 5 if only 2 > matter — clutter is a regression.
--accent — Accent colorA select with 5–8 curated swatches (don't ship a free color picker — the user will pick a bad color and blame you).
jsconst ACCENT_PRESETS = [ { id: 'rust', val: '#c96442', label: 'Rust' }, { id: 'cobalt', val: '#2c4d8e', label: 'Cobalt' }, { id: 'sage', val: '#4a7a3f', label: 'Sage' }, { id: 'plum', val: '#7a3f6a', label: 'Plum' }, { id: 'graphite',val: '#3a3a3a', label: 'Graphite' }, ];
The artifact uses var(--accent) everywhere it had a hard-coded accent before. Border / link / pull-quote rule / CTA all flip together.
--scale — Type scale (0.85 / 1.0 / 1.15)Three settings: Compact (0.85), Normal (1.0), Generous (1.15). All font-size declarations multiply by var(--scale) via calc(... * var(--scale)).
Don't go beyond ±15% — beyond that the layout breaks (column flow, breakpoints, line counts).
--density — Layout density (Tight / Normal / Roomy)Three settings that swap the spacing scale: Tight (0.75) / Normal (1.0) / Roomy (1.4). All padding / gap / margin declarations multiply by var(--density).
This is the highest-impact knob — it's also the most fragile, so every layout-critical container must declare its base spacing in custom properties before you wrap.
--mode — Light / DarkA 2-state toggle. Sets data-mode="light" vs "dark" on the <html> element and the artifact's :root selector responds with two color sets.
If the artifact already has a media-query-based dark mode, replace it with the data-attr version — the user's choice should win over their OS.
--motion — Off / Subtle / LivelyThree settings. Maps to a CSS variable --motion-mult that scales all transition-duration / animation-duration declarations:
0s (also disables WebGL canvases / decorative animation).1.0 (the artifact's authored timing).1.6 (slower transitions, more visible motion).Respect prefers-reduced-motion: default to Off if the user has that set, regardless of stored preference.
The OpenDesign viewer toolbar has a Tweaks toggle that drives panel visibility from outside the iframe. For the toggle to bind to your panel, your artifact must speak one of these two protocols (pick one; don't mix). The toolbar enables itself the moment it sees either signal.
Use this when the panel mounts via JS (React, vanilla, anything dynamic).
Artifact → host:
{ type: '__edit_mode_available', visible?: boolean } towindow.parent. Tells the toolbar a panel exists; the optional visible reports the panel's initial state so the toolbar toggle starts in sync. Omit visible for the common "panel is already on screen" case (the host treats a missing field as true so the legacy zero-arg message keeps working). Pass visible: false to declare a default-closed panel.
{ type: '__edit_mode_dismissed' }. Toolbar flips to "off".
Host → artifact:
{ type: '__activate_edit_mode' } — open the panel (setOpen(true)).{ type: '__deactivate_edit_mode' } — close the panel (setOpen(false)).Minimal listener:
jswindow.addEventListener('message', (e) => { const t = e?.data?.type; if (t === '__activate_edit_mode') setOpen(true); else if (t === '__deactivate_edit_mode') setOpen(false); }); // Or, for a default-closed panel: // window.parent.postMessage({ type: '__edit_mode_available', visible: open }, '*'); window.parent.postMessage({ type: '__edit_mode_available' }, '*'); // in your close handler: const dismiss = () => { setOpen(false); window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*'); };
Panel may default to open or closed — the host syncs its toggle to whichever state the artifact reports.
assets/wrap.html)Use this only when you wrap the template verbatim. The artifact ships a .tw-panel element and toggles a .tw-hidden class for visibility. The viewer's iframe bridge (in apps/web/src/runtime/srcdoc.ts) hides the panel on initial paint, watches the class via MutationObserver, and relays state both directions. No JS required in the artifact beyond what the template already includes.
Selectors are fixed: .tw-panel (the panel root) and .tw-hidden (the hidden state). If you rename either, the bridge can't find it.
Don't invent a third protocol or rename either set of identifiers. The toolbar toggle only binds to A or B. Custom panels with custom classes and no postMessage will leave the toolbar greyed out.
Read assets/wrap.html — it ships the panel + bridge as an inert template. Your job is to:
(search for hard-coded #hex / Npx / Nrem and convert).
wrap.html.assets/wrap.html's KNOBS array to keep only the knobsyou decided are relevant to this artifact. Don't ship 5 if 2 matter.
STORAGE_KEY to a unique slug (tweaks-<artifact-slug>).The bridge in wrap.html:
localStorage[STORAGE_KEY] JSON on first paint.document.documentElement.style.setProperty('--accent', ...).change event and writes back.Same options as the critique skill:
index.html in the project folder).Read the artifact's CSS first. For each knob, decide yes / no:
--accent — yes if the artifact has 1 accent color used ≥ 3 times.--scale — yes if the artifact is type-driven (article, deck,pricing page).
--density — yes if the artifact has consistent gap / paddingrhythm (deck, dashboard, landing). No for runbooks (already dense).
--mode — yes if the artifact has authored dark mode tokens, oryou're willing to derive them.
--motion — yes if the artifact has any transition / animationworth scaling. No for static reports / critique reports.
Default: 3 knobs is the sweet spot. Five is too busy, one is not worth a panel.
Open assets/wrap.html's <style> block — copy its custom-property naming scheme (--accent, --scale, etc.). In the user's artifact, find every place those concerns live and rewrite:
color: #c96442 → color: var(--accent)font-size: 18px → font-size: calc(18px * var(--scale))padding: 24px 32px → padding: calc(24px * var(--density)) calc(32px * var(--density))transition: opacity 200ms → transition: opacity calc(200ms * var(--motion-mult))If the artifact uses clamp() or vw already, multiply the outer value by the custom property — don't tear apart clamp(...).
Copy the artifact's <style> and <body> into the marked regions of wrap.html. Keep the panel + bridge intact.
Open the result, click each knob at least once, refresh the page, confirm the choice persists. If a knob breaks the layout — remove it, don't ship it.
<artifact identifier="tweaks-<artifact-slug>" type="text/html" title="<Artifact Title> · Tweaks">
<!doctype html>
<html>...</html>
</artifact>One sentence before the artifact ("Wrapped X with a 3-knob tweak panel — accent / scale / mode."). Stop after </artifact>.
pick bad colors when given freedom; saving them from that is the whole point.
tweaks-<slug>, not a globalkey. Two artifacts open in two tabs must not share state.
prefers-reduced-motion — default to Off for motionif the user has that set, override only on explicit click.
existing imports. Inline the panel + bridge.
via a "T" button at top-right.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | 49,550 | 37,428 | -24% | 1 | 1 | 0% | 8,298 | 11,260 | +36% | 0 | 0 | — |
case-01 | fail→pass | 48,812 | 52,330 | +7% | 1 | 1 | 0% | 8,310 | 11,272 | +36% | 0 | 0 | — |
case-02 | fail→pass | 37,776 | 48,327 | +28% | 1 | 1 | 0% | 8,292 | 11,254 | +36% | 0 | 0 | — |
case-04 | pass→pass | 19,844 | 10,644 | -46% | 1 | 1 | 0% | 2,567 | 4,308 | +68% | 0 | 0 | — |
case-05 | pass→pass | 17,941 | 8,961 | -50% | 1 | 1 | 0% | 2,286 | 3,986 | +74% | 0 | 0 | — |
case-06 | pass→fail | 16,898 | 52,308 | +210% | 1 | 1 | 0% | 1,976 | 11,200 | +467% | 0 | 0 | — |
case-07 | fail→fail | 37,801 | 34,468 | -9% | 1 | 1 | 0% | 8,284 | 11,246 | +36% | 0 | 0 | — |
case-08 | fail→pass | 52,477 | 36,453 | -31% | 1 | 1 | 0% | 8,284 | 11,246 | +36% | 0 | 0 | — |
case-09 | fail→pass | 36,400 | 47,991 | +32% | 1 | 1 | 0% | 8,285 | 11,247 | +36% | 0 | 0 | — |
case-10 | fail→fail | 36,553 | 36,431 | -0% | 1 | 1 | 0% | 8,281 | 11,243 | +36% | 0 | 0 | — |
case-11 | fail→pass | 99,427 | 36,434 | -63% | 1 | 1 | 0% | 8,290 | 11,189 | +35% | 0 | 0 | — |
case-12 | fail→pass | 24,624 | 28,713 | +17% | 1 | 1 | 0% | 4,305 | 9,216 | +114% | 0 | 0 | — |
case-13 | fail→pass | 51,276 | 47,535 | -7% | 1 | 1 | 0% | 8,280 | 11,242 | +36% | 0 | 0 | — |
case-14 | fail→pass | 37,780 | 47,520 | +26% | 1 | 1 | 0% | 6,463 | 11,118 | +72% | 0 | 0 | — |
case-20 | fail→pass | 49,889 | 48,042 | -4% | 1 | 1 | 0% | 8,265 | 11,227 | +36% | 0 | 0 | — |
case-15 | fail→pass | 16,373 | 46,191 | +182% | 1 | 1 | 0% | 2,642 | 9,558 | +262% | 0 | 0 | — |
case-16 | fail→pass | 49,669 | 30,643 | -38% | 1 | 1 | 0% | 8,289 | 10,199 | +23% | 0 | 0 | — |
case-17 | fail→fail | 52,306 | 34,669 | -34% | 1 | 1 | 0% | 8,263 | 11,225 | +36% | 0 | 0 | — |
case-18 | pass→fail | 28,758 | 48,100 | +67% | 1 | 1 | 0% | 6,707 | 11,251 | +68% | 0 | 0 | — |
case-19 | fail→pass | 36,260 | 37,629 | +4% | 1 | 1 | 0% | 8,297 | 11,259 | +36% | 0 | 0 | — |
case-21 | fail→fail | 35,707 | 35,547 | -0% | 1 | 1 | 0% | 8,237 | 11,199 | +36% | 0 | 0 | — |
case-22 | fail→pass | 54,010 | 35,511 | -34% | 1 | 1 | 0% | 8,280 | 11,008 | +33% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.