@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.
- package/.claude/commands/brainstorm.md +360 -0
- package/.claude/commands/build.md +383 -0
- package/.claude/commands/init.md +371 -0
- package/.claude/commands/ship.md +349 -0
- package/.claude/settings.json +40 -0
- package/CLAUDE.md +179 -0
- package/README.md +452 -0
- package/agents/bolt.md +84 -0
- package/agents/echo.md +87 -0
- package/agents/friday.md +83 -0
- package/agents/jarvis.md +87 -0
- package/agents/muse.md +87 -0
- package/agents/neo.md +78 -0
- package/agents/oracle.md +81 -0
- package/agents/phantom.md +85 -0
- package/agents/pulse.md +95 -0
- package/bin/pdlc.js +221 -0
- package/hooks/pdlc-context-monitor.js +129 -0
- package/hooks/pdlc-guardrails.js +307 -0
- package/hooks/pdlc-session-start.sh +73 -0
- package/hooks/pdlc-statusline.js +183 -0
- package/package.json +48 -0
- package/scripts/frame-template.html +332 -0
- package/scripts/helper.js +88 -0
- package/scripts/server.cjs +357 -0
- package/scripts/start-server.sh +173 -0
- package/scripts/stop-server.sh +54 -0
- package/skills/reflect.md +189 -0
- package/skills/repo-scan.md +266 -0
- package/skills/review.md +156 -0
- package/skills/safety-guardrails.md +168 -0
- package/skills/ship.md +148 -0
- package/skills/tdd.md +88 -0
- package/skills/test.md +153 -0
- package/templates/CONSTITUTION.md +254 -0
- package/templates/INTENT.md +120 -0
- package/templates/OVERVIEW.md +93 -0
- package/templates/PRD.md +212 -0
- package/templates/STATE.md +113 -0
- package/templates/episode.md +182 -0
- package/templates/review.md +215 -0
|
@@ -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>
|