mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 07:36:50 +03:00
355 lines
17 KiB
Plaintext
355 lines
17 KiB
Plaintext
---
|
|
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';
|
|
---
|
|
|
|
<Base
|
|
title="Live Mode | Impeccable"
|
|
description="Iterate on UI in the browser. Pick an element, drop a comment, get three production-quality variants, accept one, and it writes back to source. /impeccable live."
|
|
activeNav="live"
|
|
canonicalPath="/live-mode"
|
|
bodyClass="sub-page live-mode-page-body live-mode-kinpaku kinpaku-chrome"
|
|
>
|
|
|
|
<div class="live-mode-page">
|
|
<header class="live-mode-page-header">
|
|
<h1 class="live-mode-page-title">Live Mode <span class="live-mode-page-title-badge">Beta</span></h1>
|
|
<p class="live-mode-page-lede">Pick any element in the browser. Drop a comment or a stroke. Three production-quality variants swap in via your framework's HMR. Accept the one you want and it writes back to source.</p>
|
|
<div class="live-mode-start" aria-label="Start live mode command">
|
|
<span class="live-mode-start-prompt">$</span>
|
|
<code class="live-mode-start-cmd">/impeccable live</code>
|
|
<button class="live-mode-start-copy" type="button" aria-label="Copy command" data-copy="/impeccable live">
|
|
<svg class="live-mode-start-copy-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
|
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
|
|
</svg>
|
|
<svg class="live-mode-start-check-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="M20 6 9 17l-5-5"></path>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
<p class="live-mode-page-alpha-note"><strong>Why beta:</strong> works end-to-end across the common frameworks and is ready for daily use. Uncommon setups may still hit rough edges. Please report what breaks.</p>
|
|
</header>
|
|
|
|
<section class="live-mode-demo-wrap" aria-label="Live Mode interactive demo">
|
|
<div class="live-demo" id="live-demo" aria-label="Live Mode interactive demo loop">
|
|
<div class="live-demo-frame-col"><div class="live-demo-frame">
|
|
<div class="live-demo-chrome">
|
|
<span class="live-demo-dot"></span>
|
|
<span class="live-demo-dot"></span>
|
|
<span class="live-demo-dot"></span>
|
|
<span class="live-demo-url">localhost:3000</span>
|
|
</div>
|
|
|
|
<div class="live-demo-stage">
|
|
<div class="live-demo-skeleton" aria-hidden="true">
|
|
<div class="live-demo-skel-nav">
|
|
<span class="live-demo-skel-logo"></span>
|
|
<span class="live-demo-skel-link"></span>
|
|
<span class="live-demo-skel-link"></span>
|
|
<span class="live-demo-skel-link"></span>
|
|
<span class="live-demo-skel-cta"></span>
|
|
</div>
|
|
<div class="live-demo-skel-heading"></div>
|
|
<div class="live-demo-skel-line"></div>
|
|
<div class="live-demo-skel-line live-demo-skel-line--short"></div>
|
|
</div>
|
|
|
|
<div class="live-demo-target" data-demo-target>
|
|
<div class="live-demo-variant is-active" data-variant="original">
|
|
<div class="live-demo-card live-demo-card--plain">
|
|
<span class="live-demo-card-kicker">Newsletter</span>
|
|
<h3>Subscribe for updates</h3>
|
|
<p>Monthly-ish design notes.</p>
|
|
<button type="button">Subscribe</button>
|
|
</div>
|
|
</div>
|
|
<div class="live-demo-variant" data-variant="1">
|
|
<div class="live-demo-card live-demo-card--v1">
|
|
<span class="live-demo-card-kicker">No. 04</span>
|
|
<h3>Letters, <em>occasionally</em>.</h3>
|
|
<p>A postcard from the editor, about once a month. No tracking pixels, no "just checking in."</p>
|
|
<button type="button">Send me one</button>
|
|
</div>
|
|
</div>
|
|
<div class="live-demo-variant" data-variant="2">
|
|
<div class="live-demo-card live-demo-card--v2">
|
|
<div class="live-demo-card-stamp">☞</div>
|
|
<span class="live-demo-card-kicker">Dispatch</span>
|
|
<h3>Design notes, <br>every other<br>Thursday.</h3>
|
|
<button type="button">Join the list →</button>
|
|
</div>
|
|
</div>
|
|
<div class="live-demo-variant" data-variant="3">
|
|
<div class="live-demo-card live-demo-card--v3">
|
|
<div class="live-demo-card-sticker"><span>☆</span><span>☆</span><span>☆</span></div>
|
|
<span class="live-demo-card-kicker">Field Notes</span>
|
|
<h3>A monthly letter, for people who still read email for pleasure.</h3>
|
|
<button type="button">Receive the letter <span aria-hidden="true">✺</span></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="live-demo-outline" data-demo-outline aria-hidden="true"></div>
|
|
|
|
<div class="live-demo-annotations" data-demo-annotations aria-hidden="true">
|
|
<svg class="live-demo-stroke" viewBox="0 0 300 60" preserveAspectRatio="none" aria-hidden="true">
|
|
<path d="M 10,40 Q 60,10 110,38 T 210,32 T 290,20" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" pathLength="1"/>
|
|
</svg>
|
|
<div class="live-demo-comment">more playful</div>
|
|
</div>
|
|
|
|
<div class="live-demo-ctx" data-demo-ctx data-phase="hidden">
|
|
<div class="live-demo-ctx-row live-demo-ctx-row--configure">
|
|
<div class="live-demo-ctx-command">
|
|
<span class="live-demo-ctx-input" data-demo-input>
|
|
<button type="button" class="live-demo-ctx-selection">h1</button>
|
|
<span data-demo-input-text></span><span class="live-demo-ctx-caret"></span>
|
|
</span>
|
|
</div>
|
|
<div class="live-demo-ctx-trailing">
|
|
<div class="live-demo-ctx-modifiers">
|
|
<button type="button" class="live-demo-ctx-action" data-demo-ctx-pill>
|
|
<span data-demo-cmd-name>adapt</span>
|
|
<span class="live-demo-ctx-action-caret" aria-hidden="true">▾</span>
|
|
</button>
|
|
<button type="button" class="live-demo-ctx-count">×3</button>
|
|
</div>
|
|
<div class="live-demo-ctx-actions">
|
|
<button type="button" class="live-demo-ctx-voice" aria-label="Voice input">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" x2="12" y1="19" y2="22"></line></svg>
|
|
</button>
|
|
<button type="button" class="live-demo-ctx-go" data-demo-go aria-label="Generate variants">
|
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"></path><path d="m12 5 7 7-7 7"></path></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</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>
|
|
</div>
|
|
<div class="live-demo-ctx-row live-demo-ctx-row--cycling">
|
|
<button type="button" class="live-demo-ctx-nav" aria-label="Previous variant">‹</button>
|
|
<span class="live-demo-ctx-counter" data-demo-counter>1 / 3</span>
|
|
<button type="button" class="live-demo-ctx-nav" aria-label="Next variant">›</button>
|
|
<span class="live-demo-ctx-divider"></span>
|
|
<button type="button" class="live-demo-ctx-discard" aria-label="Discard">×</button>
|
|
<button type="button" class="live-demo-ctx-accept" data-demo-accept>Accept</button>
|
|
</div>
|
|
<div class="live-demo-ctx-row live-demo-ctx-row--accepted">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
<span>Variant 3 written to source</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="live-demo-cursor" data-demo-cursor aria-hidden="true">
|
|
<svg width="18" height="22" viewBox="0 0 18 22" fill="none">
|
|
<path d="M1 1 L1 17 L5 13 L8 20 L11 19 L7.5 12 L13 12 Z" fill="#111" stroke="#fff" stroke-width="1.2" stroke-linejoin="round"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<LiveDemoGbar />
|
|
</div></div>
|
|
<p class="live-mode-demo-caption">Click the frame or scroll it into view to start the loop. Respects <code>prefers-reduced-motion</code>.</p>
|
|
</section>
|
|
|
|
<section class="live-mode-stages" aria-label="What happens in a live mode session">
|
|
<h2 class="live-mode-stages-title">What happens, in three moves</h2>
|
|
<div class="live-mode-stages-grid">
|
|
<article class="live-mode-stage">
|
|
<span class="live-mode-stage-num">01 · Pick</span>
|
|
<h3 class="live-mode-stage-name">Point at what bugs you</h3>
|
|
<p class="live-mode-stage-desc">Click any element on your running dev server. Add a comment pin where the issue lives. Draw a stroke through the bit you want to change. Or just type "more playful".</p>
|
|
<div class="live-mode-stage-viz">
|
|
<div class="docs-viz-picker-row" style="min-height:72px;padding:10px">
|
|
<div class="docs-viz-picker-target" style="font-size:12px;padding:6px 12px">
|
|
Newsletter card
|
|
<span class="docs-viz-picker-pin" style="width:18px;height:18px;font-size:9px">1</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
<article class="live-mode-stage">
|
|
<span class="live-mode-stage-num">02 · Generate</span>
|
|
<h3 class="live-mode-stage-name">Three genuinely different takes</h3>
|
|
<p class="live-mode-stage-desc">Variants anchor to different archetypes, not three riffs on color. Each one explores a different primary axis: hierarchy, typography, density, layout, or palette strategy.</p>
|
|
<div class="live-mode-stage-viz live-mode-stage-viz--rail">
|
|
<div class="viz-rail">
|
|
<div class="viz-rail-cell"><span>No.04</span><span class="viz-rail-mark"></span></div>
|
|
<div class="viz-rail-cell is-active"><span>Dispatch</span><span class="viz-rail-mark"></span></div>
|
|
<div class="viz-rail-cell"><span>Field</span><span class="viz-rail-mark"></span></div>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
<article class="live-mode-stage">
|
|
<span class="live-mode-stage-num">03 · Accept</span>
|
|
<h3 class="live-mode-stage-name">Lands in real source</h3>
|
|
<p class="live-mode-stage-desc">The accepted variant replaces the picked element in your source file. CSS consolidates into your real stylesheet, not inline. Discard all three and the original stays.</p>
|
|
<div class="live-mode-stage-viz">
|
|
<span class="docs-viz-accept-pill">Variant 2 written to source</span>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="live-mode-pathways" aria-label="Where to go next">
|
|
<h2 class="live-mode-pathways-title">Where next</h2>
|
|
<div class="live-mode-pathways-grid">
|
|
<a class="live-mode-pathway" href="/tutorials/iterate-live">
|
|
<span class="live-mode-pathway-kind">Tutorial</span>
|
|
<h3 class="live-mode-pathway-title">Walk it step by step</h3>
|
|
<p class="live-mode-pathway-desc">A ten-minute walkthrough from first run to accepted variant. Covers CSP patching, the picker actions, and the fallback flow for generated files.</p>
|
|
<span class="live-mode-pathway-cta">Open the tutorial →</span>
|
|
</a>
|
|
<a class="live-mode-pathway" href="/docs/live">
|
|
<span class="live-mode-pathway-kind">Reference</span>
|
|
<h3 class="live-mode-pathway-title">Full command reference</h3>
|
|
<p class="live-mode-pathway-desc">Everything your AI harness reads when <code>/impeccable live</code> runs: the poll loop, the wrap/accept helpers, the CSP templates, and every event shape.</p>
|
|
<span class="live-mode-pathway-cta">Read the reference →</span>
|
|
</a>
|
|
<a class="live-mode-pathway" href="/#downloads">
|
|
<span class="live-mode-pathway-kind">Install</span>
|
|
<h3 class="live-mode-pathway-title">Get Impeccable set up</h3>
|
|
<p class="live-mode-pathway-desc">Install the skill and CLI once, then run <code>/impeccable live</code> from your AI harness. Works with Claude Code, Cursor, GitHub Copilot, Codex, Gemini, and more.</p>
|
|
<span class="live-mode-pathway-cta">See the install steps →</span>
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="live-mode-frameworks" aria-label="Supported frameworks">
|
|
<span class="live-mode-frameworks-label">Supported dev servers</span>
|
|
<ul class="live-mode-frameworks-list framework-list--matrix">
|
|
<li><small>bundler</small><strong>Vite</strong></li>
|
|
<li><small>apps</small><strong>Next.js</strong></li>
|
|
<li><small>apps</small><strong>SvelteKit</strong></li>
|
|
<li><small>pages</small><strong>Astro</strong></li>
|
|
<li><small>apps</small><strong>Nuxt</strong></li>
|
|
<li><small>runtime</small><strong>Bun</strong></li>
|
|
<li><small>static</small><strong>Plain HTML</strong></li>
|
|
</ul>
|
|
</section>
|
|
</div>
|
|
|
|
<script>
|
|
import { initLiveDemo, initGbarPageChat } from "../../scripts/components/live-demo.js";
|
|
import { initCopyFeedback } from "../../scripts/utils/copy-feedback.js";
|
|
|
|
document.addEventListener("DOMContentLoaded", () => {
|
|
initLiveDemo();
|
|
initGbarPageChat();
|
|
initCopyFeedback();
|
|
});
|
|
</script>
|
|
|
|
<script is:inline>
|
|
document.addEventListener('click', (e) => {
|
|
const toggle = e.target.closest('.skills-sidebar-toggle');
|
|
if (!toggle) return;
|
|
const expanded = toggle.getAttribute('aria-expanded') === 'true';
|
|
toggle.setAttribute('aria-expanded', String(!expanded));
|
|
});
|
|
|
|
(function initSplitCompare() {
|
|
const wrappers = document.querySelectorAll('.split-comparison');
|
|
if (wrappers.length === 0) return;
|
|
const hasHover = matchMedia('(hover: hover)').matches;
|
|
const DEFAULT_POSITION = 50;
|
|
|
|
for (const wrapper of wrappers) {
|
|
const container = wrapper.querySelector('.split-container');
|
|
const splitAfter = wrapper.querySelector('.split-after');
|
|
const splitDivider = wrapper.querySelector('.split-divider');
|
|
if (!container || !splitAfter || !splitDivider) continue;
|
|
|
|
const tanAngle = Math.tan(10 * Math.PI / 180);
|
|
let skewOffset = 8;
|
|
const recalcSkew = () => {
|
|
const r = container.getBoundingClientRect();
|
|
if (r.width > 0 && r.height > 0) {
|
|
skewOffset = 50 * r.height * tanAngle / r.width;
|
|
}
|
|
};
|
|
recalcSkew();
|
|
window.addEventListener('resize', recalcSkew, { passive: true });
|
|
|
|
let targetX = DEFAULT_POSITION;
|
|
let currentX = DEFAULT_POSITION;
|
|
let rafId = null;
|
|
|
|
const paint = (pct) => {
|
|
const x = Math.max(-skewOffset, Math.min(100 + skewOffset, pct));
|
|
splitAfter.style.clipPath =
|
|
`polygon(${x + skewOffset}% 0%, 100% 0%, 100% 100%, ${x - skewOffset}% 100%)`;
|
|
splitDivider.style.left = `${x}%`;
|
|
};
|
|
|
|
const step = () => {
|
|
currentX += (targetX - currentX) * 0.2;
|
|
if (Math.abs(targetX - currentX) < 0.1) {
|
|
currentX = targetX;
|
|
rafId = null;
|
|
} else {
|
|
rafId = requestAnimationFrame(step);
|
|
}
|
|
paint(currentX);
|
|
};
|
|
|
|
const setTarget = (pct) => {
|
|
targetX = pct;
|
|
if (rafId === null) rafId = requestAnimationFrame(step);
|
|
};
|
|
|
|
paint(DEFAULT_POSITION);
|
|
|
|
const pctFromClientX = (clientX) => {
|
|
const rect = container.getBoundingClientRect();
|
|
return ((clientX - rect.left) / rect.width) * 100;
|
|
};
|
|
|
|
let hovering = false;
|
|
let dragging = false;
|
|
|
|
wrapper.addEventListener('pointerenter', (e) => {
|
|
if (hasHover && e.pointerType === 'mouse') hovering = true;
|
|
});
|
|
|
|
wrapper.addEventListener('pointerdown', (e) => {
|
|
dragging = true;
|
|
wrapper.setPointerCapture(e.pointerId);
|
|
setTarget(pctFromClientX(e.clientX));
|
|
});
|
|
|
|
wrapper.addEventListener('pointermove', (e) => {
|
|
if (dragging || hovering) setTarget(pctFromClientX(e.clientX));
|
|
});
|
|
|
|
const endDrag = (e) => {
|
|
if (dragging) {
|
|
dragging = false;
|
|
try { wrapper.releasePointerCapture(e.pointerId); } catch {}
|
|
}
|
|
};
|
|
|
|
wrapper.addEventListener('pointerup', endDrag);
|
|
wrapper.addEventListener('pointercancel', endDrag);
|
|
|
|
wrapper.addEventListener('pointerleave', (e) => {
|
|
endDrag(e);
|
|
if (hovering) {
|
|
hovering = false;
|
|
setTarget(DEFAULT_POSITION);
|
|
}
|
|
});
|
|
}
|
|
})();
|
|
</script>
|
|
|
|
</Base>
|