SurveyStream

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

RoutePage
/Hub
/brand-referenceStrategy document
/logo · /logo-svgLockup + the 600×140 SVG
/colour · /typographyThe two systems
/patternSeamless background tile
/og-card · /og-card-canvasSocial card + the raw canvas
/printStationery sheets
/flow-and-latticeExploration
/tokensEngineering handoff — every machine-readable file
/docs · /docs/*The 11 Markdown specifications, rendered
/portalInteractive 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 glob base points outside src/. The files stay at their canonical repo paths — tokens/README.md belongs 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.json returns 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, a public/ 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.

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 to top: var(--hd-h) — page styles are injected inside <body>, so anything that needs to beat them has to come later in document order.
  • @media print hides 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

#DeliverableRouteSource
01Brand Reference — strategy, audiences, archetypes, dials, logo territories/brand-referencesrc/content/pages/surveystream-brand-guidelines.html
02Logo Lockup — geometric S mark + outlined Archivo Bold wordmark/logosrc/content/pages/logo/ · logo/
03Colour System — tokens, live WCAG validation, print conversions/coloursrc/content/pages/color-system/ · color-system/
04Typography — font evidence, 1.200 scale, numeral rules/typographysrc/content/pages/typography/ · typography/
05Background Pattern — seamless 400×400 tile, seam-tested/patternsrc/content/pages/pattern/ · pattern/
06Open Graph Card — 1200×630/og-cardsrc/content/pages/og/ · og/
07Print Stationery — card, letterhead, invoice, millimetre-exact/printsrc/content/pages/print/ · print/
08Flow & Lattice — first-pass mark studies/flow-and-latticesrc/content/pages/flow-and-lattice.html
09Design Tokens — DTCG, CSS, Tailwind v3 + v4/tokenstokens/
10Brand Portal — interactive style guide/portalsrc/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 in logo/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

  1. Pulse mint #2FE3A2 enters the palette, or the system runs on Stream alone.
  2. 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.
  3. Seven neutrals, not six — light borders cannot clear 3:1 on a tinted canvas with six steps.
  4. 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”; yS and St want manual review.
  • backdrop-filter in 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 #0F172A has no usable Pantone match (nearest 289 C, ΔE 5.8); brand Deepwater #08222E matches 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.