1---
2import Base from '../../layouts/Base.astro';
3import '../../styles/sub-pages.css';
4import '../../styles/live-mode.css';
5---
6
7<Base
8 title="Live Mode | Impeccable"
9 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."
10 activeNav="live"
11 canonicalPath="/live-mode"
12 bodyClass="sub-page live-mode-page-body"
13>
14
15<div class="live-mode-page">
16 <header class="live-mode-page-header">
17 <p class="live-mode-page-eyebrow">New in v3.0 <span class="live-mode-page-eyebrow-badge">Alpha</span></p>
18 <h1 class="live-mode-page-title">Live Mode</h1>
19 <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>
20 <p class="live-mode-page-alpha-note"><strong>Why alpha:</strong> Live Mode works end-to-end and is ready to try, but it still needs more testing against real-world repos and framework configs. Expect rough edges on uncommon setups, and please report what breaks.</p>
21 <div class="live-mode-start" aria-label="Start live mode command">
22 <span class="live-mode-start-prompt">$</span>
23 <code class="live-mode-start-cmd">/impeccable live</code>
24 <button class="live-mode-start-copy" type="button" aria-label="Copy command" data-copy="/impeccable live">
25 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
26 <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
27 <path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"></path>
28 </svg>
29 </button>
30 </div>
31 </header>
32
33 <section class="live-mode-demo-wrap" aria-label="Live Mode interactive demo">
34 <div class="live-demo" id="live-demo" aria-label="Live Mode interactive demo loop">
35 <div class="live-demo-frame-col"><div class="live-demo-frame">
36 <div class="live-demo-chrome">
37 <span class="live-demo-dot"></span>
38 <span class="live-demo-dot"></span>
39 <span class="live-demo-dot"></span>
40 <span class="live-demo-url">localhost:3000</span>
41 </div>
42
43 <div class="live-demo-stage">
44 <div class="live-demo-skeleton" aria-hidden="true">
45 <div class="live-demo-skel-nav">
46 <span class="live-demo-skel-logo"></span>
47 <span class="live-demo-skel-link"></span>
48 <span class="live-demo-skel-link"></span>
49 <span class="live-demo-skel-link"></span>
50 <span class="live-demo-skel-cta"></span>
51 </div>
52 <div class="live-demo-skel-heading"></div>
53 <div class="live-demo-skel-line"></div>
54 <div class="live-demo-skel-line live-demo-skel-line--short"></div>
55 </div>
56
57 <div class="live-demo-target" data-demo-target>
58 <div class="live-demo-variant is-active" data-variant="original">
59 <div class="live-demo-card live-demo-card--plain">
60 <span class="live-demo-card-kicker">Newsletter</span>
61 <h3>Subscribe for updates</h3>
62 <p>Monthly-ish design notes.</p>
63 <button type="button">Subscribe</button>
64 </div>
65 </div>
66 <div class="live-demo-variant" data-variant="1">
67 <div class="live-demo-card live-demo-card--v1">
68 <span class="live-demo-card-kicker">No. 04</span>
69 <h3>Letters, <em>occasionally</em>.</h3>
70 <p>A postcard from the editor, about once a month. No tracking pixels, no "just checking in."</p>
71 <button type="button">Send me one</button>
72 </div>
73 </div>
74 <div class="live-demo-variant" data-variant="2">
75 <div class="live-demo-card live-demo-card--v2">
76 <div class="live-demo-card-stamp">☞</div>
77 <span class="live-demo-card-kicker">Dispatch</span>
78 <h3>Design notes, <br>every other<br>Thursday.</h3>
79 <button type="button">Join the list →</button>
80 </div>
81 </div>
82 <div class="live-demo-variant" data-variant="3">
83 <div class="live-demo-card live-demo-card--v3">
84 <div class="live-demo-card-sticker"><span>☆</span><span>☆</span><span>☆</span></div>
85 <span class="live-demo-card-kicker">Field Notes</span>
86 <h3>A monthly letter, for people who still read email for pleasure.</h3>
87 <button type="button">Receive the letter <span aria-hidden="true">✺</span></button>
88 </div>
89 </div>
90 </div>
91
92 <div class="live-demo-outline" data-demo-outline aria-hidden="true"></div>
93
94 <div class="live-demo-annotations" data-demo-annotations aria-hidden="true">
95 <svg class="live-demo-stroke" viewBox="0 0 300 60" preserveAspectRatio="none" aria-hidden="true">
96 <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"/>
97 </svg>
98 <div class="live-demo-comment">more playful</div>
99 </div>
100
101 <div class="live-demo-ctx" data-demo-ctx data-phase="hidden">
102 <div class="live-demo-ctx-row live-demo-ctx-row--configure">
103 <button type="button" class="live-demo-ctx-pill" data-demo-ctx-pill>
104 <span data-demo-cmd-name>delight</span>
105 <span class="live-demo-ctx-pill-caret" aria-hidden="true">▾</span>
106 </button>
107 <span class="live-demo-ctx-input" data-demo-input>
108 <span data-demo-input-text></span><span class="live-demo-ctx-caret"></span>
109 </span>
110 <button type="button" class="live-demo-ctx-count">×3</button>
111 <button type="button" class="live-demo-ctx-go" data-demo-go>Go <span aria-hidden="true">→</span></button>
112 </div>
113 <div class="live-demo-ctx-row live-demo-ctx-row--generating">
114 <span class="live-demo-ctx-spinner" aria-hidden="true"></span>
115 <span>Generating variants…</span>
116 </div>
117 <div class="live-demo-ctx-row live-demo-ctx-row--cycling">
118 <button type="button" class="live-demo-ctx-nav" aria-label="Previous variant">‹</button>
119 <span class="live-demo-ctx-counter" data-demo-counter>1 / 3</span>
120 <button type="button" class="live-demo-ctx-nav" aria-label="Next variant">›</button>
121 <span class="live-demo-ctx-divider"></span>
122 <button type="button" class="live-demo-ctx-discard" aria-label="Discard">×</button>
123 <button type="button" class="live-demo-ctx-accept" data-demo-accept>Accept</button>
124 </div>
125 <div class="live-demo-ctx-row live-demo-ctx-row--accepted">
126 <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>
127 <span>Variant 3 written to source</span>
128 </div>
129 </div>
130
131 <div class="live-demo-cursor" data-demo-cursor aria-hidden="true">
132 <svg width="18" height="22" viewBox="0 0 18 22" fill="none">
133 <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"/>
134 </svg>
135 </div>
136 </div>
137
138 <div class="live-demo-gbar" data-demo-gbar>
139 <span class="live-demo-gbar-brand">/</span>
140 <button type="button" class="live-demo-gbar-btn is-active" data-demo-gbar-pick>
141 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="22" y1="12" x2="18" y2="12"/><line x1="6" y1="12" x2="2" y2="12"/><line x1="12" y1="6" x2="12" y2="2"/><line x1="12" y1="22" x2="12" y2="18"/></svg>
142 <span>Pick</span>
143 </button>
144 <button type="button" class="live-demo-gbar-btn">
145 <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
146 </button>
147 <button type="button" class="live-demo-gbar-btn">
148 <span class="live-demo-gbar-dmd" aria-hidden="true"><span></span><span></span><span></span><span></span></span>
149 </button>
150 <span class="live-demo-gbar-divider"></span>
151 <button type="button" class="live-demo-gbar-x" aria-label="Exit live mode">×</button>
152 </div>
153 </div></div>
154 </div>
155 <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>
156 </section>
157
158 <section class="live-mode-stages" aria-label="What happens in a live mode session">
159 <h2 class="live-mode-stages-title">What happens, in three moves</h2>
160 <div class="live-mode-stages-grid">
161 <article class="live-mode-stage">
162 <span class="live-mode-stage-num">01 · Pick</span>
163 <h3 class="live-mode-stage-name">Point at what bugs you</h3>
164 <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>
165 <div class="live-mode-stage-viz">
166 <div class="docs-viz-picker-row" style="min-height:72px;padding:10px">
167 <div class="docs-viz-picker-target" style="font-size:12px;padding:6px 12px">
168 Newsletter card
169 <span class="docs-viz-picker-pin" style="width:18px;height:18px;font-size:9px">1</span>
170 </div>
171 </div>
172 </div>
173 </article>
174 <article class="live-mode-stage">
175 <span class="live-mode-stage-num">02 · Generate</span>
176 <h3 class="live-mode-stage-name">Three genuinely different takes</h3>
177 <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>
178 <div class="live-mode-stage-viz">
179 <div class="docs-viz-variants" style="width:100%;gap:4px">
180 <div class="docs-viz-variant docs-viz-variant--v1" style="min-height:44px;padding:6px"><span class="docs-viz-variant-kicker" style="font-size:8px">No.04</span></div>
181 <div class="docs-viz-variant docs-viz-variant--v2 is-active" style="min-height:44px;padding:6px"><span class="docs-viz-variant-kicker" style="font-size:8px">Dispatch</span></div>
182 <div class="docs-viz-variant docs-viz-variant--v3" style="min-height:44px;padding:6px"><span class="docs-viz-variant-kicker" style="font-size:8px">Field</span></div>
183 </div>
184 </div>
185 </article>
186 <article class="live-mode-stage">
187 <span class="live-mode-stage-num">03 · Accept</span>
188 <h3 class="live-mode-stage-name">Lands in real source</h3>
189 <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>
190 <div class="live-mode-stage-viz">
191 <span class="docs-viz-accept-pill">Variant 2 written to source</span>
192 </div>
193 </article>
194 </div>
195 </section>
196
197 <section class="live-mode-pathways" aria-label="Where to go next">
198 <h2 class="live-mode-pathways-title">Where next</h2>
199 <div class="live-mode-pathways-grid">
200 <a class="live-mode-pathway" href="/tutorials/iterate-live">
201 <span class="live-mode-pathway-kind">Tutorial</span>
202 <h3 class="live-mode-pathway-title">Walk it step by step</h3>
203 <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>
204 <span class="live-mode-pathway-cta">Open the tutorial →</span>
205 </a>
206 <a class="live-mode-pathway" href="/docs/live">
207 <span class="live-mode-pathway-kind">Reference</span>
208 <h3 class="live-mode-pathway-title">Full command reference</h3>
209 <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>
210 <span class="live-mode-pathway-cta">Read the reference →</span>
211 </a>
212 <a class="live-mode-pathway" href="/#downloads">
213 <span class="live-mode-pathway-kind">Install</span>
214 <h3 class="live-mode-pathway-title">Get Impeccable set up</h3>
215 <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, Codex, Gemini, and more.</p>
216 <span class="live-mode-pathway-cta">See the install steps →</span>
217 </a>
218 </div>
219 </section>
220
221 <section class="live-mode-frameworks" aria-label="Supported frameworks">
222 <span class="live-mode-frameworks-label">Supported dev servers</span>
223 <ul class="live-mode-frameworks-list">
224 <li>Vite</li>
225 <li>Next.js (incl. monorepos)</li>
226 <li>SvelteKit</li>
227 <li>Astro</li>
228 <li>Nuxt</li>
229 <li>Bun</li>
230 <li>Plain static HTML</li>
231 </ul>
232 </section>
233</div>
234
235<script>
236 import { initLiveDemo } from "../../scripts/components/live-demo.js";
237 document.addEventListener("DOMContentLoaded", initLiveDemo);
238</script>
239
240<script is:inline>
241 document.addEventListener('click', (e) => {
242 const btn = e.target.closest('[data-copy]');
243 if (!btn) return;
244 const text = btn.getAttribute('data-copy');
245 if (!text) return;
246 navigator.clipboard.writeText(text).then(() => {
247 btn.classList.add('is-copied');
248 setTimeout(() => btn.classList.remove('is-copied'), 1500);
249 }).catch(() => {});
250 });
251
252 document.addEventListener('click', (e) => {
253 const toggle = e.target.closest('.skills-sidebar-toggle');
254 if (!toggle) return;
255 const expanded = toggle.getAttribute('aria-expanded') === 'true';
256 toggle.setAttribute('aria-expanded', String(!expanded));
257 });
258
259 (function initSplitCompare() {
260 const wrappers = document.querySelectorAll('.split-comparison');
261 if (wrappers.length === 0) return;
262 const hasHover = matchMedia('(hover: hover)').matches;
263 const DEFAULT_POSITION = 50;
264
265 for (const wrapper of wrappers) {
266 const container = wrapper.querySelector('.split-container');
267 const splitAfter = wrapper.querySelector('.split-after');
268 const splitDivider = wrapper.querySelector('.split-divider');
269 if (!container || !splitAfter || !splitDivider) continue;
270
271 const tanAngle = Math.tan(10 * Math.PI / 180);
272 let skewOffset = 8;
273 const recalcSkew = () => {
274 const r = container.getBoundingClientRect();
275 if (r.width > 0 && r.height > 0) {
276 skewOffset = 50 * r.height * tanAngle / r.width;
277 }
278 };
279 recalcSkew();
280 window.addEventListener('resize', recalcSkew, { passive: true });
281
282 let targetX = DEFAULT_POSITION;
283 let currentX = DEFAULT_POSITION;
284 let rafId = null;
285
286 const paint = (pct) => {
287 const x = Math.max(-skewOffset, Math.min(100 + skewOffset, pct));
288 splitAfter.style.clipPath =
289 `polygon(${x + skewOffset}% 0%, 100% 0%, 100% 100%, ${x - skewOffset}% 100%)`;
290 splitDivider.style.left = `${x}%`;
291 };
292
293 const step = () => {
294 currentX += (targetX - currentX) * 0.2;
295 if (Math.abs(targetX - currentX) < 0.1) {
296 currentX = targetX;
297 rafId = null;
298 } else {
299 rafId = requestAnimationFrame(step);
300 }
301 paint(currentX);
302 };
303
304 const setTarget = (pct) => {
305 targetX = pct;
306 if (rafId === null) rafId = requestAnimationFrame(step);
307 };
308
309 paint(DEFAULT_POSITION);
310
311 const pctFromClientX = (clientX) => {
312 const rect = container.getBoundingClientRect();
313 return ((clientX - rect.left) / rect.width) * 100;
314 };
315
316 let hovering = false;
317 let dragging = false;
318
319 wrapper.addEventListener('pointerenter', (e) => {
320 if (hasHover && e.pointerType === 'mouse') hovering = true;
321 });
322
323 wrapper.addEventListener('pointerdown', (e) => {
324 dragging = true;
325 wrapper.setPointerCapture(e.pointerId);
326 setTarget(pctFromClientX(e.clientX));
327 });
328
329 wrapper.addEventListener('pointermove', (e) => {
330 if (dragging || hovering) setTarget(pctFromClientX(e.clientX));
331 });
332
333 const endDrag = (e) => {
334 if (dragging) {
335 dragging = false;
336 try { wrapper.releasePointerCapture(e.pointerId); } catch {}
337 }
338 };
339
340 wrapper.addEventListener('pointerup', endDrag);
341 wrapper.addEventListener('pointercancel', endDrag);
342
343 wrapper.addEventListener('pointerleave', (e) => {
344 endDrag(e);
345 if (hovering) {
346 hovering = false;
347 setTarget(DEFAULT_POSITION);
348 }
349 });
350 }
351 })();
352</script>
353
354</Base>