",
+ "css": ".ds-ledger { background: linear-gradient(100deg, rgba(38,22,35,0.5) 0%, rgba(19,17,24,0.97) 34%, rgba(13,12,17,0.98) 100%), #0f0d12; border: 1px solid rgba(194,163,90,0.18); border-radius: 8px; max-width: 420px; box-shadow: inset 0 1px 0 rgba(255,255,255,0.024), inset 0 -1px 0 rgba(0,0,0,0.42), 0 8px 18px rgba(0,0,0,0.2); } .ds-ledger-head { font-family: Cinzel, Georgia, serif; font-weight: 600; font-size: 0.86rem; letter-spacing: 0.02em; color: #a99d8f; padding: 10px 16px; border-bottom: 1px solid rgba(194,163,90,0.16); } .ds-ledger-row { font-family: Jost, system-ui, sans-serif; font-size: 0.95rem; color: #d8c28a; padding: 10px 16px; border-bottom: 1px solid rgba(194,163,90,0.055); } .ds-ledger-row:last-child { border-bottom: none; } .ds-ledger-stamp { color: #a84a4e; font-weight: 600; }"
+ }
+ ],
+ "narrative": {
+ "northStar": "The Velvet Ledger",
+ "overview": "The forum is the account book of a vampire court: pages of near-black velvet, ruled with faint gold lines, written in tarnished gilt ink, stamped here and there with a dried-blood seal. Every surface is dark and plush. Gold appears as thin edges, rulings, and small marks of importance — the way gilt survives on an old book long after the leather has gone dark. Nothing is bright. Nothing is flat. Depth comes from fabric and candlelight, not from floating cards. It is a working forum first: text stays readable, focus stays visible, and the atmosphere never taxes reading.",
+ "keyCharacteristics": [
+ "Near-black plum-tinted surfaces with velvet gradient sheen",
+ "Tarnished gold carries all borders, links, and affordances",
+ "Engraved serif headings (Cinzel) over calm geometric body text (Jost)",
+ "Red reserved for states and small stamped details",
+ "Ambient, candlelit shadows; hierarchy by panel tone, not lift"
+ ],
+ "rules": [
+ { "name": "The Gilt Edge Rule", "body": "Gold is edging, not paint. It lives in 1px borders, thin rules, icons, and text. The only gold-filled surface in the theme is the primary button, and even that is a dark #5a4a1a, not bright gold.", "section": "colors" },
+ { "name": "The Sparing Red Rule", "body": "Red is a state and a detail — an error, a seal, a warning. It is never a background, never decoration, never loud.", "section": "colors" },
+ { "name": "The Engraving Rule", "body": "Wide letter-spacing and uppercase belong only to small Cinzel labels. Headings at reading size keep letter-spacing 0. Body text is never tracked, never uppercase.", "section": "typography" },
+ { "name": "The Candlelight Rule", "body": "Shadows are atmosphere. If you need to show one thing sits above another, change the panel tone and border alpha; do not invent a bigger shadow.", "section": "elevation" }
+ ],
+ "dos": [
+ "Do keep body text at or above 4.5:1 contrast; Ash Text (#b9b2a6) on Crypt Black is the floor. Drop to Dust Text only for genuinely secondary metadata.",
+ "Do carry the theme to every surface — wiki, custom pages, applications, reports, admin-facing lists. One unthemed page breaks the spell.",
+ "Do build hierarchy with the three panel tones and border alpha steps (8% / 14% / 18–22%), per the Candlelight Rule.",
+ "Do keep a visible 2px gold focus outline on everything interactive.",
+ "Do stay a small child theme: inherit from Harmony and override only what Westgate needs."
+ ],
+ "donts": [
+ "Don't make anything \"bright, playful, clinical, or flat SaaS-style\" (PRODUCT.md). No white surfaces, no saturated blues, no flat borderless cards.",
+ "Don't add \"loud ornament\" (PRODUCT.md). No filigree backgrounds, no large gold fills, no glow effects beyond the existing plum bloom.",
+ "Don't use red as a surface or decoration — states and small seals only (the Sparing Red Rule).",
+ "Don't track or uppercase body text; that treatment belongs to small Cinzel labels only (the Engraving Rule).",
+ "Don't ship anything that could pass as a generic default forum skin. If a screenshot could be any NodeBB forum, it is not done."
+ ]
+ }
+}
diff --git a/AGENTS.md b/AGENTS.md
index c93927e..c693a32 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -17,11 +17,49 @@ compiled in.
## How It Is Used
-There is no build step in this repo; NodeBB compiles the SCSS. Local check:
-`node scripts/check-emails.js` (if working on email templates). To see changes
-live, use the `docker-compose.dev.yml` dev loop in sow-nodebb with this repo
-as an extra mounted package. Release: merge here, re-pin the commit SHA in
-sow-nodebb's `plugins.lock`, rebuild and publish the image there.
+There is no build step in this repo; NodeBB compiles the SCSS. To see changes
+live, use the `docker-compose.dev.yml` dev loop in sow-nodebb (see How To Test
+below). Release: merge here, re-pin the commit SHA in sow-nodebb's
+`plugins.lock`, rebuild and publish the image there.
+
+## How To Test
+
+Fast checks, no forum needed:
+
+- `node tests/.test.js` — contract tests (topbar, footer, icons, wiki
+ tables). There is no `npm test` script; run each file directly.
+- `node scripts/check-emails.js` — when working on email templates.
+
+Live visual check against a real forum, using the sibling checkout at
+`../sow-nodebb`:
+
+1. `cd ../sow-nodebb && cp .env.example .env` (if not done already), then set
+ `PLUGIN_PATH=../sow-nodebb-theme` and `PLUGIN_ID=nodebb-theme-westgate` so
+ the compose file mounts this working tree.
+2. `docker compose -f docker-compose.dev.yml up`
+3. Open `http://localhost:4567` (or `HTTP_PORT` from `.env`). Dev admin login:
+ `admin` / `Admin12345!` (defaults; dev-only).
+4. If Westgate is not the active theme yet, switch to it once in
+ ACP → Appearance → Themes; the choice persists in the redis volume.
+5. NodeBB compiles this theme's SCSS and templates at build time, so a browser
+ reload alone is not enough after editing them. Rebuild, then reload:
+ `docker compose -f docker-compose.dev.yml exec nodebb ./nodebb build --config=/opt/config/config.json`
+ (restart the `nodebb` service if changes still don't show).
+6. Full reset (also required after changing `PLUGIN_*` in `.env`):
+ `docker compose -f docker-compose.dev.yml down -v`
+
+Agents with browser tooling (e.g. Playwright) should point it at
+`http://localhost:4567` to load pages, take screenshots, and verify theme
+changes on the live forum.
+
+## Design Context
+
+- `PRODUCT.md`: strategic design context — register, users, purpose,
+ positioning, brand personality, anti-references, design principles,
+ accessibility target (WCAG AA). Read it before design work.
+- `DESIGN.md`: the visual system — palette ("The Velvet Ledger"), typography,
+ elevation, components, and do's/don'ts, with machine-readable tokens in its
+ frontmatter and `.impeccable/design.json`.
## Guidance Map
diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..e855d27
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,181 @@
+---
+name: Westgate
+description: Dark velvet-and-gold NodeBB theme for Shadows Over Westgate
+colors:
+ tarnished-gold: "#c2a35a"
+ old-gilt: "#a8893f"
+ gold-lit: "#e0c878"
+ bruised-plum: "#2a1222"
+ plum-bloom: "#3a1830"
+ dried-blood: "#8e3438"
+ wax-seal: "#a84a4e"
+ crypt-black: "#0f0d12"
+ grave-black: "#09080b"
+ velvet-panel: "#18141d"
+ velvet-panel-deep: "#110f15"
+ velvet-panel-raised: "#201924"
+ bone-text: "#e6e0d6"
+ ash-text: "#b9b2a6"
+ dust-text: "#9a9086"
+ ledger-ink: "#d8c28a"
+ ledger-ink-muted: "#a99d8f"
+typography:
+ display:
+ fontFamily: "Cinzel, Georgia, 'Times New Roman', serif"
+ fontWeight: 600
+ letterSpacing: "0"
+ body:
+ fontFamily: "Jost, system-ui, sans-serif"
+ fontSize: "1rem"
+ fontWeight: 400
+ label:
+ fontFamily: "Cinzel, Georgia, serif"
+ fontSize: "0.86rem"
+ fontWeight: 600
+ letterSpacing: "0.08em"
+ code:
+ fontFamily: "'IBM Plex Mono', ui-monospace, monospace"
+ fontWeight: 400
+rounded:
+ md: "8px"
+components:
+ button-primary:
+ backgroundColor: "#5a4a1a"
+ textColor: "#f0e6d8"
+ button-primary-hover:
+ backgroundColor: "#6a5720"
+ textColor: "#fff4dd"
+ button-ghost:
+ backgroundColor: "#08070a1f"
+ textColor: "#d8cec0"
+ input:
+ backgroundColor: "#120f16f5"
+ textColor: "{colors.bone-text}"
+ card:
+ backgroundColor: "{colors.velvet-panel}"
+ textColor: "{colors.ash-text}"
+ rounded: "{rounded.md}"
+---
+
+# Design System: Westgate
+
+## 1. Overview
+
+**Creative North Star: "The Velvet Ledger"**
+
+The forum is the account book of a vampire court: pages of near-black velvet, ruled with faint gold lines, written in tarnished gilt ink, stamped here and there with a dried-blood seal. Every surface is dark and plush. Gold appears as thin edges, rulings, and small marks of importance — the way gilt survives on an old book long after the leather has gone dark. Nothing is bright. Nothing is flat. Depth comes from fabric and candlelight, not from floating cards.
+
+This system explicitly rejects anything bright, playful, clinical, or flat SaaS-style, and any loud ornament. It must never read as a generic default forum skin. It is a working forum first: text stays readable, focus stays visible, and the atmosphere never taxes reading.
+
+**Key Characteristics:**
+- Near-black plum-tinted surfaces with velvet gradient sheen
+- Tarnished gold carries all borders, links, and affordances
+- Engraved serif headings (Cinzel) over calm geometric body text (Jost)
+- Red reserved for states and small stamped details
+- Ambient, candlelit shadows; hierarchy by panel tone, not lift
+
+## 2. Colors
+
+A tarnish-and-decay palette: gold that has aged, plum that has bruised, black that has settled.
+
+### Primary
+- **Tarnished Gold** (#c2a35a): the voice of the theme. Links, borders (usually at 8–18% alpha), icons, focus rings, selection. It marks what can be touched.
+- **Old Gilt** (#a8893f): darker gold for pressed states and primary-button borders.
+- **Gold Lit** (#e0c878): link hover — the gilt catching candlelight.
+
+### Secondary
+- **Bruised Plum** (#2a1222): the tint inside velvet gradients and inner glows. Almost never a flat fill; it blooms through surfaces.
+- **Plum Bloom** (#3a1830): the brighter bruise, used at hover in the velvet gradients.
+
+### Tertiary
+- **Dried-Blood Red** (#8e3438): danger and warning states, small details. Never a large surface.
+- **Wax Seal** (#a84a4e): stamped accents inside ledger surfaces.
+
+### Neutral
+- **Crypt Black** (#0f0d12): the body background.
+- **Grave Black** (#09080b): the deepest layer (status ring backdrops, page depths).
+- **Velvet Panel** (#18141d), **Velvet Panel Deep** (#110f15), **Velvet Panel Raised** (#201924): the three panel tones that build hierarchy.
+- **Bone Text** (#e6e0d6): headings and emphasized text.
+- **Ash Text** (#b9b2a6): body text.
+- **Dust Text** (#9a9086): muted and secondary text.
+- **Ledger Ink** (#d8c28a) / **Ledger Ink Muted** (#a99d8f): text written on ledger surfaces.
+
+### Named Rules
+**The Gilt Edge Rule.** Gold is edging, not paint. It lives in 1px borders, thin rules, icons, and text. The only gold-filled surface in the theme is the primary button, and even that is a dark #5a4a1a, not bright gold.
+
+**The Sparing Red Rule.** Red is a state and a detail — an error, a seal, a warning. It is never a background, never decoration, never loud.
+
+## 3. Typography
+
+**Display Font:** Cinzel (with Georgia, 'Times New Roman', serif)
+**Body Font:** Jost (with system-ui, sans-serif)
+**Code Font:** IBM Plex Mono (with ui-monospace, monospace)
+
+**Character:** An engraved Roman capital face over a calm geometric sans. Cinzel gives headings the feel of letters cut into stone or stamped in gilt; Jost keeps long forum threads quiet and easy to read. The pairing contrasts on a real axis (carved serif vs geometric sans), so each has a clear job.
+
+### Hierarchy
+- **Display / Headings** (Cinzel 600, letter-spacing 0): all h1–h5, category titles, topic titles. Carries a faint etch: `text-shadow: 0 1px 0 rgba(0,0,0,0.9), 0 0 10px rgba(104,32,76,0.16)`.
+- **Label** (Cinzel 600, ~0.86rem, tracked 0.08–0.16em, often uppercase): topbar items, footer headings, table headers, search labels. The wider tracking is reserved for these small engraved labels.
+- **Body** (Jost 400, 1rem): posts, descriptions, lists. UI metadata (timestamps, stats) also uses Jost with letter-spacing 0.
+- **Code** (IBM Plex Mono 400/500): code blocks and inline code.
+
+### Named Rules
+**The Engraving Rule.** Wide letter-spacing and uppercase belong only to small Cinzel labels. Headings at reading size keep letter-spacing 0. Body text is never tracked, never uppercase.
+
+## 4. Elevation
+
+Shadows here are candlelight, not physics. They set mood — a soft top highlight like sheen on fabric, an inner plum glow, a wide black falloff — and they do not communicate stacking. Hierarchy between surfaces is carried by panel tone (Velvet Panel Deep → Velvet Panel → Velvet Panel Raised) and by border strength (gold at 8% → 14% → 18–22% alpha).
+
+### Shadow Vocabulary
+- **Velvet rest** (`inset 0 1px 0 rgba(255,255,255,0.025), inset 0 0 34px rgba(96,32,68,0.11), 0 10px 26px rgba(0,0,0,0.24)`): the default surface shadow — sheen, bruise, and depth.
+- **Velvet hover** (`inset 0 1px 0 rgba(255,255,255,0.035), inset 0 0 38px rgba(120,36,84,0.14), 0 12px 30px rgba(0,0,0,0.3)`): the same material, slightly warmed and lifted on hover.
+- **Ledger rest** (`inset 0 1px 0 rgba(255,255,255,0.024), inset 0 -1px 0 rgba(0,0,0,0.42), 0 8px 18px rgba(0,0,0,0.2)`): flatter, page-like; for ledger surfaces.
+- **Control depth** (`inset 0 1px 0 rgba(255,244,221,0.04), 0 2px 8px rgba(0,0,0,0.22)`): small ambient depth on buttons.
+
+### Named Rules
+**The Candlelight Rule.** Shadows are atmosphere. If you need to show one thing sits above another, change the panel tone and border alpha; do not invent a bigger shadow.
+
+## 5. Components
+
+Etched and restrained: controls read as engraved metal — thin gilt borders, quiet vertical gradients, nothing shouts.
+
+### Buttons
+- **Shape:** softly rounded (Bootstrap default radius; surfaces use 8px).
+- **Primary:** dark gold plate. Gradient #5a4a1a → #473914, border Old Gilt (#a8893f), text #f0e6d8, with a 1px inner top highlight. Hover warms the gradient to #6a5720 → #53431a and brightens the border to Tarnished Gold.
+- **Secondary / Ghost:** near-transparent dark fill (rgba(8,7,10,0.12)) with a gilt border at 28% alpha and text #d8cec0. Hover adds a faint gold wash (rgba(194,163,90,0.075)) and strengthens the border.
+- **Focus:** 2px outline in the gold focus token (rgba(194,163,90,0.28)).
+
+### Cards / Containers (the velvet surface)
+- **Corner Style:** 8px radius.
+- **Background:** the velvet gradient — a 100° sweep from bruised plum (rgba(42,18,34,0.46)) into near-black — over category rows, topic rows, post containers, and headers.
+- **Border:** 1px Tarnished Gold at 14% alpha; hover raises it to 22%.
+- **Shadow Strategy:** Velvet rest → Velvet hover (see Elevation).
+- **Plain panels** (cards, list-group items) use flat Velvet Panel (#18141d) with a soft gold border at 8% alpha.
+
+### Inputs / Fields
+- **Style:** near-black well (rgba(18,15,22,0.96)), 1px gilt border at 18% alpha, Bone Text. Placeholders are Ash Text at 62% alpha.
+- **Focus:** border rises to gold at 50% alpha plus a 1px gold ring (rgba(194,163,90,0.28)) and a faint light wash. Focus is always visible.
+- **Selects:** custom gold chevron (inline SVG, stroke #c2a35a).
+- **Switches:** dark track with gilt border; the thumb is a Ledger Ink dot.
+
+### Navigation (topbar)
+- Cinzel labels, tracked 0.08em and uppercase, over the dark base. Count bubbles honor the `hidden` class and only show with a real count.
+
+### The Ledger (signature component)
+Ruled, page-like surfaces (notification tables, list ledgers): a flat 100° gradient from dark plum into black, faint gold rulings (rgba(194,163,90,0.055)) with stronger rules (0.16) for structure, headers in Cinzel Ledger Ink Muted, text in Ledger Ink, and Wax Seal (#a84a4e) for stamped accents.
+
+## 6. Do's and Don'ts
+
+### Do:
+- **Do** keep body text at or above 4.5:1 contrast; Ash Text (#b9b2a6) on Crypt Black is the floor. Drop to Dust Text only for genuinely secondary metadata.
+- **Do** carry the theme to every surface — wiki, custom pages, applications, reports, admin-facing lists. One unthemed page breaks the spell.
+- **Do** build hierarchy with the three panel tones and border alpha steps (8% / 14% / 18–22%), per the Candlelight Rule.
+- **Do** keep a visible 2px gold focus outline on everything interactive.
+- **Do** stay a small child theme: inherit from Harmony and override only what Westgate needs.
+
+### Don't:
+- **Don't** make anything "bright, playful, clinical, or flat SaaS-style" (PRODUCT.md). No white surfaces, no saturated blues, no flat borderless cards.
+- **Don't** add "loud ornament" (PRODUCT.md). No filigree backgrounds, no large gold fills, no glow effects beyond the existing plum bloom.
+- **Don't** use red as a surface or decoration — states and small seals only (the Sparing Red Rule).
+- **Don't** track or uppercase body text; that treatment belongs to small Cinzel labels only (the Engraving Rule).
+- **Don't** ship anything that could pass as a generic default forum skin. If a screenshot could be any NodeBB forum, it is not done.
diff --git a/PRODUCT.md b/PRODUCT.md
new file mode 100644
index 0000000..71efc65
--- /dev/null
+++ b/PRODUCT.md
@@ -0,0 +1,40 @@
+# Product
+
+## Register
+
+product
+
+## Platform
+
+web
+
+## Users
+
+Active Shadows Over Westgate players. They already play on the Neverwinter Nights persistent-world server and use the forum day to day: reading announcements, posting in-character and out-of-character threads, and browsing the wiki. The forum is the whole SoW website, so the same theme also greets anyone who lands on any SoW page.
+
+## Product Purpose
+
+This is `nodebb-theme-westgate`, the visual layer for the SoW community forum and website (NodeBB, child theme of Harmony). NodeBB is used for everything: plugins add custom pages and user-interaction flows such as applications and reports, and this theme must cover those surfaces too. It exists so the community's web home looks and feels like the game world it belongs to. Success is immersion plus usability: the forum feels like part of Westgate while staying fast and easy to read.
+
+## Positioning
+
+SoW's visual home. The forum serves the entire website, so every page — boards, wiki, profiles, search, custom pages — carries the same theme with full thematic parity. No screen falls back to a plain, out-of-world look.
+
+## Brand Personality
+
+Black velvet silk, darkness, vampirism, decadence, and decay. Plum and near-black as the dominant base, muted gold for accents and important affordances, red only sparingly as a subtle detail or state color. Depth, restraint, texture, and rich contrast over loud ornament.
+
+## Anti-references
+
+Not bright, playful, clinical, or flat SaaS-style. No loud ornament. Nothing that reads as a generic default forum skin.
+
+## Design Principles
+
+- Thematic parity everywhere: every surface, including edge pages and admin-facing lists, stays in-world. An unthemed page breaks the spell.
+- Atmosphere never taxes reading: the dark palette and texture must not slow down scanning threads or posting. Usability failures are theme bugs.
+- Restraint over ornament: depth comes from texture, contrast, and material (velvet panels, etched gold), not from decoration piled on.
+- Stay a small child theme: override only what Westgate needs; inherit the rest from Harmony rather than rebuilding it.
+
+## Accessibility & Inclusion
+
+WCAG AA: at least 4.5:1 contrast for body text on the dark palette, visible focus states, usable hover and active states, and reduced-motion alternatives for animation. No text hidden purely for visual effect without an accessible alternative.