Restores and rewrites content on Home, Mobile SDK, DNS Protect, Halo CPE, For Operators, Financial Services, Why BlackDice, Investors and Contact to match the approved source copy, removing stray dashes site-wide in favour of plain punctuation. Removes an unreachable duplicate Contact page block that left a stray unclosed <div> in the markup. Shrinks and repads the Guardian Mobile SDK explorer widget so it fits without excess scrolling. Also includes in-progress nav, blog seed and demo scenario tweaks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BlackDice Cyber — Website + Studio (React + TypeScript)
The BlackDice marketing site as a Vite + React + TypeScript app, with BlackDice Studio — the CMS — built in at a single admin route, and the BlackDice Angel product demos embedded as real React components.
npm install
npm run dev # site on http://localhost:5173, CMS API on http://localhost:8787
npm run build # production build → dist/
npm start # serve dist/ + the CMS API from one Node process (port 8787)
- Site: http://localhost:5173
- Studio: http://localhost:5173/admin (dev password
blackdiceuntilADMIN_PASSWORDis set — see.env.example)
Documentation
| Doc | Audience | Covers |
|---|---|---|
| docs/ARCHITECTURE.md | Developers | How the system works: content flow, the CMS hook system, routing, the API server, resilience, threat model |
| docs/PROJECT-STRUCTURE.md | Developers | Directory-by-directory reference; "where do I make this change" table |
| docs/CMS-GUIDE.md | Editors (Paul, Mark, Campbell) | How to use Studio — editing pages, publishing articles, demos, SEO |
| docs/DEPLOYMENT.md | Ops | Environment variables, hosting (Node/nginx/IIS/Docker/static), backups, release checklist |
| CHANGELOG.md | Everyone | What changed in the CMS rebuild and why |
Why the old studio kept wiping the site
blackdice-studio.html worked by exporting HTML: saving cloned the live DOM
into a new index.html and rebuilt blog.html from a template. Anything edited in
those files by hand — the contact-form links, SEO tags, routing tweaks — was
overwritten by the next export, because the export was the source of truth.
Here, content and markup are separate:
| Old studio | This project | |
|---|---|---|
| What "save" writes | whole index.html + blog.html (27MB) |
one JSON document (~200KB) |
| Where copy lives | inside the HTML | content/site-content.json |
| Hand-made changes | lost on next export | untouched — they are in the code |
| Images | base64 inside the HTML | files under content/uploads/ |
| Rollback | manual file copies | automatic snapshot per publish |
Publishing can no longer overwrite anything a developer wrote, so Requirement 1 disappears by construction.
How content flows
Three layers, each overriding the one before:
- The build —
src/site/siteMarkup.txt(the page markup) plus defaults insrc/cms/pages.ts(routes, per-page SEO, demo slots). - Factory content —
src/cms/generated/seedContent.json, imported from the studio export. This is what the site shows before anything is published. - Published content —
content/site-content.json, written by/admin.
The site fetches layer 3 at load and merges it over 1–2 (src/cms/store.tsx). If
the API is unreachable, the site still renders layers 1–2 rather than going blank.
What the CMS can change
| Field type | Hook in the markup | Edited in |
|---|---|---|
| Copy (652 fields) | data-cms="c0001" |
Pages tab — click the text on the page |
| Images | data-cms-img="img001" |
Pages tab — click the image |
| Animated stats | data-cms-num="n001" |
Pages tab — value + suffix |
| Articles | — | Articles tab |
| Demo clips | — | Demos tab |
| Titles, meta, form recipient | — | SEO tab |
Hooks are injected by a script, so re-running it after markup edits is safe — existing ids are preserved and only new elements get new ids:
npm run cms:ids
It also writes src/cms/generated/cmsFields.json, the field index the Studio uses
to list and jump to every editable region.
Importing from the old studio
npm run cms:import -- "path/to/blackdice-studio.json"
Pulls in the post library, writes base64 heroes out as real files under
public/content/posts/ (and downscales them — some originals were 8000px/12MB),
and strips Word/Outlook formatting from article bodies. Studio drafts can also be
imported at runtime from Studio → History → Import a file.
Posts authored in this repo rather than in the studio live in
content-seed/additional-posts.json and survive re-imports.
What's inside
| Path | Purpose |
|---|---|
src/main.tsx |
Router: one real route per page, /blog/:slug, /admin, demo routes |
src/site/SiteApp.tsx |
Renders the site markup, applies CMS content, mounts the React islands, boots the ported controller |
src/site/siteMarkup.txt |
Page markup with CMS hooks (imported as a raw string) |
src/site/siteController.js |
Ported js/main.js: page switching, enquiry forms, live feed, count-ups |
src/site/styles.css |
The original stylesheet + newsroom/article/demo styles |
src/site/blog/ |
Newsroom grid and article pages |
src/site/demos/ |
Demo section (Mobile SDK) and clip players (Halo CPE) |
src/cms/ |
Content model, API client, store, SEO helpers, sanitiser |
src/cms/admin/ |
BlackDice Studio — the /admin app |
src/demo/ |
The BlackDice Angel demo (cinematic player + interactive flows + threat demos) |
server/index.mjs |
Site + CMS server: auth, content, uploads, snapshots, leads, sitemap (no dependencies) |
scripts/ |
inject-cms-ids, seed-content, generate-sitemap, resize-images, dev |
Routes
| Route | What it is |
|---|---|
/, /mobile-sdk, /dns-protect, /halo-cpe, /for-operators, /financial-services, /why-blackdice, /news, /investors, /contact, /cookie-policy, /privacy-policy |
The site — real, shareable, crawlable URLs |
/blog/<slug> |
One page per article |
/admin |
BlackDice Studio |
/threat-demos, /threat-demo/:id |
Threat-detection demos (slider + single scenario) |
/demo-video, /demo, /demo?mode=video |
Angel product video and interactive demo player |
/sitemap.xml, /robots.txt |
Generated from live content by the server |
Every route updates document.title, the meta description, canonical and OG tags,
and pushes a HubSpot virtual pageview. Articles also emit BlogPosting JSON-LD;
the home page emits Organization.
Deep links need an SPA fallback on the host. server/index.mjs does it out of the
box; vercel.json, public/_redirects and public/web.config cover Vercel,
Netlify and IIS.
Enquiry forms
Every "Talk to us", "Book a demonstration", "Request a demonstration" and
mailto: link opens the enquiry form. On submit the details are POSTed to
/api/leads (visible in Studio → Enquiries, exportable as CSV) and handed to
the visitor's mail client addressed to settings.formRecipient
(campbell.ferrier@blackdice.ai by default, changeable in the SEO tab).
Demos on the product pages
The Mobile SDK page carries a tabbed demo section — scam call (voice/VOIP), scam SMS, SIM swap, per-device DNS analytics (iOS and Android) and permissions checking. Each tab plays the clip uploaded in Studio → Demos if there is one, and otherwise plays the interactive demo built into this project, shown as the phone mockup with no surrounding frame. Halo CPE has slots for the Retina dashboard and Angel web UI walkthroughs, which appear under their screenshots once clips are uploaded.
CSS isolation
SiteApp, DemoScreen, DemoVideoPage and AdminApp are lazy-loaded as separate
chunks, so the site's global styles.css only loads on site routes (and in the
Studio, which previews the real site). The demo ships a scoped reset
(src/demo/demo.css, applied via .bd-demo-root) that keeps the phone screens
pixel-faithful to the original prototype. Studio styles are all prefixed .bdcms-.
Notes
- Fonts (Ubuntu / Ubuntu Mono) load from Google Fonts via
index.html. - The site markup is rendered with
dangerouslySetInnerHTML; its inline handlers resolve to the globals registered bysiteController.js, and React features are portalled into named mount points inside it. standalone.htmlis the old single-file build, kept for reference only.