← RedoubtTHE GUIDE · how it was built
Design showcase · build notes

Selling calm with physics.

Security marketing usually screams. Redoubt whispers: a hero that says “calm is a security posture,” a threat graph that settles instead of explodes, and a terminal that narrates an ordinary, competently-handled shift. It is a Next.js static export — no server, no backend — deployed to GitHub Pages as flat files.

The idea

The buyer of a SOC platform has read a hundred pages of red sirens and skull icons. FUD is the genre default, so the differentiated move is composure. Every design decision follows from that: a deep abyss-blue stage instead of black-and-red, violet for action and amber reserved for alerts, and copy that treats a credential-dump attempt with the tone of a flight log. The two signature pieces make the argument visually — the threat graph shows blast radius before panic, and the ops feed shows eight minutes of a shift where everything simply gets handled.

The stack

As a project site served from /redoubt/, the config pins the base path so every asset resolves under that folder on Pages:

// next.config.mjs
const nextConfig = {
  output: 'export',
  basePath: '/redoubt',
  assetPrefix: '/redoubt/',
  trailingSlash: true,
  images: { unoptimized: true },
};

Signature technique #1 — the force-directed threat graph

Twenty-four nodes (core services, assets, identities, external actors) and thirty-one edges settle under three forces: pairwise repulsion, spring tension along edges, and a soft pull toward centre. This is the actual per-frame step from components/ThreatGraph.tsx:

/* physics: repulsion + edge springs + soft centring */
const rep = W * H * 0.0075;  // repulsion scales with the stage
for (let i = 0; i < nodes.length; i++) {
  for (let j = i + 1; j < nodes.length; j++) {
    const f = rep / d2;                // inverse-square push apart
    a.vx += (dx / d) * f; b.vx -= (dx / d) * f;
  }
}
const rest = Math.min(W, H) * 0.19;  // spring rest length
for (const [i, j] of LINKS) {
  const f = (d - rest) * 0.015;     // Hooke, gently
  a.vx += (dx / d) * f; b.vx -= (dx / d) * f;
}
for (const n of nodes) {
  n.vx += (W / 2 - n.x) * 0.0022;   // soft centring
  n.vx *= 0.84;                     // damping = the calm
  n.x += n.vx;
}

The damping constant is the brand: 0.84 settles the graph in a few seconds instead of letting it jitter forever. Hover detection is a nearest-node search within 26px; the hovered node’s neighbourhood stays at full alpha while everything else drops to 30% — you read blast radius, literally. Alert nodes breathe an amber ring on a time-based phase, so the pulse costs nothing extra per frame.

The loop is disciplined: device-pixel ratio capped at 1.5, throttled to ~30fps, paused by an IntersectionObserver when off-screen. Under prefers-reduced-motion, the simulation runs 360 steps synchronously at mount and paints one static, fully-settled frame — no rAF loop ever starts, and hover still works via on-demand redraws.

Signature technique #2 — the self-typing ops feed

The terminal’s sixteen log lines are server-rendered in full, so the content exists without JavaScript and reads as a complete document. After hydration (and only if motion is allowed), the client clears the buffer and replays it: one setTimeout per tick, three characters at a time, with longer beats after CRT and ACT lines — the pacing of someone actually reading the feed. Severity is a class, not an image: WRN is amber, ACT (containment) is violet, and the loop ends on SLA: green before replaying the shift.

Details that matter

Ship it on GitHub Pages

Static export writes to out/. GitHub Pages serves the repo from /docs, so the build output is copied there with a .nojekyll marker (so Pages doesn’t strip the _next folder):

# build → copy to docs → publish
npm run build
rm -rf docs && cp -r out docs && touch docs/.nojekyll

gh repo create bswxyz/redoubt --public --source . --push
# Settings → Pages → main / docs
# live at https://bswxyz.github.io/redoubt/

Because basePath and assetPrefix are set, every chunk and font resolves under the project subpath, and trailingSlash keeps /guide/ working as a folder.


Redoubt is a design-showcase concept. The incidents, analysts, customers, certifications and prices are fictional; the demo login validates locally and sends nothing. See the repository README for the full demo-vs-real map.

← Back to Redoubt