Single-File HTML Style Guide
This skill produces premium, minimalist single-file HTML pages and verifies them visually with screenshots.
The constraints — one file, no build step, light theme, restrained palette, mandatory screenshot validation — are deliberate. They keep the output portable (double-click to render), fast to share, and resistant to drifting into generic AI aesthetics (purple gradients, drop-shadow soup, rounded-card spam, emoji bullets).
When to use this skill
Use whenever the user wants:
- A landing page, marketing page, docs page, or visual prototype
- A modification to an existing
index.htmlin this project - Any output described as "a single HTML file" or "self-contained webpage"
Skip when the user explicitly asks for a multi-file framework build (React, Vue, Next, Svelte, etc.) — different problem.
Composition with other skills
This skill is the visual chassis — palette, typography, spacing, components, the screenshot harness. When another skill applies to the same task (most commonly skill-build-educational-site for long-form explainer pages), use this composition rule:
- This skill wins on palette, typography, spacing, components, icons, and validation. The other skill's design system is overridden when it conflicts.
- The other skill wins on content architecture — section sequence, audience switching, glossary discipline, comparison-table rule, regulatory-callout shape.
- When the user explicitly names this skill ("using the style guide"), the precedence above is locked. When the user names the other skill instead and this one only ambiently applies, defer to the other skill's chassis unless asked otherwise.
If the user wants long-form explainer content rendered with this skill's chassis, the reusable component CSS for callouts, comparison tables, glossary, reading lists, audience switchers, and inline-SVG diagram frames lives in references/long-form-components.md — copy what you need rather than re-authoring it.
Workflow
-
Copy the starter template. Begin every page by copying
assets/index.htmlfrom this skill to the working directory asindex.html. It already encodes the palette, font loading, dark-mode toggle behavior, personal branding row, and a typographic baseline. Customise from there — don't rebuild the chassis. -
Edit
index.htmlto satisfy the request. Keep everything in one file. Reach for the spacing tokens (--space-*), color variables (--bg,--surface,--text,--text-muted,--border,--accent), and component patterns already in the template. Adding ad-hoc colors or one-off margin values is the fast path to incoherence. -
Verify visually. Run the screenshot harness — see "Screenshot iteration loop" below. CSS bugs hide in plain sight when you only read the code; the screenshots catch overflow, font fallback, contrast failures, and AI-generic drift.
-
Iterate up to 3 cycles or until the design satisfies the rules below — whichever comes first. Diminishing returns set in fast; don't polish past the point of improvement.
Design rules
Read these before writing CSS. They override default 2025 design instincts.
Theme
- Light by default. The page renders in the light palette on first load. Dark mode is opt-in via a toggle in the header that flips a
data-themeattribute on<html>. On first visit, honourprefers-color-scheme: darkonly if no explicit user preference is stored inlocalStorage. The pattern is wired in the template — don't reinvent it. - No gradient hero backgrounds. No drop-shadow halos. No purple. Crisp 1px borders and a single soft elevation shadow are enough.
Palette (light, defined as CSS variables in the template)
Source of truth. Both palettes (light + dark), the spacing scale, and the curated accent alternatives live in palette.json at the root of skill-build-educational-site. The standard install symlink resolves it at ~/.claude/skills/skill-build-educational-site/palette.json — read it for current hex values rather than caching from this SKILL.md. A palette change happens in palette.json once and both skills pick it up. The variables below are the contract (names + intent); the values in palette.json → palettes.personal-style.light and .dark are the values.
--bgnear-white background--surfacepure white for cards and elevated panels--textnear-black primary text--text-mutedmid-gray secondary text--borderlight gray hairlines--border-strongslightly darker dividers--accentexactly one restrained colour — pick from the alternatives inpalette.json→palettes.personal-style.accent_alternatives(blue, indigo, emerald, or warm orange). Adding a second accent dilutes hierarchy; don't.--accent-hover/--accent-softderived from--accentviacolor-mix(); do not hand-roll separate hexes.
Brand accent vs. semantic state colours. The one-accent rule applies to the brand — CTAs, links, highlights, anything that says "this is the page's voice." It does not apply to semantic state signals (success green, warning amber, danger red) when the content genuinely conveys state — e.g., a security scan with PASS / FAIL rows, a comparison contrasting a deprecated approach with a recommended one, a status badge. State colours should be restrained (one shade each, used only for the affected element + icon), kept separate from the accent variable (introduce --state-ok, --state-warn, --state-bad as needed), and absent from CTAs and headings. If a page has no genuine state to convey, don't reach for them.
Categorical (data-viz) palette. A second exception is the --cat-1 … --cat-7 namespace (ROYGBIV) wired into the template and defined in palette.json → palettes.personal-style.categorical. Use these only for visualization data — chart series, multi-category diagram fills, taxonomy badges. They are off-limits for CTAs, headings, links, chrome, or anything that carries brand voice; reaching for them outside data-viz is the rainbow-toolbar drift the skill explicitly forbids. --cat-5 deliberately equals --accent so a chart on a brand page reads as part of the same system rather than a competing palette. Pick the smallest contiguous slice of the scale you need — a two-series chart uses --cat-1 and --cat-4, not all seven.
The dark palette is the same variables re-assigned under [data-theme="dark"], with values in palette.json → palettes.personal-style.dark. Use the variables — never hard-code hex in component CSS.
Typography
- Noto Sans (400 regular, 700 bold) for body and headings, Noto Sans Mono for code. Loaded via the Google Fonts
<link>already in the template head. - Body 18px, monospace 14px. Headings use tight letter-spacing (
-0.02em). Body line-height 1.5–1.7. - Hero headings use
clamp()so they scale with viewport rather than relying on breakpoints alone. - System fonts remain in the stack as fallbacks for offline rendering — keep them.
Spacing
Use the spacing tokens defined in the template: --space-1 (4px) through --space-9 (96px), mapped to the 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96 px scale. Values live in palette.json → palettes.personal-style.spacing (same single source as the palette). Don't introduce arbitrary values like padding: 17px or margin-top: 30px. Cramped layouts and wasteful white space are equally bad — the scale is the discipline that prevents both.
Icons
Lucide SVG inlined directly into HTML. Don't load the Lucide CDN script or use icon fonts — paste the SVG markup. Common icons (sun, moon, github, twitter/x, linkedin, chevron, check, arrow, external-link) are in references/lucide-icons.md ready to copy. Replace any emoji with the equivalent Lucide glyph; never ship emojis in production output.
Personal branding
The hero or header must include three links (already wired in the template, with Lucide icons):