demo-renderer.js

  1// ============================================
  2// DEMO RENDERER - Generic rendering for command and skill demos
  3// ============================================
  4
  5import { getCommandDemo } from './demos/commands/index.js';
  6import { getSkillDemo } from './demos/skills/index.js';
  7
  8/**
  9 * Initialize a command demo's JS after its HTML has been inserted into the DOM.
 10 * Call this after innerHTML is set and split compare is initialized.
 11 */
 12export function initCommandDemo(commandId, container) {
 13  const demo = getCommandDemo(commandId);
 14  if (demo && typeof demo.init === 'function') {
 15    const demoArea = container.querySelector('.split-after .split-content') || container;
 16    console.log('[initCommandDemo]', commandId, 'demoArea:', demoArea);
 17    demo.init(demoArea);
 18  }
 19}
 20
 21/**
 22 * Render a command demo with split-screen comparison
 23 */
 24export function renderCommandDemo(commandId) {
 25  const demo = getCommandDemo(commandId);
 26
 27  if (!demo) {
 28    // impeccable has multiple modes — show a usage guide
 29    if (commandId === 'impeccable') {
 30      return `
 31        <div class="demo-container">
 32          <div class="demo-viewport" style="padding: var(--spacing-lg); font-size: 13px; line-height: 1.6;">
 33            <div style="display: flex; flex-direction: column; gap: 16px; color: var(--color-ash);">
 34              <div style="font-size: 14px; color: var(--color-text); font-weight: 600;">Three ways to use /impeccable</div>
 35              <div style="display: flex; flex-direction: column; gap: 14px;">
 36                <div style="display: flex; flex-direction: column; gap: 4px;">
 37                  <div style="display: flex; gap: 8px; align-items: baseline;">
 38                    <code style="font-size: 12px; color: var(--spread-accent, var(--color-accent)); font-weight: 600; white-space: nowrap;">/impeccable</code>
 39                    <span style="opacity: 0.4; font-size: 11px;">freeform</span>
 40                  </div>
 41                  <span style="padding-left: 0; opacity: 0.8;">Use on any task. Loads full design intelligence, anti-patterns, and reference knowledge into the current context.</span>
 42                </div>
 43                <div style="display: flex; flex-direction: column; gap: 4px;">
 44                  <div style="display: flex; gap: 8px; align-items: baseline;">
 45                    <code style="font-size: 12px; color: var(--spread-accent, var(--color-accent)); font-weight: 600; white-space: nowrap;">/impeccable teach</code>
 46                    <span style="opacity: 0.4; font-size: 11px;">one-time setup</span>
 47                  </div>
 48                  <span style="padding-left: 0; opacity: 0.8;">Scans your codebase, interviews you about brand and audience, then saves a Design Context that all other commands use automatically.</span>
 49                </div>
 50                <div style="display: flex; flex-direction: column; gap: 4px;">
 51                  <div style="display: flex; gap: 8px; align-items: baseline;">
 52                    <code style="font-size: 12px; color: var(--spread-accent, var(--color-accent)); font-weight: 600; white-space: nowrap;">/impeccable craft</code>
 53                    <span style="opacity: 0.4; font-size: 11px;">build a feature</span>
 54                  </div>
 55                  <span style="padding-left: 0; opacity: 0.8;">Runs /shape to plan UX first, loads the right references, then builds and iterates visually until the result delights.</span>
 56                </div>
 57              </div>
 58              <div style="font-size: 12px; opacity: 0.5; margin-top: 2px; font-style: italic;">Start with <code style="font-size: 11px;">/impeccable teach</code> once per project. Then use the other modes as needed.</div>
 59            </div>
 60          </div>
 61        </div>
 62      `;
 63    }
 64    // shape is a planning skill — show the process
 65    if (commandId === 'shape') {
 66      return `
 67        <div class="demo-container">
 68          <div class="demo-viewport" style="padding: var(--spacing-lg); font-size: 13px; line-height: 1.6;">
 69            <div style="display: flex; flex-direction: column; gap: 16px; color: var(--color-ash);">
 70              <div style="font-size: 14px; color: var(--color-text); font-weight: 600;">Design before you build</div>
 71              <div style="display: flex; flex-direction: column; gap: 14px;">
 72                <div style="display: flex; flex-direction: column; gap: 4px;">
 73                  <div style="display: flex; gap: 8px; align-items: baseline;">
 74                    <span style="color: var(--spread-accent, var(--color-accent)); font-weight: 600; font-size: 12px;">1. Discovery</span>
 75                  </div>
 76                  <span style="opacity: 0.8;">Interviews you about purpose, audience, content, constraints, and anti-goals. Adapts questions based on your answers.</span>
 77                </div>
 78                <div style="display: flex; flex-direction: column; gap: 4px;">
 79                  <div style="display: flex; gap: 8px; align-items: baseline;">
 80                    <span style="color: var(--spread-accent, var(--color-accent)); font-weight: 600; font-size: 12px;">2. Design Brief</span>
 81                  </div>
 82                  <span style="opacity: 0.8;">Synthesizes a 9-section brief: feature summary, primary action, design direction, layout strategy, key states, interaction model, content needs, recommended references, and open questions.</span>
 83                </div>
 84                <div style="display: flex; flex-direction: column; gap: 4px;">
 85                  <div style="display: flex; gap: 8px; align-items: baseline;">
 86                    <span style="color: var(--spread-accent, var(--color-accent)); font-weight: 600; font-size: 12px;">3. Handoff</span>
 87                  </div>
 88                  <span style="opacity: 0.8;">The confirmed brief guides <code style="font-size: 11px;">/impeccable craft</code> or any other implementation approach. No code written, just the thinking that makes code good.</span>
 89                </div>
 90              </div>
 91              <div style="font-size: 12px; opacity: 0.5; margin-top: 2px; font-style: italic;">Use standalone or as the first step of <code style="font-size: 11px;">/impeccable craft</code>.</div>
 92            </div>
 93          </div>
 94        </div>
 95      `;
 96    }
 97    return `
 98      <div class="demo-container">
 99        <div class="demo-viewport">
100          <div style="text-align: center; color: var(--color-ash); font-style: italic; padding: var(--spacing-lg);">
101            Visual demo for /${commandId} coming soon
102          </div>
103        </div>
104      </div>
105    `;
106  }
107
108  // Use split-screen comparison
109  return `
110    <div class="demo-split-comparison" data-demo="command-${demo.id}">
111      <div class="split-container">
112        <div class="split-before">
113          <div class="split-content">${demo.before}</div>
114        </div>
115        <div class="split-after">
116          <div class="split-content">${demo.after || demo.before}</div>
117        </div>
118        <div class="split-divider"></div>
119      </div>
120      <div class="demo-caption">${demo.caption}</div>
121    </div>
122  `;
123}
124
125/**
126 * Render a skill demo (with tabs if multiple demos)
127 */
128export function renderSkillDemo(skillId) {
129  const skill = getSkillDemo(skillId);
130
131  if (!skill || !skill.tabs || skill.tabs.length === 0) {
132    return `
133      <div class="demo-container">
134        <div class="demo-viewport">
135          <div style="text-align: center; color: var(--color-ash); padding: var(--spacing-xl);">
136            <p>Demo for ${skillId.replace(/-/g, ' ')} coming soon</p>
137          </div>
138        </div>
139      </div>
140    `;
141  }
142
143  const showTabs = skill.tabs.length > 1;
144
145  const tabs = showTabs ? skill.tabs.map((tab, i) => `
146    <button class="demo-tab ${i === 0 ? 'active' : ''}" data-demo-tab="${tab.id}" data-skill="${skillId}">
147      ${tab.label}
148    </button>
149  `).join('') : '';
150
151  const panels = skill.tabs.map((tab, i) => `
152    <div class="demo-panel ${i === 0 ? 'active' : ''}" data-demo-panel="${tab.id}">
153      ${renderSkillTabDemo(skillId, tab)}
154    </div>
155  `).join('');
156
157  return `
158    <div class="demo-tabbed-container">
159      ${showTabs ? `<div class="demo-tabs">${tabs}</div>` : ''}
160      <div class="demo-panels">
161        ${panels}
162      </div>
163    </div>
164  `;
165}
166
167/**
168 * Render a single skill tab demo
169 */
170function renderSkillTabDemo(skillId, tab) {
171  const hasToggle = tab.hasToggle !== false;
172  const demoId = `${skillId}-${tab.id}`;
173
174  return `
175    <div class="demo-container">
176      <div class="demo-header">
177        ${hasToggle ? `
178          <div class="demo-toggle">
179            <span class="demo-toggle-label active" id="${demoId}-before-label">Before</span>
180            <button class="demo-toggle-switch" data-demo="${demoId}" role="switch" aria-checked="false" aria-labelledby="${demoId}-before-label ${demoId}-after-label"></button>
181            <span class="demo-toggle-label" id="${demoId}-after-label">After</span>
182          </div>
183        ` : ''}
184      </div>
185      <div class="demo-viewport" data-state="before" id="${demoId}-viewport">
186        ${tab.before}
187      </div>
188      <div class="demo-caption">${tab.caption}</div>
189    </div>
190  `;
191}
192
193/**
194 * Setup demo tab switching
195 */
196export function setupDemoTabs() {
197  document.querySelectorAll('.demo-tab').forEach(tab => {
198    tab.addEventListener('click', () => {
199      const tabId = tab.dataset.demoTab;
200      const container = tab.closest('.demo-tabbed-container');
201
202      container.querySelectorAll('.demo-tab').forEach(t => t.classList.remove('active'));
203      tab.classList.add('active');
204
205      container.querySelectorAll('.demo-panel').forEach(p => p.classList.remove('active'));
206      container.querySelector(`[data-demo-panel="${tabId}"]`)?.classList.add('active');
207    });
208  });
209}
210
211
212