@pdlc-os/pdlc 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,73 @@
1
+ #!/usr/bin/env bash
2
+ # pdlc-session-start.sh — PDLC SessionStart hook for Claude Code
3
+ # Injects PDLC context (STATE.md contents) into the session on startup.
4
+ # Registered as a `SessionStart` hook in Claude Code settings.
5
+
6
+ set -euo pipefail
7
+
8
+ # ── Resolve project directory ─────────────────────────────────────────────────
9
+ project_dir="${CLAUDE_PROJECT_DIR:-${PWD}}"
10
+ state_file="${project_dir}/docs/pdlc/memory/STATE.md"
11
+
12
+ # ── JSON string escaping ──────────────────────────────────────────────────────
13
+ # Escapes a string for safe embedding in a JSON double-quoted value.
14
+ # Handles backslashes, double-quotes, and control characters (tab, newline, CR).
15
+ json_escape() {
16
+ local input="$1"
17
+ # Use printf + python if available for robust encoding; fall back to sed chain
18
+ if command -v python3 &>/dev/null; then
19
+ printf '%s' "$input" | python3 -c '
20
+ import sys, json
21
+ data = sys.stdin.read()
22
+ # json.dumps adds surrounding quotes — strip them
23
+ print(json.dumps(data)[1:-1], end="")
24
+ '
25
+ elif command -v python &>/dev/null; then
26
+ printf '%s' "$input" | python -c '
27
+ import sys, json
28
+ data = sys.stdin.read()
29
+ print(json.dumps(data)[1:-1], end="")
30
+ '
31
+ else
32
+ # Pure bash/sed fallback: escape backslashes, double-quotes, tabs, newlines
33
+ printf '%s' "$input" \
34
+ | sed 's/\\/\\\\/g' \
35
+ | sed 's/"/\\"/g' \
36
+ | sed 's/\t/\\t/g' \
37
+ | awk '{printf "%s\\n", $0}' \
38
+ | sed '$ s/\\n$//'
39
+ fi
40
+ }
41
+
42
+ # ── Build and emit the systemMessage JSON ────────────────────────────────────
43
+ emit_json() {
44
+ local message="$1"
45
+
46
+ # Prefer jq for robust JSON serialisation
47
+ if command -v jq &>/dev/null; then
48
+ jq -cn --arg msg "$message" '{"systemMessage": $msg}'
49
+ else
50
+ local escaped
51
+ escaped="$(json_escape "$message")"
52
+ printf '{"systemMessage": "%s"}\n' "$escaped"
53
+ fi
54
+ }
55
+
56
+ # ── Main logic ────────────────────────────────────────────────────────────────
57
+ if [[ ! -f "$state_file" ]]; then
58
+ # PDLC not yet initialized for this project
59
+ emit_json "PDLC is installed but not initialized for this project. Run /pdlc init to set up PDLC for this project."
60
+ exit 0
61
+ fi
62
+
63
+ # STATE.md exists — read it and inject into the session
64
+ state_content="$(cat "$state_file")"
65
+
66
+ message="$(printf '%s\n\n%s\n\n%s' \
67
+ "📋 PDLC Active — resuming from STATE.md" \
68
+ "## Current State
69
+ ${state_content}" \
70
+ "See CLAUDE.md for the full PDLC flow.")"
71
+
72
+ emit_json "$message"
73
+ exit 0
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ // pdlc-statusline.js — PDLC statusLine hook for Claude Code
3
+ // Reads stdin JSON from Claude Code, outputs a formatted status string.
4
+ // Registered as a `statusLine` hook in Claude Code settings.
5
+
6
+ 'use strict';
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ // ── ANSI helpers ────────────────────────────────────────────────────────────
12
+ const RESET = '\x1b[0m';
13
+ const GREEN = '\x1b[32m';
14
+ const YELLOW = '\x1b[33m';
15
+ const ORANGE = '\x1b[93m'; // bright yellow, renders orange-ish in most terminals
16
+ const RED_BLINK = '\x1b[31m\x1b[5m';
17
+
18
+ // ── Progress bar ─────────────────────────────────────────────────────────────
19
+ function buildBar(usedPct) {
20
+ const total = 10;
21
+ const filled = Math.round((usedPct / 100) * total);
22
+ const empty = total - filled;
23
+ const bar = '█'.repeat(Math.max(0, filled)) + '░'.repeat(Math.max(0, empty));
24
+
25
+ let color;
26
+ let suffix = '';
27
+ if (usedPct >= 80) {
28
+ color = RED_BLINK;
29
+ suffix = ' ☠';
30
+ } else if (usedPct >= 65) {
31
+ color = ORANGE;
32
+ } else if (usedPct >= 50) {
33
+ color = YELLOW;
34
+ } else {
35
+ color = GREEN;
36
+ }
37
+
38
+ return `${color}[${bar}]${RESET}${suffix}`;
39
+ }
40
+
41
+ // ── STATE.md parsing ─────────────────────────────────────────────────────────
42
+ function parseStateMd(content) {
43
+ let phase = null;
44
+ let task = null;
45
+
46
+ const lines = content.split('\n');
47
+
48
+ // Find "## Current Phase" section — the value is the first non-blank,
49
+ // non-comment, non-heading line after the section header.
50
+ let inPhaseSection = false;
51
+ let inTaskSection = false;
52
+
53
+ for (const line of lines) {
54
+ const trimmed = line.trim();
55
+
56
+ if (trimmed === '## Current Phase') {
57
+ inPhaseSection = true;
58
+ inTaskSection = false;
59
+ continue;
60
+ }
61
+ if (trimmed === '## Active Beads Task') {
62
+ inTaskSection = true;
63
+ inPhaseSection = false;
64
+ continue;
65
+ }
66
+ // Any other ## header ends the active section
67
+ if (trimmed.startsWith('## ')) {
68
+ inPhaseSection = false;
69
+ inTaskSection = false;
70
+ continue;
71
+ }
72
+
73
+ // Skip blank lines and HTML comments
74
+ if (!trimmed || trimmed.startsWith('<!--')) continue;
75
+
76
+ if (inPhaseSection && !phase) {
77
+ phase = trimmed;
78
+ }
79
+ if (inTaskSection && !task) {
80
+ task = trimmed;
81
+ }
82
+
83
+ // Stop as soon as both are found
84
+ if (phase && task) break;
85
+ }
86
+
87
+ return {
88
+ phase: phase || 'Unknown',
89
+ task: task || 'none',
90
+ };
91
+ }
92
+
93
+ // ── Bridge file helpers ───────────────────────────────────────────────────────
94
+ function readBridge(bridgePath) {
95
+ try {
96
+ const raw = fs.readFileSync(bridgePath, 'utf8');
97
+ return JSON.parse(raw);
98
+ } catch (_) {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ function writeBridge(bridgePath, data) {
104
+ try {
105
+ fs.writeFileSync(bridgePath, JSON.stringify(data, null, 2), 'utf8');
106
+ } catch (_) {
107
+ // Best-effort — never crash the hook
108
+ }
109
+ }
110
+
111
+ // ── Main ──────────────────────────────────────────────────────────────────────
112
+ function main() {
113
+ let input = {};
114
+
115
+ try {
116
+ const raw = fs.readFileSync('/dev/stdin', 'utf8').trim();
117
+ if (raw) input = JSON.parse(raw);
118
+ } catch (_) {
119
+ // If we can't read / parse stdin, output the "not initialized" fallback
120
+ process.stdout.write(`PDLC │ Not initialized │ [░░░░░░░░░░] --%${RESET}\n`);
121
+ process.exit(0);
122
+ }
123
+
124
+ const sessionId = input.session_id || 'unknown';
125
+ const cwd = input.cwd || process.cwd();
126
+
127
+ const bridgePath = `/tmp/pdlc-ctx-${sessionId}.json`;
128
+ const stateMdPath = path.join(cwd, 'docs', 'pdlc', 'memory', 'STATE.md');
129
+
130
+ // ── Read bridge file ────────────────────────────────────────────────────────
131
+ let bridge = readBridge(bridgePath) || {};
132
+ let usedPct = typeof bridge.used_pct === 'number' ? bridge.used_pct : 0;
133
+
134
+ // ── Read STATE.md ───────────────────────────────────────────────────────────
135
+ let stateExists = false;
136
+ let phase = 'Unknown';
137
+ let task = 'none';
138
+
139
+ try {
140
+ const content = fs.readFileSync(stateMdPath, 'utf8');
141
+ stateExists = true;
142
+ const parsed = parseStateMd(content);
143
+ phase = parsed.phase;
144
+ task = parsed.task;
145
+
146
+ // Estimate used_pct from STATE.md mtime if bridge doesn't already have it
147
+ if (!bridge.used_pct) {
148
+ const stat = fs.statSync(stateMdPath);
149
+ const ageMs = Date.now() - stat.mtimeMs;
150
+ // Rough heuristic: assume context grows ~1% per minute of activity
151
+ // Cap at 95 so we never claim 100% just from age
152
+ usedPct = Math.min(95, Math.round(ageMs / 60000));
153
+ }
154
+ } catch (_) {
155
+ stateExists = false;
156
+ }
157
+
158
+ // ── Update bridge file with latest used_pct ─────────────────────────────────
159
+ const updatedBridge = Object.assign({}, bridge, {
160
+ used_pct: usedPct,
161
+ session_id: sessionId,
162
+ cwd,
163
+ updated_at: new Date().toISOString(),
164
+ });
165
+ writeBridge(bridgePath, updatedBridge);
166
+
167
+ // ── Build output string ─────────────────────────────────────────────────────
168
+ if (!stateExists) {
169
+ process.stdout.write(`PDLC │ Not initialized │ [░░░░░░░░░░] --%${RESET}\n`);
170
+ process.exit(0);
171
+ }
172
+
173
+ // Truncate task title to 40 chars
174
+ const taskDisplay = task.length > 40 ? task.slice(0, 37) + '...' : task;
175
+
176
+ const bar = buildBar(usedPct);
177
+ const pctStr = String(usedPct).padStart(2, ' ') + '%';
178
+
179
+ process.stdout.write(`${phase} │ ${taskDisplay} │ ${bar} ${pctStr}${RESET}\n`);
180
+ process.exit(0);
181
+ }
182
+
183
+ main();
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@pdlc-os/pdlc",
3
+ "version": "0.1.0",
4
+ "description": "Product Development Lifecycle — a Claude Code plugin for small startup teams",
5
+ "keywords": [
6
+ "claude-code",
7
+ "claude-code-plugin",
8
+ "pdlc",
9
+ "ai-development",
10
+ "product-lifecycle",
11
+ "tdd",
12
+ "agentic"
13
+ ],
14
+ "author": "",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/pdlc-os/pdlc.git"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/pdlc-os/pdlc/issues"
22
+ },
23
+ "homepage": "https://github.com/pdlc-os/pdlc#readme",
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "bin": {
28
+ "pdlc": "bin/pdlc.js"
29
+ },
30
+ "main": "./bin/pdlc.js",
31
+ "files": [
32
+ "CLAUDE.md",
33
+ ".claude/commands/",
34
+ ".claude/settings.json",
35
+ "agents/",
36
+ "skills/",
37
+ "hooks/",
38
+ "scripts/",
39
+ "templates/",
40
+ "bin/"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "scripts": {
46
+ "postinstall": "node bin/pdlc.js postinstall"
47
+ }
48
+ }
@@ -0,0 +1,332 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>PDLC Visual Companion</title>
6
+ <style>
7
+ /*
8
+ * PDLC VISUAL COMPANION FRAME TEMPLATE
9
+ *
10
+ * This template provides a consistent frame with:
11
+ * - Dark navy/blue PDLC-branded theming
12
+ * - Fixed header and selection indicator bar
13
+ * - Scrollable main content area
14
+ * - CSS helpers for common UI patterns
15
+ *
16
+ * Content is injected via placeholder comment in #claude-content.
17
+ */
18
+
19
+ * { box-sizing: border-box; margin: 0; padding: 0; }
20
+ html, body { height: 100%; overflow: hidden; }
21
+
22
+ /* ===== THEME VARIABLES ===== */
23
+ :root {
24
+ /* PDLC dark navy/blue palette */
25
+ --bg-primary: #0d1b2a;
26
+ --bg-secondary: #132337;
27
+ --bg-tertiary: #1e3550;
28
+ --border: #2a4a6b;
29
+ --text-primary: #e2e8f0;
30
+ --text-secondary: #94a3b8;
31
+ --text-tertiary: #64748b;
32
+ --accent: #3b82f6;
33
+ --accent-hover: #60a5fa;
34
+ --accent-dim: rgba(59, 130, 246, 0.15);
35
+ --success: #22c55e;
36
+ --warning: #f59e0b;
37
+ --error: #ef4444;
38
+ --selected-bg: rgba(59, 130, 246, 0.18);
39
+ --selected-border: #3b82f6;
40
+ /* Header gradient strip */
41
+ --header-bg: #0a1628;
42
+ --header-accent: #1d4ed8;
43
+ }
44
+
45
+ /* Light mode override — still respects OS preference */
46
+ @media (prefers-color-scheme: light) {
47
+ :root {
48
+ --bg-primary: #f0f4f8;
49
+ --bg-secondary: #ffffff;
50
+ --bg-tertiary: #dde6f0;
51
+ --border: #b8cee3;
52
+ --text-primary: #0f2040;
53
+ --text-secondary: #4a6080;
54
+ --text-tertiary: #7a95b0;
55
+ --accent: #1d4ed8;
56
+ --accent-hover: #2563eb;
57
+ --accent-dim: rgba(29, 78, 216, 0.1);
58
+ --selected-bg: rgba(29, 78, 216, 0.1);
59
+ --selected-border: #1d4ed8;
60
+ --header-bg: #0a1628;
61
+ --header-accent: #1d4ed8;
62
+ }
63
+ }
64
+
65
+ body {
66
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
67
+ background: var(--bg-primary);
68
+ color: var(--text-primary);
69
+ display: flex;
70
+ flex-direction: column;
71
+ line-height: 1.5;
72
+ }
73
+
74
+ /* ===== FRAME STRUCTURE ===== */
75
+ .header {
76
+ background: var(--header-bg);
77
+ padding: 0 1.5rem;
78
+ height: 2.75rem;
79
+ display: flex;
80
+ justify-content: space-between;
81
+ align-items: center;
82
+ border-bottom: 2px solid var(--header-accent);
83
+ flex-shrink: 0;
84
+ }
85
+ .header-left { display: flex; align-items: center; gap: 0.75rem; }
86
+ .header-logo {
87
+ width: 20px; height: 20px;
88
+ background: var(--accent);
89
+ border-radius: 4px;
90
+ display: flex; align-items: center; justify-content: center;
91
+ font-size: 0.7rem; font-weight: 800; color: white; letter-spacing: -0.05em;
92
+ flex-shrink: 0;
93
+ }
94
+ .header h1 {
95
+ font-size: 0.8rem;
96
+ font-weight: 600;
97
+ color: #cbd5e1;
98
+ letter-spacing: 0.02em;
99
+ text-transform: uppercase;
100
+ }
101
+ .header .status {
102
+ font-size: 0.7rem;
103
+ color: var(--success);
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 0.4rem;
107
+ }
108
+ .header .status::before {
109
+ content: '';
110
+ width: 6px; height: 6px;
111
+ background: var(--success);
112
+ border-radius: 50%;
113
+ animation: pulse 2s ease-in-out infinite;
114
+ }
115
+ @keyframes pulse {
116
+ 0%, 100% { opacity: 1; }
117
+ 50% { opacity: 0.4; }
118
+ }
119
+
120
+ .main { flex: 1; overflow-y: auto; }
121
+ #claude-content { padding: 2rem; min-height: 100%; }
122
+
123
+ .indicator-bar {
124
+ background: var(--header-bg);
125
+ border-top: 1px solid var(--border);
126
+ padding: 0.5rem 1.5rem;
127
+ flex-shrink: 0;
128
+ text-align: center;
129
+ }
130
+ .indicator-bar span {
131
+ font-size: 0.75rem;
132
+ color: var(--text-secondary);
133
+ }
134
+ .indicator-bar .selected-text {
135
+ color: var(--accent-hover);
136
+ font-weight: 600;
137
+ }
138
+
139
+ /* ===== TYPOGRAPHY ===== */
140
+ h2 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem; }
141
+ h3 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.25rem; }
142
+ .subtitle { color: var(--text-secondary); margin-bottom: 1.5rem; }
143
+ .section { margin-bottom: 2rem; }
144
+ .label {
145
+ font-size: 0.7rem;
146
+ color: var(--text-secondary);
147
+ text-transform: uppercase;
148
+ letter-spacing: 0.07em;
149
+ margin-bottom: 0.5rem;
150
+ font-weight: 600;
151
+ }
152
+
153
+ /* ===== OPTIONS (for A/B/C choices) ===== */
154
+ .options { display: flex; flex-direction: column; gap: 0.75rem; }
155
+ .option {
156
+ background: var(--bg-secondary);
157
+ border: 2px solid var(--border);
158
+ border-radius: 10px;
159
+ padding: 1rem 1.25rem;
160
+ cursor: pointer;
161
+ transition: all 0.15s ease;
162
+ display: flex;
163
+ align-items: flex-start;
164
+ gap: 1rem;
165
+ }
166
+ .option:hover { border-color: var(--accent); background: var(--bg-tertiary); }
167
+ .option.selected { background: var(--selected-bg); border-color: var(--selected-border); }
168
+ .option .letter {
169
+ background: var(--bg-tertiary);
170
+ color: var(--text-secondary);
171
+ width: 1.75rem; height: 1.75rem;
172
+ border-radius: 6px;
173
+ display: flex; align-items: center; justify-content: center;
174
+ font-weight: 700; font-size: 0.85rem; flex-shrink: 0;
175
+ border: 1px solid var(--border);
176
+ }
177
+ .option.selected .letter { background: var(--accent); color: white; border-color: var(--accent); }
178
+ .option .content { flex: 1; }
179
+ .option .content h3 { font-size: 0.95rem; margin-bottom: 0.15rem; }
180
+ .option .content p { color: var(--text-secondary); font-size: 0.85rem; margin: 0; }
181
+
182
+ /* ===== CARDS (for showing designs/mockups) ===== */
183
+ .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
184
+ .card {
185
+ background: var(--bg-secondary);
186
+ border: 1px solid var(--border);
187
+ border-radius: 10px;
188
+ overflow: hidden;
189
+ cursor: pointer;
190
+ transition: all 0.15s ease;
191
+ }
192
+ .card:hover {
193
+ border-color: var(--accent);
194
+ transform: translateY(-2px);
195
+ box-shadow: 0 4px 16px rgba(59, 130, 246, 0.15);
196
+ }
197
+ .card.selected { border-color: var(--selected-border); border-width: 2px; }
198
+ .card-image {
199
+ background: var(--bg-tertiary);
200
+ aspect-ratio: 16/10;
201
+ display: flex; align-items: center; justify-content: center;
202
+ }
203
+ .card-body { padding: 1rem; }
204
+ .card-body h3 { margin-bottom: 0.25rem; }
205
+ .card-body p { color: var(--text-secondary); font-size: 0.85rem; }
206
+
207
+ /* ===== MOCKUP CONTAINER ===== */
208
+ .mockup {
209
+ background: var(--bg-secondary);
210
+ border: 1px solid var(--border);
211
+ border-radius: 10px;
212
+ overflow: hidden;
213
+ margin-bottom: 1.5rem;
214
+ }
215
+ .mockup-header {
216
+ background: var(--bg-tertiary);
217
+ padding: 0.5rem 1rem;
218
+ font-size: 0.75rem;
219
+ color: var(--text-secondary);
220
+ border-bottom: 1px solid var(--border);
221
+ font-weight: 500;
222
+ letter-spacing: 0.02em;
223
+ }
224
+ .mockup-body { padding: 1.5rem; }
225
+
226
+ /* ===== SPLIT VIEW (side-by-side comparison) ===== */
227
+ .split { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
228
+ @media (max-width: 700px) { .split { grid-template-columns: 1fr; } }
229
+
230
+ /* ===== PROS/CONS ===== */
231
+ .pros-cons { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1rem 0; }
232
+ .pros, .cons { background: var(--bg-secondary); border-radius: 8px; padding: 1rem; }
233
+ .pros { border-left: 3px solid var(--success); }
234
+ .cons { border-left: 3px solid var(--error); }
235
+ .pros h4 { color: var(--success); font-size: 0.85rem; margin-bottom: 0.5rem; }
236
+ .cons h4 { color: var(--error); font-size: 0.85rem; margin-bottom: 0.5rem; }
237
+ .pros ul, .cons ul { margin-left: 1.25rem; font-size: 0.85rem; color: var(--text-secondary); }
238
+ .pros li, .cons li { margin-bottom: 0.25rem; }
239
+
240
+ /* ===== PLACEHOLDER (for mockup areas) ===== */
241
+ .placeholder {
242
+ background: var(--bg-tertiary);
243
+ border: 2px dashed var(--border);
244
+ border-radius: 8px;
245
+ padding: 2rem;
246
+ text-align: center;
247
+ color: var(--text-tertiary);
248
+ font-size: 0.85rem;
249
+ }
250
+
251
+ /* ===== INLINE MOCKUP ELEMENTS ===== */
252
+ .mock-nav {
253
+ background: var(--header-bg);
254
+ border-bottom: 1px solid var(--border);
255
+ color: var(--text-primary);
256
+ padding: 0.75rem 1rem;
257
+ display: flex;
258
+ gap: 1.5rem;
259
+ font-size: 0.9rem;
260
+ align-items: center;
261
+ }
262
+ .mock-nav a, .mock-nav span { color: var(--text-secondary); text-decoration: none; font-size: 0.85rem; }
263
+ .mock-nav a.active, .mock-nav span.active { color: var(--accent-hover); font-weight: 500; }
264
+ .mock-sidebar {
265
+ background: var(--bg-tertiary);
266
+ padding: 1rem;
267
+ min-width: 180px;
268
+ border-right: 1px solid var(--border);
269
+ }
270
+ .mock-content { padding: 1.5rem; flex: 1; }
271
+ .mock-button {
272
+ background: var(--accent);
273
+ color: white;
274
+ border: none;
275
+ padding: 0.5rem 1rem;
276
+ border-radius: 6px;
277
+ font-size: 0.85rem;
278
+ cursor: pointer;
279
+ font-weight: 500;
280
+ transition: background 0.15s;
281
+ }
282
+ .mock-button:hover { background: var(--accent-hover); }
283
+ .mock-input {
284
+ background: var(--bg-primary);
285
+ border: 1px solid var(--border);
286
+ border-radius: 6px;
287
+ padding: 0.5rem 0.75rem;
288
+ width: 100%;
289
+ color: var(--text-primary);
290
+ font-size: 0.9rem;
291
+ }
292
+ .mock-input:focus { outline: none; border-color: var(--accent); }
293
+
294
+ /* ===== PHASE / STATUS BADGES ===== */
295
+ .badge {
296
+ display: inline-flex;
297
+ align-items: center;
298
+ gap: 0.3rem;
299
+ padding: 0.2rem 0.6rem;
300
+ border-radius: 999px;
301
+ font-size: 0.7rem;
302
+ font-weight: 600;
303
+ text-transform: uppercase;
304
+ letter-spacing: 0.05em;
305
+ }
306
+ .badge-inception { background: rgba(59,130,246,0.2); color: #60a5fa; }
307
+ .badge-design { background: rgba(168,85,247,0.2); color: #c084fc; }
308
+ .badge-build { background: rgba(34,197,94,0.2); color: #4ade80; }
309
+ .badge-review { background: rgba(245,158,11,0.2); color: #fbbf24; }
310
+ </style>
311
+ </head>
312
+ <body>
313
+ <div class="header">
314
+ <div class="header-left">
315
+ <div class="header-logo">P</div>
316
+ <h1>PDLC Visual Companion</h1>
317
+ </div>
318
+ <div class="status">Connected</div>
319
+ </div>
320
+
321
+ <div class="main">
322
+ <div id="claude-content">
323
+ <!-- CONTENT -->
324
+ </div>
325
+ </div>
326
+
327
+ <div class="indicator-bar">
328
+ <span id="indicator-text">Click an option above, then return to the terminal</span>
329
+ </div>
330
+
331
+ </body>
332
+ </html>