darkwatch/docs/plans/2026-04-12-brochure.md
Aaron Wood 7c7bdf2ee5 chore: consolidate docs into flat structure and commit all plans
- 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>
2026-04-11 23:55:45 -04:00

1176 lines
37 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 12 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 &nbsp;·&nbsp; 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 &nbsp;·&nbsp; 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 &nbsp;·&nbsp; Next: based on demand</p>
<!-- After (example with Cairn added): -->
<p>Currently: Shadowdark RPG &nbsp;·&nbsp; Cairn &nbsp;·&nbsp; 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/`