pi-design-deck 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,357 @@
1
+ /**
2
+ * preview.css
3
+ * Preview blocks (mermaid, code, image, html) and .pv-* UI mockup components
4
+ */
5
+
6
+ /* ─────────────────────────────────────────────────────────────
7
+ PREVIEW BLOCKS
8
+ ───────────────────────────────────────────────────────────── */
9
+
10
+ .preview {
11
+ padding: 0;
12
+ font-family: var(--p-f, 'Albert Sans', sans-serif);
13
+ background: var(--p-bg);
14
+ color: var(--p-t);
15
+ overflow: hidden;
16
+ min-height: 260px;
17
+ }
18
+
19
+ .preview.preview-blocks { padding: 16px; min-height: auto; }
20
+
21
+ .preview-block { border-radius: 8px; overflow: hidden; }
22
+ .preview-block + .preview-block { margin-top: 8px; }
23
+
24
+ .preview-block-mermaid {
25
+ background: #1a1a22; padding: 20px;
26
+ display: flex; justify-content: center; align-items: center;
27
+ overflow-x: auto; min-height: 180px;
28
+ }
29
+ .preview-block-mermaid svg { max-width: 100%; height: auto; }
30
+
31
+ .preview-block-code { background: #141420; }
32
+ .preview-block-code pre {
33
+ margin: 0; padding: 12px 14px;
34
+ font-family: var(--dk-font-mono); font-size: 12px; line-height: 1.6;
35
+ white-space: pre-wrap;
36
+ overflow-wrap: break-word;
37
+ }
38
+ .preview-block-code pre code { background: transparent; }
39
+
40
+ .preview-block-image { background: #1a1a22; border-radius: 8px; overflow: hidden; }
41
+ .preview-block-image img { width: 100%; height: auto; display: block; }
42
+
43
+ .preview-block-caption {
44
+ padding: 8px 12px; font-size: 11px; color: var(--dk-text);
45
+ font-family: var(--dk-font-mono); letter-spacing: 0.2px;
46
+ border-top: 1px solid rgba(var(--dk-ink),0.12);
47
+ }
48
+
49
+ .preview-block-html { padding: 0; }
50
+
51
+ code[class*="language-"], pre[class*="language-"] {
52
+ background: transparent !important;
53
+ white-space: pre-wrap !important;
54
+ overflow-wrap: break-word !important;
55
+ word-break: break-word !important;
56
+ }
57
+
58
+ /* ─────────────────────────────────────────────────────────────
59
+ OPTION ASIDE
60
+ ───────────────────────────────────────────────────────────── */
61
+
62
+ .option-aside {
63
+ margin: 0 14px 14px;
64
+ padding: 12px 14px;
65
+ border-left: 2px solid rgba(138,190,183,0.25);
66
+ border-radius: 0 6px 6px 0;
67
+ background: rgba(var(--dk-ink),0.04);
68
+ font-size: 12px; line-height: 1.7;
69
+ color: var(--dk-text-aside);
70
+ font-family: var(--dk-font-mono);
71
+ font-style: italic;
72
+ letter-spacing: -0.01em;
73
+ }
74
+
75
+ /* ─────────────────────────────────────────────────────────────
76
+ PREVIEW COMPONENT SYSTEM (.pv-* classes for generated mockups)
77
+ ───────────────────────────────────────────────────────────── */
78
+
79
+ .pv-header {
80
+ display: flex; align-items: center; gap: 10px;
81
+ padding: 8px 14px;
82
+ background: color-mix(in srgb, var(--p-bg) 80%, var(--p-s));
83
+ border-bottom: 1px solid var(--p-b);
84
+ font-size: 11px;
85
+ }
86
+ .pv-brand { font-weight: 700; font-size: 12px; color: var(--p-t); display: flex; align-items: center; gap: 5px; }
87
+ .pv-brand svg { width: 12px; height: 12px; }
88
+ .pv-sep { width: 1px; height: 14px; background: var(--p-b); }
89
+ .pv-task { color: var(--p-td); font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
90
+ .pv-metrics { font-family: var(--p-fm, monospace); font-size: 9px; color: var(--p-td); white-space: nowrap; }
91
+ .pv-approve {
92
+ font-size: 10px; font-weight: 600; padding: 3px 10px;
93
+ border-radius: 4px; border: none;
94
+ background: var(--p-a); color: #fff;
95
+ }
96
+
97
+ .pv-body { padding: 12px 14px; display: flex; flex-direction: column; gap: 10px; }
98
+
99
+ /* Dispatch card */
100
+ .pv-dispatch {
101
+ border: 1px solid color-mix(in srgb, var(--p-warn, #f59e0b) 25%, transparent);
102
+ border-left: 3px solid var(--p-warn, #f59e0b);
103
+ border-radius: 6px; padding: 10px 12px;
104
+ }
105
+ .pv-dispatch-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
106
+ .pv-dispatch-spinner {
107
+ width: 10px; height: 10px; border: 1.5px solid var(--p-b);
108
+ border-top-color: var(--p-warn, #f59e0b); border-radius: 50%;
109
+ animation: spin 0.8s linear infinite;
110
+ }
111
+ @keyframes spin { to { transform: rotate(360deg) } }
112
+ .pv-dispatch-label { font-size: 10px; font-weight: 600; color: var(--p-warn, #f59e0b); }
113
+ .pv-dispatch-task {
114
+ font-size: 9px; color: var(--p-td); padding: 5px 8px;
115
+ background: color-mix(in srgb, var(--p-bg) 70%, var(--p-s));
116
+ border-radius: 4px; line-height: 1.4;
117
+ border-left: 2px solid color-mix(in srgb, var(--p-warn, #f59e0b) 20%, transparent);
118
+ }
119
+ .pv-dispatch-bar { height: 3px; background: var(--p-b); border-radius: 2px; margin-top: 6px; overflow: hidden; }
120
+ .pv-dispatch-fill {
121
+ height: 100%; width: 60%; border-radius: 2px;
122
+ background: linear-gradient(90deg, var(--p-warn, #f59e0b), var(--p-warn, #f59e0b) 45%, transparent 45%);
123
+ background-size: 200% 100%; animation: shimmer 2s linear infinite;
124
+ }
125
+ @keyframes shimmer { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
126
+
127
+ /* Finding card */
128
+ .pv-card {
129
+ background: var(--p-s);
130
+ border: 1px solid var(--p-b);
131
+ border-left: 3px solid var(--p-a);
132
+ border-radius: 6px; overflow: hidden;
133
+ }
134
+ .pv-card-head {
135
+ display: flex; align-items: center; gap: 6px;
136
+ padding: 8px 10px 0;
137
+ }
138
+ .pv-toggle {
139
+ width: 14px; height: 14px; border-radius: 3px;
140
+ background: var(--p-a); color: #fff;
141
+ display: flex; align-items: center; justify-content: center;
142
+ font-size: 9px; font-weight: 700; flex-shrink: 0;
143
+ border: none;
144
+ }
145
+ .pv-num { font-family: var(--p-fm, monospace); font-size: 9px; color: var(--p-td); font-weight: 600; }
146
+ .pv-title { font-size: 11px; font-weight: 600; color: var(--p-t); flex: 1; min-width: 0; }
147
+ .pv-badge {
148
+ font-family: var(--p-fm, monospace); font-size: 8px; font-weight: 600;
149
+ padding: 1px 5px; border-radius: 3px; white-space: nowrap;
150
+ text-transform: uppercase; letter-spacing: 0.3px;
151
+ }
152
+ .pv-badge-iter { background: color-mix(in srgb, var(--p-purple, #8b5cf6) 12%, transparent); color: var(--p-purple, #8b5cf6); }
153
+ .pv-badge-new { background: color-mix(in srgb, var(--p-warn, #f59e0b) 12%, transparent); color: var(--p-warn, #f59e0b); }
154
+
155
+ .pv-card-text {
156
+ padding: 6px 10px 0 30px;
157
+ font-size: 10px; line-height: 1.5; color: var(--p-td);
158
+ }
159
+ .pv-card-text code {
160
+ font-family: var(--p-fm, monospace); font-size: 9px;
161
+ padding: 0 3px; border-radius: 2px;
162
+ background: color-mix(in srgb, var(--p-a) 10%, transparent);
163
+ color: var(--p-a);
164
+ }
165
+ .pv-card-sources {
166
+ display: flex; gap: 3px; flex-wrap: wrap;
167
+ padding: 5px 10px 0 30px;
168
+ }
169
+ .pv-src {
170
+ font-family: var(--p-fm, monospace); font-size: 8px;
171
+ padding: 1px 5px; border-radius: 2px;
172
+ background: color-mix(in srgb, var(--p-link, #8abeb7) 10%, transparent);
173
+ color: var(--p-link, #8abeb7);
174
+ }
175
+ .pv-card-actions {
176
+ display: flex; gap: 3px; padding: 6px 10px 8px 30px;
177
+ }
178
+ .pv-act {
179
+ font-size: 9px; font-weight: 500; padding: 2px 7px;
180
+ border-radius: 3px; border: 1px solid var(--p-b);
181
+ background: transparent; color: var(--p-td);
182
+ }
183
+ .pv-annotation {
184
+ margin: 0 10px 8px 30px; padding: 5px 8px;
185
+ border-radius: 4px; font-size: 9px; line-height: 1.4;
186
+ background: color-mix(in srgb, var(--p-warn, #f59e0b) 8%, transparent);
187
+ border-left: 2px solid var(--p-warn, #f59e0b);
188
+ color: var(--p-warn, #f59e0b);
189
+ }
190
+ .pv-ann-icon { font-family: var(--p-fm, monospace); font-size: 8px; font-weight: 600; }
191
+ .pv-card-new { border-left-color: var(--p-warn, #f59e0b); }
192
+
193
+ /* ─────────────────────────────────────────────────────────────
194
+ LAYOUT VARIANTS
195
+ ───────────────────────────────────────────────────────────── */
196
+
197
+ .pv-layout-sidebar {
198
+ display: grid; grid-template-columns: 160px 1fr;
199
+ min-height: 370px;
200
+ }
201
+ .pv-layout-full {
202
+ display: flex; flex-direction: column;
203
+ min-height: 370px;
204
+ }
205
+
206
+ .pv-sidebar {
207
+ background: color-mix(in srgb, var(--p-bg) 60%, var(--p-s));
208
+ border-right: 1px solid var(--p-b);
209
+ padding: 8px 0;
210
+ }
211
+ .pv-sb-label {
212
+ font-family: var(--p-fm, monospace); font-size: 8px; font-weight: 600;
213
+ text-transform: uppercase; letter-spacing: 0.8px;
214
+ color: var(--p-td); padding: 4px 10px;
215
+ }
216
+ .pv-sb-item {
217
+ display: flex; align-items: center; gap: 5px;
218
+ padding: 3px 10px; font-size: 9px; color: var(--p-td);
219
+ }
220
+ .pv-sb-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--p-a); flex-shrink: 0; }
221
+ .pv-sb-link { color: var(--p-link, #8abeb7); font-size: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
222
+ .pv-sb-iter { font-family: var(--p-fm, monospace); font-size: 7px; color: var(--p-td); margin-left: auto; background: var(--p-b); padding: 0 3px; border-radius: 2px; }
223
+
224
+ .pv-sources-row {
225
+ display: flex; gap: 4px; flex-wrap: wrap; padding: 8px 14px;
226
+ border-bottom: 1px solid var(--p-b);
227
+ }
228
+ .pv-sources-row .pv-src { font-size: 8px; }
229
+
230
+ /* ─────────────────────────────────────────────────────────────
231
+ KANBAN LAYOUT
232
+ ───────────────────────────────────────────────────────────── */
233
+
234
+ .pv-kanban {
235
+ display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px;
236
+ padding: 10px; min-height: 370px;
237
+ }
238
+ .pv-kanban-col {
239
+ background: color-mix(in srgb, var(--p-bg) 60%, var(--p-s));
240
+ border-radius: 6px; padding: 6px; display: flex; flex-direction: column; gap: 5px;
241
+ }
242
+ .pv-kanban-label {
243
+ font-family: var(--p-fm, monospace); font-size: 8px; font-weight: 700;
244
+ text-transform: uppercase; letter-spacing: 0.8px; padding: 4px 6px;
245
+ display: flex; align-items: center; gap: 4px;
246
+ }
247
+ .pv-kanban-label .dot { width: 5px; height: 5px; border-radius: 50%; }
248
+ .pv-kanban-card {
249
+ background: var(--p-s); border: 1px solid var(--p-b);
250
+ border-radius: 4px; padding: 6px 8px; font-size: 9px; color: var(--p-t);
251
+ line-height: 1.3; cursor: grab;
252
+ }
253
+ .pv-kanban-card .pv-badge { font-size: 7px; margin-top: 3px; display: inline-block; }
254
+ .pv-kanban-card .pv-src { font-size: 7px; margin-top: 2px; display: inline-block; }
255
+ .pv-kanban-card-dim { opacity: 0.4; border-style: dashed; }
256
+ .pv-kanban-drop {
257
+ border: 1px dashed var(--p-b); border-radius: 4px;
258
+ padding: 8px; font-size: 8px; color: var(--p-td);
259
+ text-align: center; font-style: italic;
260
+ }
261
+
262
+ /* ─────────────────────────────────────────────────────────────
263
+ TABLE LAYOUT
264
+ ───────────────────────────────────────────────────────────── */
265
+
266
+ .pv-table { min-height: 370px; }
267
+ .pv-tbl {
268
+ width: 100%; border-collapse: collapse; font-size: 9px;
269
+ }
270
+ .pv-tbl th {
271
+ font-family: var(--p-fm, monospace); font-size: 8px; font-weight: 600;
272
+ text-transform: uppercase; letter-spacing: 0.5px; color: var(--p-td);
273
+ text-align: left; padding: 6px 8px;
274
+ border-bottom: 2px solid var(--p-b); background: color-mix(in srgb, var(--p-bg) 70%, var(--p-s));
275
+ }
276
+ .pv-tbl td { padding: 6px 8px; border-bottom: 1px solid var(--p-b); color: var(--p-t); vertical-align: middle; }
277
+ .pv-tbl tr:hover { background: rgba(255,255,255,0.02); }
278
+ .pv-tbl .pv-tbl-check {
279
+ width: 12px; height: 12px; border-radius: 3px; border: none;
280
+ display: inline-flex; align-items: center; justify-content: center;
281
+ font-size: 8px; font-weight: 700; color: #fff;
282
+ }
283
+ .pv-tbl .pv-tbl-title { font-weight: 600; }
284
+ .pv-tbl .pv-badge { font-size: 7px; }
285
+ .pv-tbl .pv-tbl-expand {
286
+ font-size: 8px; color: var(--p-td); background: none; border: 1px solid var(--p-b);
287
+ padding: 1px 5px; border-radius: 3px; cursor: pointer;
288
+ }
289
+ .pv-tbl-actions {
290
+ display: flex; gap: 8px; padding: 8px;
291
+ border-top: 1px solid var(--p-b); font-size: 9px;
292
+ }
293
+ .pv-tbl-batch {
294
+ font-size: 9px; padding: 3px 8px; border-radius: 3px;
295
+ border: 1px solid var(--p-b); background: transparent; color: var(--p-td);
296
+ }
297
+ .pv-tbl-batch-primary { background: var(--p-a); color: #fff; border-color: var(--p-a); }
298
+
299
+ /* ─────────────────────────────────────────────────────────────
300
+ SPLIT DISPATCH VIEW
301
+ ───────────────────────────────────────────────────────────── */
302
+
303
+ .pv-split {
304
+ display: grid; grid-template-columns: 1fr 1fr;
305
+ min-height: 370px;
306
+ }
307
+ .pv-split-left { border-right: 1px solid var(--p-b); }
308
+ .pv-split-right {
309
+ background: color-mix(in srgb, var(--p-bg) 60%, var(--p-s));
310
+ display: flex; flex-direction: column;
311
+ }
312
+ .pv-terminal-header {
313
+ display: flex; align-items: center; gap: 6px;
314
+ padding: 6px 10px;
315
+ border-bottom: 1px solid var(--p-b);
316
+ font-family: var(--p-fm, monospace); font-size: 9px; color: var(--p-td);
317
+ }
318
+ .pv-terminal-dot { width: 6px; height: 6px; border-radius: 50%; }
319
+ .pv-terminal-body {
320
+ flex: 1; padding: 8px 10px;
321
+ font-family: var(--p-fm, monospace); font-size: 8px; line-height: 1.6;
322
+ color: var(--p-td); overflow: hidden;
323
+ }
324
+ .pv-terminal-line { display: block; }
325
+ .pv-terminal-line .cmd { color: var(--p-a); }
326
+ .pv-terminal-line .out { color: var(--p-td); opacity: 0.7; }
327
+ .pv-terminal-line .active { color: var(--p-warn, #f59e0b); }
328
+
329
+ /* ─────────────────────────────────────────────────────────────
330
+ PALETTE THEMES
331
+ ───────────────────────────────────────────────────────────── */
332
+
333
+ .preview[data-theme="midnight-rose"] {
334
+ --p-bg: #13111c; --p-s: #1e1a2e; --p-a: #f43f5e;
335
+ --p-t: #e4e0f0; --p-td: #8b85a3; --p-b: rgba(255,255,255,0.06);
336
+ --p-link: #a78bfa; --p-warn: #fb923c; --p-purple: #c084fc;
337
+ }
338
+ .preview[data-theme="slate-jade"] {
339
+ --p-bg: #0c0f17; --p-s: #171b28; --p-a: #10b981;
340
+ --p-t: #dfe1eb; --p-td: #8e92a6; --p-b: rgba(255,255,255,0.06);
341
+ --p-link: #8abeb7; --p-warn: #f59e0b; --p-purple: #8b5cf6;
342
+ }
343
+ .preview[data-theme="warm-copper"] {
344
+ --p-bg: #f7f3ee; --p-s: #ffffff; --p-a: #b5652b;
345
+ --p-t: #2d2826; --p-td: #847a72; --p-b: rgba(0,0,0,0.08);
346
+ --p-link: #2563eb; --p-warn: #d97706; --p-purple: #7c3aed;
347
+ }
348
+ .preview[data-theme="ocean-cyan"] {
349
+ --p-bg: #0a1628; --p-s: #12203a; --p-a: #22d3ee;
350
+ --p-t: #e2e8f0; --p-td: #64748b; --p-b: rgba(255,255,255,0.08);
351
+ --p-link: #818cf8; --p-warn: #fbbf24; --p-purple: #c084fc;
352
+ }
353
+
354
+ /* Font themes */
355
+ .preview[data-fonts="albert"] { --p-f: 'Albert Sans', sans-serif; --p-fm: 'JetBrains Mono', monospace; }
356
+ .preview[data-fonts="jakarta"] { --p-f: 'Plus Jakarta Sans', sans-serif; --p-fm: 'Fira Code', monospace; }
357
+ .preview[data-fonts="grotesk"] { --p-f: 'Space Grotesk', sans-serif; --p-fm: 'IBM Plex Mono', monospace; }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * variables.css
3
+ * Theme tokens for dark and light modes
4
+ */
5
+
6
+ :root {
7
+ --dk-font: 'Outfit', system-ui, -apple-system, sans-serif;
8
+ --dk-font-mono: 'Space Mono', 'SF Mono', Consolas, monospace;
9
+ --dk-accent: #8abeb7;
10
+
11
+ --dk-ink: 255,255,255;
12
+ --dk-bg: #18181e;
13
+ --dk-glow: rgba(138,190,183,0.06);
14
+ --dk-surface: #1e1e24;
15
+ --dk-surface-shimmer: #262630;
16
+ --dk-elevated: #252530;
17
+ --dk-elevated-hover: #2b2b37;
18
+ --dk-text: #b0b0b0;
19
+ --dk-text-strong: #e0e0e0;
20
+ --dk-text-label: #c8c8c8;
21
+ --dk-text-secondary: #808080;
22
+ --dk-text-aside: #888888;
23
+ --dk-text-hint: #666666;
24
+ --dk-text-dim: #555555;
25
+ --dk-text-placeholder: #404040;
26
+ --dk-accent-text: #9dcec7;
27
+ --dk-status-success: #34d399;
28
+ --dk-status-warn: #f0c674;
29
+ --dk-status-error: #f87171;
30
+ --dk-overlay: rgba(24,24,30,0.95);
31
+ }
32
+
33
+ [data-theme="light"] {
34
+ --dk-ink: 0,0,0;
35
+ --dk-bg: #f8f8f8;
36
+ --dk-glow: rgba(95,135,135,0.08);
37
+ --dk-surface: #ffffff;
38
+ --dk-surface-shimmer: #ececec;
39
+ --dk-elevated: #f0f0f0;
40
+ --dk-elevated-hover: #e8e8e8;
41
+ --dk-text: #6c6c6c;
42
+ --dk-text-strong: #1a1a1a;
43
+ --dk-text-label: #3a3a3a;
44
+ --dk-text-secondary: #808080;
45
+ --dk-text-aside: #7a7a7a;
46
+ --dk-text-hint: #8a8a8a;
47
+ --dk-text-dim: #a0a0a0;
48
+ --dk-text-placeholder: #c8c8c8;
49
+ --dk-accent-text: #4a7a73;
50
+ --dk-status-success: #059669;
51
+ --dk-status-warn: #d97706;
52
+ --dk-status-error: #af5f5f;
53
+ --dk-overlay: rgba(255,255,255,0.95);
54
+ }
package/form/deck.html ADDED
@@ -0,0 +1,83 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="theme-color" content="#18181e">
7
+ <title>Design Deck</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Albert+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Plus+Jakarta+Sans:wght@400;500;600;700&family=Fira+Code:wght@400;500&family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Outfit:wght@400;500;600;700&family=Space+Mono:wght@400;500;600&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1/themes/prism-tomorrow.min.css">
12
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1/prism.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/prismjs@1/plugins/autoloader/prism-autoloader.min.js"></script>
14
+ <link rel="stylesheet" href="/deck.css">
15
+ </head>
16
+ <body>
17
+ <div class="deck">
18
+ <div class="deck-header">
19
+ <div class="progress"><div class="progress-fill" id="progress-fill" style="width: 0%"></div></div>
20
+ <div class="deck-meta" id="deck-meta"><h1 class="deck-title" id="deck-title">Design Decisions</h1></div>
21
+ </div>
22
+
23
+ <div class="slides-wrap" id="slides-wrap"></div>
24
+
25
+ <!-- Loading overlay - fades out when deck is ready -->
26
+ <div class="deck-loading" id="deck-loading">
27
+ <div class="deck-loading-spinner"></div>
28
+ <div class="deck-loading-text">Loading deck...</div>
29
+ </div>
30
+
31
+ <div class="confirm-bar" id="confirm-bar">
32
+ <span class="confirm-bar-text">Cancel deck? Selections will be lost.</span>
33
+ <button class="confirm-bar-btn confirm-bar-cancel" id="confirm-cancel" type="button">Cancel</button>
34
+ <button class="confirm-bar-btn confirm-bar-keep" id="confirm-keep" type="button">Keep Going</button>
35
+ </div>
36
+
37
+ <div class="deck-close-overlay" id="close-overlay">
38
+ <div class="close-overlay-content" id="close-overlay-content"></div>
39
+ </div>
40
+
41
+ <div class="deck-footer">
42
+ <button class="btn-nav" id="btn-back" type="button" disabled>&larr; Back</button>
43
+ <div class="deck-keys">
44
+ <span class="deck-key"><kbd>&larr;</kbd><kbd>&rarr;</kbd> Navigate</span>
45
+ <span class="deck-key"><kbd>1</kbd><kbd>2</kbd><kbd>3</kbd> Select</span>
46
+ <span class="deck-key"><kbd>Enter</kbd> Confirm</span>
47
+ <span class="deck-key"><kbd class="mod-key">⌘</kbd><kbd>S</kbd> Save</span>
48
+ <span class="deck-key hidden" id="theme-shortcut"></span>
49
+ </div>
50
+ <button class="btn-nav primary" id="btn-next" type="button">Next &rarr;</button>
51
+ </div>
52
+ </div>
53
+
54
+ <div id="save-toast" class="save-toast hidden" aria-live="polite"></div>
55
+
56
+ <script>
57
+ window.__DECK_DATA__ = /* __DECK_DATA_PLACEHOLDER__ */;
58
+ </script>
59
+ <script src="/deck.js"></script>
60
+ <script type="module">
61
+ import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
62
+ window.__mermaid = mermaid;
63
+ mermaid.initialize({
64
+ startOnLoad: false,
65
+ theme: 'base',
66
+ themeVariables: {
67
+ background: '#1a1a22',
68
+ primaryColor: '#2a3f3d',
69
+ primaryTextColor: '#e0e0e0',
70
+ primaryBorderColor: '#8abeb7',
71
+ lineColor: '#555555',
72
+ secondaryColor: '#1e2a2e',
73
+ tertiaryColor: '#1a1a22',
74
+ noteBkgColor: '#222230',
75
+ noteTextColor: '#b0b0b0',
76
+ fontSize: '13px',
77
+ fontFamily: "'Space Mono', monospace",
78
+ }
79
+ });
80
+ window.dispatchEvent(new Event('mermaid-ready'));
81
+ </script>
82
+ </body>
83
+ </html>
@@ -0,0 +1,199 @@
1
+ // ─── STATE & CONFIGURATION ───────────────────────────────────
2
+
3
+ const deckData = window.__DECK_DATA__ || {};
4
+ const config =
5
+ deckData && typeof deckData === "object" && deckData.config && typeof deckData.config === "object"
6
+ ? deckData.config
7
+ : { slides: [] };
8
+
9
+ const sessionToken = typeof deckData.sessionToken === "string" ? deckData.sessionToken : "";
10
+
11
+ const slides = Array.isArray(config.slides)
12
+ ? config.slides.map((slide) => ({
13
+ ...slide,
14
+ options: Array.isArray(slide.options) ? [...slide.options] : [],
15
+ }))
16
+ : [];
17
+
18
+ const totalSlides = slides.length + 1;
19
+ let current = 0;
20
+ let events = null;
21
+ let heartbeatTimer = null;
22
+ let isClosed = false;
23
+ let isSubmitting = false;
24
+
25
+ const selections = {};
26
+ const pendingGenerate = new Map();
27
+ let selectedModel = "";
28
+ let selectedThinking = "off";
29
+ let hasModelBar = false;
30
+
31
+ // DOM references
32
+ const progressFill = document.getElementById("progress-fill");
33
+ const slidesWrap = document.getElementById("slides-wrap");
34
+ const btnBack = document.getElementById("btn-back");
35
+ const btnNext = document.getElementById("btn-next");
36
+
37
+ // ─── UTILITIES ───────────────────────────────────────────────
38
+
39
+ function createElement(tag, className, text) {
40
+ const el = document.createElement(tag);
41
+ if (className) el.className = className;
42
+ if (text !== undefined) el.textContent = text;
43
+ return el;
44
+ }
45
+
46
+ function escapeHtml(str) {
47
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
48
+ }
49
+
50
+ function equalizeBlockHeights(container) {
51
+ const types = ["mermaid", "code", "html", "image"];
52
+ for (const type of types) {
53
+ const blocks = container.querySelectorAll(`.preview-block-${type}`);
54
+ if (blocks.length < 2) continue;
55
+ blocks.forEach((b) => { b.style.height = ""; });
56
+ let maxH = 0;
57
+ blocks.forEach((b) => { maxH = Math.max(maxH, b.offsetHeight); });
58
+ if (maxH > 0) blocks.forEach((b) => { b.style.height = maxH + "px"; });
59
+ }
60
+ }
61
+
62
+ function setMetaLabel() {
63
+ const deckTitle = document.getElementById("deck-title");
64
+ if (!deckTitle) return;
65
+ const title = typeof config.title === "string" && config.title ? config.title : "Design Decisions";
66
+ const cwd = typeof deckData.cwd === "string" ? deckData.cwd : "";
67
+ const gitBranch = typeof deckData.gitBranch === "string" ? deckData.gitBranch : "";
68
+ if (cwd) {
69
+ deckTitle.textContent = `${title} - ${cwd}${gitBranch ? ` (${gitBranch})` : ""}`;
70
+ return;
71
+ }
72
+ deckTitle.textContent = title;
73
+ }
74
+
75
+ // ─── THEME SYSTEM ────────────────────────────────────────────
76
+
77
+ const themeConfig = deckData && typeof deckData === "object" && deckData.theme && typeof deckData.theme === "object" ? deckData.theme : {};
78
+ const themeMode = typeof themeConfig.mode === "string" ? themeConfig.mode : "dark";
79
+ const themeToggleHotkey = typeof themeConfig.toggleHotkey === "string" ? themeConfig.toggleHotkey : "";
80
+ const THEME_OVERRIDE_KEY = "pi-deck-theme-override";
81
+
82
+ function getSystemTheme() {
83
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
84
+ }
85
+
86
+ function getStoredThemeOverride() {
87
+ try {
88
+ const value = localStorage.getItem(THEME_OVERRIDE_KEY);
89
+ return value === "light" || value === "dark" ? value : null;
90
+ } catch { return null; }
91
+ }
92
+
93
+ function setStoredThemeOverride(value) {
94
+ try {
95
+ if (!value) { localStorage.removeItem(THEME_OVERRIDE_KEY); return; }
96
+ localStorage.setItem(THEME_OVERRIDE_KEY, value);
97
+ } catch {}
98
+ }
99
+
100
+ function getEffectiveTheme() {
101
+ const override = getStoredThemeOverride();
102
+ if (override) return override;
103
+ if (themeMode === "auto") return getSystemTheme();
104
+ return themeMode;
105
+ }
106
+
107
+ function applyTheme(mode) {
108
+ document.documentElement.dataset.theme = mode;
109
+ document.documentElement.style.colorScheme = mode;
110
+ const meta = document.querySelector('meta[name="theme-color"]');
111
+ if (meta) meta.content = mode === "light" ? "#f8f8f8" : "#18181e";
112
+ }
113
+
114
+ function toggleTheme() {
115
+ const currentTheme = getEffectiveTheme();
116
+ const next = currentTheme === "dark" ? "light" : "dark";
117
+ if (themeMode === "auto") {
118
+ setStoredThemeOverride(next === getSystemTheme() ? null : next);
119
+ } else {
120
+ setStoredThemeOverride(next);
121
+ }
122
+ applyTheme(next);
123
+ }
124
+
125
+ function parseHotkey(value) {
126
+ if (!value) return null;
127
+ const parts = value.toLowerCase().split("+").map((p) => p.trim()).filter(Boolean);
128
+ if (parts.length === 0) return null;
129
+ const key = parts[parts.length - 1];
130
+ const mods = parts.slice(0, -1);
131
+ const hotkey = { key, mod: false, shift: false, alt: false };
132
+ mods.forEach((m) => {
133
+ if (m === "mod" || m === "cmd" || m === "meta" || m === "ctrl" || m === "control") hotkey.mod = true;
134
+ else if (m === "shift") hotkey.shift = true;
135
+ else if (m === "alt" || m === "option") hotkey.alt = true;
136
+ });
137
+ return key ? hotkey : null;
138
+ }
139
+
140
+ function matchesHotkey(event, hotkey) {
141
+ if (event.key.toLowerCase() !== hotkey.key) return false;
142
+ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
143
+ const modPressed = isMac ? event.metaKey : event.ctrlKey;
144
+ if (hotkey.mod !== modPressed) return false;
145
+ if (hotkey.shift !== event.shiftKey) return false;
146
+ if (hotkey.alt !== event.altKey) return false;
147
+ if (!hotkey.mod && (event.metaKey || event.ctrlKey)) return false;
148
+ if (!hotkey.shift && event.shiftKey) return false;
149
+ if (!hotkey.alt && event.altKey) return false;
150
+ return true;
151
+ }
152
+
153
+ function initTheme() {
154
+ applyTheme(getEffectiveTheme());
155
+
156
+ if (themeMode === "auto") {
157
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
158
+ media.addEventListener("change", () => {
159
+ if (!getStoredThemeOverride()) applyTheme(getSystemTheme());
160
+ });
161
+ }
162
+
163
+ const hotkey = parseHotkey(themeToggleHotkey);
164
+ if (hotkey) {
165
+ const shortcut = document.getElementById("theme-shortcut");
166
+ if (shortcut) {
167
+ const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
168
+ const parts = [];
169
+ if (hotkey.mod) parts.push(isMac ? "⌘" : "Ctrl");
170
+ if (hotkey.shift) parts.push("Shift");
171
+ if (hotkey.alt) parts.push(isMac ? "Option" : "Alt");
172
+ parts.push(hotkey.key.length === 1 ? hotkey.key.toUpperCase() : hotkey.key);
173
+ shortcut.innerHTML = parts.map((p) => `<kbd>${p}</kbd>`).join("") + " Theme";
174
+ shortcut.classList.remove("hidden");
175
+ }
176
+ document.addEventListener("keydown", (event) => {
177
+ if (matchesHotkey(event, hotkey)) { event.preventDefault(); toggleTheme(); }
178
+ });
179
+ }
180
+ }
181
+
182
+ // ─── SELECTION PERSISTENCE ────────────────────────────────────
183
+
184
+ const SELECTIONS_KEY = `pi-deck-${typeof deckData.sessionId === "string" ? deckData.sessionId : "unknown"}`;
185
+
186
+ function saveSelectionsToStorage() {
187
+ try { localStorage.setItem(SELECTIONS_KEY, JSON.stringify(selections)); } catch {}
188
+ }
189
+
190
+ function loadSelectionsFromStorage() {
191
+ try {
192
+ const saved = localStorage.getItem(SELECTIONS_KEY);
193
+ return saved ? JSON.parse(saved) : null;
194
+ } catch { return null; }
195
+ }
196
+
197
+ function clearSelectionsStorage() {
198
+ try { localStorage.removeItem(SELECTIONS_KEY); } catch {}
199
+ }