pandazki/pneuma-webcraft
> Pneuma WebCraft Mode workspace guidelines with Impeccable.style design intelligence. styling, animations, responsive design, accessibility, performance optimization, design system extraction, UX writing, and visual refinement. This skill defines how the live-preview environment works, the Impeccable design principles to follow, and the 22 design commands available. Consult before your first edit in a new conversation.
npx skills add https://github.com/pandazki/pneuma-skills --skill pneuma-webcraft
WebCraft is a live web development surface backed by Impeccable.style design intelligence: a comprehensive set of design principles and commands that help you produce distinctive, production-grade frontend interfaces. The user watches an iframe preview render your edits in real time, and a toolbar exposes 22 Impeccable design commands for structured passes.
The webcraft viewer is the user's window into the workspace. It renders an iframe preview of the active HTML page, exposes responsive viewport controls, the 22 Impeccable design commands, and per-page / per-content-set switching. Everything below is how you (the agent) coordinate with that surface.
Each user message arrives wrapped with two channels you should read before acting:
<viewer-context> — the live state of the preview at send time. For webcraft this includes the active content set (top-level dir), the active page (file="about.html"), the viewport size of the responsive preview, and — when the user clicked an element in the iframe — a CSS-selector-style Selected path, a human-readable element description (tag, classes, accessible name), and an Address: line: a machine-readable ViewerAddress you can paste straight into a capture call or a <viewer-locator> card. Treat this as the resolution surface for "this section", "this button", "here", "make it tighter", etc.<user-actions> — discrete UI actions the user took since their last turn: page tab switches, content set switches, viewport size changes, and explicit invocations of an Impeccable design command from the toolbar (audit, critique, polish, …). Always check this before responding — a command:audit action means "do an audit", even if the chat text is just "go".Resolve ambiguous references against <viewer-context> first, then fall back to asking.
Webcraft has one vocabulary for "which object in the viewer". The same
shape — a ViewerAddress — is what a <viewer-locator> card points at, what
the capture action screenshots, and what a <viewer-context> selection
reports back to you. Learn it once; it works across all three.
| Key | Half | Meaning |
|---|---|---|
| contentSet | coarse | Top-level directory acting as a switchable site (pneuma, gazette, pneuma-console). |
| page | coarse | HTML page filename inside the content set (about.html, pricing/index.html). |
| selector | fine | A CSS selector resolved inside the rendered page (section.pricing, #hero .cta). |
| anchor | fine | A page anchor — shorthand for an #id selector. |
Use only the keys you need: {"page":"about.html"} names a whole page;
{"page":"about.html","selector":"section.pricing"} names one region of it.
When the user clicks an element, the Address: line in <viewer-context>
hands you a ready-made ViewerAddress — copy that JSON straight back.
After creating or editing pages, embed <viewer-locator> cards in your reply so the user can jump straight to the result. The card's address attribute is a ViewerAddress — locators navigate the user to a page, so use the coarse keys (contentSet, page):
<viewer-locator label="Open about.html" address='{"page":"about.html"}' />
<viewer-locator label="Switch to pneuma-console" address='{"contentSet":"pneuma-console"}' />
<viewer-locator label="Switch to gazette / contact" address='{"contentSet":"gazette","page":"contact.html"}' />
Embed one card per landmark you want the user to verify — don't dump a wall of cards.
Webcraft exposes one agent-invocable workspace action via POST $PNEUMA_API/api/viewer/action:
scaffold — Initialize the current content set with HTML pages from a structure spec. Params: title (required, site/project title) and pages (required, JSON array of {name, title?} for each HTML page). Honors clearPatterns: ["/*.html", "/manifest.json"] — it wipes existing pages in the target set, so always pass contentSet for new sites and always confirm with the user before invoking.curl -X POST "$PNEUMA_API/api/viewer/action" \
-H "Content-Type: application/json" \
-d '{
"action": "scaffold",
"params": {
"contentSet": "studio-portfolio",
"title": "Studio Portfolio",
"pages": "[{\"name\":\"index.html\",\"title\":\"Home\"},{\"name\":\"work.html\",\"title\":\"Work\"},{\"name\":\"contact.html\",\"title\":\"Contact\"}]"
}
}'
The 22 Impeccable design commands (init, document, shape, craft, audit, critique, polish, …) are NOT viewer actions — they're toolbar commands the user invokes, surfaced to you via <user-actions> (see "Reading what the user sees" above and the "Impeccable Commands" section below).
The user is already watching a live iframe preview of every edit you make — you do not need to prove the page renders.
Hard rule: do NOT open an external browser, the chrome-devtools MCP, headless Chrome, or browser-use tooling to verify your work.
Why: those tools render the raw files *outside* the webcraft viewer. Webcraft pages live inside content sets — asset paths, manifest.json page tabs, and proxy routes are all resolved by the viewer at render time. Open an HTML file directly and you see broken assets and a page detached from its set. What an external browser shows is not what the user sees. The Pneuma viewer is the only faithful render.
When you genuinely need to *see* the rendered result for a "quality check → improve" loop, use the framework-level capture viewer action — it returns a PNG screenshot of the live viewer, exactly what the user sees:
# Full viewer
curl -s -X POST "$PNEUMA_API/api/viewer/action" \
-H 'Content-Type: application/json' \
-d '{"actionId":"capture"}'
# One region — pass a ViewerAddress; `selector` resolves inside the rendered page
curl -s -X POST "$PNEUMA_API/api/viewer/action" \
-H 'Content-Type: application/json' \
-d '{"actionId":"capture","params":{"address":{"selector":"section.hero"}}}'
# A region on another page — capture navigates there first, then shoots
curl -s -X POST "$PNEUMA_API/api/viewer/action" \
-H 'Content-Type: application/json' \
-d '{"actionId":"capture","params":{"address":{"page":"pricing.html","selector":"section.plans"}}}'
params.address is a ViewerAddress — omit it for a full-viewer shot. On success the response is {"success":true,"data":{"path":"<absolute .png path>","width":<n>,"height":<n>}}. Use your Read tool on that path to view the screenshot inline, then iterate.
The webcraft workspace is organized around content sets — each top-level directory (e.g. pneuma/, gazette/, pneuma-console/) is a self-contained, switchable site. The active set appears as the content-set attribute in <viewer-context>; the user can switch sets from the viewer chrome. Per-set features (page tabs, theming, export, deploy) all key off this.
Rules:
manifest.json live inside a content set.portfolio/, landing-page/) and a manifest.json, then edit inside it.For multi-page sites, drop a manifest.json at the content set root so the viewer renders page tabs at the bottom:
{
"title": "My Project",
"pages": [
{ "file": "index.html", "title": "Home" },
{ "file": "about.html", "title": "About" },
{ "file": "contact.html", "title": "Contact" }
]
}
The first entry is the default page. Keep pages in sync whenever you add or remove HTML files.
scaffold is the structured way to seed a content set with empty-but-valid HTML pages from a spec. Use it when the user describes a new site by listing its pages ("a portfolio with home, work, about, contact"), rather than hand-writing each file. Two non-negotiables:
contentSet for any new site — without it, scaffold's clearPatterns wipe the active set's HTML.title + pages list in chat first.After scaffold returns, the viewer auto-switches to the new set; follow up with the actual design pass.
.html, .css, .js, .jsx, .ts, .tsx, .json, .svg, etc.){{#imageGenEnabled}}
Two scripts live under {SKILL_PATH}/scripts/:
generate_image.mjs — text-to-image (and precise URL+mask edits via GPT-Image-2)edit_image.mjs — modify an existing local image with an optional highlighter annotation (Gemini vision via OpenRouter)Default model is gpt-image-2. It is especially strong at the things webcraft reaches for often: legible typography, labels, landing-page mockups with real copy, signage, wordmark-style logos, and diagrams with text. Switch to --model gemini-3-pro for painterly / watercolor / broad artistic illustration, or when only OPENROUTER_API_KEY is configured (gpt-image-2 is fal.ai-only and will error out otherwise).
Webcraft can render many things with HTML/CSS/SVG. Generate an image only when the asset can't plausibly be composed from code:
| Want | Use |
|---|---|
| Geometric shapes, icons, gradients, patterns, decorative lines | CSS / SVG / <canvas> — faster, responsive, theme-aware |
| A photograph, a painterly illustration, a mood image, a hand-made texture | Generate |
| A product-shot mockup (phone, laptop, poster) with real copy on screen | Generate (gpt-image-2 — it renders legible text) |
| A logo or wordmark concept to iterate on | Generate with clear typography + mark direction |
| "Hero abstract 3D gradient swoosh thing" | Stop. See the Image Slop Test below. |
You already know the AI Slop Test for design — the same reflex applies to imagery. Before you call the generator, predict how the image will read. If the honest answer is *"this looks like every AI hero image on every AI landing page from 2024"*, that's the problem.
Reflex images to reject — your training-data defaults:
These are the visual equivalent of the reflex_fonts_to_reject list. Reject them every time. Look further.
Image-led surfaces don't get to degrade into abstract panels. Travel, editorial, portfolio, venue, product showcase, entertainment, and education work needs credible imagery — generated plates, illustrations, maps, renders, destination scenes — when the approved mock or subject matter calls for them. Substituting a tasteful gradient or geometric pattern for the hero photograph a brief actually demands is a missing-asset defect, not a stylistic choice. Generate the image, or surface the deviation to the user before shipping.
An image in a webcraft project has to live next to the site's typography, color system, and voice. If the site is a brutalist concrete manifesto and the hero image is a pastel unicorn, you've failed. Before typing the prompt:
PRODUCT.md / .impeccable.md / CLAUDE.md Design Context (tone, audience, brand personality). If none exists, run the init command first — same rule as any other design work.Match the image's palette to the site's theme. Write palette descriptors in prompts using concrete visual references rather than hex codes — models respond better to "muted clay red, bone white, a single cold-steel accent" than to oklch(0.55 0.15 30). If the site uses OKLCH custom properties, paraphrase them for the prompt, don't paste them in.
Most text-to-image calls look like this. Run from the skill's directory so .env is picked up:
cd {SKILL_PATH} && node scripts/generate_image.mjs \
"Your context-matched prompt here" \
--aspect-ratio 16:9 \
--quality high \
--output-format png \
--output-dir <workspace-relative>/<content-set>/assets \
--filename-prefix hero-context
Webcraft-specific flag guidance:
| Flag | Guidance |
|---|---|
| --model | Default gpt-image-2. Switch to --model gemini-3-pro for painterly / watercolor / broad artistic illustration, or when only OpenRouter is configured. |
| --aspect-ratio | 16:9 for hero banners above the fold, 4:3 or 3:2 for content images and card thumbs, 1:1 for avatars and icon-sized art, 9:16 for mobile-first hero or vertical feature images. |
| --quality | high for anything the user will actually look at; drop to medium for draft passes while iterating prompts. GPT-Image-2 only. |
| --output-format | png for illustrations or anything needing clean edges and legible text; jpeg for photographs; webp when size matters more than max fidelity. |
| --output-dir | Always the active content set's assets/ directory. |
| --filename-prefix | Describe the image's role: hero-context-lab, about-team-portrait, logo-wordmark-v1. |
For edits on an already-deployed / uploaded image, prefer --image-urls <url> --mask-url <url> against gpt-image-2 — it respects text and layout much better than Gemini vision. The annotation-driven edit_image.mjs is for the *local file + highlighter* flow (takes --input <path> and optional --annotation <path>).
<img> with meaningful alt, or <picture> when you need art direction across breakpoints).loading="lazy" for anything below the fold; add decoding="async" to hero images.max-width and aspect-ratio in CSS so layout doesn't jump while it loads.--num-images), wire both up behind a comment so the user can pick — don't silently discard.When generating multiple images for one site (hero + about + feature cards), record your style descriptors on the first call and reuse them verbatim on subsequent calls. The viewer lives next to the prompts; drifting midway through a batch is how decks start looking stitched-together.
{{/imageGenEnabled}}
Edit tool (preferred) for surgical changes to existing contentWrite tool for creating new files or full rewrites.claude/ directory contents — managed by the runtimeThis skill integrates the Impeccable.style design system. Follow these principles for ALL frontend work: produce ready-to-ship, production-grade code, not prototypes or starting points. Take no shortcuts unless the user asks for them (when in doubt, ask). Don't stop until arriving at a complete implementation (beautiful, responsive, fast, precise, bug-free, on brand). Claude is capable of extraordinary work. Don't hold back.
You MUST do these steps before your first design edit in a conversation:
init command first.<user-actions>, or by clear intent), read its references/cmd-<command>.md next. Non-optional. The reference defines the command's flow; without it you will skip steps the user expects.register field in PRODUCT.md / .impeccable.md.node {SKILL_PATH}/scripts/palette.mjs to receive a brand seed color and composition guidance. This is the anchor for your primary brand color. Compose the rest of the palette (bg, surface, ink, accent, muted) around it per the script's instructions. Use OKLCH throughout. Skip this step only if step 3 found committed brand colors in existing tokens; in that case identity-preservation wins.Design skills produce generic output without project context. You MUST have confirmed design context before doing any design work.
Required context — every design command needs at minimum:
CRITICAL: You cannot infer this context by reading the codebase. Code tells you what was built, not who it's for or what it should feel like. Only the creator can provide this context.
Gathering order:
CLAUDE.md already contains a Design Context section, proceed immediately.PRODUCT.md (and DESIGN.md when present) from the project root; .impeccable.md is the accepted legacy single-file equivalent. If either exists and contains the required context, proceed.init command (REQUIRED): If neither source has context, you MUST run the init command NOW before doing anything else (reference: cmd-init). Do NOT skip this step. Do NOT attempt to infer context from the codebase instead.Every design task is one of two registers — identify before designing:
The shared rules below apply to both registers; the register reference adjusts the dial.
Existing project? Preserving its identity wins over imposing a fresh look: read its tokens, theme, and components first and work within them. New project? The rules below plus the "New projects only" section end the cold-start drift toward the same safe choices every time.
text-wrap: balance on h1–h3 for even line lengths; text-wrap: pretty on long prose to reduce orphans.flex-wrap would be simpler.repeat(auto-fit, minmax(280px, 1fr)).@media (prefers-reduced-motion: reduce) alternative: typically a crossfade or instant transition.position: absolute inside an overflow: hidden or overflow: auto container will be clipped. Use the native <dialog> / popover API, position: fixed, or a portal to escape the stacking context.<img> elements on hover — no transform on :hover of an image, and no parent-hover patterns that scale/rotate/translate a child image. It adds no information (the image isn't an action target) and reads as "AI animated this because it could". If a card needs hover feedback, animate the card's background, border, or shadow.--.--paper, --cream, --sand, --bone, --linen, --parchment, --ivory are tells in themselves. If the brief is "warm, traditional" or "magazine-warm" or "editorial-restraint", DO NOT translate that into a near-white warm-tinted bg; that's the AI move. Pick: (a) a saturated brand color as the body (terracotta, oxblood, deep ochre, near-black), (b) a true off-white at chroma 0 (or chroma toward the brand's own hue, not toward warmth-by-default), or (c) a darker mid-tone tinted neutral that's clearly the brand's own. "Warmth" in the brand is carried by accent + typography + imagery, not by body bg.Match-and-refuse. If you're about to write any of these, rewrite the element with different structure.
border-left or border-right greater than 1px as a colored accent on cards, list items, callouts, or alerts — hard-coded colors AND CSS variables alike. Never intentional. Rewrite with full borders, background tints, leading numbers/icons, or nothing. Do not just swap to box-shadow inset.background-clip: text combined with a gradient background. Decorative, never meaningful. Use a single solid color. Emphasis via weight or size.01 · About / 02 · Process / 03 · Pricing above every section is the eyebrow trope one tier deeper: reach for it because "landing pages do this" and you're scaffolding by reflex. Numbers earn their place when the section actually IS a sequence and the order carries information the reader needs. One deliberate numbered sequence on one page is voice; numbered eyebrows on every section across the site is AI grammar.Model-tell bans — frequent giveaways of specific code models; refuse-and-rewrite regardless of which model you are:
border: 1px solid X + box-shadow: 0 Npx Mpx ... with blur ≥ 16px on the same element. Don't pair a 1px border with a soft wide drop shadow as decoration. Pick one (a single solid border at the brand color, OR a defined shadow at no more than 8px blur), never both.border-radius: 32px+ on cards / sections / inputs. Cards top out at 12–16px; full-pill is fine for tags/buttons. 24/28/32/40px radii on a card read as "insanely rounded", and no brand wants that.loose-sketch, doodle, wavy; feTurbulence / feDisplacementMap "paper grain" filters; crude 5-to-30-path scenes meant to depict a tangible subject. These read as amateurish, not whimsical. If you can't render the scene with real assets, ship no illustration.repeating-linear-gradient(...) stripe backgrounds: diagonal stripes in body:before or section backgrounds are pure decoration. Don't.linear-gradient(... 1px, transparent 1px) plus background-size are a tell unless the surface is an actual canvas, map, blueprint, or measurement tool. Use product structure, real artifacts, or a plain surface instead.Critical quality check: If someone could look at this interface and say "AI made that" without doubt, it's failed. If you showed it to someone and asked "which AI made this?", the honest answer should be "none — a designer did." Cross-register failures are the absolute bans above. Register-specific failures live in the register references.
Category-reflex check. Run at two altitudes; the second one catches what the first one misses.
Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices across generations.
The user invokes these commands from the toolbar. When a command is invoked, follow the corresponding reference document. The available commands are:
PRODUCT.md (or legacy .impeccable.md), offer DESIGN.md, recommend next steps. teach is a deprecated alias — treat a teach invocation exactly as init. Reference: cmd-initDESIGN.md at the project root capturing the current visual design system, so future agents stay on-brand. Reference: cmd-documentcommand:X in <user-actions>): load the command's reference file and follow it. The chat text (if any) is the target.teach → init alias), treat it as an invocation; everything after it is the target.layout, "rewrite this error message" → clarify, "the colors feel flat" → colorize), load that command's reference and proceed as if invoked. If two commands could fit, ask once which.When the user invokes a command:
{{ask_instruction}} with: STOP and ask the user using a normal message{{config_file}} with: CLAUDE.md{{model}} with: Claude{{available_commands}} with the list of 22 commands abovereferences/*.md files. Both live in the pneuma-webcraft skill — consult them directly, no separate impeccable skill needs to be invoked. Deep topic material lives inline in the command references themselves (each has a "Reference Material" section); references/interaction-design.md covers forms, focus, and loading patterns.Take pandazki/pneuma-webcraft from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npx.
Without those the skill loads but fails at the first command.