Framework and sources of truth
- SvelteKit with TypeScript, served through
adapter-node. Fonts are self-hosted via@fontsource(Inter, Urbanist, Geist). - No Tailwind in application code. Every design token, reset rule, and base type style lives in
src/app.css; component-local styling uses Svelte's auto-scoped<style>blocks. - Two visual skins are selected with a
data-skinattribute —"marketing"for the public site and"app"for the authenticated product.
Routing map
Route groups keep the three surfaces separate while sharing one deployment.
| Segment | URL | Purpose |
|---|---|---|
src/routes/(marketing) | /, /pricing, … | Public site; shared header + footer chrome. |
src/routes/(auth) | /signin | Sign-in; no marketing chrome. |
src/routes/app | /app | Authenticated product, guarded in +layout.server.ts. |
Group folders in parentheses set layout and grouping without adding a URL segment, so (marketing)/pricing still resolves to /pricing.
Tokens and theming
src/app.css is the single source of truth for colour, spacing,
radius, shadow, and typography. Marketing-specific values (section padding, header offsets,
the hero gradient, stat/heading sizes) sit alongside the core palette.
- Reach for a token before a literal:
var(--color-accent),var(--section-gutter-x),var(--header-stack-height). - The header is fixed, so full-bleed pages clear it with
calc(var(--header-stack-height) + …)rather than a magic number. - Switching
data-skinre-points the same semantic tokens, so components don't hard-code skin colours.
Data layer
UI never calls fetch directly. A typed client in $lib/api talks to PUBLIC_API_BASE_URL. In development that points at the local mock; in
production it points at the real backend — a one-variable swap, no code change.
backend/ — FastAPI over file-based SQLite (db.py + mock_data.py)
$lib/api — typed request helpers, shared response types
PUBLIC_API_BASE_URL — dev: http://localhost:8000 · prod: real API
Authentication
- Sign-in works without JavaScript (progressive enhancement) and with it.
- The session is an httpOnly cookie; a CSRF token guards state-changing posts.
- The
app/segment is guarded server-side, so an unauthenticated request never renders the shell — it redirects to/signin.
The legacy quarantine
Two kinds of UI live in this repo. Genuine, clean interface is hand-written against tokens. A few Figma exports are too dense to re-hand-write faithfully — those are isolated instead of omitted.
- The source React component is server-rendered once to static HTML and stored under
src/lib/legacy. - A thin Svelte wrapper injects that snapshot with
{@html …}. - Tailwind is precompiled only for those snapshots (
legacy-utilities.css), with no global preflight, so it can't leak into the hand-written surface.
This keeps pixel fidelity on the hard artboards (the footer illustration, the workspace dashboard) without dragging their complexity into the rest of the codebase. Anything interactive — the scrollspy catalogs, the billing toggle — is rebuilt cleanly in Svelte, since a static snapshot can't carry behaviour.
Marketing section system
Pages in $lib/marketing compose small primitives: a section shell and
header, hero variants, the closing CTA, and testimonials. Catalog pages (use-cases, teams)
share one scrollspy pattern built on IntersectionObserver — the observer sets the
active tab as each panel enters a centred band, and tab clicks scroll their panel into view
behind a short scroll-lock so the two never fight.
Running it locally
# backend (mock API)
cd backend && uvicorn mock_data:app --reload
# frontend
npm install
npm run dev
Type-check and build with npx svelte-check and npm run build; the production server runs via node build.