- Move docs/superpowers/{plans,specs}/ → docs/{plans,specs}/
- Add 4 previously untracked implementation plans to git
- Update CLAUDE.md with docs path overrides for superpowers skills
- Update HANDBOOK.md repo structure and workflow paths
- Add per-enemy dice rolls to ROADMAP planned section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1176 lines
37 KiB
Markdown
1176 lines
37 KiB
Markdown
# The Brochure — Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Build a single-page showcase site (`site/`) for Darkwatch with a dark hero section, parchment feature rows with Playwright-generated screenshots, a system support strip, and a publisher contact section.
|
||
|
||
**Architecture:** Plain HTML/CSS/JS with no build step or framework. A Playwright Node script (`site/screenshots.js`) logs into the running dev app and captures feature screenshots automatically. A `update-brochure` Claude skill keeps the site current as features change.
|
||
|
||
**Tech Stack:** HTML · CSS · Vanilla JS · Playwright (Node, screenshot script only)
|
||
|
||
---
|
||
|
||
## File Map
|
||
|
||
| Action | Path | Purpose |
|
||
|---|---|---|
|
||
| Create | `site/index.html` | The page — all sections |
|
||
| Create | `site/style.css` | All styles — CSS variables, fonts, layout |
|
||
| Create | `site/main.js` | Smooth scroll + hero parallax |
|
||
| Create | `site/assets/screenshots/.gitkeep` | Ensures directory exists in git |
|
||
| Create | `site/package.json` | Playwright dependency for screenshot script |
|
||
| Create | `site/screenshots.js` | Playwright script — captures all feature screenshots |
|
||
| Create | `.claude/skills/update-brochure.md` | Maintenance skill |
|
||
| Modify | `CLAUDE.md` | Register update-brochure skill |
|
||
| Modify | `.gitignore` | Ignore `site/node_modules/` |
|
||
|
||
---
|
||
|
||
## Task 1: CSS foundation
|
||
|
||
**Files:**
|
||
- Create: `site/style.css`
|
||
- Create: `site/assets/screenshots/.gitkeep`
|
||
|
||
- [ ] **Step 1: Create `site/assets/screenshots/.gitkeep`**
|
||
|
||
```bash
|
||
mkdir -p site/assets/screenshots
|
||
touch site/assets/screenshots/.gitkeep
|
||
```
|
||
|
||
- [ ] **Step 2: Create `site/style.css`**
|
||
|
||
```css
|
||
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Alegreya:ital,wght@0,400;0,500;1,400&display=swap');
|
||
|
||
/* ── Variables ─────────────────────────────────────────── */
|
||
:root {
|
||
--gold: #c9a84c;
|
||
--gold-dark: #8b6914;
|
||
--gold-dim: rgba(201, 168, 76, 0.12);
|
||
--gold-border: rgba(201, 168, 76, 0.3);
|
||
--bg-dark: #0d0a05;
|
||
--bg-dark-2: #1a1408;
|
||
--bg-parchment: #f5f0e8;
|
||
--bg-parchment-alt: #ede8d5;
|
||
--border-dark: #2a2010;
|
||
--border-parchment: #d4c9a8;
|
||
--border-light: #e0d5b5;
|
||
--text-gold: #c9a84c;
|
||
--text-parchment: #e8dcc8;
|
||
--text-dark: #2a1f0a;
|
||
--text-muted: #6a5a3a;
|
||
--text-label: #8b6914;
|
||
--font-display: 'Cinzel', serif;
|
||
--font-body: 'Alegreya', serif;
|
||
}
|
||
|
||
/* ── Reset ──────────────────────────────────────────────── */
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
html { scroll-behavior: smooth; }
|
||
body { font-family: var(--font-body); background: var(--bg-dark); color: var(--text-parchment); }
|
||
img { display: block; max-width: 100%; height: auto; border-radius: 6px; }
|
||
a { text-decoration: none; }
|
||
|
||
/* ── Hero ───────────────────────────────────────────────── */
|
||
.hero {
|
||
min-height: 100vh;
|
||
background: var(--bg-dark);
|
||
border-bottom: 2px solid var(--border-dark);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
padding: 80px 24px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(ellipse 80% 60% at 50% 40%, rgba(201,168,76,0.07) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.hero-content { position: relative; max-width: 680px; }
|
||
|
||
.wordmark {
|
||
font-family: var(--font-display);
|
||
font-size: 11px;
|
||
letter-spacing: 4px;
|
||
text-transform: uppercase;
|
||
color: var(--gold);
|
||
margin-bottom: 28px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.hero-headline {
|
||
font-family: var(--font-display);
|
||
font-size: clamp(2.2rem, 5vw, 3.6rem);
|
||
font-weight: 700;
|
||
color: var(--text-parchment);
|
||
line-height: 1.2;
|
||
margin-bottom: 20px;
|
||
text-shadow: 0 0 40px rgba(201,168,76,0.15);
|
||
}
|
||
|
||
.hero-subhead {
|
||
font-family: var(--font-body);
|
||
font-size: 1.15rem;
|
||
color: #9a8a6a;
|
||
line-height: 1.65;
|
||
margin-bottom: 40px;
|
||
max-width: 520px;
|
||
margin-left: auto;
|
||
margin-right: auto;
|
||
}
|
||
|
||
.hero-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 28px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.btn-primary {
|
||
font-family: var(--font-display);
|
||
font-size: 13px;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
background: var(--gold);
|
||
color: var(--bg-dark);
|
||
padding: 14px 32px;
|
||
border-radius: 3px;
|
||
font-weight: 700;
|
||
transition: background 0.2s, transform 0.1s;
|
||
}
|
||
.btn-primary:hover { background: #e0bc60; transform: translateY(-1px); }
|
||
|
||
.scroll-hint {
|
||
font-family: var(--font-body);
|
||
font-size: 0.95rem;
|
||
color: var(--text-muted);
|
||
transition: color 0.2s;
|
||
}
|
||
.scroll-hint:hover { color: var(--gold); }
|
||
|
||
/* ── Features ───────────────────────────────────────────── */
|
||
.features { background: var(--bg-parchment); }
|
||
|
||
.features-intro {
|
||
padding: 64px 24px 40px;
|
||
text-align: center;
|
||
border-bottom: 1px solid var(--border-parchment);
|
||
}
|
||
|
||
.section-label {
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 3px;
|
||
text-transform: uppercase;
|
||
color: var(--text-label);
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.features-intro h2 {
|
||
font-family: var(--font-display);
|
||
font-size: clamp(1.4rem, 3vw, 2rem);
|
||
color: var(--text-dark);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.feature-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
align-items: center;
|
||
gap: 0;
|
||
border-bottom: 1px solid var(--border-light);
|
||
background: var(--bg-parchment);
|
||
}
|
||
|
||
.feature-row:nth-child(even) { background: var(--bg-parchment-alt); }
|
||
|
||
.feature-image {
|
||
padding: 48px 40px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.feature-image img {
|
||
box-shadow: 0 8px 32px rgba(42,31,10,0.18);
|
||
border: 1px solid var(--border-parchment);
|
||
}
|
||
|
||
.feature-text {
|
||
padding: 48px 56px 48px 24px;
|
||
}
|
||
|
||
.feature-row.reverse .feature-image { order: 2; }
|
||
.feature-row.reverse .feature-text { order: 1; padding: 48px 24px 48px 56px; }
|
||
|
||
.feature-label {
|
||
font-family: var(--font-display);
|
||
font-size: 10px;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
color: var(--text-label);
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.feature-heading {
|
||
font-family: var(--font-display);
|
||
font-size: 1.5rem;
|
||
font-weight: 600;
|
||
color: var(--text-dark);
|
||
margin-bottom: 14px;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.feature-desc {
|
||
font-family: var(--font-body);
|
||
font-size: 1.05rem;
|
||
color: #4a3a1a;
|
||
line-height: 1.7;
|
||
}
|
||
|
||
/* Torch & Luck callout cards */
|
||
.callout-row {
|
||
padding: 56px 40px;
|
||
background: var(--bg-parchment-alt);
|
||
border-bottom: 1px solid var(--border-light);
|
||
display: flex;
|
||
gap: 32px;
|
||
justify-content: center;
|
||
}
|
||
|
||
.callout-card {
|
||
background: var(--bg-parchment);
|
||
border: 1px solid var(--border-parchment);
|
||
border-radius: 6px;
|
||
padding: 32px 36px;
|
||
max-width: 320px;
|
||
text-align: center;
|
||
}
|
||
|
||
.callout-icon { font-size: 2rem; margin-bottom: 12px; }
|
||
|
||
.callout-card h3 {
|
||
font-family: var(--font-display);
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
color: var(--text-dark);
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.callout-card p {
|
||
font-family: var(--font-body);
|
||
font-size: 0.95rem;
|
||
color: #4a3a1a;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* ── System Support ─────────────────────────────────────── */
|
||
.system-support {
|
||
background: var(--bg-dark-2);
|
||
border-top: 2px solid var(--border-dark);
|
||
border-bottom: 2px solid var(--border-dark);
|
||
padding: 48px 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.system-support .section-label { color: var(--gold); opacity: 0.7; }
|
||
|
||
.system-support h2 {
|
||
font-family: var(--font-display);
|
||
font-size: 1.4rem;
|
||
color: var(--text-parchment);
|
||
font-weight: 600;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.system-support p {
|
||
font-family: var(--font-body);
|
||
color: var(--text-muted);
|
||
font-size: 1rem;
|
||
}
|
||
|
||
/* ── Publisher ──────────────────────────────────────────── */
|
||
.publisher {
|
||
background: var(--bg-parchment);
|
||
border-bottom: 1px solid var(--border-parchment);
|
||
padding: 80px 24px;
|
||
text-align: center;
|
||
}
|
||
|
||
.publisher .section-label { margin-bottom: 14px; }
|
||
|
||
.publisher h2 {
|
||
font-family: var(--font-display);
|
||
font-size: clamp(1.4rem, 3vw, 2rem);
|
||
color: var(--text-dark);
|
||
font-weight: 600;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.publisher p {
|
||
font-family: var(--font-body);
|
||
font-size: 1.1rem;
|
||
color: #4a3a1a;
|
||
line-height: 1.75;
|
||
max-width: 520px;
|
||
margin: 0 auto 32px;
|
||
}
|
||
|
||
.btn-secondary {
|
||
font-family: var(--font-display);
|
||
font-size: 12px;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
background: var(--bg-dark);
|
||
color: var(--gold);
|
||
padding: 13px 30px;
|
||
border-radius: 3px;
|
||
font-weight: 600;
|
||
border: 1px solid var(--border-dark);
|
||
transition: background 0.2s;
|
||
display: inline-block;
|
||
}
|
||
.btn-secondary:hover { background: var(--bg-dark-2); }
|
||
|
||
/* ── Footer ─────────────────────────────────────────────── */
|
||
.footer {
|
||
background: var(--bg-dark);
|
||
border-top: 1px solid var(--border-dark);
|
||
padding: 28px 40px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
|
||
.footer-left {
|
||
font-family: var(--font-display);
|
||
font-size: 11px;
|
||
letter-spacing: 1px;
|
||
color: #4a3a1a;
|
||
}
|
||
|
||
.footer-right { display: flex; gap: 20px; }
|
||
|
||
.footer-right a {
|
||
font-family: var(--font-display);
|
||
font-size: 11px;
|
||
letter-spacing: 1px;
|
||
color: #4a3a1a;
|
||
transition: color 0.2s;
|
||
}
|
||
.footer-right a:hover { color: var(--gold); }
|
||
|
||
/* ── Responsive ─────────────────────────────────────────── */
|
||
@media (max-width: 768px) {
|
||
.feature-row,
|
||
.feature-row.reverse {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.feature-row.reverse .feature-image { order: 0; }
|
||
.feature-row.reverse .feature-text { order: 0; }
|
||
.feature-image { padding: 32px 24px 0; }
|
||
.feature-text,
|
||
.feature-row.reverse .feature-text { padding: 24px 24px 40px; }
|
||
.callout-row { flex-direction: column; align-items: center; }
|
||
.footer { justify-content: center; text-align: center; }
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Verify fonts load**
|
||
|
||
```bash
|
||
open site/index.html # will 404 — we'll create it in Task 2. Just confirm style.css has no syntax errors:
|
||
node -e "require('fs').readFileSync('site/style.css', 'utf8'); console.log('OK')"
|
||
```
|
||
|
||
Expected: `OK`
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add site/style.css site/assets/screenshots/.gitkeep
|
||
git commit -m "feat: add brochure CSS foundation and screenshot directory"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: Hero section
|
||
|
||
**Files:**
|
||
- Create: `site/index.html`
|
||
|
||
- [ ] **Step 1: Create `site/index.html` with skeleton + hero**
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Darkwatch — Real-Time Shadowdark Companion</title>
|
||
<meta name="description" content="A real-time session companion for Shadowdark RPG. Characters, dice, spells, and atmosphere — synced live across every device at the table.">
|
||
<link rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ── Hero ─────────────────────────────────────────── -->
|
||
<section class="hero" id="top">
|
||
<div class="hero-content">
|
||
<div class="wordmark">◈ Darkwatch</div>
|
||
<h1 class="hero-headline">Your whole party.<br>One screen. No paper.</h1>
|
||
<p class="hero-subhead">
|
||
A real-time session companion for Shadowdark RPG. Characters, dice, spells,
|
||
and atmosphere — synced live across every device at the table.
|
||
</p>
|
||
<div class="hero-actions">
|
||
<a href="https://discord.gg/PLACEHOLDER" class="btn-primary">Join the Beta</a>
|
||
<a href="#features" class="scroll-hint">↓ See what it does</a>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- features, system support, publisher, footer go here in later tasks -->
|
||
|
||
<script src="main.js"></script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
- [ ] **Step 2: Open in browser and verify hero**
|
||
|
||
```bash
|
||
open site/index.html
|
||
```
|
||
|
||
Expected: dark background, gold wordmark, large serif headline, parchment-colored subhead, gold CTA button. Fonts may not load from `file://` — if Cinzel/Alegreya don't appear, use a local server:
|
||
|
||
```bash
|
||
npx serve site -l 4000
|
||
# then open http://localhost:4000
|
||
```
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add site/index.html
|
||
git commit -m "feat: add brochure hero section"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: Feature rows
|
||
|
||
**Files:**
|
||
- Modify: `site/index.html`
|
||
|
||
- [ ] **Step 1: Add features intro and all 7 feature rows**
|
||
|
||
Replace `<!-- features, system support, publisher, footer go here in later tasks -->` with:
|
||
|
||
```html
|
||
<!-- ── Features ──────────────────────────────────────── -->
|
||
<section class="features" id="features">
|
||
|
||
<div class="features-intro">
|
||
<div class="section-label">Everything at the table</div>
|
||
<h2>Built for Shadowdark. Designed for the whole party.</h2>
|
||
</div>
|
||
|
||
<!-- Row 1: screenshot left, text right -->
|
||
<div class="feature-row">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/character-sheet.png" alt="Darkwatch character sheet showing stats, gear, and talents">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">Characters</div>
|
||
<h2 class="feature-heading">Every stat, always in sync.</h2>
|
||
<p class="feature-desc">
|
||
Full Shadowdark character sheets — STR, DEX, CON, INT, WIS, CHA, HP, AC, XP,
|
||
gear, and talents. Click any value to edit it. Changes sync to the DM and every
|
||
player at the table in real time.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 2: text left, screenshot right -->
|
||
<div class="feature-row reverse">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/character-creation.png" alt="Character creation wizard showing 3d6 stat rolling">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">Character Creation</div>
|
||
<h2 class="feature-heading">Roll to begin.</h2>
|
||
<p class="feature-desc">
|
||
A guided four-step wizard: name, class, and ancestry — then roll 3d6 for each
|
||
stat and watch them land. Background, alignment, and deity last. HP, gear slots,
|
||
gold, and your title are all derived automatically.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 3: screenshot left, text right -->
|
||
<div class="feature-row">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/dice-roll.png" alt="3D dice rolling with character-colored dice and shared roll log">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">Dice Rolling</div>
|
||
<h2 class="feature-heading">3D dice. Shared results. No hiding rolls.</h2>
|
||
<p class="feature-desc">
|
||
Dice roll in 3D, in your character's color. Results are determined server-side —
|
||
no one can fudge them. Every roll appears in a shared log that the whole table
|
||
sees at the same time.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 4: text left, screenshot right -->
|
||
<div class="feature-row reverse">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/spellcasting.png" alt="Spellcasting panel with spell slots and exhaustion tracking">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">Spellcasting</div>
|
||
<h2 class="feature-heading">Cast, fail, and suffer — together.</h2>
|
||
<p class="feature-desc">
|
||
34 Tier 1–2 spells for Wizard and Priest. Every cast rolls a d20 — fail and
|
||
lose a spell slot. Wizards risk the mishap table. Priests do penance. Rest to
|
||
recover. The DM sees your spell focus state at a glance.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 5: screenshot left, text right -->
|
||
<div class="feature-row">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/atmosphere.png" alt="Atmospheric fog effect covering the campaign view">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">Atmosphere</div>
|
||
<h2 class="feature-heading">Set the scene.</h2>
|
||
<p class="feature-desc">
|
||
The DM controls atmosphere effects that every player sees in real time:
|
||
creeping fog, crackling fire, rain, or drifting embers. One click and the
|
||
whole table feels the dungeon shift.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 6: text left, screenshot right -->
|
||
<div class="feature-row reverse">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/initiative-active.png" alt="Initiative tracker showing party vs enemies with round counter">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">Initiative Tracker</div>
|
||
<h2 class="feature-heading">Shadowdark initiative, handled.</h2>
|
||
<p class="feature-desc">
|
||
Everyone rolls. The tracker takes the party's highest result against the DM's
|
||
single enemy roll. Whoever wins goes first — then the DM clicks Next Turn and
|
||
the round counter climbs. Enemy HP is visible to the DM only.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Row 7: screenshot left, text right -->
|
||
<div class="feature-row">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/dm-cards.png" alt="DM view showing compact character cards for the whole party">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">DM View</div>
|
||
<h2 class="feature-heading">The whole party, at a glance.</h2>
|
||
<p class="feature-desc">
|
||
The DM sees every character in a compact grid: HP, AC, luck token, torch timer,
|
||
and all six stat modifiers — without opening a single sheet. Drill in when you
|
||
need to, stay out when you don't.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Torch + Luck callout cards (no screenshot) -->
|
||
<div class="callout-row">
|
||
<div class="callout-card">
|
||
<div class="callout-icon">🕯</div>
|
||
<h3>Torch Timer</h3>
|
||
<p>A real 60-minute countdown, synced across every device. The DM starts it when
|
||
the torch is lit. Everyone watches it burn.</p>
|
||
</div>
|
||
<div class="callout-card">
|
||
<div class="callout-icon">✦</div>
|
||
<h3>Luck Tokens</h3>
|
||
<p>Each character has a luck token the DM and player can both see and toggle.
|
||
No more forgetting who spent their luck last session.</p>
|
||
</div>
|
||
</div>
|
||
|
||
</section>
|
||
|
||
<!-- system support, publisher, footer go here in Task 4 -->
|
||
```
|
||
|
||
- [ ] **Step 2: Open in browser and verify feature rows**
|
||
|
||
```bash
|
||
npx serve site -l 4000
|
||
# open http://localhost:4000
|
||
```
|
||
|
||
Expected: seven alternating rows with broken image icons (screenshots don't exist yet) and text descriptions. Parchment background. Callout cards at the bottom. Layout alternates left/right correctly.
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add site/index.html
|
||
git commit -m "feat: add brochure feature rows"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: Lower sections — system support, publisher, footer
|
||
|
||
**Files:**
|
||
- Modify: `site/index.html`
|
||
|
||
- [ ] **Step 1: Replace the `<!-- system support... -->` comment with the remaining sections**
|
||
|
||
Replace `<!-- system support, publisher, footer go here in Task 4 -->` with:
|
||
|
||
```html
|
||
<!-- ── System Support ────────────────────────────────── -->
|
||
<section class="system-support">
|
||
<div class="section-label">System Support</div>
|
||
<h2>Built for Shadowdark RPG. More systems coming.</h2>
|
||
<p>Currently: Shadowdark RPG · Next: based on demand</p>
|
||
</section>
|
||
|
||
<!-- ── Publisher ─────────────────────────────────────── -->
|
||
<section class="publisher">
|
||
<div class="section-label">For game publishers</div>
|
||
<h2>"We're not here to sell your rules."</h2>
|
||
<p>
|
||
Darkwatch is a session companion — a torch timer, a dice roller, an initiative
|
||
tracker. Your players still need the book. We just want to make the table more
|
||
fun. If you'd like to talk, we'd love to.
|
||
</p>
|
||
<a href="mailto:email@email.com" class="btn-secondary">Get in touch</a>
|
||
</section>
|
||
|
||
<!-- ── Footer ────────────────────────────────────────── -->
|
||
<footer class="footer">
|
||
<div class="footer-left">Darkwatch · Built for the table</div>
|
||
<div class="footer-right">
|
||
<a href="https://discord.gg/PLACEHOLDER">Discord</a>
|
||
<a href="https://github.com/PLACEHOLDER/darkwatch">GitHub</a>
|
||
</div>
|
||
</footer>
|
||
```
|
||
|
||
- [ ] **Step 2: Open in browser and verify complete page**
|
||
|
||
```bash
|
||
npx serve site -l 4000
|
||
# open http://localhost:4000
|
||
```
|
||
|
||
Scroll through the full page. Expected:
|
||
- Dark hero → parchment features → dark system strip → parchment publisher → dark footer
|
||
- Publisher section has the "Get in touch" button
|
||
- Footer has Discord and GitHub links (both currently `PLACEHOLDER`)
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add site/index.html
|
||
git commit -m "feat: add brochure system support, publisher, and footer sections"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: main.js — smooth scroll and hero parallax
|
||
|
||
**Files:**
|
||
- Create: `site/main.js`
|
||
|
||
- [ ] **Step 1: Create `site/main.js`**
|
||
|
||
```javascript
|
||
// Smooth scroll for anchor links (CSS scroll-behavior covers most cases,
|
||
// this handles the ↓ scroll-hint click on browsers that need it)
|
||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||
anchor.addEventListener('click', e => {
|
||
const target = document.querySelector(anchor.getAttribute('href'));
|
||
if (target) {
|
||
e.preventDefault();
|
||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
}
|
||
});
|
||
});
|
||
|
||
// Subtle parallax on hero background glow
|
||
const hero = document.querySelector('.hero');
|
||
if (hero) {
|
||
window.addEventListener('scroll', () => {
|
||
const scrolled = window.scrollY;
|
||
const heroHeight = hero.offsetHeight;
|
||
if (scrolled < heroHeight) {
|
||
// Move the pseudo-element glow upward slightly as user scrolls
|
||
hero.style.setProperty('--parallax-offset', `${scrolled * 0.3}px`);
|
||
}
|
||
}, { passive: true });
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Wire the parallax offset into CSS**
|
||
|
||
In `site/style.css`, update the `.hero::before` rule to use the variable:
|
||
|
||
Find:
|
||
```css
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(ellipse 80% 60% at 50% 40%, rgba(201,168,76,0.07) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
```
|
||
|
||
Replace with:
|
||
```css
|
||
.hero::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(ellipse 80% 60% at 50% calc(40% - var(--parallax-offset, 0px)), rgba(201,168,76,0.07) 0%, transparent 70%);
|
||
pointer-events: none;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Verify in browser**
|
||
|
||
```bash
|
||
npx serve site -l 4000
|
||
# open http://localhost:4000
|
||
```
|
||
|
||
Scroll slowly down from the hero. Expected: the gold glow in the hero drifts upward subtly as you scroll. Effect is gentle — if it's too strong, reduce `0.3` multiplier in `main.js`.
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add site/main.js site/style.css
|
||
git commit -m "feat: add smooth scroll and hero parallax"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: Playwright screenshot script
|
||
|
||
**Files:**
|
||
- Create: `site/package.json`
|
||
- Create: `site/screenshots.js`
|
||
- Modify: `.gitignore` (add `site/node_modules/`)
|
||
|
||
- [ ] **Step 1: Add `site/node_modules/` to .gitignore**
|
||
|
||
Append to `.gitignore`:
|
||
```
|
||
site/node_modules/
|
||
```
|
||
|
||
- [ ] **Step 2: Create `site/package.json`**
|
||
|
||
```json
|
||
{
|
||
"name": "darkwatch-brochure-screenshots",
|
||
"type": "module",
|
||
"scripts": {
|
||
"screenshots": "node screenshots.js",
|
||
"screenshots:only": "node screenshots.js --only"
|
||
},
|
||
"devDependencies": {
|
||
"playwright": "^1.43.0"
|
||
}
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Install Playwright and its Chromium browser**
|
||
|
||
```bash
|
||
cd site
|
||
npm install
|
||
npx playwright install chromium
|
||
cd ..
|
||
```
|
||
|
||
Expected: `node_modules/` created in `site/`, Chromium downloaded.
|
||
|
||
- [ ] **Step 4: Create `site/screenshots.js`**
|
||
|
||
```javascript
|
||
import { chromium } from 'playwright';
|
||
import { resolve, dirname } from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
import { mkdirSync } from 'fs';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const SHOT_DIR = resolve(__dirname, 'assets', 'screenshots');
|
||
const APP = 'http://localhost:5173';
|
||
const DM_EMAIL = 'dm@darkwatch.test';
|
||
const DM_PASS = 'password';
|
||
const ONLY = process.argv.includes('--only')
|
||
? process.argv[process.argv.indexOf('--only') + 1]
|
||
: null;
|
||
|
||
mkdirSync(SHOT_DIR, { recursive: true });
|
||
|
||
// ── helpers ────────────────────────────────────────────────────────────────
|
||
|
||
async function shot(page, name, fn) {
|
||
if (ONLY && name !== ONLY) return;
|
||
console.log(` capturing ${name}...`);
|
||
await fn(page);
|
||
await page.screenshot({
|
||
path: resolve(SHOT_DIR, `${name}.png`),
|
||
fullPage: false,
|
||
});
|
||
console.log(` ✓ ${name}.png`);
|
||
}
|
||
|
||
async function login(page) {
|
||
await page.goto(`${APP}/login`);
|
||
await page.fill('input[type="email"]', DM_EMAIL);
|
||
await page.fill('input[type="password"]', DM_PASS);
|
||
await page.click('button[type="submit"]');
|
||
await page.waitForURL(`${APP}/`);
|
||
}
|
||
|
||
async function openCampaign(page) {
|
||
// Seeded campaign is named "Tomb of the Serpent King"
|
||
await page.click('text=Tomb of the Serpent King');
|
||
await page.waitForURL(/\/campaign\/\d+/);
|
||
await page.waitForTimeout(1500); // socket connect + data load
|
||
}
|
||
|
||
// ── captures ───────────────────────────────────────────────────────────────
|
||
|
||
async function captureCharacterSheet(page) {
|
||
// DM view — click the first character card to open full sheet
|
||
// Selector: first .characterCard element in the card grid
|
||
// Adjust class name if different in the actual DOM
|
||
const card = page.locator('[class*="characterCard"]').first();
|
||
await card.click();
|
||
await page.waitForTimeout(500);
|
||
}
|
||
|
||
async function captureCharacterCreation(page) {
|
||
// Open character creation wizard and advance to step 2 (stat rolling)
|
||
await page.click('button:has-text("New Character")');
|
||
await page.waitForTimeout(400);
|
||
// Fill required fields on step 1 so Next is enabled
|
||
const nameInput = page.locator('input[placeholder*="name" i]').first();
|
||
await nameInput.fill('Screenshot Hero');
|
||
// Advance to step 2
|
||
await page.click('button:has-text("Next")');
|
||
await page.waitForTimeout(400);
|
||
}
|
||
|
||
async function captureDiceRoll(page) {
|
||
// Roll a d20 — find the d20 button in the dice panel
|
||
await page.click('button:has-text("d20")');
|
||
await page.waitForTimeout(2000); // wait for 3D animation to settle
|
||
}
|
||
|
||
async function captureSpellcasting(page) {
|
||
// Open the Spells tab/panel for the first Wizard or Priest character
|
||
// The spell panel may be a tab within the character sheet
|
||
await page.click('text=Spells');
|
||
await page.waitForTimeout(400);
|
||
}
|
||
|
||
async function captureAtmosphere(page) {
|
||
// Open atmosphere panel and enable fog
|
||
await page.click('button[title*="tmosphere" i], button:has-text("Atmosphere")');
|
||
await page.waitForTimeout(300);
|
||
await page.click('button:has-text("Fog")');
|
||
await page.waitForTimeout(600); // wait for fog to render
|
||
}
|
||
|
||
async function captureInitiativeActive(page) {
|
||
// Click the Combat button to open the start combat modal
|
||
await page.click('button:has-text("Combat")');
|
||
await page.waitForTimeout(300);
|
||
// Start combat with whatever defaults are present
|
||
await page.click('button:has-text("Start Combat")');
|
||
await page.waitForTimeout(500);
|
||
// Roll initiative for enemies (DM roll)
|
||
const rollBtn = page.locator('button:has-text("Roll")').first();
|
||
await rollBtn.click();
|
||
await page.waitForTimeout(400);
|
||
}
|
||
|
||
async function captureDmCards(page) {
|
||
// The DM compact card grid is the default campaign view.
|
||
// Navigate back to campaign root to ensure no modals are open.
|
||
const url = page.url();
|
||
await page.goto(url);
|
||
await page.waitForTimeout(1500); // let socket reconnect
|
||
}
|
||
|
||
// ── main ───────────────────────────────────────────────────────────────────
|
||
|
||
async function run() {
|
||
console.log('Starting Darkwatch screenshot capture...');
|
||
console.log(`App: ${APP}`);
|
||
if (ONLY) console.log(`Only: ${ONLY}`);
|
||
console.log('');
|
||
|
||
const browser = await chromium.launch();
|
||
const context = await browser.newContext({
|
||
viewport: { width: 1280, height: 800 },
|
||
});
|
||
const page = await context.newPage();
|
||
|
||
await login(page);
|
||
console.log('✓ Logged in as DM');
|
||
|
||
await openCampaign(page);
|
||
console.log('✓ Campaign open');
|
||
console.log('');
|
||
|
||
await shot(page, 'dm-cards', captureDmCards);
|
||
await shot(page, 'character-sheet', captureCharacterSheet);
|
||
await shot(page, 'character-creation',captureCharacterCreation);
|
||
await shot(page, 'dice-roll', captureDiceRoll);
|
||
await shot(page, 'spellcasting', captureSpellcasting);
|
||
await shot(page, 'atmosphere', captureAtmosphere);
|
||
await shot(page, 'initiative-active', captureInitiativeActive);
|
||
|
||
await browser.close();
|
||
console.log('');
|
||
console.log('Done! Screenshots saved to site/assets/screenshots/');
|
||
}
|
||
|
||
run().catch(err => {
|
||
console.error(err);
|
||
process.exit(1);
|
||
});
|
||
```
|
||
|
||
- [ ] **Step 5: Run the script against the running dev server**
|
||
|
||
Ensure the dev server is running first (`npm run dev` in `server/` and `client/`). Then:
|
||
|
||
```bash
|
||
cd site
|
||
node screenshots.js
|
||
```
|
||
|
||
Expected output:
|
||
```
|
||
Starting Darkwatch screenshot capture...
|
||
App: http://localhost:5173
|
||
|
||
✓ Logged in as DM
|
||
✓ Campaign open
|
||
|
||
capturing dm-cards...
|
||
✓ dm-cards.png
|
||
capturing character-sheet...
|
||
...
|
||
Done! Screenshots saved to site/assets/screenshots/
|
||
```
|
||
|
||
**If a capture fails:** The most likely cause is a selector mismatch. Open Playwright in headed mode to debug:
|
||
|
||
```javascript
|
||
// Temporarily change launch() to:
|
||
const browser = await chromium.launch({ headless: false, slowMo: 500 });
|
||
```
|
||
|
||
Use browser DevTools to find the correct selector, update the relevant `capture*` function, then switch back to headless.
|
||
|
||
- [ ] **Step 6: Verify screenshots look good**
|
||
|
||
```bash
|
||
open site/assets/screenshots/
|
||
```
|
||
|
||
Open each PNG and verify:
|
||
- `dm-cards.png` — compact DM character card grid, 2+ cards visible
|
||
- `character-sheet.png` — full character stats visible
|
||
- `character-creation.png` — stat rolling step, 3d6 dice area visible
|
||
- `dice-roll.png` — dice visible (mid-roll or result), roll log visible
|
||
- `spellcasting.png` — spell list with slot indicators
|
||
- `atmosphere.png` — fog or fire effect clearly visible over the UI
|
||
- `initiative-active.png` — tracker showing two sides, round counter
|
||
|
||
If any screenshot is blank, too dark, or shows the wrong view, debug with headed mode (Step 5 note) and re-run `node screenshots.js --only <name>`.
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add site/package.json site/screenshots.js .gitignore site/assets/screenshots/
|
||
git commit -m "feat: add Playwright screenshot script for brochure"
|
||
```
|
||
|
||
Note: `site/node_modules/` is gitignored. The PNG screenshots are committed so the site renders without running the script.
|
||
|
||
---
|
||
|
||
## Task 7: update-brochure skill + CLAUDE.md
|
||
|
||
**Files:**
|
||
- Create: `.claude/skills/update-brochure.md`
|
||
- Modify: `CLAUDE.md`
|
||
|
||
- [ ] **Step 1: Create `.claude/skills/update-brochure.md`**
|
||
|
||
```markdown
|
||
# update-brochure
|
||
|
||
Use this skill when a Darkwatch feature is added, changed, or removed, and the brochure needs updating. It keeps `site/index.html` and `site/screenshots.js` in sync with the app.
|
||
|
||
## Scope
|
||
|
||
- `site/index.html` — add, update, or remove feature rows
|
||
- `site/screenshots.js` — add, update, or remove capture blocks
|
||
- System support strip in `site/index.html` — update when a new RPG system is added
|
||
|
||
**Not in scope:** Hero copy, publisher section copy, footer links. Those are editorial and manual.
|
||
|
||
## Process: Feature added
|
||
|
||
1. Add a new `.feature-row` block to `site/index.html` after the last existing feature row (before the `.callout-row`). Follow the alternating pattern:
|
||
- Rows 1, 3, 5, 7 (odd positions): screenshot on the left, text on the right (no `reverse` class)
|
||
- Rows 2, 4, 6 (even positions): `class="feature-row reverse"` (screenshot right, text left)
|
||
- Count existing rows to determine whether to add `reverse` or not
|
||
|
||
Template for a new row:
|
||
```html
|
||
<div class="feature-row">
|
||
<div class="feature-image">
|
||
<img src="assets/screenshots/FEATURE-NAME.png" alt="Description of screenshot">
|
||
</div>
|
||
<div class="feature-text">
|
||
<div class="feature-label">Short Label</div>
|
||
<h2 class="feature-heading">Punchy headline.</h2>
|
||
<p class="feature-desc">
|
||
Two sentences. Write for players and DMs, not developers.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
2. Add a capture block to `site/screenshots.js`:
|
||
- Write a `captureFeatureName(page)` async function that navigates to the right UI state
|
||
- Add `await shot(page, 'feature-name', captureFeatureName);` to the `run()` function, in the same order as the HTML row
|
||
|
||
3. Run the screenshot script for the new capture:
|
||
```bash
|
||
cd site && node screenshots.js --only feature-name
|
||
```
|
||
|
||
4. Commit all three files:
|
||
```bash
|
||
git add site/index.html site/screenshots.js site/assets/screenshots/feature-name.png
|
||
git commit -m "docs: add [feature name] to brochure"
|
||
```
|
||
|
||
## Process: Feature removed
|
||
|
||
1. Delete the feature's `.feature-row` block from `site/index.html`
|
||
2. Recheck alternating pattern — renumber remaining rows if needed
|
||
3. Delete the `capture*` function and `shot()` call from `site/screenshots.js`
|
||
4. Delete the PNG from `site/assets/screenshots/`
|
||
5. Commit
|
||
|
||
## Process: Feature changed (copy update only)
|
||
|
||
1. Update the heading and description in the relevant `.feature-row` in `site/index.html`
|
||
2. Re-run the screenshot if the UI changed visually:
|
||
```bash
|
||
cd site && node screenshots.js --only feature-name
|
||
```
|
||
3. Commit
|
||
|
||
## Process: New RPG system added
|
||
|
||
1. Update the system support strip in `site/index.html`:
|
||
```html
|
||
<!-- Before (Shadowdark only): -->
|
||
<p>Currently: Shadowdark RPG · Next: based on demand</p>
|
||
|
||
<!-- After (example with Cairn added): -->
|
||
<p>Currently: Shadowdark RPG · Cairn · More based on demand</p>
|
||
```
|
||
2. Commit
|
||
|
||
## Tone guidelines
|
||
|
||
Write feature descriptions for players and DMs, not developers.
|
||
|
||
- ✓ "Everyone rolls. The tracker takes the party's highest result."
|
||
- ✗ "The server computes max(party_rolls) and compares to the enemy roll."
|
||
|
||
Keep headings punchy and short. Keep descriptions to two sentences max.
|
||
```
|
||
|
||
- [ ] **Step 2: Read current CLAUDE.md to find the Project Skills table**
|
||
|
||
```bash
|
||
grep -n "update-changelog\|update-handbook\|Project Skills" CLAUDE.md
|
||
```
|
||
|
||
- [ ] **Step 3: Add update-brochure to the Project Skills table in CLAUDE.md**
|
||
|
||
Find the existing table:
|
||
```markdown
|
||
| `update-changelog` | `.claude/skills/update-changelog.md` | After a feature ships, bug is fixed, or feature is removed |
|
||
| `update-handbook` | `.claude/skills/update-handbook.md` | After a feature is added, changed, or removed |
|
||
```
|
||
|
||
Add a row:
|
||
```markdown
|
||
| `update-changelog` | `.claude/skills/update-changelog.md` | After a feature ships, bug is fixed, or feature is removed |
|
||
| `update-handbook` | `.claude/skills/update-handbook.md` | After a feature is added, changed, or removed |
|
||
| `update-brochure` | `.claude/skills/update-brochure.md` | After a feature is added, changed, or removed — keeps the site current |
|
||
```
|
||
|
||
- [ ] **Step 4: Commit**
|
||
|
||
```bash
|
||
git add .claude/skills/update-brochure.md CLAUDE.md
|
||
git commit -m "feat: add update-brochure skill and register in CLAUDE.md"
|
||
```
|
||
|
||
---
|
||
|
||
## Self-Review Checklist
|
||
|
||
After all tasks complete, verify against the spec:
|
||
|
||
- [ ] `site/index.html` has all 5 sections: hero, features (7 rows + callout cards), system support, publisher, footer
|
||
- [ ] Feature rows alternate left/right correctly (1,3,5,7 image-left; 2,4,6 image-right)
|
||
- [ ] All 7 screenshot `img` tags reference `assets/screenshots/*.png`
|
||
- [ ] Hero CTA links to Discord (placeholder is acceptable pre-launch)
|
||
- [ ] Publisher section uses `mailto:email@email.com` placeholder
|
||
- [ ] `site/screenshots.js` has a named capture function for each of the 7 PNGs
|
||
- [ ] `--only` flag works: `node screenshots.js --only dm-cards` captures only that one
|
||
- [ ] `.claude/skills/update-brochure.md` covers add, remove, change, and new-system processes
|
||
- [ ] `CLAUDE.md` lists `update-brochure` in the Project Skills table
|
||
- [ ] `site/node_modules/` is gitignored
|
||
- [ ] Screenshots are committed to `site/assets/screenshots/`
|