mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-16 08:06:24 +03:00
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:
co-authored by
Claude Opus 4.8
parent
0939528b95
commit
1f975a69e4
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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">▾</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">×3</button>
|
||||
<button type="button" class="live-demo-ctx-go" data-demo-go>Go <span aria-hidden="true">→</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…</span>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user