index.astro

  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">&#x261E;</div>
 77                <span class="live-demo-card-kicker">Dispatch</span>
 78                <h3>Design&nbsp;notes, <br>every&nbsp;other<br>Thursday.</h3>
 79                <button type="button">Join the list &rarr;</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>&star;</span><span>&star;</span><span>&star;</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">&#x273A;</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">&#x25BE;</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">&times;3</button>
111              <button type="button" class="live-demo-ctx-go" data-demo-go>Go <span aria-hidden="true">&rarr;</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&hellip;</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">&lsaquo;</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">&rsaquo;</button>
121              <span class="live-demo-ctx-divider"></span>
122              <button type="button" class="live-demo-ctx-discard" aria-label="Discard">&times;</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">&times;</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 &middot; 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 &middot; 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 &middot; 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 &rarr;</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 &rarr;</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 &rarr;</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>