Hero: vocabulary-first copy + command-palette switcher in the demo

Rewrite the hero around the why (the missing design vocabulary) instead of the
live-mode how: "The missing design vocabulary for agents." The live demo now
opens the picker's command palette and picks a verb before generating, which is
the move that makes the live approach unique and was previously skipped.

- Demo palette mirrors the real action picker (live-browser.js): same 12 verbs,
  the same SVG icons, a 4-col icon-over-label grid, selected chip on a kinpaku
  wash with its icon recolored. Light + dark covered.
- Shared <LiveDemoPalette> component renders the grid from one list, so the hero
  and /live-mode no longer hand-copy the markup. /live-mode lands on "delight",
  the hero on "colorize" (via data-demo-pick); pages without a palette filter the
  switcher beats out of the shared timeline.
- Trim the opening beats so the cursor clicks the element at ~1.3s (was ~2s), and
  slow the palette browse so the vocabulary is readable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-06-05 14:05:41 -07:00
co-authored by Claude Opus 4.8
parent 0939528b95
commit 1f975a69e4
6 changed files with 198 additions and 11 deletions
+41
View File
@@ -0,0 +1,41 @@
---
// Command-palette switcher for the marketing live-demo loop. Renders the
// design-command vocabulary as a 4-column icon grid; the demo loop in
// live-demo.js opens it and lands on the `pick` verb.
//
// The values/labels/icons mirror the real picker's ICONS + ACTIONS in
// skill/scripts/live-browser.js (which is served raw and injected as an IIFE, so
// it can't import shared modules at runtime — see the chat thread for the
// proposed convergence). Keep this list in sync if a verb is added or reordered.
const A = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"';
const COMMANDS = [
{ value: 'impeccable', label: 'Freeform', icon: `<svg ${A}><path d="M4 20l4-1L18 9l-3-3L5 16z"/><path d="M14 7l3 3"/></svg>` },
{ value: 'bolder', label: 'Bolder', icon: `<svg ${A}><rect x="6" y="12" width="4" height="7" rx="0.5"/><rect x="14" y="5" width="4" height="14" rx="0.5"/></svg>` },
{ value: 'quieter', label: 'Quieter', icon: `<svg ${A}><rect x="6" y="5" width="4" height="14" rx="0.5"/><rect x="14" y="12" width="4" height="7" rx="0.5"/></svg>` },
{ value: 'distill', label: 'Distill', icon: `<svg ${A}><path d="M4 5h16l-6 8v7l-4-2v-5z"/></svg>` },
{ value: 'polish', label: 'Polish', icon: `<svg ${A}><path d="M15 3l1 3 3 1-3 1-1 3-1-3-3-1 3-1z"/><path d="M7 13l0.6 1.8 1.8 0.6-1.8 0.6-0.6 1.8-0.6-1.8-1.8-0.6 1.8-0.6z"/></svg>` },
{ value: 'typeset', label: 'Typeset', icon: `<svg ${A}><path d="M5 6h14" stroke-width="2.6"/><path d="M5 12h9" stroke-width="1.9"/><path d="M5 18h5" stroke-width="1.3"/></svg>` },
{ value: 'colorize', label: 'Colorize', icon: `<svg ${A}><circle cx="9" cy="10" r="5"/><circle cx="15" cy="10" r="5"/><circle cx="12" cy="15" r="5"/></svg>` },
{ value: 'layout', label: 'Layout', icon: `<svg ${A}><rect x="3" y="4" width="8" height="16" rx="0.5"/><rect x="13" y="4" width="8" height="7" rx="0.5"/><rect x="13" y="13" width="8" height="7" rx="0.5"/></svg>` },
{ value: 'adapt', label: 'Adapt', icon: `<svg ${A}><rect x="2.5" y="5" width="12" height="11" rx="1"/><line x1="2.5" y1="19" x2="14.5" y2="19"/><rect x="16.5" y="8" width="5" height="11" rx="1"/></svg>` },
{ value: 'animate', label: 'Animate', icon: `<svg ${A}><path d="M3 18c4-4 6-10 10-10"/><path d="M13 8c3 0 5 5 8 10"/><circle cx="13" cy="8" r="1.6" fill="currentColor" stroke="none"/></svg>` },
{ value: 'delight', label: 'Delight', icon: `<svg ${A}><path d="M12 3l2 6 6 2-6 2-2 6-2-6-6-2 6-2z"/></svg>` },
{ value: 'overdrive', label: 'Overdrive', icon: `<svg ${A}><path d="M13 3L5 13h5l-1 8 9-12h-6z"/></svg>` },
];
interface Props {
pick: string;
}
const { pick } = Astro.props;
---
<div class="live-demo-ctx-palette" data-demo-palette data-demo-pick={pick} aria-hidden="true">
<div class="live-demo-ctx-palette-grid">
{COMMANDS.map((c) => (
<button type="button" class="live-demo-ctx-palette-chip" data-cmd={c.value}>
<span class="live-demo-ctx-palette-ico" set:html={c.icon}></span>
<span class="live-demo-ctx-palette-name">{c.label}</span>
</button>
))}
</div>
</div>
+6 -4
View File
@@ -1,5 +1,6 @@
---
import Base from '../layouts/Base.astro';
import LiveDemoPalette from '../components/LiveDemoPalette.astro';
import Testimonials from '../components/Testimonials.astro';
import LiveDemoGbar from '../components/LiveDemoGbar.astro';
import '../styles/main.css';
@@ -54,12 +55,12 @@ import '../styles/testimonials.css';
<div class="hero-rebuild-art" aria-hidden="true"></div>
<div class="hero-rebuild-container">
<div class="hero-rebuild-left">
<h1 class="hero-rebuild-title">Tune interfaces<br>while they run.</h1>
<h1 class="hero-rebuild-title">The missing design<br>vocabulary for agents.</h1>
<p class="hero-rebuild-body">
Your AI ships generic frontend by default. Impeccable is an agent skill that teaches it
to design: a real visual vocabulary, slop detection, and live iteration in your production
codebase. A CLI and Chrome extension extend it into your CI and browser.
It's why AI frontends all share one look: no words for hierarchy, contrast, or restraint.
Impeccable gives your agent the designer's vocabulary, and gives you the same commands,
so you both stop guessing and start directing, live, in your production codebase.
</p>
<div class="hero-rebuild-actions">
@@ -206,6 +207,7 @@ import '../styles/testimonials.css';
<button type="button" class="live-demo-ctx-count">×3</button>
<button type="button" class="live-demo-ctx-go" data-demo-go>Go <span aria-hidden="true">→</span></button>
</div>
<LiveDemoPalette pick="colorize" />
<div class="live-demo-ctx-row live-demo-ctx-row--generating">
<span class="live-demo-ctx-spinner" aria-hidden="true"></span>
<span>Generating variants…</span>
+3 -1
View File
@@ -1,6 +1,7 @@
---
import Base from '../../layouts/Base.astro';
import LiveDemoGbar from '../../components/LiveDemoGbar.astro';
import LiveDemoPalette from '../../components/LiveDemoPalette.astro';
import '../../styles/sub-pages.css';
import '../../styles/live-mode.css';
import '../../styles/live-mode-kinpaku.css';
@@ -103,7 +104,7 @@ import '../../styles/live-mode-kinpaku.css';
<div class="live-demo-ctx" data-demo-ctx data-phase="hidden">
<div class="live-demo-ctx-row live-demo-ctx-row--configure">
<button type="button" class="live-demo-ctx-pill" data-demo-ctx-pill>
<span data-demo-cmd-name>delight</span>
<span data-demo-cmd-name>adapt</span>
<span class="live-demo-ctx-pill-caret" aria-hidden="true">&#x25BE;</span>
</button>
<span class="live-demo-ctx-input" data-demo-input>
@@ -112,6 +113,7 @@ import '../../styles/live-mode-kinpaku.css';
<button type="button" class="live-demo-ctx-count">&times;3</button>
<button type="button" class="live-demo-ctx-go" data-demo-go>Go <span aria-hidden="true">&rarr;</span></button>
</div>
<LiveDemoPalette pick="delight" />
<div class="live-demo-ctx-row live-demo-ctx-row--generating">
<span class="live-demo-ctx-spinner" aria-hidden="true"></span>
<span>Generating variants&hellip;</span>
+61 -6
View File
@@ -14,11 +14,17 @@ const PHASE = {
};
const TIMELINE = [
{ dt: 400, action: 'cursor-show' },
{ dt: 400, action: 'cursor-to-target' },
{ dt: 800, action: 'outline-show', caption: 'Pick any element on your live page.' },
{ dt: 450, action: 'cursor-click' },
{ dt: 200, action: 'open-ctx' },
{ dt: 220, action: 'cursor-show' },
{ dt: 260, action: 'cursor-to-target' },
{ dt: 560, action: 'outline-show', caption: 'Pick any element on your live page.' },
{ dt: 300, action: 'cursor-click' },
{ dt: 180, action: 'open-ctx' },
{ dt: 340, action: 'cursor-to-cmd' },
{ dt: 320, action: 'open-palette', caption: 'Open the command palette: the design vocabulary.' },
{ dt: 950, action: 'browse-palette', cmd: 'bolder' },
{ dt: 700, action: 'browse-palette', cmd: 'typeset' },
{ dt: 700, action: 'browse-target' },
{ dt: 1000, action: 'pick-target', caption: 'Pick a design verb, or just describe it.' },
{ dt: 560, action: 'cursor-to-button' },
{ dt: 250, action: 'draw-circle', caption: 'Circle whats off-brand…' },
{ dt: 700, action: 'drop-pin' },
@@ -34,6 +40,11 @@ const TIMELINE = [
{ dt: 1800, action: 'reset', caption: 'Pick any element on your live page.' },
];
// Steps that drive the command-palette switcher. Pages whose context bar has no
// [data-demo-palette] (e.g. /live-mode) get these filtered out of the timeline,
// so they don't sit through dead pauses for a UI they don't render.
const PALETTE_ACTIONS = new Set(['cursor-to-cmd', 'open-palette', 'browse-palette', 'browse-target', 'pick-target']);
export function initLiveDemo() {
const root = document.getElementById('live-demo');
if (!root) return;
@@ -46,6 +57,11 @@ export function initLiveDemo() {
const ctx = root.querySelector('[data-demo-ctx]');
const inputText = root.querySelector('[data-demo-input-text]');
const noteText = root.querySelector('[data-demo-note-text]');
const palette = root.querySelector('[data-demo-palette]');
const cmdName = root.querySelector('[data-demo-cmd-name]');
const defaultCmd = cmdName ? cmdName.textContent : '';
// Drop the palette-switcher beats on pages that don't render the palette.
const timeline = palette ? TIMELINE : TIMELINE.filter((s) => !PALETTE_ACTIONS.has(s.action));
const counter = root.querySelector('[data-demo-counter]');
const captionLabel = root.querySelector('[data-demo-caption-label]');
const variants = Array.from(root.querySelectorAll('.live-demo-variant'));
@@ -140,12 +156,28 @@ export function initLiveDemo() {
annotations.classList.remove('is-visible', 'is-pin-visible', 'is-note-visible');
if (noteText) noteText.textContent = '';
if (inputText) inputText.textContent = '';
if (palette) palette.classList.remove('is-open');
if (palette) palette.querySelectorAll('.live-demo-ctx-palette-chip').forEach((r) => r.classList.remove('is-highlight'));
if (cmdName) cmdName.textContent = defaultCmd;
showVariant(0);
};
const clearAnnotations = () =>
annotations.classList.remove('is-visible', 'is-pin-visible', 'is-note-visible');
const setPalette = (open) => {
if (!palette) return;
palette.classList.toggle('is-open', open);
if (!open) palette.querySelectorAll('.live-demo-ctx-palette-chip').forEach((r) => r.classList.remove('is-highlight'));
};
const highlightCmd = (cmd) => {
if (!palette) return null;
const chip = palette.querySelector(`[data-cmd="${cmd}"]`);
palette.querySelectorAll('.live-demo-ctx-palette-chip').forEach((r) => r.classList.toggle('is-highlight', r === chip));
return chip;
};
const setCaption = (text) => {
if (text && captionLabel) captionLabel.textContent = text;
};
@@ -183,6 +215,29 @@ export function initLiveDemo() {
case 'open-ctx':
setCtxPhase(PHASE.CONFIGURING);
break;
case 'cursor-to-cmd':
moveCursor(root.querySelector('[data-demo-ctx-pill]'));
break;
case 'open-palette':
setPalette(true);
break;
case 'browse-palette': {
const chip = highlightCmd(s.cmd);
if (chip) moveCursor(chip);
break;
}
case 'browse-target': {
// The verb each page lands on, declared via data-demo-pick on the palette.
const chip = highlightCmd(palette && palette.dataset.demoPick);
if (chip) moveCursor(chip);
break;
}
case 'pick-target':
cursor.classList.add('is-click');
setTimeout(() => cursor.classList.remove('is-click'), 220);
if (cmdName && palette && palette.dataset.demoPick) cmdName.textContent = palette.dataset.demoPick;
setPalette(false);
break;
case 'cursor-to-button':
// Over the purple "Book Now" button (lower-left).
moveCursor(target, -150, 26);
@@ -243,7 +298,7 @@ export function initLiveDemo() {
const myToken = ++cancelToken;
while (running && myToken === cancelToken) {
reset();
for (const s of TIMELINE) {
for (const s of timeline) {
const stillMe = await sleep(s.dt, myToken);
if (!stillMe || !running) return;
await step(s);
+73
View File
@@ -1368,6 +1368,79 @@
border: 0;
}
/* Command-palette switcher — the picker's design vocabulary. Mirrors the real
action picker in live-browser.js: a 4-column grid of icon-over-label chips,
the selected chip on a soft kinpaku wash with its icon recolored to brand.
This is the move that makes the live approach unique, so the demo shows it. */
.home-kinpaku .live-demo-ctx-palette,
.live-mode-kinpaku .live-demo-ctx-palette {
position: absolute;
left: 6px;
bottom: calc(100% + 8px);
z-index: 2;
padding: 6px;
border-radius: 8px;
background: var(--ks-lacquer-deep);
border: 1px solid oklch(92% 0 0 / 0.13);
box-shadow: 0 18px 40px -14px oklch(0% 0 0 / 0.7);
opacity: 0;
transform: translateY(6px) scale(0.97);
transform-origin: bottom left;
pointer-events: none;
transition: opacity 160ms ease, transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
}
.home-kinpaku .live-demo-ctx-palette.is-open,
.live-mode-kinpaku .live-demo-ctx-palette.is-open {
opacity: 1;
transform: none;
}
.home-kinpaku .live-demo-ctx-palette-grid,
.live-mode-kinpaku .live-demo-ctx-palette-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 3px;
}
.home-kinpaku .live-demo-ctx-palette-chip,
.live-mode-kinpaku .live-demo-ctx-palette-chip {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
width: 62px;
padding: 9px 6px;
border: 0;
border-radius: 6px;
background: transparent;
color: var(--ks-champagne);
font-family: var(--ks-font);
font-size: 11px;
font-weight: 500;
white-space: nowrap;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease;
}
.home-kinpaku .live-demo-ctx-palette-ico,
.live-mode-kinpaku .live-demo-ctx-palette-ico {
display: flex;
align-items: center;
justify-content: center;
height: 20px;
opacity: 0.82;
}
.home-kinpaku .live-demo-ctx-palette-name,
.live-mode-kinpaku .live-demo-ctx-palette-name {
line-height: 1;
}
.home-kinpaku .live-demo-ctx-palette-chip.is-highlight,
.live-mode-kinpaku .live-demo-ctx-palette-chip.is-highlight {
background: oklch(78% 0.12 82 / 0.16);
color: var(--ks-kinpaku);
}
.home-kinpaku .live-demo-ctx-palette-chip.is-highlight .live-demo-ctx-palette-ico,
.live-mode-kinpaku .live-demo-ctx-palette-chip.is-highlight .live-demo-ctx-palette-ico {
opacity: 1;
}
.home-kinpaku .live-demo-ctx-nav,
.live-mode-kinpaku .live-demo-ctx-nav {
background: transparent;
+14
View File
@@ -1352,6 +1352,20 @@ html.light .live-mode-kinpaku .live-demo-ctx-accept {
color: oklch(14% 0.018 95);
}
/* Palette switcher — light surface, kinpaku-tinted highlight (the dark graphite
chip would be a black smudge on the pale menu). */
html.light .home-kinpaku .live-demo-ctx-palette,
html.light .live-mode-kinpaku .live-demo-ctx-palette {
background: var(--ks-lacquer-raised);
border-color: var(--ks-rule);
box-shadow: 0 18px 40px -14px oklch(25% 0.02 95 / 0.18);
}
html.light .home-kinpaku .live-demo-ctx-palette-chip.is-highlight,
html.light .live-mode-kinpaku .live-demo-ctx-palette-chip.is-highlight {
background: oklch(78% 0.12 82 / 0.16);
color: var(--ks-kinpaku-ink);
}
html.light .home-kinpaku .live-demo-ctx-caret,
html.light .live-mode-kinpaku .live-demo-ctx-caret {
background: var(--ks-kinpaku-rich);