thevoidforge 21.0.0 → 21.0.2

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,394 @@
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
+ <title>VoidForge — Setup Wizard</title>
7
+ <link rel="icon" type="image/svg+xml" href="favicon.svg">
8
+ <link rel="stylesheet" href="styles.css">
9
+ </head>
10
+ <body>
11
+ <!-- UX-R2-010: Skip navigation for keyboard/screen reader users -->
12
+ <a href="#wizard-body" class="skip-nav">Skip to main content</a>
13
+ <!-- UX-R2-030: noscript fallback -->
14
+ <noscript><div class="noscript-msg">VoidForge requires JavaScript to run. Please enable JavaScript and reload.</div></noscript>
15
+ <div class="wizard" id="wizard">
16
+ <header class="wizard-header">
17
+ <div class="logo">Gandalf — VoidForge Setup</div>
18
+ <div class="progress" id="progress">
19
+ <div class="progress-bar" id="progress-bar" role="progressbar" aria-valuenow="14" aria-valuemin="0" aria-valuemax="100" aria-label="Wizard progress"></div>
20
+ </div>
21
+ <div class="step-label" id="step-label">Step 1 of 5</div>
22
+ </header>
23
+
24
+ <main class="wizard-body" id="wizard-body">
25
+
26
+ <!-- Act 1: Secure Your Forge -->
27
+
28
+ <!-- Step 1: Vault Only -->
29
+ <section class="step" id="step-1" data-step="1" aria-labelledby="step-1-heading">
30
+ <h1 id="step-1-heading">Secure Your Forge</h1>
31
+ <p class="subtitle">This password protects everything you build.</p>
32
+
33
+ <div class="card" id="vault-card">
34
+ <h2>Credential Vault</h2>
35
+ <p>Your API keys are encrypted with a password you choose. The vault is stored at <code id="vault-path">~/.voidforge/vault.enc</code> — AES-256-GCM encrypted.</p>
36
+
37
+ <div class="field">
38
+ <label for="vault-password" id="vault-password-label">Choose a Vault Password</label>
39
+ <div class="input-row">
40
+ <input type="password" id="vault-password" placeholder="Enter a password to encrypt your credentials" autocomplete="off">
41
+ <button class="btn btn-secondary" id="toggle-vault-visibility" type="button" aria-label="Toggle password visibility">Show</button>
42
+ </div>
43
+ <div class="field-hint" id="vault-hint">This password encrypts your credentials locally. It never leaves your machine.</div>
44
+ </div>
45
+
46
+ <div class="status-row" id="vault-status" role="status" aria-live="polite"></div>
47
+
48
+ <button class="btn btn-primary" id="unlock-vault" type="button">Unlock Vault</button>
49
+ </div>
50
+ </section>
51
+
52
+ <!-- Step 2: API Key Only -->
53
+ <section class="step hidden" id="step-2" data-step="2" aria-labelledby="step-2-heading">
54
+ <h1 id="step-2-heading">Connect Claude</h1>
55
+ <p class="subtitle">Claude needs an Anthropic API key to help you build.</p>
56
+
57
+ <div class="card" id="apikey-card">
58
+ <div class="field">
59
+ <label for="anthropic-key">API Key</label>
60
+ <div class="input-row">
61
+ <input type="password" id="anthropic-key" placeholder="sk-ant-..." autocomplete="off" spellcheck="false">
62
+ <button class="btn btn-secondary" id="toggle-key-visibility" type="button" aria-label="Toggle key visibility">Show</button>
63
+ </div>
64
+ <div class="field-hint">Get your key at <a href="https://console.anthropic.com/settings/keys" target="_blank" rel="noopener">console.anthropic.com</a></div>
65
+ </div>
66
+
67
+ <div class="status-row" id="key-status" role="status" aria-live="polite"></div>
68
+
69
+ <button class="btn btn-primary" id="validate-key" type="button">Validate & Store</button>
70
+ <button class="btn btn-secondary" id="skip-key" type="button" style="margin-left: 8px;">Skip for now</button>
71
+ </div>
72
+ </section>
73
+
74
+ <!-- Act 2: Describe Your Vision -->
75
+
76
+ <!-- Step 3: Project Setup -->
77
+ <section class="step hidden" id="step-3" data-step="3" aria-labelledby="step-3-heading">
78
+ <h1 id="step-3-heading">Name Your Project</h1>
79
+ <p class="subtitle">What are you building?</p>
80
+
81
+ <div class="card">
82
+ <div class="field">
83
+ <label for="project-name">Project Name</label>
84
+ <input type="text" id="project-name" placeholder="My Awesome App">
85
+ </div>
86
+
87
+ <div class="field">
88
+ <label for="project-dir">Project Directory</label>
89
+ <input type="text" id="project-dir" placeholder="/Users/you/Projects/my-app">
90
+ <div class="field-hint">Where the project files will be created</div>
91
+ </div>
92
+
93
+ <div class="field">
94
+ <label for="project-desc">One-Line Description <span class="optional">(optional)</span></label>
95
+ <input type="text" id="project-desc" placeholder="A tool for managing widget inventories">
96
+ </div>
97
+ </div>
98
+ </section>
99
+
100
+ <!-- Blueprint Detection Banner (shown when docs/PRD.md exists) -->
101
+ <div id="blueprint-detection" class="hidden" role="alert" aria-live="assertive" style="background: var(--surface, #111); border: 1px solid var(--accent, #5b5bf7); border-radius: 8px; padding: 1.5rem; margin: 1rem 0; text-align: center;">
102
+ <h2 style="margin: 0 0 0.5rem 0; color: var(--accent, #5b5bf7);">Blueprint Detected</h2>
103
+ <p style="margin: 0 0 1rem 0;">Gandalf notices a scroll on the table. You already have a PRD for <strong id="blueprint-project-name">your project</strong>.</p>
104
+ <p style="margin: 0 0 1.5rem 0; opacity: 0.8;">Shall I use this as your blueprint, or would you prefer to start the interview fresh?</p>
105
+ <div style="display: flex; gap: 1rem; justify-content: center;">
106
+ <button type="button" id="btn-use-blueprint" style="padding: 0.75rem 1.5rem; background: var(--accent, #5b5bf7); color: #fff; border: none; border-radius: 6px; font-weight: bold; cursor: pointer;">Use my blueprint</button>
107
+ <button type="button" id="btn-start-fresh" style="padding: 0.75rem 1.5rem; background: transparent; color: var(--text, #ccc); border: 1px solid var(--border, #333); border-radius: 6px; cursor: pointer;">Start fresh</button>
108
+ </div>
109
+ </div>
110
+
111
+ <!-- Step 4: PRD -->
112
+ <section class="step hidden" id="step-4" data-step="4" aria-labelledby="step-4-heading">
113
+ <h1 id="step-4-heading">Describe Your Vision</h1>
114
+ <p class="subtitle">Every great project starts with a PRD.</p>
115
+
116
+ <!-- UX-R2-011/019: ARIA tab linkage with ids and aria-controls -->
117
+ <div class="tabs" role="tablist">
118
+ <button class="tab active" id="tab-btn-generate" data-tab="generate" type="button" role="tab" aria-selected="true" aria-controls="tab-generate">Generate with Claude</button>
119
+ <button class="tab" id="tab-btn-paste" data-tab="paste" type="button" role="tab" aria-selected="false" aria-controls="tab-paste">Bring Your Own PRD</button>
120
+ <button class="tab" id="tab-btn-skip" data-tab="skip" type="button" role="tab" aria-selected="false" aria-controls="tab-skip">Skip for Now</button>
121
+ </div>
122
+
123
+ <div class="tab-panel active" id="tab-generate" role="tabpanel" aria-labelledby="tab-btn-generate">
124
+ <div class="card">
125
+ <p>Describe your idea and Claude will generate a production-ready PRD.</p>
126
+
127
+ <div class="field">
128
+ <label for="prd-idea">Your Idea</label>
129
+ <textarea id="prd-idea" rows="4" placeholder="A marketplace where freelance developers can sell reusable code components..."></textarea>
130
+ </div>
131
+
132
+ <div class="field-row">
133
+ <div class="field">
134
+ <label for="pref-framework">Framework <span class="optional">(optional)</span></label>
135
+ <select id="pref-framework">
136
+ <option value="">Auto-detect</option>
137
+ <option value="next.js">Next.js</option>
138
+ <option value="express">Express</option>
139
+ <option value="django">Django</option>
140
+ <option value="rails">Rails</option>
141
+ </select>
142
+ </div>
143
+ <div class="field">
144
+ <label for="pref-database">Database <span class="optional">(optional)</span></label>
145
+ <select id="pref-database">
146
+ <option value="">Auto-detect</option>
147
+ <option value="postgres">PostgreSQL</option>
148
+ <option value="mysql">MySQL</option>
149
+ <option value="sqlite">SQLite</option>
150
+ <option value="mongodb">MongoDB</option>
151
+ </select>
152
+ </div>
153
+ <div class="field">
154
+ <label for="pref-deploy">Deploy Target <span class="optional">(optional)</span></label>
155
+ <select id="pref-deploy">
156
+ <option value="">Auto-detect</option>
157
+ <option value="vps">VPS (EC2)</option>
158
+ <option value="vercel">Vercel</option>
159
+ <option value="railway">Railway</option>
160
+ <option value="docker">Docker</option>
161
+ </select>
162
+ </div>
163
+ </div>
164
+
165
+ <button class="btn btn-primary" id="generate-prd" type="button">Generate PRD with Claude</button>
166
+ <div class="status-row" id="generate-status" role="status" aria-live="polite"></div>
167
+
168
+ <div class="generation-output hidden" id="generation-output">
169
+ <div class="generation-header">
170
+ <span>Generated PRD</span>
171
+ <button class="btn btn-small" id="copy-generated" type="button">Copy</button>
172
+ </div>
173
+ <pre id="generated-prd-content"></pre>
174
+ </div>
175
+ </div>
176
+ </div>
177
+
178
+ <div class="tab-panel" id="tab-paste" role="tabpanel" aria-labelledby="tab-btn-paste">
179
+ <div class="card">
180
+ <p>Already have a PRD, or prefer to generate one with a different AI? Paste the finished result below.</p>
181
+
182
+ <div class="field">
183
+ <label>PRD Generator Prompt</label>
184
+ <p class="field-hint" style="margin-bottom: 8px;">Copy this prompt into ChatGPT, Gemini, or any AI — then paste the output below.</p>
185
+ <button class="btn btn-secondary" id="copy-prd-prompt" type="button">Copy Generator Prompt to Clipboard</button>
186
+ <div class="status-row" id="prompt-copy-status" role="status" aria-live="polite"></div>
187
+ </div>
188
+
189
+ <div class="field">
190
+ <label for="prd-paste">Your PRD</label>
191
+ <textarea id="prd-paste" rows="16" placeholder="Paste your PRD here. Include the YAML frontmatter block:&#10;&#10;```yaml&#10;name: &quot;My Project&quot;&#10;type: &quot;full-stack&quot;&#10;framework: &quot;next.js&quot;&#10;...&#10;```"></textarea>
192
+ </div>
193
+ <button class="btn btn-secondary" id="validate-prd" type="button">Validate Frontmatter</button>
194
+ <div class="status-row" id="prd-status" role="status" aria-live="polite"></div>
195
+ </div>
196
+ </div>
197
+
198
+ <div class="tab-panel" id="tab-skip" role="tabpanel" aria-labelledby="tab-btn-skip">
199
+ <div class="card">
200
+ <p>A blank PRD template will be included in your project at <code>docs/PRD.md</code>. You'll need to fill it out before running <code>/build</code>.</p>
201
+ <div class="status-row info">This is fine if you want to set up the project structure first and write the PRD later.</div>
202
+ </div>
203
+ </div>
204
+ </section>
205
+
206
+ <!-- Step 4b: Project Credentials (PRD-driven) -->
207
+ <section class="step hidden" id="step-4b" data-step="4b" aria-labelledby="step-4b-heading">
208
+ <h1 id="step-4b-heading">Project Credentials</h1>
209
+ <p class="subtitle">Your PRD references these services. Add API keys now, or fill in <code>.env</code> later.</p>
210
+
211
+ <div id="env-credentials-list"></div>
212
+
213
+ <div id="env-credentials-empty" class="hidden">
214
+ <div class="status-row info">No project-specific credentials detected in your PRD. You're all set.</div>
215
+ </div>
216
+
217
+ <div class="status-row" id="env-store-status" role="status" aria-live="polite"></div>
218
+
219
+ <div class="btn-row" style="margin-top: 16px;">
220
+ <button class="btn btn-primary" id="store-env-credentials" type="button">Store All in Vault</button>
221
+ <button class="btn btn-secondary" id="skip-env-credentials" type="button">Skip — I'll add them to .env later</button>
222
+ </div>
223
+ </section>
224
+
225
+ <!-- Act 3: Equip Your Project -->
226
+
227
+ <!-- Step 5: Operations Menu -->
228
+ <section class="step hidden" id="step-5" data-step="5" aria-labelledby="step-5-heading">
229
+ <h1 id="step-5-heading">Equip Your Project</h1>
230
+ <p class="subtitle">Configure what you need. Skip what you don't.</p>
231
+
232
+ <!-- Deploy Target Card -->
233
+ <div class="ops-card" id="ops-deploy">
234
+ <div class="ops-card-header" role="button" tabindex="0" aria-expanded="false" aria-controls="ops-deploy-body">
235
+ <span class="ops-card-title">Deploy Target</span>
236
+ <span class="ops-card-summary" id="ops-deploy-summary">Docker (default)</span>
237
+ <span class="ops-card-chevron" aria-hidden="true">&#9654;</span>
238
+ </div>
239
+ <div class="ops-card-body hidden" id="ops-deploy-body">
240
+ <p class="field-hint" style="margin-bottom: 12px;">Where will this project run?</p>
241
+ <div id="deploy-targets-list" role="radiogroup" aria-label="Deploy target"></div>
242
+ <div id="deploy-note"></div>
243
+ </div>
244
+ </div>
245
+
246
+ <!-- Cloud Credentials Card -->
247
+ <div class="ops-card" id="ops-cloud">
248
+ <div class="ops-card-header" role="button" tabindex="0" aria-expanded="false" aria-controls="ops-cloud-body">
249
+ <span class="ops-card-title">Cloud Credentials</span>
250
+ <span class="ops-card-summary" id="ops-cloud-summary">Not configured</span>
251
+ <span class="ops-card-chevron" aria-hidden="true">&#9654;</span>
252
+ </div>
253
+ <div class="ops-card-body hidden" id="ops-cloud-body">
254
+ <p class="field-hint" style="margin-bottom: 12px;">Connect providers for your deploy target.</p>
255
+ <div id="cloud-providers-list"></div>
256
+ </div>
257
+ </div>
258
+
259
+ <!-- Domain & Hostname Card -->
260
+ <div class="ops-card" id="ops-domain">
261
+ <div class="ops-card-header" role="button" tabindex="0" aria-expanded="false" aria-controls="ops-domain-body">
262
+ <span class="ops-card-title">Domain & Hostname</span>
263
+ <span class="ops-card-summary" id="ops-domain-summary">Not set</span>
264
+ <span class="ops-card-chevron" aria-hidden="true">&#9654;</span>
265
+ </div>
266
+ <div class="ops-card-body hidden" id="ops-domain-body">
267
+ <div class="field">
268
+ <label for="project-domain">Domain</label>
269
+ <input type="text" id="project-domain" placeholder="SaaS, E-commerce, Developer Tools...">
270
+ </div>
271
+ <div class="field">
272
+ <label for="project-hostname">Hostname</label>
273
+ <input type="text" id="project-hostname" placeholder="myapp.com">
274
+ <div class="field-hint">Your domain for DNS wiring (must be on Cloudflare)</div>
275
+ </div>
276
+ </div>
277
+ </div>
278
+
279
+ <!-- Resilience Pack Card -->
280
+ <div class="ops-card" id="ops-resilience">
281
+ <div class="ops-card-header" role="button" tabindex="0" aria-expanded="false" aria-controls="ops-resilience-body">
282
+ <span class="ops-card-title">Resilience Pack</span>
283
+ <span class="ops-card-summary">Optional hardening</span>
284
+ <span class="ops-card-chevron" aria-hidden="true">&#9654;</span>
285
+ </div>
286
+ <div class="ops-card-body hidden" id="ops-resilience-body">
287
+ <p class="field-hint" style="margin-bottom: 12px;">Operational resilience features. Smart defaults based on your deploy target.</p>
288
+ <div class="resilience-grid">
289
+ <label class="resilience-toggle"><input type="checkbox" id="res-multi-env"> Multi-environment (.env per stage)</label>
290
+ <label class="resilience-toggle"><input type="checkbox" id="res-preview"> Preview deployments (PR previews)</label>
291
+ <label class="resilience-toggle"><input type="checkbox" id="res-rollback"> Auto-rollback (one-click revert)</label>
292
+ <label class="resilience-toggle"><input type="checkbox" id="res-migrations"> Migration automation (deploy-time)</label>
293
+ <label class="resilience-toggle"><input type="checkbox" id="res-backups"> Backup automation (daily DB dumps)</label>
294
+ <label class="resilience-toggle"><input type="checkbox" id="res-health"> Health check endpoint (/api/health)</label>
295
+ <label class="resilience-toggle"><input type="checkbox" id="res-shutdown"> Graceful shutdown (SIGTERM handling)</label>
296
+ <label class="resilience-toggle"><input type="checkbox" id="res-errors"> Error boundaries (React + global)</label>
297
+ <label class="resilience-toggle"><input type="checkbox" id="res-ratelimit"> Rate limiting (auth endpoints)</label>
298
+ <label class="resilience-toggle"><input type="checkbox" id="res-dlq"> Dead letter queue (if workers enabled)</label>
299
+ </div>
300
+ </div>
301
+ </div>
302
+
303
+ <div style="margin-top: 16px; text-align: center;">
304
+ <button class="btn btn-secondary" id="ops-skip-all" type="button">Skip All — I'll configure later</button>
305
+ </div>
306
+ </section>
307
+
308
+ <!-- Step 6: Review -->
309
+ <section class="step hidden" id="step-6" data-step="6" aria-labelledby="step-6-heading">
310
+ <h1 id="step-6-heading">Review</h1>
311
+ <p class="subtitle">Confirm everything before we create your project.</p>
312
+
313
+ <div class="card">
314
+ <table class="review-table" id="review-table">
315
+ <tbody>
316
+ <tr><td class="review-label">Project</td><td id="review-name"></td></tr>
317
+ <tr><td class="review-label">Directory</td><td id="review-dir"></td></tr>
318
+ <tr><td class="review-label">Description</td><td id="review-desc"></td></tr>
319
+ <tr><td class="review-label">Domain</td><td id="review-domain"></td></tr>
320
+ <tr><td class="review-label">Hostname</td><td id="review-hostname"></td></tr>
321
+ <tr><td class="review-label">PRD</td><td id="review-prd"></td></tr>
322
+ <tr><td class="review-label">API Keys</td><td id="review-env-credentials"></td></tr>
323
+ <tr><td class="review-label">Deploy</td><td id="review-deploy"></td></tr>
324
+ </tbody>
325
+ </table>
326
+ </div>
327
+
328
+ <div class="card">
329
+ <h2>Files to be created</h2>
330
+ <ul class="file-list">
331
+ <li><code>CLAUDE.md</code> — Root context with project name</li>
332
+ <li><code>.claude/commands/</code> — Slash commands (/build, /qa, etc.)</li>
333
+ <li><code>.claude/settings.json</code> — Permissions and hooks</li>
334
+ <li><code>docs/PRD.md</code> — Your product requirements</li>
335
+ <li><code>docs/methods/</code> — 14 agent protocols</li>
336
+ <li><code>docs/patterns/</code> — 7 code reference implementations</li>
337
+ <li><code>logs/build-state.md</code> — Build journal</li>
338
+ <li><code>.env</code> — Environment variables (gitignored)</li>
339
+ <li><code>.gitignore</code> — Standard ignores</li>
340
+ </ul>
341
+ </div>
342
+ </section>
343
+
344
+ <!-- Step 7: Creating / Done -->
345
+ <section class="step hidden" id="step-7" data-step="7" aria-labelledby="step-7-heading">
346
+ <div id="creating-state">
347
+ <h1>Creating Your Project...</h1>
348
+ <div class="spinner" role="status" aria-label="Creating project"></div>
349
+ <p class="subtitle" id="create-status-text">Setting up project files...</p>
350
+ </div>
351
+
352
+ <div id="done-state" class="hidden">
353
+ <h1 id="step-7-heading" tabindex="-1">Project Created!</h1>
354
+ <p class="subtitle">Gandalf's work is done. Your project is scaffolded and ready for development.</p>
355
+
356
+ <div class="card success-card">
357
+ <div class="success-icon" aria-hidden="true">&#10003;</div>
358
+ <div id="done-details"></div>
359
+ </div>
360
+
361
+ <div class="card">
362
+ <h2>What's Next</h2>
363
+ <ol class="next-steps">
364
+ <li>Open a terminal in your project directory</li>
365
+ <li>Review <code>docs/PRD.md</code> — fill in any remaining sections</li>
366
+ <li>Open Claude Code and run <code>/build</code> to build your app</li>
367
+ </ol>
368
+
369
+ <div class="card" style="margin-top: 16px; border-color: var(--accent); background: rgba(91, 91, 247, 0.03);">
370
+ <h2>Ready to deploy?</h2>
371
+ <p style="font-size: 14px; color: var(--text-dim);">After building your app, run the deploy wizard to provision infrastructure and ship.</p>
372
+ <p style="font-family: var(--mono); font-size: 14px; margin-top: 8px;"><code>npm run deploy</code></p>
373
+ </div>
374
+
375
+ <div class="btn-row">
376
+ <button class="btn btn-primary" id="open-tower" type="button">Open in Avengers Tower</button>
377
+ <button class="btn btn-secondary" id="open-terminal" type="button">Copy Terminal Command</button>
378
+ <button class="btn btn-secondary" id="open-finder" type="button">Copy Project Path</button>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ </section>
383
+
384
+ </main>
385
+
386
+ <footer class="wizard-footer">
387
+ <button class="btn btn-secondary" id="btn-back" type="button" disabled>Back</button>
388
+ <button class="btn btn-primary" id="btn-next" type="button">Next</button>
389
+ </footer>
390
+ </div>
391
+
392
+ <script src="app.js"></script>
393
+ </body>
394
+ </html>
@@ -0,0 +1,228 @@
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
+ <title>The Lobby — VoidForge</title>
7
+ <link rel="icon" type="image/svg+xml" href="favicon.svg">
8
+ <link rel="stylesheet" href="styles.css">
9
+ <style>
10
+ body { min-height: 100vh; display: flex; flex-direction: column; }
11
+
12
+ /* Lobby Layout */
13
+ .lobby { max-width: 960px; margin: 0 auto; width: 100%; flex: 1; display: flex; flex-direction: column; }
14
+ .lobby-header {
15
+ padding: 24px 24px 16px;
16
+ border-bottom: 1px solid var(--border);
17
+ display: flex; justify-content: space-between; align-items: center;
18
+ }
19
+ .lobby-title { font-size: 18px; font-weight: 700; color: var(--accent); }
20
+ .lobby-subtitle { font-size: 13px; color: var(--text-dim); margin-top: 2px; }
21
+ .lobby-actions { display: flex; gap: 8px; }
22
+
23
+ /* Project Grid */
24
+ .project-grid {
25
+ padding: 24px;
26
+ display: grid;
27
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
28
+ gap: 16px;
29
+ flex: 1;
30
+ }
31
+
32
+ .project-card {
33
+ background: var(--bg-card);
34
+ border: 1px solid var(--border);
35
+ border-radius: var(--radius);
36
+ padding: 20px;
37
+ cursor: pointer;
38
+ transition: border-color 0.2s, background 0.2s;
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 12px;
42
+ }
43
+ .project-card:hover { border-color: var(--accent); background: rgba(91, 91, 247, 0.03); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); }
44
+ .project-card:focus-visible { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(91, 91, 247, 0.25); outline: none; }
45
+
46
+ .project-card-header { display: flex; justify-content: space-between; align-items: flex-start; }
47
+ .project-card-name { font-size: 16px; font-weight: 600; }
48
+ .project-card-meta { font-size: 12px; color: var(--text-dim); }
49
+
50
+ /* Health Indicator — color + text label for a11y (WCAG 1.4.1) */
51
+ .health-indicator {
52
+ display: flex; align-items: center; gap: 4px; flex-shrink: 0;
53
+ }
54
+ .health-dot {
55
+ width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0;
56
+ }
57
+ .health-dot.healthy { background: var(--success); animation: healthPulse 2s ease-in-out infinite; }
58
+ @keyframes healthPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.4); } 50% { box-shadow: 0 0 6px 2px rgba(34, 197, 94, 0.2); } }
59
+ .health-dot.degraded { background: var(--warning); }
60
+ .health-dot.down { background: var(--error); }
61
+ .health-dot.unchecked { background: var(--text-muted); }
62
+ .health-label { font-size: 10px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
63
+
64
+ /* Badges */
65
+ .badge-row { display: flex; gap: 6px; flex-wrap: wrap; }
66
+ .badge {
67
+ font-size: 11px; padding: 2px 8px; border-radius: 10px; font-weight: 500;
68
+ background: rgba(91, 91, 247, 0.1); color: var(--accent);
69
+ }
70
+ .badge.deploy { background: rgba(34, 197, 94, 0.1); color: var(--success); }
71
+ .badge.role-owner { background: rgba(234, 179, 8, 0.15); color: #ca8a04; }
72
+ .badge.role-deployer { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
73
+ .badge.role-viewer { background: rgba(156, 163, 175, 0.15); color: #6b7280; }
74
+ .badge.role-admin { background: rgba(234, 179, 8, 0.15); color: #ca8a04; }
75
+ .badge.linked { background: rgba(168, 85, 247, 0.1); color: #a855f7; }
76
+ .badge.build-success { background: rgba(34, 197, 94, 0.1); color: var(--success); }
77
+ .badge.build-info { background: rgba(59, 130, 246, 0.1); color: #3b82f6; }
78
+ .badge.build-warning { background: rgba(245, 158, 11, 0.15); color: #f59e0b; }
79
+ .badge.build-accent { background: rgba(91, 91, 247, 0.15); color: var(--accent); }
80
+ .user-role-badge { font-size: 10px; padding: 1px 6px; border-radius: 8px; margin-left: 4px; }
81
+
82
+ /* Deploy URL */
83
+ .project-url {
84
+ font-size: 12px; color: var(--accent); font-family: var(--mono);
85
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
86
+ }
87
+ .project-url:hover { text-decoration: underline; }
88
+
89
+ /* Quick Actions */
90
+ .project-actions { display: flex; gap: 8px; margin-top: auto; }
91
+ .project-actions .btn { padding: 4px 10px; font-size: 11px; }
92
+
93
+ /* Cost & Activity */
94
+ .project-footer { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); }
95
+
96
+ /* The Penthouse (footer bar) */
97
+ .penthouse {
98
+ padding: 12px 24px;
99
+ border-top: 1px solid var(--border);
100
+ display: flex; justify-content: space-between; align-items: center;
101
+ font-size: 12px; color: var(--text-dim);
102
+ }
103
+ .penthouse-stats { display: flex; gap: 16px; }
104
+
105
+ /* Empty State */
106
+ .empty-state {
107
+ text-align: center; padding: 80px 24px;
108
+ color: var(--text-dim); grid-column: 1 / -1;
109
+ }
110
+ .empty-state h2 { font-size: 20px; color: var(--text); margin-bottom: 8px; }
111
+ .empty-state p { margin-bottom: 24px; }
112
+
113
+ /* Import Modal */
114
+ .modal-backdrop {
115
+ position: fixed; inset: 0; background: rgba(0,0,0,0.6);
116
+ display: none; align-items: center; justify-content: center; z-index: 100;
117
+ }
118
+ .modal-backdrop.active { display: flex; }
119
+ .modal {
120
+ background: var(--bg-card); border: 1px solid var(--border);
121
+ border-radius: var(--radius); padding: 24px; width: 90%; max-width: 480px;
122
+ }
123
+ .modal h2 { margin-bottom: 16px; }
124
+
125
+ @media (max-width: 600px) {
126
+ .project-grid { grid-template-columns: 1fr; padding: 16px; }
127
+ .lobby-header { flex-direction: column; gap: 12px; align-items: flex-start; }
128
+ .penthouse { flex-direction: column; gap: 8px; align-items: flex-start; }
129
+ }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ <a href="#project-grid" class="skip-nav">Skip to main content</a>
134
+ <noscript><div class="noscript-msg">VoidForge requires JavaScript to run.</div></noscript>
135
+ <div class="lobby">
136
+ <header class="lobby-header">
137
+ <div>
138
+ <div class="lobby-title">The Lobby</div>
139
+ <div class="lobby-subtitle">VoidForge Operations Console</div>
140
+ </div>
141
+ <div class="lobby-actions">
142
+ <span id="auth-user" style="font-size: 12px; color: var(--text-dim); display: none;"></span>
143
+ <button class="btn" id="btn-logout" title="Sign out" style="display: none;">Logout</button>
144
+ <a href="danger-room.html" class="btn" title="Open the Danger Room dashboard" style="text-decoration:none;">Danger Room</a>
145
+ <button class="btn" id="btn-import" title="Import existing VoidForge project">+ Import Project</button>
146
+ <button class="btn btn-primary" id="btn-new" title="Create a new project with Gandalf">+ New Project</button>
147
+ </div>
148
+ </header>
149
+
150
+ <main class="project-grid" id="project-grid" role="list">
151
+ <div class="empty-state" id="empty-state">
152
+ <h2>The Lobby is quiet... for now</h2>
153
+ <p>Every great forge starts with its first project. Gandalf is ready when you are.</p>
154
+ <button class="btn btn-primary" id="btn-new-empty">+ New Project</button>
155
+ </div>
156
+ </main>
157
+
158
+ <footer class="penthouse" id="penthouse">
159
+ <div class="penthouse-stats">
160
+ <span id="stat-projects">0 projects</span>
161
+ <span id="stat-cost">$0/mo</span>
162
+ </div>
163
+ <div>The Penthouse</div>
164
+ </footer>
165
+ </div>
166
+
167
+ <!-- Import Modal -->
168
+ <div class="modal-backdrop" id="import-modal" role="dialog" aria-labelledby="import-title" aria-modal="true">
169
+ <div class="modal">
170
+ <h2 id="import-title">Import Existing Project</h2>
171
+ <div class="field">
172
+ <label for="import-dir">Project Directory</label>
173
+ <input type="text" id="import-dir" placeholder="/home/user/projects/my-app" autocomplete="off">
174
+ <div class="field-hint">Absolute path to a directory containing CLAUDE.md</div>
175
+ </div>
176
+ <div id="import-status" class="status-row" role="status" aria-live="polite"></div>
177
+ <div class="btn-row">
178
+ <button class="btn" id="import-cancel">Cancel</button>
179
+ <button class="btn btn-primary" id="import-confirm">Import</button>
180
+ </div>
181
+ </div>
182
+ </div>
183
+
184
+ <!-- Access Modal -->
185
+ <div class="modal-backdrop" id="access-modal" role="dialog" aria-labelledby="access-title" aria-modal="true">
186
+ <div class="modal">
187
+ <h2 id="access-title">Manage Access</h2>
188
+ <div id="access-owner" style="font-size: 12px; color: var(--text-dim); margin-bottom: 12px;"></div>
189
+ <div id="access-list" style="margin-bottom: 16px;"></div>
190
+ <div class="field">
191
+ <label for="access-username">Grant Access</label>
192
+ <div style="display: flex; gap: 8px;">
193
+ <input type="text" id="access-username" placeholder="username" autocomplete="off" style="flex: 1;">
194
+ <select id="access-role" style="padding: 6px; border-radius: var(--radius); border: 1px solid var(--border); background: var(--bg-card); color: var(--text);">
195
+ <option value="deployer">Deployer</option>
196
+ <option value="viewer">Viewer</option>
197
+ </select>
198
+ </div>
199
+ </div>
200
+ <div id="access-status" class="status-row" role="status" aria-live="polite"></div>
201
+ <div class="btn-row">
202
+ <button class="btn" id="access-cancel">Close</button>
203
+ <button class="btn btn-primary" id="access-grant">Grant</button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ <!-- Link Modal -->
209
+ <div class="modal-backdrop" id="link-modal" role="dialog" aria-labelledby="link-title" aria-modal="true">
210
+ <div class="modal">
211
+ <h2 id="link-title">Link Projects</h2>
212
+ <div id="link-current" style="font-size: 12px; color: var(--text-dim); margin-bottom: 8px;"></div>
213
+ <div id="link-existing" style="margin-bottom: 12px;"></div>
214
+ <div class="field">
215
+ <label for="link-select">Link to project</label>
216
+ <select id="link-select" style="width: 100%; padding: 6px; border-radius: var(--radius); border: 1px solid var(--border); background: var(--bg-card); color: var(--text);"></select>
217
+ </div>
218
+ <div id="link-status" class="status-row" role="status" aria-live="polite"></div>
219
+ <div class="btn-row">
220
+ <button class="btn" id="link-cancel">Close</button>
221
+ <button class="btn btn-primary" id="link-confirm">Link</button>
222
+ </div>
223
+ </div>
224
+ </div>
225
+
226
+ <script src="lobby.js"></script>
227
+ </body>
228
+ </html>