Documentdocs/README.md
SurveyStream — Brand System
/docs/brand-guidelines is the master specification — strategy,
logo, colour, typography, assets and print in one document, generated from the token files so it
cannot drift from the code. It renders as a page at /docs/brand-guidelines; the file itself is
docs/BRAND-GUIDELINES.md. Start there for the what; the pages are the why.
/portal is the interactive quick reference — click-to-copy swatches with live contrast
badges, a typography playground, and copy-the-raw-SVG asset cards. One self-contained document with
the three webfonts embedded, generated from tokens/design-tokens.json and the real SVGs.
An Astro site. npm install && npm run build emits ./dist — copy that to any web server.
npm install
npm run dev # http://localhost:4330
npm run build # -> dist/
npm run preview # serve the build locally
npm run portal # regenerate the portal document on its own
npm run hub # regenerate the hub
npm run links # crawl the running dev server for broken links
Serving from a sub-path? Set base: '/brand' in astro.config.mjs and every asset URL follows.
For Netlify / Vercel / Cloudflare Pages: build command npm run build, output directory dist.
The dev port is pinned to 4330 with strictPort: true, not Astro’s default 4321. Another
project on this machine sits on 4321, and Astro silently increments to the next free port when its
default is taken — so this site would come up on 4322 while every note said 4321, and you would be
reading a different project’s 404s as bugs in this one. Strict means it now refuses to start rather
than moving, which tells you the truth immediately. npx astro dev stop frees a stuck instance.
Routes
| Route | Page |
|---|---|
/ | Hub |
/brand-reference | Strategy document |
/logo · /logo-svg | Lockup + the 600×140 SVG |
/colour · /typography | The two systems |
/pattern | Seamless background tile |
/og-card · /og-card-canvas | Social card + the raw canvas |
/print | Stationery sheets |
/flow-and-lattice | Exploration |
/tokens | Engineering handoff — every machine-readable file |
/docs · /docs/* | The 11 Markdown specifications, rendered |
/portal | Interactive style guide |
/files/* | Raw file access, one endpoint per deliverable |
Everything is a route
This is the rule the site is built on, and it exists because breaking it is invisible until someone clicks.
Astro’s dev server answers a navigation request from its route table. When
nothing matches it returns its own 404 — it does not fall through to
public/. A file sitting in public/ therefore returns 200 to curl, to
fetch(), and to an <img>, and 404s for every human who clicks a link to it.
Worse, it only misbehaves in dev, so a production smoke test says everything is
fine.
So:
- Markdown renders at
/docs/*via a content collection whose globbasepoints outsidesrc/. The files stay at their canonical repo paths —tokens/README.mdbelongs beside the tokens — and are rendered, not copied. - Raw files are served by
src/pages/files/[...path].ts, a real route, at/files/<repo path>.curl {host}/files/tokens/design-tokens.jsonreturns the same bytes the repo holds. public/holds subresources only — stylesheets, SVGs, images, fonts. Those are fetched as subresources, so falling through is correct for them.- Never put a clickable file in
public/. Beyond the 404, apublic/file with the same name as a route shadows the route entirely — Astro skips it with a build warning that is easy to miss.
npm run links crawls the running dev server and checks every link with
browser navigation headers. A checker that uses default curl headers reports
a clean bill of health on a site whose links are all broken; that is exactly what
happened here.
Every file the system ships is reachable from the header nav. A page gets a
route and a NAV entry in src/components/SiteHeader.astro; a machine-readable
file gets a row in FILE_GROUPS in src/lib/files.ts, which /tokens renders
and /files serves from the same array. Both stat the real file, so a path that
stops existing fails the build instead of shipping a dead link.
Header
Every page carries a sticky site header: the mark and wordmark link home, then nav to each
deliverable with the current page marked via aria-current="page", and the theme toggle on the
right. It is defined once in src/components/SiteHeader.astro and mounted by the layout, so adding
a route means adding one line to the NAV array there.
Details worth knowing if you edit it:
- Header tokens are namespaced
--hd-*and toggle tokens--tg-*. Every page brings its own palette, so the chrome must not inherit from or collide with it. html { scroll-padding-top }clears the header for in-page anchors.- The hub has its own sticky section nav. A style block placed after the
<slot />offsets it totop: var(--hd-h)— page styles are injected inside<body>, so anything that needs to beat them has to come later in document order. @media printhides the header, which matters for/print.
Theme
Every page carries a Light / Dark / Auto toggle, in the header. It writes ss-theme to
localStorage and stamps data-theme on <html>; “Auto” clears the stamp so the page follows
prefers-color-scheme again. A tiny inline script in <head> applies the stored choice before
first paint, so a dark-mode visitor never gets a white flash.
Two pages are deliberately single-theme and hide the toggle, because they are artwork rather than
documents: /print (paper is paper) and /og-card-canvas (a fixed 1200×630 image). They
set fixedTheme on the layout.
The portal is a route (/portal) but bypasses Base.astro — it is a complete document with its
own header, toggle and embedded fonts, so the layout would give it two of each. Its toggle writes a
separate ss-portal-theme key, so it does not fight the site’s ss-theme.
The dark-mode specimens inside /pattern and /flow-and-lattice stay dark in both themes — the
page chrome flips around them. Showing a dark-mode asset on white would misrepresent it.
Deliverables
| # | Deliverable | Route | Source |
|---|---|---|---|
| 01 | Brand Reference — strategy, audiences, archetypes, dials, logo territories | /brand-reference | src/content/pages/surveystream-brand-guidelines.html |
| 02 | Logo Lockup — geometric S mark + outlined Archivo Bold wordmark | /logo | src/content/pages/logo/ · logo/ |
| 03 | Colour System — tokens, live WCAG validation, print conversions | /colour | src/content/pages/color-system/ · color-system/ |
| 04 | Typography — font evidence, 1.200 scale, numeral rules | /typography | src/content/pages/typography/ · typography/ |
| 05 | Background Pattern — seamless 400×400 tile, seam-tested | /pattern | src/content/pages/pattern/ · pattern/ |
| 06 | Open Graph Card — 1200×630 | /og-card | src/content/pages/og/ · og/ |
| 07 | Print Stationery — card, letterhead, invoice, millimetre-exact | /print | src/content/pages/print/ · print/ |
| 08 | Flow & Lattice — first-pass mark studies | /flow-and-lattice | src/content/pages/flow-and-lattice.html |
| 09 | Design Tokens — DTCG, CSS, Tailwind v3 + v4 | /tokens | tokens/ |
| 10 | Brand Portal — interactive style guide | /portal | src/content/brand-portal.html (generated) |
Layout
src/
pages/
index.astro the hub
[page].astro ONE route for all ten authored HTML pages
tokens.astro the machine-readable file index
portal.astro serves the generated portal document whole
docs/index.astro documentation index
docs/[...slug].astro renders the 11 Markdown files
files/[...path].ts raw file access, as a route
layouts/
Base.astro head, header, theme toggle, no-flash script
Doc.astro prose layout + on-this-page rail
components/ SiteHeader.astro, ThemeToggle.astro
lib/
pages.ts the ten HTML pages: route, source, title
files.ts the file manifest /tokens renders and /files serves
docs.ts titles and blurbs for the Markdown docs
content/
hub.html GENERATED by scripts/build-hub.mjs
brand-portal.html GENERATED by scripts/build-portal.mjs
pages/*.html the ten authored page sources
assets/fonts/ the webfonts (source; public/ gets a copy)
content.config.ts the docs collection, globbed from outside src/
styles/theme.css three-state theme scaffolding
scripts/
build-hub.mjs regenerates src/content/hub.html
build-portal.mjs regenerates src/content/brand-portal.html
sync-public.mjs assembles public/ (subresources only)
check-links.mjs crawls with browser navigation headers
rehype-repo-links.mjs rewrites repo-path links in Markdown to routes
BRAND-GUIDELINES.md the master specification (generated), renders at /docs/brand-guidelines
tokens/ design-tokens.json, theme.css, tailwind configs, README
logo/ 16 SVGs + interlock/ variant + metrics + README
color-system/ tokens.json, tokens.css, COLOR-SYSTEM.md
typography/ type-tokens.json, type.css, TYPOGRAPHY.md
pattern/ 5 tiles, pattern.css, README
og/ 1× and 2× PNGs, README
print/ 15 stationery SVGs, PRINT-SPECS.md
public/ DERIVED — gitignored, subresources only
dist/ build output. This is what you deploy.
Page content lives in src/; deliverables live at their repo paths. The ten authored HTML
pages moved into src/content/pages/ because nothing but the routes consumes them. The tokens,
SVGs and Markdown stayed put: tokens/design-tokens.json is the address the handoff quotes, and
burying it in src/ would make it unservable. They are reached through /files and /docs
instead.
The HTML pages are injected rather than converted to .astro for a concrete reason: .astro
parses { as the start of a JS expression, and every one of these files carries large <style>
and <script> blocks full of braces.
Canonical choices
- Palette: the teal-navy system — Deepwater
#08222E, Stream#10BBD1, Signal#5FE3EC, Payout#F2A23C. The slate/mint variants (logo/lockup-600x140*.svg,pattern/tile-slate-*) are alternates, not canonical. - Primary lockup:
logo/lockup-horizontal.svg. The interlock variant inlogo/interlock/is a live option, not a replacement. - Faces: Archivo (display/wordmark), Inter (UI/body), JetBrains Mono (telemetry).
Engineering handoff
brand-portal.html interactive reference — copy hexes and asset SVGs straight from it
tokens/design-tokens.json W3C DTCG — the consolidated export. Consume this.
tokens/theme.css CSS custom properties, all three theme mechanisms
tokens/tailwind.config.js Tailwind v3
tokens/tailwind-v4.css Tailwind v4 @theme
color-system/tokens.json Domain source — carries the contrast validation
color-system/tokens.css Custom properties, all three theme states
typography/type-tokens.json DTCG — off-scale steps flagged $offScale
typography/type.css Scale, numerals, metric-matched fallbacks
pattern/pattern.css Tile data URIs + ready classes
logo/metrics.json Lockup measurements
og/README.md Export commands and meta tags
Both CSS token files handle the three theme states correctly (bare :root,
prefers-color-scheme, and an explicit [data-theme] stamp). Never declare a colour only inside a
media or [data-theme] block.
Open decisions
- Pulse mint
#2FE3A2enters the palette, or the system runs on Stream alone. - Primary or interlock mark. The interlock is stronger at display size; it needs its small-size fallback rule (accent dropped below ~40 px) enforced wherever it is used.
- Seven neutrals, not six — light borders cannot clear 3:1 on a tinted canvas with six steps.
- The taker side’s action colour — share Stream, or give it Pulse outright.
Known gaps
- Pantone matches are computed against published screen approximations. Confirm on a physical Bridge guide under D50 before releasing artwork.
- The premium font recommendation (Klim Söhne suite, GT Walsheim alternate) is unverified — those binaries are paywalled.
- Archivo’s GPOS fires exactly one kern pair on “SurveyStream”;
ySandStwant manual review. backdrop-filterin the OG card does not survive server-side rendering (Satori /@vercel/og). Ship the exported PNG.- The print SVGs are RGB layout masters — SVG has no CMYK. Re-apply colour as CMYK or spot
swatches and export PDF/X-4 before anything reaches a press. See
print/PRINT-SPECS.md§1. - The card front colour needs a decision: the specified
#0F172Ahas no usable Pantone match (nearest 289 C, ΔE 5.8); brand Deepwater#08222Ematches 5395 C at ΔE 1.7. - No motion system, iconography, or component library yet.
Licence
Archivo, Inter, JetBrains Mono and Gabarito are SIL OFL 1.1 — commercial use, web embedding, PDF
embedding and outlining are all permitted. The wordmark SVGs ship as outlines, so no font is
required at render time. Add the four OFL.txt files alongside src/assets/fonts/ before sending
this bundle outside the company.