diff --git a/README.md b/README.md index a4eaacb9c..daf06cf3e 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Impeccable -The vocabulary you didn't know you needed. 1 skill, 23 commands, and curated anti-patterns for impeccable frontend design. +Design guidance for AI coding agents. 1 skill, 23 commands, live browser iteration, and 41 deterministic detector rules for AI-generated frontend design. -> **Quick start:** Visit [impeccable.style](https://impeccable.style) to download ready-to-use bundles. +> **Quick start:** From your project root, run `npx impeccable skills install`, then run `/impeccable init` inside your AI coding tool. Full docs: [impeccable.style](https://impeccable.style). ## Why Impeccable? @@ -11,25 +11,27 @@ Anthropic's [frontend-design](https://github.com/anthropics/skills/tree/main/ski Every model trained on the same SaaS templates. Skip the guidance and you get the same handful of tells on every project: Inter for everything, purple-to-blue gradients, cards nested in cards, gray text on colored backgrounds, the rounded-square icon tile above every heading. Impeccable adds: -- **7 domain reference files** ([view source](skill/)). Typography, color, motion, spatial, interaction, responsive, UX writing. Load on every command, alongside a brand-vs-product register that adjusts the defaults. +- **One setup flow.** `/impeccable init` writes `PRODUCT.md` and offers `DESIGN.md`, so later commands know the audience, brand/product lane, voice, anti-references, colors, type, and components. - **23 commands.** A shared design vocabulary with your AI: `polish`, `audit`, `critique`, `distill`, `animate`, `bolder`, `quieter`, and more. -- **27 deterministic anti-pattern rules** plus a 12-rule LLM critique pass. CLI and browser extension run the deterministic ones with no LLM and no API key. Each is tied to specific design guidance the skill teaches against. +- **41 deterministic detector rules** plus LLM-only critique checks. The CLI and browser extension run the deterministic rules with no LLM and no API key. ## What's Included ### The Skill: impeccable -A comprehensive design skill with 7 domain-specific references ([view skill](skill/SKILL.src.md)): +The skill installs as one command: -| Reference | Covers | -|-----------|--------| -| [typography](skill/reference/typography.md) | Type systems, font pairing, modular scales, OpenType | -| [color-and-contrast](skill/reference/color-and-contrast.md) | OKLCH, tinted neutrals, dark mode, accessibility | -| [spatial-design](skill/reference/spatial-design.md) | Spacing systems, grids, visual hierarchy | -| [motion-design](skill/reference/motion-design.md) | Easing curves, staggering, reduced motion | -| [interaction-design](skill/reference/interaction-design.md) | Forms, focus states, loading patterns | -| [responsive-design](skill/reference/responsive-design.md) | Mobile-first, fluid design, container queries | -| [ux-writing](skill/reference/ux-writing.md) | Button labels, error messages, empty states | +```bash +/impeccable +``` + +Start every new project with: + +```bash +/impeccable init +``` + +`init` asks whether the surface is brand (marketing, landing, portfolio) or product (app UI, dashboard, tool), then writes project context that every later command reads. ### 23 Commands @@ -258,7 +260,7 @@ npx impeccable detect https://example.com # scan a URL (Puppeteer) npx impeccable detect --fast --json . # regex-only, JSON output ``` -The detector catches 24 issues across AI slop (side-tab borders, purple gradients, bounce easing, dark glows) and general design quality (line length, cramped padding, small touch targets, skipped headings, and more). +The detector catches 41 deterministic issues across AI slop (side-tab borders, purple gradients, bounce easing, dark glows) and general design quality (line length, cramped padding, small touch targets, skipped headings, and more). ## Supported Tools diff --git a/README.npm.md b/README.npm.md index 7e3cfaa32..2e68ceafc 100644 --- a/README.npm.md +++ b/README.npm.md @@ -1,6 +1,6 @@ # Impeccable CLI -Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 27 specific patterns including AI-generated UI tells, accessibility violations, and general design quality problems. +Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 41 deterministic rules, including AI-generated UI tells, accessibility violations, and general design quality problems. ## Quick Start @@ -8,6 +8,9 @@ Detect UI anti-patterns and design quality issues from the command line. Scans H # Install skills into your AI harness (Claude, Cursor, Gemini, etc.) npx impeccable skills install +# First command to run inside your AI harness +/impeccable init + # Update skills to the latest version npx impeccable skills update @@ -26,7 +29,7 @@ npx impeccable detect https://example.com # JSON output for CI/tooling npx impeccable detect --json src/ -# Regex-only mode (faster, no jsdom) +# Deprecated compatibility flag; full scan still runs npx impeccable detect --fast src/ ``` @@ -47,7 +50,7 @@ npx impeccable detect --fast src/ **Quality**: tiny body text, cramped padding, long line lengths, small touch targets -25 detections in total. See the full list at [impeccable.style](https://impeccable.style). +41 deterministic detector rules in total. See the full catalog at [impeccable.style/slop](https://impeccable.style/slop). ## Exit Codes @@ -72,7 +75,7 @@ impeccable detect [options] [file-or-dir-or-url...] ## Part of Impeccable -This CLI is part of [Impeccable](https://impeccable.style), a cross-provider design skill pack for AI-powered development tools. The full suite includes 22 steering commands for Claude, Cursor, Gemini, Codex, and more. +This CLI is part of [Impeccable](https://impeccable.style), a cross-provider design skill pack for AI-powered development tools. The full suite includes 23 commands for Claude, Cursor, Gemini, Codex, and more. ## License diff --git a/site/content/skills/document.md b/site/content/skills/document.md index f167c3927..2b5ac49bc 100644 --- a/site/content/skills/document.md +++ b/site/content/skills/document.md @@ -80,7 +80,7 @@ Reach for it when: - **The design has drifted** from an older DESIGN.md and the file no longer describes the live system. - **Before a large redesign**, to capture current state as a reference for the next direction. -For projects with no code yet (fresh `init` run, nothing built), there is a seed mode: `/impeccable document --seed` asks five quick strategic questions (color strategy, type direction, motion energy, references, anti-references) and writes a scaffold. Re-run in scan mode once there is code. +For projects with no code yet (fresh `init` run, nothing built), there is a seed mode: `/impeccable document --seed` asks five quick strategic questions (color strategy, type direction, motion style, references, anti-references) and writes a scaffold. Re-run in scan mode once there is code. ## How it works diff --git a/site/content/skills/impeccable.md b/site/content/skills/impeccable.md index 1e7dbb6b1..fcc33e03d 100644 --- a/site/content/skills/impeccable.md +++ b/site/content/skills/impeccable.md @@ -1,32 +1,35 @@ --- -tagline: "The design intelligence behind every command." +tagline: "Get a next-step recommendation, or describe design work in plain English." --- ## When to use it -`/impeccable` is the home command, and it works two ways. Run it **with no command** and it reads the project, then recommends the best next move. Run it **with a plain-English request** and it does freeform design work with the full guidebook loaded, no specialized command to pick. It is also the fallback you reach for when none of the 23 specialists (`audit`, `polish`, `critique`, and the rest) map cleanly onto what you are trying to do. +`/impeccable` is the main command. Use it in two ways: + +- Run `/impeccable` by itself when you want the skill to inspect the project and recommend what to do next. +- Add a plain-English request when you know the outcome but not the exact command. Reach for `/impeccable` directly when: -- **You are not sure where to start.** Run it with no command and it sizes up the project (whether PRODUCT.md and DESIGN.md exist, your uncommitted changes, your latest critique, a quick detector pass) and recommends the two or three highest-value commands to run next, with reasons. It always asks before running anything. +- **You are not sure where to start.** It checks whether setup files exist, looks at the current project state, and recommends two or three next commands. It asks before running anything. - **You are not sure which command fits.** Describe what you want in plain English and let the skill pick the right approach. - **The work spans multiple disciplines.** "Redo this hero section" touches layout, type, color, and motion. One command cannot own that. -- **You want the full design intelligence without constraints.** Every reference file loaded, every anti-pattern checked, no pre-set workflow. +- **You want freeform design help.** Use the main command when no specialist command maps cleanly to the work. -For structured flows, reach for the specialized commands in the sidebar. Run `/impeccable init` first on any new project to establish PRODUCT.md and DESIGN.md. `/impeccable craft` chains a discovery interview into a full build with live visual iteration. `/impeccable shape` produces a design brief without touching code. `/impeccable live` gives you a browser picker with three variants per element. The evaluation and refinement commands (`audit`, `critique`, `polish`, `typeset`, `layout`, `colorize`, and the rest) each own a specific slice of the work. +If this is a new project, start with `/impeccable init`. That creates the setup files every other command reads. ## How it works -Most AI-generated UIs fail the same way: generic fonts, purple gradients, card grids on card grids, glassmorphism everywhere. `/impeccable` gives your AI a strong point of view. It loads an opinionated design handbook plus a long list of anti-patterns, then pushes the model to commit to a specific aesthetic direction before writing a single line of code. +Most AI-generated UIs fail the same way: generic fonts, purple gradients, card grids on card grids, glassmorphism everywhere. `/impeccable` gives the model stronger design instructions before it writes code. Two files at your project root shape everything the skill does: -- **`PRODUCT.md`** carries register (brand vs product), target users, brand personality, anti-references, design principles. Answers "who, what, why". -- **`DESIGN.md`** carries colors, typography, elevation, components, do's and don'ts, in the six-section Google Stitch format. Answers "how it looks". +- **`PRODUCT.md`** says what the project is for: audience, product purpose, voice, anti-references, and whether the surface is brand or product. +- **`DESIGN.md`** says how the interface should look: colors, typography, components, elevation, and design rules. -Every command reads both files before generating. **Register** decides which defaults load. Brand (marketing, landing, portfolio, where design IS the product) and product (app UI, dashboards, tools, where design SERVES the product) have different defaults for type, motion, color, and density. Specifying it once in PRODUCT.md means `/impeccable typeset` will not push editorial-magazine fonts on a dashboard, and will not push product-fluent defaults on a campaign page. See the [brand vs product tutorial](/tutorials/brand-vs-product) for how the two diverge. +Every command reads both files before generating. The most important setup choice is **brand vs product**: is this a marketing surface where the impression is the product, or an app surface where design helps someone finish a task? The docs call this choice **register**. See [Brand vs product](/tutorials/brand-vs-product) for examples. -On first use in a project, the skill runs the `init` flow automatically: a short interview that writes PRODUCT.md and then delegates to `/impeccable document` for DESIGN.md. Future commands read the files without asking again. +On first use in a project, `/impeccable` may route you into `init`: a short interview that writes `PRODUCT.md` and offers to write `DESIGN.md`. Future commands read those files without asking again. ## Try it @@ -36,7 +39,7 @@ Run it with no command to get your bearings: /impeccable ``` -It sizes up the project and points you at the best next move (no DESIGN.md yet, run document; unresolved findings in the files you are editing, run polish), then waits for you to choose. The full command menu sits right below the recommendation. +It sizes up the project and points you at the best next move. For example: no `DESIGN.md` yet, run `document`; unresolved findings in files you are editing, run `polish`. It waits for you to choose. Or describe what you want and it does the work directly: @@ -48,7 +51,7 @@ Or describe what you want and it does the work directly: /impeccable build me a pricing page for a developer tool ``` -Both prompts are vague on purpose. `/impeccable` will pick a strong aesthetic direction consistent with your register, commit to non-default fonts, avoid the AI color palette, and make the kind of specific choices that a designer would make. No command name to pick first, no step-by-step workflow to follow. +Both prompts are vague on purpose. `/impeccable` will choose the right command or run the work directly, using your setup files when they exist. For visual iteration in the browser rather than chat: @@ -60,7 +63,7 @@ Pick any element on your running dev server. Drop a comment or stroke. Get three ## Pin commands back as shortcuts -v3.0 consolidated 18 standalone skills into a single `/impeccable` with 23 sub-commands. If you miss the short form of a specific command, pin it back: +v3.0 consolidated 18 standalone skills into a single `/impeccable` with 23 commands. If you miss the short form of a command, pin it back: ``` /impeccable pin critique @@ -81,5 +84,5 @@ To remove: `/impeccable unpin critique`. Pins live as directories named after th - **Treating it like a style guide.** It is an opinionated design partner, not a linter. The defaults exist to raise the floor, not to overrule your judgment. If you have a real reason to push back (brand guideline, accessibility constraint, user research), push back and explain why. The skill will work with you. What produces worse output is ignoring the opinion without a reason. - **Expecting it to fix existing code.** `/impeccable` is for creation. For refinement, reach for `/impeccable polish`, `/impeccable distill`, or `/impeccable critique` instead. -- **Running it before `init` has had a chance to save context.** On a fresh project it will interview you mid-flight, which is fine but slower. Running `/impeccable init` explicitly as your very first command is a tiny bit smoother. -- **Skipping the register question.** Brand and product defaults diverge enough that running on the wrong register produces subtly off output. If `PRODUCT.md` has no `## Register` field (legacy), run `/impeccable init` to add it. +- **Running it before `init` has saved context.** On a fresh project it will interview you mid-flight, which is fine but slower. Running `/impeccable init` first is smoother. +- **Picking the wrong brand/product lane.** Marketing pages and app screens need different defaults. If `PRODUCT.md` has no `## Register` field (legacy), run `/impeccable init` to add it. diff --git a/site/content/skills/init.md b/site/content/skills/init.md index c808b0f07..d7c7bb8ff 100644 --- a/site/content/skills/init.md +++ b/site/content/skills/init.md @@ -33,7 +33,7 @@ tagline: "Set up a project for Impeccable, once. Context, live mode, and where t ## When to use it -Run `/impeccable init` once at the start of a project. It is the onramp. Without it, every other command will produce design that is technically competent but generically toned: stock SaaS voice, safe-default fonts, the AI color palette. With it, every command reads your answers before it generates. +Run `/impeccable init` once at the start of a project. Without it, every other command has to guess: generic SaaS voice, safe-default fonts, the AI color palette. With it, every command reads your answers before it generates. Reach for it when: @@ -43,13 +43,18 @@ Reach for it when: ## How it works -One codebase crawl feeds everything init writes: +One codebase scan feeds everything init writes: -- **`PRODUCT.md`** is the strategic file. Register (brand or product), target users, product purpose, brand personality, anti-references, design principles, accessibility needs. Answers "who, what, why". +- **`PRODUCT.md`** is the strategic file. It stores the audience, product purpose, voice, anti-references, design principles, accessibility needs, and the brand/product choice. Answers "who, what, why". - **`DESIGN.md`** is the visual file. Colors, typography, elevation, components, do's and don'ts. Answers "how it looks". Written by the delegated `/impeccable document` command, which init invokes at the end. - **Live mode config.** Since the same crawl already knows your framework and entry files, init pre-configures `/impeccable live` so it opens straight into variant mode with no first-time setup. -The flow scans the codebase first (README, package.json, components, tokens, brand assets) and forms a **register hypothesis**: brand (landing, marketing, portfolio, where design IS the product) or product (app UI, dashboards, tools, where design SERVES the product). Register is the first question, because it shapes every downstream answer: typography defaults, motion energy, color strategy, the reference set commands like `/impeccable typeset` pull from. After register, init asks only what it could not infer: users, personality in three real words, references and anti-references, accessibility requirements. +The flow scans the codebase first (README, package.json, components, tokens, brand assets) and asks you to confirm one core choice: is this a brand surface or a product surface? + +- **Brand:** landing pages, marketing pages, portfolios, campaigns. The impression is the product. +- **Product:** app UI, dashboards, admin screens, tools. The design helps someone finish a task. + +The docs call that choice **register**. It shapes typography, motion, color, and density. After that, init asks only what it could not infer: users, personality in three real words, references and anti-references, accessibility requirements. PRODUCT.md is strategic only. No colors, no fonts, no pixel values. Those live in DESIGN.md. Keeping the two files separate is deliberate: strategy can stay stable while the visual system evolves. @@ -61,7 +66,7 @@ It closes by pointing you at the best commands to run next, picked from what the /impeccable init ``` -Expect a 5 to 8 minute interview. The first question is usually about register; the rest are short. Init will quote back what it inferred from your code ("from the routes, this looks like a product surface, match?") so you are confirming, not starting from scratch. +Expect a 5 to 8 minute interview. The first question is usually the brand/product choice; the rest are short. Init will quote back what it inferred from your code ("from the routes, this looks like a product surface, match?") so you are confirming, not starting from scratch. Along the way it offers to run `/impeccable document` for you. Say yes unless you have a specific reason to hold off. A real DESIGN.md is what keeps variants, polishes, and audits on-brand. diff --git a/site/content/tutorials/brand-vs-product.md b/site/content/tutorials/brand-vs-product.md new file mode 100644 index 000000000..83c709b6e --- /dev/null +++ b/site/content/tutorials/brand-vs-product.md @@ -0,0 +1,73 @@ +--- +title: Brand vs product +tagline: "The setup choice that keeps Impeccable from applying the wrong design defaults." +order: 3 +description: "Understand the brand/product choice in /impeccable init: marketing surfaces where the impression is the product, and app surfaces where design helps users finish a task." +--- + +## The short version + +During `/impeccable init`, the first important choice is this: + +- **Brand surface:** marketing site, landing page, campaign, portfolio, editorial page. The visitor's impression is the product. +- **Product surface:** app UI, dashboard, admin screen, workflow tool, settings page. The user is trying to finish a task. + +The docs call this choice **register**. You do not need to use that word. You only need to answer which kind of surface you are working on. + +## Why it matters + +The same visual move can be right in one surface and wrong in another. + +A campaign page can afford a huge image, a strange type choice, one dominant idea per screen, and more expressive motion. It needs to create an impression. + +A dashboard usually needs density, predictable components, readable states, stable navigation, and quieter motion. It needs to help someone act quickly. + +When `PRODUCT.md` stores the right choice, every command adjusts. `/impeccable typeset` will not push editorial display type into a dense admin screen. `/impeccable colorize` will not make a campaign page timid because product UIs usually need restraint. + +## How to choose + +Ask what the person came to do. + +If they came to evaluate, trust, remember, compare, or feel a brand: choose **brand**. + +Examples: + +- SaaS landing page +- Product launch page +- Agency portfolio +- Restaurant homepage +- Conference site +- Case study page + +If they came to configure, monitor, submit, search, compare data, or complete a workflow: choose **product**. + +Examples: + +- Analytics dashboard +- Checkout flow +- Settings screen +- Admin table +- Onboarding flow inside an app +- Internal operations tool + +## Mixed projects + +Many codebases have both. A SaaS project can have a brand landing page and a product dashboard. + +Set the project default to the surface you work on most, then be specific in the command: + +``` +/impeccable polish the marketing homepage as a brand surface +``` + +``` +/impeccable audit the billing settings as a product surface +``` + +You can also edit `PRODUCT.md` directly if the default is wrong. + +## Common mistakes + +- **Calling everything product because it is a software company.** A landing page is still a brand surface. +- **Calling everything brand because the visual identity matters.** A dashboard can be on-brand and still be a product surface. +- **Skipping the choice.** Impeccable can still work, but it will ask more questions and lean on safer defaults. diff --git a/site/content/tutorials/critique-with-overlay.md b/site/content/tutorials/critique-with-overlay.md index 76e2f119a..18fd0e9c8 100644 --- a/site/content/tutorials/critique-with-overlay.md +++ b/site/content/tutorials/critique-with-overlay.md @@ -35,7 +35,7 @@ It labels the tab it opens with `[LLM]` in the title so you can tell which one i ### What the automated detector does -The second assessment runs `npx impeccable detect` against the page. This is deterministic: around thirty specific pattern checks that fire or do not fire. Gradient text, purple palettes, side-tab borders, nested cards, line length problems, low contrast, tiny body text, and the rest. The [full catalog](/anti-patterns) lists every rule and which layer (CLI, browser, or LLM-only) catches it. +The second assessment runs `npx impeccable detect` against the page. This is deterministic: specific pattern checks that fire or do not fire. Gradient text, purple palettes, side-tab borders, nested cards, line length problems, low contrast, tiny body text, and the rest. The [full catalog](/slop#catalog) lists every rule and which layer (CLI, browser, or LLM-only) catches it. You get back a JSON list of every finding with its element selector, the rule that fired, and a short description. diff --git a/site/content/tutorials/getting-started.md b/site/content/tutorials/getting-started.md index 8ad43f14c..0c38fa931 100644 --- a/site/content/tutorials/getting-started.md +++ b/site/content/tutorials/getting-started.md @@ -1,13 +1,13 @@ --- title: Getting started -tagline: "From zero to your first polish pass in five minutes." +tagline: "From install to your first polish pass in about ten minutes." order: 1 description: "Install Impeccable, run /impeccable init once to establish project context, and run /impeccable polish on something that already exists. The fastest path to seeing what Impeccable changes about AI-generated design." --- ## What you'll build -You will end this tutorial with Impeccable installed in your project, a `PRODUCT.md` plus `DESIGN.md` pair that captures your brand, audience, and visual system, and one hand-polished page that went through a polish pass. Total time: about ten minutes. +You will end this tutorial with Impeccable installed, project context saved, and one existing page improved with a polish pass. Total time: about ten minutes. ## Prerequisites @@ -16,7 +16,7 @@ You will end this tutorial with Impeccable installed in your project, a `PRODUCT ## How Impeccable works -Impeccable installs as a single agent skill called `impeccable`. You access all 23 sub-commands through it: +Impeccable installs as a single agent skill called `impeccable`. You access all 23 commands through it: ``` /impeccable @@ -26,6 +26,14 @@ For example: `/impeccable polish the pricing page`, or `/impeccable audit the ch If you use a command often, pin it with `/impeccable pin ` to create a standalone shortcut (for example, `/impeccable pin audit` gives you `/audit` directly). +If you only remember one sequence, make it this: + +``` +npx impeccable skills install +/impeccable init +/impeccable polish the page you care about +``` + ## Step 1. Install From the root of your project, run: @@ -34,7 +42,7 @@ From the root of your project, run: npx impeccable skills install ``` -This auto-detects your harness and writes the build compiled for it to the right location (e.g., `.claude/skills/`, `.cursor/skills/`). It works with Cursor, Claude Code, GitHub Copilot, Gemini CLI, Codex CLI, and every other major harness. Reload your harness and type `/`. You should see `/impeccable` in the autocomplete. Type it and the skill's argument hint will show all available commands. +This auto-detects your AI coding tool and writes the right skill files for it (for example, `.claude/skills/` or `.cursor/skills/`). It works with Cursor, Claude Code, GitHub Copilot, Gemini CLI, Codex CLI, and every other major harness. Reload your tool and type `/`. You should see `/impeccable` in the autocomplete. Type it and the argument hint will show the available commands. Prefer a different setup? Claude Code users can install the plugin with `/plugin marketplace add pbakaus/impeccable`, and the general-purpose `npx skills add pbakaus/impeccable` still works (though it installs one shared build for all harnesses rather than the one compiled for yours). @@ -42,7 +50,7 @@ When a new version ships later, run `npx impeccable skills update` from the same ## Step 2. Set up Impeccable for your project -This is the most important step. Design without context produces generic output. The `/impeccable init` command runs a short discovery interview and writes a `PRODUCT.md` file at the root of your project. +This is the most important step. Design without context produces generic output. The `/impeccable init` command runs a short setup interview and writes `PRODUCT.md` at the root of your project. Run: @@ -50,7 +58,12 @@ Run: /impeccable init ``` -The first question is about **register**: is this a brand surface (marketing site, landing page, portfolio, where design IS the product) or a product surface (app UI, dashboard, tools, where design SERVES the product)? Register shapes every downstream default, from type lanes to motion energy. See [brand vs product](/tutorials/brand-vs-product) for how the two diverge. Init will form a hypothesis from your codebase and ask you to confirm, rather than starting cold. +The first question is simple: is this a **brand surface** or a **product surface**? + +- **Brand surface:** marketing site, landing page, campaign, portfolio. The impression is the product. +- **Product surface:** app UI, dashboard, admin, workflow tool. The design helps someone finish a task. + +The docs call this choice **register**. It changes the defaults Impeccable uses for type, color, density, and motion. See [Brand vs product](/tutorials/brand-vs-product) for examples. Init forms a hypothesis from your codebase and asks you to confirm it. Then a handful of shorter questions: @@ -59,15 +72,15 @@ Then a handful of shorter questions: - **Any visual references?** Named brands, products, or printed objects, not adjectives. "Klim Type Foundry specimen pages", not "technical and clean". - **Anti-references?** Things the product should explicitly not look like, equally named. -Answer in your own words. The skill writes `PRODUCT.md` with the answers. Every future command run reads it automatically. +Answer in your own words. The skill writes `PRODUCT.md` with the answers, and every future command reads it automatically. Open `PRODUCT.md` and read what it wrote. Edit anything that does not feel right. The file is yours. ## Step 2.5. Capture the visual system -At the end of `/impeccable init`, the skill offers to run `/impeccable document` for you. Say yes. It scans your tokens (CSS custom properties, Tailwind config, CSS-in-JS themes), extracts colors and typography, asks one grouped question for the parts that need creative input (a Creative North Star, descriptive color names), and writes a `DESIGN.md` that follows the [Google Stitch DESIGN.md format](https://stitch.withgoogle.com/docs/design-md/format/). +At the end of `/impeccable init`, the skill offers to run `/impeccable document` for you. Say yes. It looks at your existing colors, type, components, and tokens, then writes `DESIGN.md` in the [Google Stitch DESIGN.md format](https://stitch.withgoogle.com/docs/design-md/format/). -On a fresh project with no tokens yet, document runs in seed mode: five quick questions about color strategy, type direction, and motion energy, and writes a scaffold you can refresh once there is code. +On a fresh project with no visual system yet, document asks a few setup questions and writes a starting scaffold. Refresh it once there is real code. `PRODUCT.md` carries strategy (who, what, why). `DESIGN.md` carries visuals (colors, typography, components). Every command reads both before generating. diff --git a/site/pages/designing/index.astro b/site/pages/designing/index.astro index 1bde5c5e4..e3d5a76b4 100644 --- a/site/pages/designing/index.astro +++ b/site/pages/designing/index.astro @@ -16,7 +16,7 @@ import '../../styles/designing-kinpaku.css';

Designing with Impeccable

-

Most of the work isn't the blank page. It's everything after: refine, polish, pay down the drift. Four phases, one place to begin in each.

+

Use Impeccable in four phases: set context, improve what exists, check before shipping, and keep the design system from drifting.

@@ -81,8 +81,8 @@ import '../../styles/designing-kinpaku.css';
-

Net-new is the hard mode.

-

From a blank file is where coding agents are weakest, Impeccable included. It can start cold, but it works better reacting to something real, so start from a picture, not a paragraph. Bring a direction from Figma, a phone photo of a sketch, or Google Stitch, or let /impeccable craft generate the mock with GPT Image 2 (sharpest in Codex). /impeccable init writes the brief first either way.

+

Start with context.

+

Before asking an agent to design, give it the basics: who the interface is for, what it should feel like, what it should avoid, and whether this is a marketing surface or an app screen. /impeccable init saves that context first. Then use /impeccable shape for a brief or /impeccable craft for the full build.

/impeccable init /impeccable shape @@ -118,13 +118,13 @@ import '../../styles/designing-kinpaku.css';
-

Teach runs a short discovery interview about audience, register, voice, and anti-references. It writes PRODUCT.md and, if there's code to scan, a DESIGN.md. Every later command reads both files before generating.

+

Init runs a short setup interview about audience, brand/product lane, voice, and anti-references. It writes PRODUCT.md and, if there's code to scan, offers a DESIGN.md. Every later command reads both files before generating.

-

Since image generation crossed the reference-quality threshold, shape can draft a brand toolkit you review at a glance, and craft can code toward a hi-fi mock instead of a paragraph. Neo Mirai is the full loop: generated direction, implemented page, browser iteration.

+

For blank-slate work, a visual reference helps more than a long prompt. shape can draft a brand toolkit for review, and craft can code toward a hi-fi mock instead of a paragraph. Neo Mirai is the full loop: generated direction, implemented page, browser iteration.

diff --git a/site/pages/docs/index.astro b/site/pages/docs/index.astro index 70a6b4bb3..12b2edc5c 100644 --- a/site/pages/docs/index.astro +++ b/site/pages/docs/index.astro @@ -33,6 +33,63 @@ const session = [ { slug: 'polish', verb: 'Refine' }, ]; +const startSteps = [ + { + label: 'Install', + command: 'npx impeccable skills install', + line: 'Run this from the project root, then reload your AI coding tool.', + }, + { + label: 'Set context', + command: '/impeccable init', + line: 'Answer the short setup interview. Say yes when it offers DESIGN.md.', + }, + { + label: 'Try it', + command: '/impeccable polish the pricing page', + line: 'Pick any page or component that already exists.', + }, +]; + +const choices = [ + { + need: 'Set up a project', + command: '/impeccable init', + href: '/docs/init', + line: 'Create PRODUCT.md and DESIGN.md.', + }, + { + need: 'Improve an existing page', + command: '/impeccable polish', + href: '/docs/polish', + line: 'Fix spacing, type, states, copy, and consistency.', + }, + { + need: 'Find design issues', + command: '/impeccable critique', + href: '/docs/critique', + line: 'Get the highest-priority design problems.', + }, + { + need: 'Check implementation quality', + command: '/impeccable audit', + href: '/docs/audit', + line: 'Check accessibility, responsiveness, performance, and detector findings.', + }, + { + need: 'Point at UI in the browser', + command: '/impeccable live', + href: '/docs/live', + line: 'Pick an element, generate variants, accept one into source.', + }, + { + need: 'Build a new feature', + command: '/impeccable craft', + href: '/docs/craft', + line: 'Plan, build, and visually check the feature.', + }, +]; + const chains: { id: string; a: string; b: string; arrow: string; line: string }[] = [ { id: 'bolder-quieter', a: 'bolder', b: 'quieter', arrow: '↔', line: 'Two halves of voice. Loud or restrained, never neutral.' }, { id: 'audit-harden', a: 'audit', b: 'harden', arrow: '→', line: 'Find the technical issues. Then fix them.' }, @@ -43,7 +100,7 @@ const chains: { id: string; a: string; b: string; arrow: string; line: string }[ -
+
-
-

How you invoke /impeccable.

-

A loop, not a list. Run one sub-command in a chat, chain a few in a session, or open the live picker and iterate on the surface in your browser.

-
+
+
+

Start with /impeccable.

+

Install, run setup, then try one command on an existing page.

+
- +
    + {startSteps.map((step, i) => ( +
  1. + {String(i + 1).padStart(2, "0")} + {step.label} + {step.command} + {step.line} +
  2. + ))} +
+ +

Full walkthrough: Getting started.

+
+ +
+
+

Choose a command.

+
+ +
+ {choices.map(choice => ( + + {choice.need} + {choice.command} + {choice.line} + + ))} +
+
+ +

Iterate where you can see it.

-

Click an element in the running app. Pick a sub-command, optionally annotate, hit Go. Impeccable wraps the element, writes two or three variants into the source, and HMR cycles them in front of you. Accept the one you want. The rest are removed.

+

Use Live Mode when an issue is easier to point at than describe. Pick an element, generate variants, accept one into source.

- Read the live spec + Read the live docs @@ -102,8 +190,8 @@ const chains: { id: string; a: string; b: string; arrow: string; line: string }[

-

Plan, build, review, refine.

-

One canonical shape. Each step is a sub-command you invoke in chat.

+

Full session path.

+

Plan, build, review, refine.

    @@ -124,15 +212,15 @@ const chains: { id: string; a: string; b: string; arrow: string; line: string }[

- Already shipped? Skip ahead with /impeccable live on the page that needs work, or /impeccable audit for a technical pass. + Already shipped? Use /impeccable polish on the page that needs work, or /impeccable audit for checks.

-

How commands imply each other.

-

Four shapes that show up across most sessions. Learn these and the rest is filling in detail.

+

Useful command pairs.

+

Use these when one pass is not enough.

@@ -152,7 +240,7 @@ const chains: { id: string; a: string; b: string; arrow: string; line: string }[
-

Every sub-command, by lifecycle phase.

+

Full command reference.

diff --git a/site/pages/index.astro b/site/pages/index.astro index 8a8bf3668..a94bf251c 100644 --- a/site/pages/index.astro +++ b/site/pages/index.astro @@ -609,6 +609,24 @@ import '../styles/testimonials.css';
+
+ First run +
+ + /impeccable init + +
+
+
Update
@@ -627,7 +645,7 @@ import '../styles/testimonials.css';
-

Works with Cursor, Claude Code, GitHub Copilot, Gemini CLI, Codex CLI, and every other major AI coding harness. install sets up the build for your harness; update pulls the latest. Run npx impeccable skills check to see if you're behind. Installed the Claude Code plugin? Update it from the /plugin menu instead.

+

Works with Cursor, Claude Code, GitHub Copilot, Gemini CLI, Codex CLI, and every other major AI coding harness. install sets up the build for your harness. Then run /impeccable init inside your AI tool so the skill can save your project context. update pulls the latest version. Run npx impeccable skills check to see if you're behind. Installed the Claude Code plugin? Update it from the /plugin menu instead.

@@ -672,7 +690,7 @@ import '../styles/testimonials.css';
-

See what's new · Got questions?

+

Read the setup guide · See what's new · Got questions?

@@ -695,7 +713,7 @@ import '../styles/testimonials.css'; -

Learn more: Designing with Impeccable · the command docs

+

Learn more: Getting started · Designing with Impeccable · command docs

diff --git a/site/public/llms.txt b/site/public/llms.txt index d1a6d08b1..2e369b3e8 100644 --- a/site/public/llms.txt +++ b/site/public/llms.txt @@ -8,7 +8,7 @@ Use the website pages below as the current public documentation. Use the GitHub - [Home](https://impeccable.style/): Product overview, install options, supported AI coding harnesses, and download entry points. - [Designing with Impeccable](https://impeccable.style/designing): The end-to-end workflow from setup and brief to iteration, polish, and maintenance. -- [Documentation](https://impeccable.style/docs): Command reference index for `/impeccable` and its sub-commands. +- [Documentation](https://impeccable.style/docs): Command reference index for `/impeccable` and its commands. - [Getting started](https://impeccable.style/tutorials/getting-started): Install Impeccable, run `/impeccable init`, create project context, and start with a polish pass. - [Live Mode](https://impeccable.style/live-mode): Browser-based UI iteration with element picking, annotations, generated variants, and source writeback. - [Slop](https://impeccable.style/slop): Catalog of AI-generated UI anti-patterns and the detection overlay behind the CLI, extension, and critique workflow. @@ -17,6 +17,7 @@ Use the website pages below as the current public documentation. Use the GitHub - [Tutorials index](https://impeccable.style/tutorials): Step-by-step guides for installation, live iteration, and visual critique. - [Iterate on UI with Live Mode](https://impeccable.style/tutorials/iterate-live): Use `/impeccable live` against a running dev server to generate and accept UI variants. +- [Brand vs product](https://impeccable.style/tutorials/brand-vs-product): Explain the setup choice between marketing surfaces and app surfaces. - [Critique with the visual overlay](https://impeccable.style/tutorials/critique-with-overlay): Combine `/impeccable critique`, deterministic detection, and the browser overlay. ## Command Reference diff --git a/site/styles/docs-kinpaku.css b/site/styles/docs-kinpaku.css index 2f92c9d41..87c880af0 100644 --- a/site/styles/docs-kinpaku.css +++ b/site/styles/docs-kinpaku.css @@ -29,6 +29,13 @@ surfaces without a per-page remap. Only the --ks-muted alias (read by name in this file) and the page shell remain. */ --ks-muted: var(--ks-text-muted); + --docs-accent: var(--ks-kinpaku); + --docs-accent-hover: var(--ks-kinpaku-pale); + --docs-accent-line: oklch(78% 0.12 82 / 0.42); + --docs-accent-wash: oklch(77% 0.13 82 / 0.10); + --docs-brand-accent: var(--ks-kinpaku); + --docs-panel-bg: var(--ks-lacquer-raised); + --docs-panel-hover-bg: oklch(11% 0.012 82); background: linear-gradient(180deg, oklch(7% 0.006 95), oklch(4% 0.004 95)); @@ -37,6 +44,27 @@ min-height: 100vh; } +html.light .docs-kinpaku { + --ks-lacquer: oklch(100% 0 0); + --ks-lacquer-deep: oklch(100% 0 0); + --ks-lacquer-raised: oklch(99% 0 0); + --ks-graphite: oklch(94% 0 0); + --ks-graphite-2: oklch(90% 0 0); + --ks-champagne: oklch(16% 0 0); + --ks-text: oklch(24% 0 0); + --ks-text-muted: oklch(42% 0 0); + --ks-text-faint: oklch(55% 0 0); + --ks-rule: oklch(20% 0 0 / 0.12); + --docs-accent: var(--ks-patina); + --docs-accent-hover: var(--ks-patina-deep); + --docs-accent-line: oklch(70% 0.12 188 / 0.36); + --docs-accent-wash: oklch(70% 0.12 188 / 0.08); + --docs-brand-accent: var(--ks-kinpaku); + --docs-panel-bg: oklch(100% 0 0 / 0.76); + --docs-panel-hover-bg: oklch(70% 0.12 188 / 0.07); + background: oklch(100% 0 0); +} + .docs-kinpaku a { color: inherit; } @@ -62,7 +90,7 @@ @media (max-width: 960px) { .docs-kinpaku .skills-layout { grid-template-columns: 1fr; - gap: 18px; + gap: 10px; } } @@ -70,9 +98,8 @@ /* ============================================================================ 2. SIDEBAR — commands rail - Sticky dark rail. Mono command names in champagne; the active command - gets a kinpaku 2px border-left accent. Category labels are mono caps in - kinpaku, mirroring the .ks-section-eyebrow voice. + Sticky rail. Command names use the main UI family; monospace is reserved + for literal commands and code, not navigation. ============================================================================ */ .docs-kinpaku .skills-sidebar { @@ -131,11 +158,12 @@ .docs-kinpaku .skills-sidebar-category { display: block; - font-family: var(--ks-mono); - font-size: 0.66rem; - letter-spacing: 0.24em; + font-family: var(--ks-font); + font-size: 0.72rem; + font-weight: 650; + letter-spacing: 0.14em; text-transform: uppercase; - color: var(--ks-kinpaku); + color: var(--docs-accent); margin-bottom: 10px; padding: 0 0 0 14px; } @@ -154,8 +182,8 @@ display: block; padding: 5px 0 5px 12px; border-left: 2px solid transparent; - font-family: var(--ks-mono); - font-size: 0.86rem; + font-family: var(--ks-font); + font-size: 0.94rem; font-weight: 400; line-height: 1.5; color: var(--ks-text); @@ -165,13 +193,13 @@ } .docs-kinpaku .skills-sidebar-list a:hover { - color: var(--ks-kinpaku); + color: var(--docs-accent-hover); } .docs-kinpaku .skills-sidebar-list a[aria-current="page"] { - color: var(--ks-kinpaku); - font-weight: 500; - border-left-color: var(--ks-kinpaku); + color: var(--docs-accent); + font-weight: 600; + border-left-color: var(--docs-accent); } @@ -196,16 +224,17 @@ background: transparent; border: 0; color: var(--ks-champagne); - font-family: var(--ks-mono); - font-size: 0.72rem; - letter-spacing: 0.22em; + font-family: var(--ks-font); + font-size: 0.76rem; + font-weight: 650; + letter-spacing: 0.14em; text-transform: uppercase; cursor: pointer; } .docs-kinpaku .skills-sidebar-toggle-chevron { transition: transform 220ms var(--ks-ease); - color: var(--ks-kinpaku); + color: var(--docs-accent); flex-shrink: 0; } @@ -228,66 +257,69 @@ 3. /docs INDEX Sections, in render order: - a. Hero - b. Live mode callout (the headliner capability) - c. A typical session (linear workflow of 4 commands) - d. Pairs and chains (relational schema) - e. Full reference (cheatsheet by lifecycle phase) + a. Starter hero + b. Intent chooser + c. Live mode callout + d. A typical session (linear workflow of 4 commands) + e. Pairs and chains (relational schema) + f. Full reference (cheatsheet by lifecycle phase) ============================================================================ */ .docs-kinpaku .docs-index { - padding: clamp(64px, 8vw, 110px) 0 clamp(80px, 10vw, 140px); + padding: clamp(34px, 5vw, 70px) 0 clamp(80px, 10vw, 140px); } -/* a. Hero -------------------------------------------------------------- */ +/* a. Starter rail ------------------------------------------------------ */ -.docs-kinpaku .docs-index-header { - margin-bottom: clamp(72px, 9vw, 110px); - max-width: 760px; +.docs-kinpaku .docs-start-rail { + display: grid; + gap: clamp(26px, 4vw, 44px); + margin-bottom: clamp(48px, 7vw, 88px); } -.docs-kinpaku .docs-index-eyebrow { - color: var(--ks-kinpaku); - font-family: var(--ks-mono); - font-size: var(--ks-type-eyebrow-size); - letter-spacing: var(--ks-type-eyebrow-track); - text-transform: uppercase; - margin: 0 0 18px; +@media (max-width: 520px) { + .docs-kinpaku .docs-index { + padding-top: 22px; + } } -.docs-kinpaku .docs-index-title { +.docs-kinpaku .docs-start-rail-head { + display: grid; + gap: 18px; + max-width: 1040px; +} + +.docs-kinpaku .docs-start-rail-title { font-family: var(--ks-font-display); font-style: normal; font-weight: var(--ks-type-display-weight); - font-size: var(--ks-type-display-size); + font-size: clamp(3.2rem, calc(3.1rem + 2vw), 5.6rem); line-height: var(--ks-type-display-line); letter-spacing: var(--ks-type-display-track); color: var(--ks-champagne); - margin: 0 0 22px; + margin: 0; text-wrap: balance; } -.docs-kinpaku .docs-index-mark { - color: var(--ks-kinpaku); - font-family: var(--ks-mono); - font-weight: 400; - letter-spacing: 0; - font-size: 0.82em; - vertical-align: 0.04em; +.docs-kinpaku .docs-start-rail-title span { + color: var(--docs-brand-accent); + font-family: var(--ks-font-wordmark); + font-weight: 500; + font-size: 0.96em; } -.docs-kinpaku .docs-index-lede { - font-size: 1.1rem; - line-height: 1.65; +.docs-kinpaku .docs-start-rail-lede { + font-size: 1.04rem; + line-height: 1.62; color: var(--ks-text); margin: 0; - max-width: 58ch; } -/* Shared section scaffolding for the four index sections ---------------- */ +/* Shared section scaffolding for the index sections ---------------- */ +.docs-kinpaku .docs-chooser-section, .docs-kinpaku .docs-live, .docs-kinpaku .docs-flow-section, .docs-kinpaku .docs-chains-section, @@ -311,6 +343,7 @@ margin: 0 0 14px; } +.docs-kinpaku .docs-chooser-title, .docs-kinpaku .docs-live-title, .docs-kinpaku .docs-flow-title, .docs-kinpaku .docs-chains-title, @@ -320,7 +353,7 @@ font-weight: var(--ks-type-headline-weight); font-size: var(--ks-type-headline-size); line-height: var(--ks-type-headline-line); - letter-spacing: -0.005em; + letter-spacing: 0; color: var(--ks-champagne); margin: 0 0 14px; text-wrap: balance; @@ -337,15 +370,178 @@ } -/* b. Live mode callout ------------------------------------------------- */ +/* Quick start rail ----------------------------------------------------- */ + +.docs-kinpaku .docs-start-rail-steps { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + border-top: 1px solid var(--ks-rule); + border-bottom: 1px solid var(--ks-rule); +} + +@media (max-width: 940px) { + .docs-kinpaku .docs-start-rail-steps { + grid-template-columns: 1fr; + } +} + +.docs-kinpaku .docs-start-rail-step { + display: grid; + grid-template-rows: auto auto minmax(3.2em, auto) auto; + gap: 10px; + min-width: 0; + padding: 22px clamp(18px, 2.8vw, 30px) 24px; + border-right: 1px solid var(--ks-rule); +} + +.docs-kinpaku .docs-start-rail-step:last-child { + border-right: 0; +} + +.docs-kinpaku .docs-start-rail-num { + font-family: var(--ks-mono); + font-size: 0.72rem; + line-height: 1.1; + letter-spacing: 0.18em; + color: var(--docs-accent); +} + +.docs-kinpaku .docs-start-rail-label { + font-family: var(--ks-mono); + font-size: 0.66rem; + line-height: 1.1; + letter-spacing: 0.24em; + text-transform: uppercase; + color: var(--ks-text-muted); +} + +.docs-kinpaku .docs-start-rail-command { + display: block; + font-family: var(--ks-mono); + font-size: 0.94rem; + line-height: 1.45; + color: var(--docs-accent-hover); + min-height: 2.8em; + white-space: normal; + overflow-wrap: anywhere; +} + +.docs-kinpaku .docs-start-rail-line { + font-size: 0.9rem; + line-height: 1.5; + color: var(--ks-text-muted); +} + +.docs-kinpaku .docs-start-rail-note { + margin: 20px 0 0; + font-size: 0.95rem; + line-height: 1.6; + color: var(--ks-text-muted); +} + +.docs-kinpaku .docs-start-rail-note a { + color: var(--docs-accent); + text-decoration: none; + border-bottom: 1px solid var(--docs-accent-line); +} + +.docs-kinpaku .docs-start-rail-note a:hover { + color: var(--docs-accent-hover); + border-bottom-color: var(--docs-accent-hover); +} + +@media (max-width: 940px) { + .docs-kinpaku .docs-start-rail-step, + .docs-kinpaku .docs-start-rail-step:last-child { + border-right: 0; + border-left: 0; + border-top: 0; + border-bottom: 1px solid var(--ks-rule); + padding-inline: 0; + } + + .docs-kinpaku .docs-start-rail-command { + min-height: 0; + } +} + + +/* b. Intent chooser ---------------------------------------------------- */ + +.docs-kinpaku .docs-chooser-head { + margin-bottom: 26px; +} + +.docs-kinpaku .docs-chooser-list { + display: grid; + grid-template-columns: 1fr; + border-top: 1px solid var(--ks-rule); +} + +.docs-kinpaku .docs-chooser-list-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px 22px; + align-items: baseline; + padding: 18px 0 20px; + background: transparent; + border: 0; + border-bottom: 1px solid var(--ks-rule); + border-radius: 0; + text-decoration: none; + color: inherit; + transition: border-color 160ms var(--ks-ease), + color 160ms var(--ks-ease); +} + +.docs-kinpaku .docs-chooser-list-row:hover { + border-bottom-color: var(--docs-accent-line); +} + +.docs-kinpaku .docs-chooser-list-need { + font-size: 1rem; + line-height: 1.35; + color: var(--ks-champagne); +} + +.docs-kinpaku .docs-chooser-list-command { + font-family: var(--ks-mono); + font-size: 0.78rem; + color: var(--docs-accent); + line-height: 1.4; + white-space: nowrap; +} + +.docs-kinpaku .docs-chooser-list-line { + grid-column: 1 / -1; + font-size: 0.9rem; + line-height: 1.5; + color: var(--ks-text-muted); + max-width: 68ch; +} + +@media (max-width: 520px) { + .docs-kinpaku .docs-chooser-list-row { + grid-template-columns: 1fr; + } + + .docs-kinpaku .docs-chooser-list-command { + white-space: normal; + } +} + + +/* c. Live mode callout ------------------------------------------------- */ .docs-kinpaku .docs-live { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr); gap: clamp(36px, 5vw, 64px); align-items: center; - padding: 36px 0 0; - border-top: 1px solid var(--ks-rule); + padding: 0; } @media (max-width: 880px) { @@ -359,20 +555,20 @@ margin: 26px 0 0; } -/* Static browser frame mock. Lacquer-deep ground, kinpaku active outline - around the targeted element, mock action bar underneath. No animation; - this is a teaching diagram, not a demo. */ +/* Static browser frame mock. Light, low-shadow frame with a precise target + outline and mock action bar underneath. No animation; this is a teaching + diagram, not a demo. */ .docs-kinpaku .docs-live-figure { margin: 0; } .docs-kinpaku .docs-live-frame { position: relative; - background: var(--ks-lacquer-deep); + background: var(--ks-lacquer-raised); border: 1px solid var(--ks-rule); border-radius: 4px; overflow: hidden; - box-shadow: 0 24px 60px oklch(2% 0.004 95 / 0.55); + box-shadow: 0 14px 30px oklch(0% 0 0 / 0.08); } .docs-kinpaku .docs-live-chrome { @@ -380,7 +576,7 @@ align-items: center; gap: 8px; padding: 12px 14px; - background: var(--ks-graphite); + background: transparent; border-bottom: 1px solid var(--ks-rule); } @@ -388,7 +584,7 @@ width: 9px; height: 9px; border-radius: 50%; - background: oklch(28% 0.01 95); + background: var(--ks-rule); } .docs-kinpaku .docs-live-url { @@ -401,21 +597,18 @@ .docs-kinpaku .docs-live-stage { position: relative; - padding: 42px 28px 28px; + padding: 38px 28px 28px; min-height: 220px; + background: transparent; } .docs-kinpaku .docs-live-target { position: relative; padding: 22px 24px; - border: 1px solid var(--ks-kinpaku); + border: 1px solid var(--docs-accent-line); border-radius: 2px; - background: oklch(11% 0.012 82); - /* Picker outline corner ticks (top-left & bottom-right) */ - box-shadow: - inset 0 0 0 1px transparent, - -2px -2px 0 -1px var(--ks-kinpaku), - 2px 2px 0 -1px var(--ks-kinpaku); + background: var(--ks-lacquer); + box-shadow: none; } .docs-kinpaku .docs-live-target-title { @@ -433,7 +626,7 @@ font-size: 0.7rem; letter-spacing: 0.22em; text-transform: uppercase; - color: var(--ks-kinpaku); + color: var(--docs-accent-hover); } .docs-kinpaku .docs-live-bar { @@ -442,7 +635,7 @@ align-items: center; gap: 14px; padding: 10px 14px; - background: oklch(8% 0.006 95); + background: var(--ks-lacquer); border: 1px solid var(--ks-rule); border-radius: 2px; font-family: var(--ks-mono); @@ -458,7 +651,7 @@ .docs-kinpaku .docs-live-bar-accept { margin-left: auto; - color: var(--ks-kinpaku); + color: var(--docs-accent-hover); } .docs-kinpaku .docs-live-bar-divider { @@ -469,7 +662,7 @@ } -/* c. A typical session (linear flow of commands) ----------------------- */ +/* d. A typical session (linear flow of commands) ----------------------- */ .docs-kinpaku .docs-flow-head { margin-bottom: 32px; @@ -501,7 +694,7 @@ align-content: start; gap: 8px; padding: 22px 22px 24px; - background: var(--ks-lacquer-raised); + background: var(--docs-panel-bg); border: 1px solid var(--ks-rule); border-radius: 2px; text-decoration: none; @@ -512,8 +705,8 @@ } .docs-kinpaku .docs-flow-link:hover { - border-color: var(--ks-kinpaku); - background: oklch(11% 0.012 82); + border-color: var(--docs-accent-line); + background: var(--docs-panel-hover-bg); } .docs-kinpaku .docs-flow-verb { @@ -521,7 +714,7 @@ font-size: 0.66rem; letter-spacing: 0.24em; text-transform: uppercase; - color: var(--ks-kinpaku); + color: var(--docs-accent); } .docs-kinpaku .docs-flow-cmd { @@ -540,7 +733,7 @@ display: grid; place-items: center; width: 36px; - color: var(--ks-kinpaku); + color: var(--docs-accent); } .docs-kinpaku .docs-flow-arrow svg { @@ -566,20 +759,21 @@ } .docs-kinpaku .docs-flow-alt a { - color: var(--ks-kinpaku); + color: var(--docs-accent); font-family: var(--ks-mono); font-size: 0.88em; text-decoration: none; - border-bottom: 1px solid oklch(78% 0.12 82 / 0.4); + border-bottom: 1px solid var(--docs-accent-line); transition: border-color 160ms var(--ks-ease); } .docs-kinpaku .docs-flow-alt a:hover { - border-bottom-color: var(--ks-kinpaku); + color: var(--docs-accent-hover); + border-bottom-color: var(--docs-accent-hover); } -/* d. Pairs and chains -------------------------------------------------- */ +/* e. Pairs and chains -------------------------------------------------- */ .docs-kinpaku .docs-chains-head { margin-bottom: 32px; @@ -601,7 +795,7 @@ display: grid; gap: 10px; padding: 28px 28px 30px; - background: var(--ks-lacquer-raised); + background: var(--docs-panel-bg); border: 1px solid var(--ks-rule); border-radius: 2px; } @@ -617,18 +811,19 @@ } .docs-kinpaku .docs-chain-cmd { - color: var(--ks-kinpaku); + color: var(--docs-accent); text-decoration: none; border-bottom: 1px solid transparent; transition: border-color 160ms var(--ks-ease); } .docs-kinpaku .docs-chain-cmd:hover { - border-bottom-color: var(--ks-kinpaku); + color: var(--docs-accent-hover); + border-bottom-color: var(--docs-accent-hover); } .docs-kinpaku .docs-chain-arrow { - color: var(--ks-kinpaku); + color: var(--docs-accent); font-size: 1.1em; } @@ -640,12 +835,10 @@ } -/* e. Full reference (cheatsheet) --------------------------------------- */ +/* f. Full reference (cheatsheet) --------------------------------------- */ .docs-kinpaku .docs-reference-head { margin-bottom: 32px; - padding-top: 36px; - border-top: 1px solid var(--ks-rule); } .docs-kinpaku .docs-reference { @@ -705,7 +898,7 @@ } .docs-kinpaku .docs-reference-row:hover .docs-reference-name { - color: var(--ks-kinpaku); + color: var(--docs-accent-hover); } .docs-kinpaku .docs-reference-row:hover .docs-reference-line { @@ -736,6 +929,10 @@ padding: 48px 0 clamp(80px, 10vw, 140px); } +.docs-kinpaku .skills-main--docs-index { + padding-top: 0; +} + .docs-kinpaku .skills-detail { max-width: 900px; } diff --git a/site/styles/kinpaku-tokens.css b/site/styles/kinpaku-tokens.css index 93df3cae4..e61e4fb0b 100644 --- a/site/styles/kinpaku-tokens.css +++ b/site/styles/kinpaku-tokens.css @@ -61,7 +61,7 @@ /* ============================================================ Rules. The hairline is neutral so borders/dividers/labels don't carry warmth (warmth lives in the gold accents and surfaces). For a - gold accent border, use --ks-kinpaku directly, not a muddy variant. + gold accent border, use --ks-kinpaku directly, not a readability variant. ============================================================ */ --ks-rule: oklch(78% 0 0 / 0.16); @@ -150,21 +150,21 @@ html.dark { } /* ============================================================ - Light mode — warm paper surfaces, dark ink type, kinpaku gold - accents deepened for contrast. Activated via html.light on + Light mode — pale surfaces, dark ink type, and kinpaku kept as the + real brand gold. Activated via html.light on (set by theme.js + inline FOUC guard in Base.astro). ============================================================ */ html.light { color-scheme: light; /* Brand gold — same fills as dark mode (logo, buttons, marks). - Use --ks-kinpaku-ink for small text on paper where bright gold - lacks contrast; never swap the core token to a muddy orange. */ + For decorative small text on paper, use patina. Use --ks-link-on-paper + for body-sized links that need stronger contrast. */ --ks-kinpaku: oklch(84% 0.19 80.46); --ks-kinpaku-pale: oklch(86% 0.07 84); --ks-kinpaku-rich: oklch(77% 0.13 82); --ks-kinpaku-deep: oklch(61% 0.085 78); - --ks-kinpaku-ink: oklch(56% 0.11 82); + --ks-kinpaku-ink: var(--ks-patina); /* Text links + active nav on paper — patina, not fill gold. */ --ks-link-on-paper: var(--ks-patina-deep); @@ -202,5 +202,5 @@ html.light { --ks-code-block-fg: oklch(30% 0.02 95); --ks-code-block-bg: var(--ks-lacquer-raised); --ks-code-block-border: var(--ks-rule); - --ks-code-cmd: var(--ks-kinpaku-ink); + --ks-code-cmd: var(--ks-link-on-paper); } diff --git a/site/styles/light-mode.css b/site/styles/light-mode.css index 30b8684ad..08a81538a 100644 --- a/site/styles/light-mode.css +++ b/site/styles/light-mode.css @@ -208,7 +208,8 @@ html.light .ks-button.ks-button-secondary:active { background: oklch(77% 0.13 82 / 0.16); } -/* Small labels / eyebrows — readable gold, not bright fill gold */ +/* Small labels / eyebrows on paper use the light-mode compatibility accent, + now patina rather than the old low-contrast gold alias. */ html.light :is( .ks-section-eyebrow, .ks-subsection-label, @@ -220,28 +221,39 @@ html.light :is( .home-kinpaku .ks-bento-num, .home-kinpaku .fisheye-item.is-active, .home-kinpaku .spread-command-name, - .docs-kinpaku .docs-index-eyebrow, - .docs-kinpaku .docs-live-eyebrow, - .docs-kinpaku .docs-flow-eyebrow, - .docs-kinpaku .docs-chains-eyebrow, - .docs-kinpaku .docs-reference-eyebrow, - .docs-kinpaku .docs-flow-verb, - .docs-kinpaku .skills-sidebar-category, - .docs-kinpaku .docs-live-target-sub, - .docs-kinpaku .docs-live-bar-accept, .cf-eyebrow ) { color: var(--ks-kinpaku-ink); } +/* Docs on paper: real kinpaku works for large display and fills, but small + links and labels should use patina instead of gold tuned for paper. */ +html.light body.docs-kinpaku :is( + .docs-index-eyebrow, + .docs-start-eyebrow, + .docs-chooser-eyebrow, + .docs-live-eyebrow, + .docs-flow-eyebrow, + .docs-chains-eyebrow, + .docs-reference-eyebrow, + .docs-start-num, + .docs-flow-verb, + .docs-flow-arrow, + .skills-sidebar-category, + .docs-live-target-sub, + .docs-live-bar-accept +) { + color: var(--docs-accent); +} + /* Command palette (magazine spread). The kicker, skill name (big title + active list item) all read in the section accent (--spread-accent) — gold for create/refine/simplify, verdigris for evaluate/harden, neutral for system — - matching the divider. On the near-white card these read fine. This clears the - muddier kinpaku-ink the shared eyebrow :is() rule above assigns; scoped under - .magazine-container so its specificity ties/beats that rule (and only touches - the palette). Other eyebrows keep kinpaku-ink for paper. Dark mode gets the - same accent colors via home-kinpaku.css. */ + matching the divider. On the near-white card these read fine. This keeps the + palette colors distinct from the shared light-mode small-text accent; scoped + under .magazine-container so its specificity ties/beats that rule (and only + touches the palette). Dark mode gets the same accent colors via + home-kinpaku.css. */ html.light .home-kinpaku .magazine-container .spread-command-name, html.light .home-kinpaku .magazine-container .spread-category-label, html.light .home-kinpaku .magazine-container .fisheye-item.is-active { @@ -540,7 +552,7 @@ html.light .docs-kinpaku .skills-sidebar { } html.light .docs-kinpaku .skills-sidebar-list a[aria-current="page"] { - background: oklch(77% 0.13 82 / 0.08); + background: var(--docs-accent-wash); } html.light .docs-kinpaku .docs-tile, @@ -553,6 +565,11 @@ html.light .docs-kinpaku .docs-live-frame { border-color: var(--ks-rule); } +html.light .docs-kinpaku .docs-chain, +html.light .docs-kinpaku .docs-flow-link { + background: var(--docs-panel-bg); +} + html.light .docs-kinpaku .docs-tile:hover, html.light .docs-kinpaku .docs-category-card:hover, html.light .docs-kinpaku .docs-related-card:hover, @@ -798,8 +815,8 @@ html.light .live-mode-kinpaku .live-mode-terminal { override here was dead twice over: it targeted a .changelog-faq-kinpaku wrapper that is never on the body, and the wrong (.faq-item / .changelog-entry) element names — the real classes are cf-prefixed. Flip the actual surfaces to - the shared light card treatment, and drop the bright-kinpaku accents to - --ks-kinpaku-ink so they stay legible on paper (matching the .cf-eyebrow). */ + the shared light card treatment, and drop the bright-kinpaku accents to the + light-mode small-text accent so they stay legible on paper. */ html.light .cf-entry, html.light .cf-stats, html.light .cf-faq-item { @@ -1147,6 +1164,14 @@ html.light .changelog-faq-kinpaku { background: linear-gradient(180deg, var(--ks-lacquer), var(--ks-lacquer-deep)); } +html.light body.docs-kinpaku { + background: oklch(100% 0 0); +} + +html.light body.docs-kinpaku .site-header { + background: oklch(100% 0 0 / 0.96); +} + html.light .skills-layout-page .skills-sidebar, html.light .skills-layout-page .skills-content { background: transparent; @@ -1167,10 +1192,16 @@ html.light .docs-kinpaku .docs-live-target { } html.light .docs-kinpaku .docs-flow-link { - background: var(--ks-lacquer-raised); + background: var(--docs-panel-bg); } -/* Docs inline viz — bright fill gold on pale kinpaku wash or paper */ +html.light .docs-kinpaku .docs-flow-link:hover { + background: var(--docs-panel-hover-bg); + border-color: var(--docs-accent-line); +} + +/* Docs inline viz — small text on paper uses patina; kinpaku stays for large + brand marks and fill surfaces. */ html.light .docs-kinpaku .prose .docs-viz-flow-step--accent .docs-viz-flow-name, html.light .docs-kinpaku .prose .docs-viz-variant.is-active .docs-viz-variant-kicker, html.light .docs-kinpaku .prose .docs-viz-designmd-chip, @@ -1178,15 +1209,15 @@ html.light .docs-kinpaku .prose .docs-viz-file-status, html.light .docs-kinpaku .prose .docs-viz-live-kicker, html.light .docs-kinpaku .prose .docs-viz-live-title em, html.light .docs-kinpaku .prose th { - color: var(--ks-kinpaku-ink); + color: var(--docs-accent); } html.light .docs-kinpaku .prose th { - border-bottom-color: var(--ks-kinpaku-deep); + border-bottom-color: var(--docs-accent-line); } html.light .docs-kinpaku .prose .docs-viz-flow-step + .docs-viz-flow-step::before { - color: var(--ks-kinpaku-deep); + color: var(--docs-accent); } html.light .live-mode-kinpaku .live-mode-stage-viz .docs-viz-variant.is-active .docs-viz-variant-kicker, @@ -1291,8 +1322,7 @@ html.light .live-mode-kinpaku .live-demo-ctx { } /* Standalone brand mark (no wordmark) reads as the header logo does in light - mode — crisp near-black ink, not the muddy mid-gold that --ks-kinpaku-ink - washes to on a pale bar. */ + mode — crisp near-black ink, not the small-text accent used elsewhere. */ html.light .home-kinpaku .live-demo-gbar-brand, html.light .live-mode-kinpaku .live-demo-gbar-brand { color: var(--ks-champagne); @@ -1700,7 +1730,7 @@ html.light .downloads-rebuild-card { --card-link-secondary: var(--ks-text-muted); /* Command-frame accents on paper. Install keeps bright kinpaku; the prompt - glyph drops to the readable kinpaku-ink so it carries on near-white. */ + glyph uses the light-mode small-text accent so it carries on near-white. */ --card-cmd-border: var(--ks-kinpaku); --card-prompt: var(--ks-kinpaku-ink); --card-cmd-border-update: oklch(49% 0.08 188 / 0.6); diff --git a/tests/docs-integrity.test.js b/tests/docs-integrity.test.js new file mode 100644 index 000000000..fd64e280d --- /dev/null +++ b/tests/docs-integrity.test.js @@ -0,0 +1,152 @@ +import { describe, expect, test } from 'bun:test'; +import fs from 'fs'; +import path from 'path'; +import { ANTIPATTERNS } from '../cli/engine/registry/antipatterns.mjs'; + +const ROOT = process.cwd(); + +function walk(dir, predicate, out = []) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const abs = path.join(dir, entry.name); + if (entry.isDirectory()) { + walk(abs, predicate, out); + } else if (predicate(abs)) { + out.push(abs); + } + } + return out; +} + +function normalizeRoute(route) { + if (route === '/') return route; + return route.replace(/\/$/, ''); +} + +function pageRoute(file) { + const rel = path.relative(path.join(ROOT, 'site/pages'), file).replaceAll(path.sep, '/'); + if (rel.includes('[') || !rel.endsWith('.astro')) return null; + const stem = rel.replace(/\.astro$/, ''); + if (stem === 'index') return '/'; + if (stem.endsWith('/index')) return normalizeRoute(`/${stem.slice(0, -'/index'.length)}`); + return normalizeRoute(`/${stem}`); +} + +function knownRoutes() { + const routes = new Set(); + + for (const file of walk(path.join(ROOT, 'site/pages'), file => file.endsWith('.astro'))) { + const route = pageRoute(file); + if (route) routes.add(route); + } + + for (const file of fs.readdirSync(path.join(ROOT, 'site/content/skills')).filter(file => file.endsWith('.md'))) { + routes.add(`/docs/${file.replace(/\.md$/, '')}`); + } + routes.add('/docs'); + + for (const file of fs.readdirSync(path.join(ROOT, 'site/content/tutorials')).filter(file => file.endsWith('.md'))) { + routes.add(`/tutorials/${file.replace(/\.md$/, '')}`); + } + routes.add('/tutorials'); + + if (fs.existsSync(path.join(ROOT, 'site/public/neo-mirai/index.html'))) { + routes.add('/neo-mirai'); + } + + return routes; +} + +function docsFiles() { + const roots = ['site/pages', 'site/content', 'site/components', 'site/layouts']; + return roots.flatMap(root => walk(path.join(ROOT, root), file => /\.(astro|md|ts|js)$/.test(file))); +} + +function routeFromUrl(url) { + if (!url.startsWith('/') || url.startsWith('//')) return null; + if (url.startsWith('/api/') || url.startsWith('/_data/')) return null; + + const clean = url.split('#')[0].split('?')[0]; + if (!clean) return '/'; + + if ( + clean.startsWith('/assets/') || + clean.startsWith('/antipattern-') || + clean.startsWith('/js/') || + clean.startsWith('/neo-mirai/assets/') + ) { + return null; + } + + if (path.extname(clean)) return null; + return normalizeRoute(clean); +} + +describe('docs integrity', () => { + test('internal docs links point at canonical local routes', () => { + const routes = knownRoutes(); + const broken = []; + + for (const file of docsFiles()) { + const rel = path.relative(ROOT, file); + const src = fs.readFileSync(file, 'utf8'); + const urls = []; + + for (const match of src.matchAll(/(?:href|src)=["']([^"']+)["']/g)) { + urls.push(match[1]); + } + for (const match of src.matchAll(/\]\((\/[^)\s]+)\)/g)) { + urls.push(match[1]); + } + + for (const url of urls) { + const route = routeFromUrl(url); + if (route && !routes.has(route)) { + broken.push(`${rel}: ${url}`); + } + } + } + + expect(broken).toEqual([]); + }); + + test('public docs use source-of-truth command and detector counts', () => { + const commandMetadata = JSON.parse( + fs.readFileSync(path.join(ROOT, 'skill/scripts/command-metadata.json'), 'utf8') + ); + const commandCount = Object.keys(commandMetadata).length; + const detectorCount = new Set(ANTIPATTERNS.map(rule => rule.id)).size; + + const files = [ + 'README.md', + 'README.npm.md', + 'site/pages/index.astro', + 'site/content/tutorials/getting-started.md', + 'site/content/skills/impeccable.md', + 'site/public/llms.txt', + ]; + + const stale = []; + const commandPattern = /\b(\d+)\s+(?:sub-)?commands\b|\b(\d+)\s+steering commands\b/gi; + const detectorPattern = /\b(\d+)\s+(?:deterministic\s+)?(?:detector\s+)?(?:rules|detections|checks|patterns)\b/gi; + + for (const rel of files) { + const src = fs.readFileSync(path.join(ROOT, rel), 'utf8'); + + for (const match of src.matchAll(commandPattern)) { + const count = Number(match[1] || match[2]); + if (count !== commandCount && count !== 1) { + stale.push(`${rel}: "${match[0]}" should be ${commandCount}`); + } + } + + for (const match of src.matchAll(detectorPattern)) { + const count = Number(match[1]); + if (count > 10 && count !== detectorCount) { + stale.push(`${rel}: "${match[0]}" should be ${detectorCount}`); + } + } + } + + expect(stale).toEqual([]); + }); +});