From 1f975a69e4d09b84f692928ce074baa5b0c40000 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Fri, 5 Jun 2026 13:48:01 -0700 Subject: [PATCH] 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 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) --- site/components/LiveDemoPalette.astro | 41 +++++++++++++++ site/pages/index.astro | 10 ++-- site/pages/live-mode/index.astro | 4 +- site/scripts/components/live-demo.js | 67 +++++++++++++++++++++--- site/styles/kinpaku-kit.css | 73 +++++++++++++++++++++++++++ site/styles/light-mode.css | 14 +++++ 6 files changed, 198 insertions(+), 11 deletions(-) create mode 100644 site/components/LiveDemoPalette.astro diff --git a/site/components/LiveDemoPalette.astro b/site/components/LiveDemoPalette.astro new file mode 100644 index 000000000..b551b2281 --- /dev/null +++ b/site/components/LiveDemoPalette.astro @@ -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: `` }, + { value: 'bolder', label: 'Bolder', icon: `` }, + { value: 'quieter', label: 'Quieter', icon: `` }, + { value: 'distill', label: 'Distill', icon: `` }, + { value: 'polish', label: 'Polish', icon: `` }, + { value: 'typeset', label: 'Typeset', icon: `` }, + { value: 'colorize', label: 'Colorize', icon: `` }, + { value: 'layout', label: 'Layout', icon: `` }, + { value: 'adapt', label: 'Adapt', icon: `` }, + { value: 'animate', label: 'Animate', icon: `` }, + { value: 'delight', label: 'Delight', icon: `` }, + { value: 'overdrive', label: 'Overdrive', icon: `` }, +]; + +interface Props { + pick: string; +} +const { pick } = Astro.props; +--- + + diff --git a/site/pages/index.astro b/site/pages/index.astro index e81427121..f47f2db72 100644 --- a/site/pages/index.astro +++ b/site/pages/index.astro @@ -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';
-

Tune interfaces
while they run.

+

The missing design
vocabulary for agents.

- 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.

@@ -206,6 +207,7 @@ import '../styles/testimonials.css';
+
Generating variants… diff --git a/site/pages/live-mode/index.astro b/site/pages/live-mode/index.astro index b16360ae7..46d564dcc 100644 --- a/site/pages/live-mode/index.astro +++ b/site/pages/live-mode/index.astro @@ -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';
@@ -112,6 +113,7 @@ import '../../styles/live-mode-kinpaku.css';
+
Generating variants… diff --git a/site/scripts/components/live-demo.js b/site/scripts/components/live-demo.js index 11315a549..e783f7f65 100644 --- a/site/scripts/components/live-demo.js +++ b/site/scripts/components/live-demo.js @@ -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 what’s 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); diff --git a/site/styles/kinpaku-kit.css b/site/styles/kinpaku-kit.css index 194f8d58e..7440dbb87 100644 --- a/site/styles/kinpaku-kit.css +++ b/site/styles/kinpaku-kit.css @@ -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; diff --git a/site/styles/light-mode.css b/site/styles/light-mode.css index 7d8450627..30b8684ad 100644 --- a/site/styles/light-mode.css +++ b/site/styles/light-mode.css @@ -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);