diff --git a/public/app.js b/public/app.js
index 397fdc563..0de7d11db 100644
--- a/public/app.js
+++ b/public/app.js
@@ -227,11 +227,22 @@ function initWhyTabs() {
const panels = Array.from(container.querySelectorAll('.why-panel'));
if (!tabs.length || !panels.length) return;
- const activate = (index) => {
+ const CYCLE_MS = 7000;
+ const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
+ let current = 0;
+ let timer = null;
+ let autoRotate = !reducedMotion;
+ let visible = false;
+
+ const activate = (index, fromAuto = false) => {
+ current = index;
tabs.forEach((tab, i) => {
const on = i === index;
tab.classList.toggle('is-active', on);
tab.setAttribute('aria-selected', on ? 'true' : 'false');
+ // Reset cycling class, re-add on the new active tab so the
+ // progress indicator restarts cleanly.
+ tab.classList.remove('is-cycling');
});
panels.forEach((panel, i) => {
const on = i === index;
@@ -239,19 +250,80 @@ function initWhyTabs() {
if (on) panel.removeAttribute('hidden');
else panel.setAttribute('hidden', '');
});
+ if (autoRotate && visible) {
+ // Force reflow so the animation restart is picked up.
+ const active = tabs[index];
+ void active.offsetWidth;
+ active.classList.add('is-cycling');
+ }
+ };
+
+ const scheduleNext = () => {
+ clearTimeout(timer);
+ if (!autoRotate || !visible) return;
+ timer = setTimeout(() => {
+ const next = (current + 1) % tabs.length;
+ activate(next, true);
+ scheduleNext();
+ }, CYCLE_MS);
+ };
+
+ const stopAuto = () => {
+ autoRotate = false;
+ clearTimeout(timer);
+ tabs.forEach((t) => t.classList.remove('is-cycling'));
};
tabs.forEach((tab, index) => {
- tab.addEventListener('click', () => activate(index));
+ tab.addEventListener('click', () => {
+ stopAuto();
+ activate(index);
+ });
tab.addEventListener('keydown', (e) => {
if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp') return;
e.preventDefault();
+ stopAuto();
const dir = e.key === 'ArrowDown' ? 1 : -1;
const next = (index + dir + tabs.length) % tabs.length;
tabs[next].focus();
activate(next);
});
});
+
+ container.addEventListener('mouseenter', () => {
+ // Pause auto-rotation on hover. Resume only if still allowed and
+ // user hasn't interacted (stopAuto flips autoRotate off).
+ clearTimeout(timer);
+ tabs.forEach((t) => t.classList.remove('is-cycling'));
+ });
+ container.addEventListener('mouseleave', () => {
+ if (autoRotate && visible) {
+ // Re-apply cycling class to current tab and resume the timer.
+ const active = tabs[current];
+ void active.offsetWidth;
+ active.classList.add('is-cycling');
+ scheduleNext();
+ }
+ });
+
+ // Observe visibility so we only rotate while the user can see it.
+ const io = new IntersectionObserver((entries) => {
+ entries.forEach((e) => {
+ visible = e.isIntersecting;
+ if (visible) {
+ if (autoRotate) {
+ const active = tabs[current];
+ void active.offsetWidth;
+ active.classList.add('is-cycling');
+ scheduleNext();
+ }
+ } else {
+ clearTimeout(timer);
+ tabs.forEach((t) => t.classList.remove('is-cycling'));
+ }
+ });
+ }, { threshold: 0.35 });
+ io.observe(container);
}
if (document.readyState === "loading") {
diff --git a/public/css/workflow.css b/public/css/workflow.css
index 7101f8814..13d8f70d8 100644
--- a/public/css/workflow.css
+++ b/public/css/workflow.css
@@ -1573,3 +1573,587 @@
font-size: 0.625rem;
}
}
+
+/* ============================================
+ WHY — per-panel visuals + auto-rotation
+ ============================================ */
+
+.why-tab-progress {
+ position: absolute;
+ left: -2px;
+ top: 0;
+ bottom: 0;
+ width: 2px;
+ background: var(--color-accent);
+ transform-origin: top;
+ transform: scaleY(0);
+ pointer-events: none;
+ opacity: 0;
+ transition: opacity 180ms var(--ease-out);
+}
+
+.why-tab { position: relative; }
+.why-tab.is-active .why-tab-progress { opacity: 1; }
+.why-tab.is-active.is-cycling .why-tab-progress {
+ animation: whyTabProgress var(--why-cycle-ms, 7000ms) linear forwards;
+}
+
+@keyframes whyTabProgress {
+ from { transform: scaleY(0); }
+ to { transform: scaleY(1); }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .why-tab.is-active.is-cycling .why-tab-progress {
+ animation: none;
+ transform: scaleY(1);
+ }
+}
+
+/* Visual frame — common base */
+.why-visual {
+ background: var(--color-paper);
+ border: 1px solid var(--color-mist);
+ border-radius: 8px;
+ padding: var(--spacing-md);
+ margin-bottom: var(--spacing-md);
+ min-height: 240px;
+ display: flex;
+ align-items: stretch;
+ position: relative;
+ overflow: hidden;
+}
+
+/* ─ Panel 01: Generic vs PRODUCT.md ─ */
+.why-visual--compare {
+ gap: var(--spacing-md);
+}
+.why-compare-card {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ min-width: 0;
+}
+.why-compare-label {
+ font-family: var(--font-mono);
+ font-size: 0.625rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--color-ash);
+}
+.why-slop-card {
+ flex: 1;
+ background: linear-gradient(135deg, oklch(35% 0.14 280), oklch(55% 0.18 220));
+ border-radius: 10px;
+ padding: 14px;
+ color: white;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ box-shadow: 0 20px 40px oklch(40% 0.14 270 / 0.25);
+}
+.why-slop-pill {
+ font-family: 'Inter', system-ui, sans-serif;
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ background: rgba(255, 255, 255, 0.2);
+ padding: 2px 6px;
+ border-radius: 999px;
+ align-self: flex-start;
+}
+.why-slop-title {
+ font-family: 'Inter', system-ui, sans-serif;
+ font-size: 14px;
+ font-weight: 700;
+ background: linear-gradient(135deg, #fff, #c4b5fd);
+ -webkit-background-clip: text;
+ background-clip: text;
+ color: transparent;
+ line-height: 1.1;
+}
+.why-slop-line {
+ height: 4px;
+ background: rgba(255, 255, 255, 0.15);
+ border-radius: 2px;
+}
+.why-slop-line--short { width: 60%; }
+.why-slop-cta {
+ font-family: 'Inter', system-ui, sans-serif;
+ font-size: 10px;
+ font-weight: 600;
+ padding: 6px 10px;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 6px;
+ align-self: flex-start;
+ margin-top: auto;
+}
+.why-impeccable-card {
+ flex: 1;
+ background: var(--color-cream);
+ border: 1px solid var(--color-mist);
+ padding: 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+.why-impeccable-kicker {
+ font-family: var(--font-mono);
+ font-size: 9px;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--color-accent);
+}
+.why-impeccable-title {
+ font-family: var(--font-display);
+ font-size: 22px;
+ line-height: 1.05;
+ color: var(--color-ink);
+}
+.why-impeccable-title em {
+ font-style: italic;
+ color: var(--color-accent);
+}
+.why-impeccable-line {
+ height: 4px;
+ background: var(--color-mist);
+ border-radius: 2px;
+}
+.why-impeccable-line--short { width: 55%; }
+.why-impeccable-cta {
+ font-family: var(--font-body);
+ font-size: 11px;
+ font-weight: 500;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--color-ink);
+ border-bottom: 1.5px solid var(--color-ink);
+ align-self: flex-start;
+ margin-top: auto;
+ padding-bottom: 3px;
+}
+
+/* ─ Panel 02: Brand vs Product registers ─ */
+.why-visual--registers {
+ gap: var(--spacing-md);
+}
+.why-register {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ min-width: 0;
+}
+.why-register-label {
+ font-family: var(--font-mono);
+ font-size: 0.625rem;
+ letter-spacing: 0.2em;
+ text-transform: uppercase;
+ color: var(--color-accent);
+}
+.why-register-mock {
+ flex: 1;
+ border-radius: 6px;
+ padding: 18px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+.why-register-mock--brand {
+ background: var(--color-cream);
+ border: 1px solid var(--color-mist);
+ gap: 12px;
+}
+.why-brand-hero-mono {
+ font-family: var(--font-mono);
+ font-size: 9px;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: var(--color-ash);
+}
+.why-brand-hero-title {
+ font-family: var(--font-display);
+ font-size: 28px;
+ line-height: 1;
+ color: var(--color-ink);
+ letter-spacing: -0.02em;
+}
+.why-brand-hero-title em {
+ font-style: italic;
+ color: var(--color-accent);
+}
+.why-register-mock--product {
+ background: var(--color-paper);
+ border: 1px solid var(--color-mist);
+ padding: 0;
+ gap: 0;
+}
+.why-product-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: baseline;
+ padding: 10px 14px;
+ border-bottom: 1px solid var(--color-mist);
+}
+.why-product-row:last-child { border-bottom: 0; }
+.why-product-k {
+ font-family: var(--font-mono);
+ font-size: 10px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--color-ash);
+}
+.why-product-v {
+ font-family: var(--font-body);
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--color-ink);
+}
+
+/* ─ Panel 03: Terminal output ─ */
+.why-visual--terminal,
+.why-visual--ci {
+ padding: 0;
+ background: oklch(12% 0 0);
+ border-color: oklch(18% 0 0);
+}
+.why-terminal {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+.why-terminal-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 10px 14px;
+ border-bottom: 1px solid oklch(20% 0 0);
+ background: oklch(14% 0 0);
+ border-radius: 7px 7px 0 0;
+}
+.why-terminal-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: oklch(35% 0 0);
+}
+.why-terminal-title {
+ font-family: var(--font-mono);
+ font-size: 11px;
+ color: oklch(65% 0 0);
+ margin-left: 10px;
+}
+.why-terminal-body {
+ padding: 14px;
+ font-family: var(--font-mono);
+ font-size: 12px;
+ line-height: 1.7;
+ color: oklch(80% 0 0);
+}
+.why-terminal-line { white-space: pre; }
+.why-terminal-line--prompt { color: oklch(90% 0 0); }
+.why-terminal-prompt { color: var(--color-accent); margin-right: 4px; }
+.why-terminal-ok { color: oklch(75% 0.15 145); }
+.why-terminal-arrow { color: var(--color-accent); }
+.why-terminal-line--hint {
+ margin-top: 6px;
+ color: oklch(90% 0 0);
+}
+
+/* ─ Panel 04: Harness grid ─ */
+.why-visual--harnesses {
+ flex-direction: column;
+ padding: 16px;
+ gap: 14px;
+}
+.why-prompt-bar {
+ font-family: var(--font-mono);
+ font-size: 12px;
+ color: var(--color-ink);
+ background: var(--color-cream);
+ border: 1px solid var(--color-mist);
+ border-radius: 4px;
+ padding: 8px 12px;
+ display: flex;
+ align-items: center;
+ gap: 2px;
+}
+.why-prompt-slash {
+ color: var(--color-accent);
+ margin-right: 4px;
+ font-weight: 500;
+}
+.why-prompt-caret {
+ display: inline-block;
+ width: 7px;
+ height: 14px;
+ background: var(--color-accent);
+ margin-left: 2px;
+ animation: whyCaret 1.1s steps(1) infinite;
+}
+@keyframes whyCaret {
+ 0%, 50% { opacity: 1; }
+ 50.01%, 100% { opacity: 0; }
+}
+.why-harness-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 8px;
+ flex: 1;
+}
+.why-harness {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ padding: 10px 4px;
+ background: var(--color-cream);
+ border: 1px solid var(--color-mist);
+ border-radius: 6px;
+ font-family: var(--font-mono);
+ font-size: 9px;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+ color: var(--color-charcoal);
+}
+.why-harness img {
+ width: 24px;
+ height: 24px;
+ object-fit: contain;
+ opacity: 0.85;
+}
+
+/* ─ Panel 05: DESIGN.md file ─ */
+.why-visual--designmd {
+ padding: 0;
+}
+.why-designmd-file {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: var(--color-paper);
+ border-radius: 7px;
+}
+.why-designmd-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 14px;
+ border-bottom: 1px solid var(--color-mist);
+ background: var(--color-cream);
+ border-radius: 7px 7px 0 0;
+}
+.why-designmd-filename {
+ font-family: var(--font-mono);
+ font-size: 11px;
+ font-weight: 500;
+ color: var(--color-ink);
+}
+.why-designmd-badge {
+ font-family: var(--font-mono);
+ font-size: 9px;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ color: var(--color-accent);
+ background: var(--color-accent-dim);
+ border: 1px solid var(--color-accent-soft);
+ padding: 3px 8px;
+ border-radius: 999px;
+}
+.why-designmd-sections {
+ list-style: none;
+ padding: 10px 14px;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ font-family: var(--font-mono);
+ font-size: 12px;
+ line-height: 2;
+ color: var(--color-ink);
+}
+.why-designmd-num {
+ color: var(--color-accent);
+ margin-right: 8px;
+ font-weight: 500;
+}
+.why-designmd-footer {
+ padding: 10px 14px;
+ border-top: 1px solid var(--color-mist);
+ font-family: var(--font-display);
+ font-style: italic;
+ font-size: 13px;
+ color: var(--color-charcoal);
+}
+
+/* ─ Panel 06: CI output ─ */
+.why-ci-window {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+}
+.why-ci-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 10px 14px;
+ border-bottom: 1px solid oklch(20% 0 0);
+ background: oklch(14% 0 0);
+ border-radius: 7px 7px 0 0;
+}
+.why-ci-branch {
+ font-family: var(--font-mono);
+ font-size: 11px;
+ color: oklch(75% 0 0);
+}
+.why-ci-status {
+ font-family: var(--font-mono);
+ font-size: 10px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ padding: 2px 8px;
+ border-radius: 999px;
+}
+.why-ci-status--fail {
+ color: oklch(78% 0.18 25);
+ background: oklch(30% 0.15 25 / 0.3);
+}
+.why-ci-body {
+ padding: 14px;
+ font-family: var(--font-mono);
+ font-size: 12px;
+ line-height: 1.8;
+ color: oklch(80% 0 0);
+}
+.why-ci-line { color: oklch(85% 0 0); }
+.why-ci-cmd { color: var(--color-accent); margin-right: 6px; }
+.why-ci-issue {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ padding-left: 4px;
+ color: oklch(85% 0 0);
+}
+.why-ci-issue code {
+ background: transparent;
+ color: oklch(70% 0.12 220);
+ padding: 0;
+ font-size: 1em;
+}
+.why-ci-x { color: oklch(75% 0.18 25); }
+.why-ci-summary {
+ margin-top: 8px;
+ padding-top: 8px;
+ border-top: 1px solid oklch(20% 0 0);
+ font-weight: 500;
+ color: oklch(85% 0 0);
+}
+
+/* ─ Panel 07: Chrome extension ─ */
+.why-visual--extension {
+ padding: 0;
+ background: var(--color-cream);
+}
+.why-browser {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ background: var(--color-paper);
+ border-radius: 7px;
+ overflow: hidden;
+}
+.why-browser-chrome {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 10px 14px;
+ background: var(--color-mist);
+ border-bottom: 1px solid oklch(86% 0 0);
+}
+.why-browser-dot {
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: oklch(75% 0 0);
+}
+.why-browser-url {
+ margin-left: 12px;
+ padding: 3px 10px;
+ background: var(--color-paper);
+ border-radius: 4px;
+ font-family: var(--font-mono);
+ font-size: 10px;
+ color: var(--color-charcoal);
+ flex: 1;
+}
+.why-browser-body {
+ flex: 1;
+ position: relative;
+ padding: 20px;
+ background:
+ linear-gradient(var(--color-cream), var(--color-cream)) padding-box,
+ repeating-linear-gradient(90deg, transparent 0 60px, var(--color-mist) 60px 61px);
+}
+.why-ext-popup {
+ position: absolute;
+ top: 16px;
+ right: 16px;
+ width: 180px;
+ background: var(--color-paper);
+ border: 1px solid var(--color-mist);
+ border-radius: 6px;
+ box-shadow: 0 8px 24px oklch(0% 0 0 / 0.12);
+ overflow: hidden;
+ z-index: 2;
+}
+.why-ext-popup-header {
+ font-family: var(--font-mono);
+ font-size: 10px;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--color-ink);
+ padding: 8px 12px;
+ background: var(--color-cream);
+ border-bottom: 1px solid var(--color-mist);
+}
+.why-ext-popup-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px;
+ font-size: 11px;
+ color: var(--color-charcoal);
+ border-bottom: 1px solid var(--color-mist);
+}
+.why-ext-popup-row:last-child { border-bottom: 0; }
+.why-ext-sev {
+ color: var(--color-accent);
+ font-weight: 600;
+}
+.why-ext-overlay-a,
+.why-ext-overlay-b {
+ position: absolute;
+ border: 2px solid var(--color-accent);
+ border-radius: 4px;
+ pointer-events: none;
+}
+.why-ext-overlay-a {
+ left: 20px;
+ top: 30px;
+ width: 42%;
+ height: 36px;
+}
+.why-ext-overlay-b {
+ left: 20px;
+ bottom: 24px;
+ width: 30%;
+ height: 30px;
+}
+
+/* Mobile */
+@media (max-width: 700px) {
+ .why-visual { min-height: 200px; }
+ .why-harness-grid { grid-template-columns: repeat(3, 1fr); }
+ .why-compare-card, .why-register { gap: 8px; }
+}
diff --git a/public/index.html b/public/index.html
index df08eb076..efa700036 100644
--- a/public/index.html
+++ b/public/index.html
@@ -294,57 +294,181 @@
Why Impeccable
-
Seven reasons this isn't another AI design tool.
-
- 01 Intentional design
- 02 Brand and product, both
- 03 Production codebases
- 04 Where you code, not on a canvas
- 05 DESIGN.md, spec-compliant
- 06 CI/CD-ready
- 07 Chrome extension
+ 01 Intentional design
+ 02 Brand and product, both
+ 03 Production codebases
+ 04 Where you code, not on a canvas
+ 05 DESIGN.md, spec-compliant
+ 06 CI/CD-ready
+ 07 Chrome extension
+
+
+
Generic AI output
+
+
PRO
+
Boost your workflow
+
+
+
Get Started →
+
+
+
+
With PRODUCT.md loaded
+
+
Vol. 03
+
Design with intent.
+
+
+
Read the brief →
+
+
+
Design that knows who it's for.
Most AI tools one-shot a plausible-looking mock. Impeccable starts with a conversation: audience, brand personality, anti-references, aesthetic direction. It captures the answer in PRODUCT.md and DESIGN.md and loads them on every command. The output is design that fits the actual business, not a generic impression of one.
/impeccable teach · /impeccable document · /impeccable shape
+
+
+
Brand register
+
+
Issue 04
+
Shape the story.
+
+
+
+
Product register
+
+
Users 12,482
+
Active now 1,207
+
Conversion 4.8%
+
+
+
Brand work is not product UI.
- Most design skills, including Anthropic's own frontend-design skill, collapse everything into one vocabulary. But a landing page and a dashboard live by contradictory rules. Impeccable carries two registers: brand (design IS the product: marketing, portfolios, editorial) and product (design SERVES the product: app UI, tools, dashboards). Commands that care about register, typeset, animate, colorize, bolder, quieter, layout, delight, adjust their choices to match.
+ Most design skills, including Anthropic's own frontend-design skill, collapse everything into one vocabulary. But a landing page and a dashboard live by contradictory rules. Impeccable carries two registers: brand (design IS the product: marketing, portfolios, editorial) and product (design SERVES the product: app UI, tools, dashboards). Register-aware commands (typeset, animate, colorize, bolder, quieter, layout, delight) adjust their choices to match.
The register is inferred from your task and confirmed in PRODUCT.md.
+
+
+
+
+
› Reading DESIGN.md…
+
✓ 14 color tokens loaded
+
✓ Respecting Card + Button APIs
+
✓ Matching 1.6 line-height rule
+
→ 3 proposals: tighter rhythm, clearer hierarchy, remove nested card
+
+
+
Built to live in your production codebase.
Impeccable isn't a sketchpad. It reads your existing tokens, components, and rendered output; polishes what's there; flags regressions; iterates with your design system rather than around it. Daily-driver for real work on real apps, not a one-shot mock factory.
Scans your tokens, respects your components, grows with your system.
+
+
+ /
+ impeccable craft hero section
+
+
+
+
Claude Code
+
Cursor
+
Codex
+
Gemini
+
Copilot
+
Antigravity
+
Kiro
+
OpenCode
+
+
Design where the work actually happens.
The waterfall era (designer in canvas, throws a mock over the wall) is over. Most product work now starts directly in Claude Code, Cursor, Codex, Gemini, or Copilot, on the real codebase, in the browser. Impeccable is native to that world. No canvas. No handoff. Open your AI harness, type a command, ship the change.
Works in Claude Code, Cursor, Codex, Gemini CLI, Antigravity, Kiro, OpenCode, and more.
+
+
+
+
+ ## 1. Overview
+ ## 2. Colors
+ ## 3. Typography
+ ## 4. Elevation
+ ## 5. Components
+ ## 6. Do's and Don'ts
+
+
+
+
Your design system in a portable file.
/impeccable document scans your codebase and writes a DESIGN.md that follows the Google Stitch DESIGN.md format , the emerging standard. Six fixed sections in a fixed order, parseable by every other DESIGN.md-aware tool. Your visual system travels. Impeccable also consumes DESIGN.md on every command, so it stays on brand without being told twice.
Spec-compliant. Interoperable. Not a proprietary sidecar.
+
+
+
+
+
$ npx impeccable detect src/
+
✗ src/Hero.tsx:42 gradient-text
+
✗ src/Card.tsx:8 side-stripe-border
+
✗ src/Button.tsx:17 ai-color-palette
+
3 anti-patterns · exit 1
+
+
+
Block AI slop before it ships.
Run npx impeccable detect src/ on a PR branch and get JSON output with every purple gradient, nested card, low-contrast label, and gradient-text heading caught. Fail the build on severity. 25 deterministic checks, no LLM required, no API key, no flaky scoring. Gate your PRs the same way you gate lint and types.
25 rules · --json · --fast · exit codes for CI.
+
+
+
+
+
+
+ example.com
+
+
+
+
One click on any site.
The Impeccable Chrome extension runs the same 25-rule detector on every page you visit, in-browser, with zero setup. Audit your own staging. Audit the design your PM showed in Slack. Audit your competitor on a Tuesday. The overlay highlights each issue in place so you never have to map a JSON report back to a DOM node.
Install from the Chrome Web Store →