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