Dhwani editorial design system (Aspire-style) — reference for Swasti KB
Companion to
01-kickoff-brief.md. We’re standardizing project knowledge-base sites on the editorial design used by~/Dhwani/Aspire-Impact/website/. The proposals viewer at~/Dhwani/proposals/_website/is a near-identical reimplementation. This doc reverse-engineers both so any project (Swasti included) can clone the visual language consistently.Canonical source:
~/Dhwani/Aspire-Impact/website/Reference reuse:~/Dhwani/proposals/_website/
1. Tech stack
| Concern | Choice | Notes |
|---|---|---|
| Framework | Astro 5.14 | package.json → "astro": "^5.14.0" |
| Styling | Tailwind 3.4 via @astrojs/tailwind | applyBaseStyles: false — global.css owns base |
| Markdown | MDX via @astrojs/mdx | Mix .astro and .mdx pages freely |
| Diagrams (optional) | rehype-mermaid + client-side mermaid loader | Aspire uses; proposals doesn’t |
| Sitemap (optional) | @astrojs/sitemap | Aspire-only; drop for local viewer |
| Image render (Aspire-only) | playwright | Used as a build-time tool for screenshot generation |
| Node | @types/node ^22.10 (devDep) | Node 20+ recommended |
| Deploy | Static astro build → dist/ | No CI workflow in either repo today; built locally and (presumably) shipped to GitHub Pages / Netlify / wherever |
package.json (Aspire): ~/Dhwani/Aspire-Impact/website/package.json — dev, build, preview, astro scripts.
package.json (Proposals): ~/Dhwani/proposals/_website/package.json — same scripts, port 4322.
2. Directory structure
Both sites use the same layout:
website/
├── astro.config.mjs # integrations: tailwind, mdx, [sitemap, mermaid]
├── tailwind.config.cjs # extended palette + fonts + spacing tokens
├── tsconfig.json # extends astro/tsconfigs/strict
├── package.json
├── public/ # static assets (favicon.svg in Aspire, empty in proposals)
└── src/
├── styles/
│ └── global.css # ~1100 lines — design tokens + every component class
├── layouts/
│ └── BaseLayout.astro # html shell, <head>, mobile header, <Sidebar />, <slot/>
├── components/
│ ├── Sidebar.astro # left-rail nav (mandatory)
│ ├── Card.astro # generic card wrapper
│ ├── Footer.astro
│ ├── Nav.astro # top nav (Aspire only)
│ └── Stat.astro # stat tile
└── pages/
├── index.astro # landing
├── *.mdx # long-form prose (architecture, formula-engine, etc.)
├── *.astro # interactive / data-driven (audit, benchmarking, decisions...)
└── modules/ # nested route group (Aspire: scoring.mdx)
Aspire pages list (gives you a sense of the editorial scope): architecture.mdx, audit.astro, benchmarking.mdx, decisions.astro, explainer.astro, formula-engine.mdx, glossary.astro, index.astro, modules/scoring.mdx, monday.astro, overview.astro, platform.astro, proposed-architecture.mdx, risks.astro, roadmap.astro, stakeholders.astro, walkthrough.astro, weightage-engine.mdx.
3. Design tokens
CSS custom properties (src/styles/global.css:1-26)
:root {
--bg: #FDFCFA; /* paper / page background */
--card-bg: #FFFFFF;
--sidebar-bg: #F2EDE7;
--sidebar-border: rgba(0,0,0,0.08);
--text: #2C2C2C; /* ink */
--text-mid: #525250;
--text-light: #96968E;
--primary: #8B1A1A; /* Dhwani crimson */
--primary-bg: rgba(139,26,26,0.05);
--primary-light: rgba(139,26,26,0.08);
--accent: #D4842A; /* ochre / gold */
--accent-bg: rgba(212,132,42,0.06);
--accent-light: rgba(212,132,42,0.12);
--green: #1a7a1a; /* leaf */
--green-bg: rgba(34,139,34,0.07);
--rule: rgba(0,0,0,0.05);
--border: rgba(0,0,0,0.08);
--code-bg: #1E1E1E; /* terminal-dark code blocks */
--code-fg: #E8A435; /* code highlight gold */
--sidebar-w: 260px;
}
Tailwind extension (tailwind.config.cjs)
theme: {
extend: {
colors: {
paper: '#FDFCFA', card: '#FFFFFF', sidebar: '#F2EDE7',
rule: 'rgba(0,0,0,0.08)', hairline: 'rgba(0,0,0,0.05)',
surface: { subtle: 'rgba(0,0,0,0.025)', muted: 'rgba(0,0,0,0.04)' },
ink: { DEFAULT: '#2C2C2C', mid: '#525250', light: '#96968E' },
brand: { DEFAULT: '#8B1A1A', bg: 'rgba(139,26,26,0.05)', light: 'rgba(139,26,26,0.08)' },
accent: { DEFAULT: '#D4842A', bg: 'rgba(212,132,42,0.06)', light: 'rgba(212,132,42,0.12)' },
leaf: { DEFAULT: '#1a7a1a', bg: 'rgba(34,139,34,0.07)' },
code: { bg: '#1E1E1E', fg: '#E8A435' },
},
fontFamily: {
sans: ['"Space Grotesk"', 'system-ui', 'sans-serif'],
mono: ['"JetBrains Mono"', 'ui-monospace', 'SFMono-Regular', 'monospace'],
},
maxWidth: { content: '780px' },
spacing: { sidebar: '260px' },
},
}
Spacing / sizing scale
- Page max width:
780px(maxWidth.content) - Sidebar width:
260px(spacing.sidebar, also--sidebar-w) - Mobile breakpoint:
840px(only one — sidebar collapses to drawer)
Shadow / radius vocabulary (from component CSS)
- Card radii:
8px(small),10px(default),14px(hero/scorecard) - Hover shadow on
.card.link:box-shadow: 0 2px 10px rgba(139,26,26,0.06)— crimson-tinted lift - Border lines are mostly hairline
1px solid rgba(0,0,0,0.05–0.08)
4. Typography
Fonts (Google Fonts, preconnected in <head>)
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
- Sans (body / UI): Space Grotesk 400, 500, 600, 700
- Mono (numbers, labels, code): JetBrains Mono 400, 500, 600, 700
- No serif — this is editorial-feeling sans, not a serif body. The “editorial” comes from generous line-height (1.75) + crimson accents + uppercase mono micro-labels, not from a serif.
Body & heading scale (global.css:30-65)
| Element | Size | Weight | Tracking | Color | Notes |
|---|---|---|---|---|---|
body | 15px / 1.75 | 400 | — | --text | -webkit-font-smoothing: antialiased |
h1 | 26px / 1.3 | 700 | -0.5px | --text | dense |
h2 | 11px / — | 700 | 2.5px | --primary | uppercase, mono-feeling micro-section header |
h3 | 15px / — | 600 | — | --text | inline-section subhead |
h4 | 13px / — | 600 | — | --text | tight |
code (inline) | 12.5px | 500 | — | --primary on --primary-bg | crimson chip |
pre | 12.5px / 1.6 | — | — | --code-fg on --code-bg | dark terminal |
The h2 treatment is the signature: small uppercase letterspaced crimson micro-label with a 14px hairline rule before it (section h2::before). It makes long pages scan like a magazine spread.
5. Layout primitives
BaseLayout.astro (the shell)
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet" />
<title>{title} · Project Name</title>
</head>
<body>
<div class="mobile-header">
<button class="hamburger" aria-label="Open menu"
onclick="document.getElementById('sidebar').classList.toggle('open')">☰</button>
<span>Project Name</span>
</div>
<Sidebar />
<main class="main">
<div class="content">
<slot />
</div>
</main>
</body>
</html>
- Aspire’s BaseLayout adds an inline
<script>that loads mermaid from CDN and rewritescode.language-mermaidblocks into<div class="mermaid">— copy verbatim if you want diagrams. - Aspire also adds a global
#zoom-overlayfor click-to-zoom on diagrams/screenshots — copy if useful.
Sidebar (.sidebar, fixed left rail, 260px)
position: fixed; top: 0; left: 0; height: 100vh; width: var(--sidebar-w);- Background
--sidebar-bg(#F2EDE7— warm cream). - Sections:
.sidebar-logo(title + sub),.sidebar-section(.sidebar-section-title+.sidebar-links),.sidebar-footer. - Each link has
.link-icon(a small mono-font badge — ASCII char or single letter) and optional.badge-new. - Active state:
background: var(--primary-bg); color: var(--primary); font-weight: 600. - Mobile (
max-width: 840px): sidebar transformstranslateX(-100%), opens via.openclass; main content margin resets to 0 and a.mobile-headerbar appears.
Main column
.main { margin-left: var(--sidebar-w); padding: 32px 40px; }.content { max-width: 780px; }(matchesmaxWidth.content)
Optional .page-header pattern (used in MDX pages)
<div class="page-header">
<div class="breadcrumb"><a href="/">Home</a> / Platform / Architecture</div>
<h1>Architecture (v1) <span class="tag-label">Diagrams</span></h1>
<p class="page-meta">Reverse-engineered from the 4 Aspire repos + …</p>
</div>
<div class="prose-dhwani">
…content…
</div>
6. Component library
The bulk of the system is CSS-only in global.css (~1100 lines). Astro components are minimal wrappers; the look comes from the CSS classes. Cherry-pick the ones you need.
| Pattern | Primary class | Where it shines |
|---|---|---|
| Hero banner | .hero-banner (with .kicker, .cta-row, .cta.primary, .cta.ghost) | Landing-page top — dark crimson gradient with radial gold glows; h1 em is gold-accented inline emphasis |
| Callout | .callout (+ .accent, .green variants) with .label | ”Note”, “Decision”, “Risk” boxes — left-bordered, tinted bg |
| Stat row | .stat-row > .stat-item (.stat-value mono number, .stat-label, .stat-sub) | Counts / metrics — mono numbers in crimson by default, colored variants .accent, .green, .ink |
| Numbered step | .step > .step-head > .step-num + .step-title, .step-body | Sequential procedures (kickoff phases, A16 jobs, etc.) — circular crimson badge with mono number |
| Generic card | .card (.card.link for hover state), .card-head, .card-num, .card-title, .card-sub, .card-body | Grid of summary cards |
| Scorecard (overall) | .score-overall (with .score-grade, .score-num, .score-total, .score-badge, .score-headline, .gap-row) | Big dark crimson metric block — borrowed for the kickoff status banner |
| Scorecard (per-item) | .score-card (with .sc-head, .sc-title, .sc-score, .sc-bar, .sc-fill, .sc-target) | Per-dimension progress bars — colored fills (.sc-fill.amber, .sc-fill.green) |
| Module description | .module, .module-badge, .module-title, .module-stack | Per-component documentation with stack badges |
| Matrix table | <table class="matrix"> | Dense data tables — uppercase mono headers, hairline rules, mono cells via td.mono |
| Prose container | .prose-dhwani | MDX page body — gives consistent type rhythm, table styles, lists |
| Mobile header | .mobile-header + .hamburger | Sticky top bar shown only <840px |
| Zoom overlay | #zoom-overlay, .zoom-stage, .zoom-image, .zoom-hint | Click-to-zoom on diagrams + screenshots |
Astro components (small, in src/components/):
Sidebar.astro— accepts asectionsprop list with{ title, links: [{ href, label, icon, isNew }] }(Aspire) or auto-derives from content glob (proposals).Card.astro,Footer.astro,Nav.astro,Stat.astro— thin wrappers around the CSS classes. Optional.
7. Content model
- Pages live in
src/pages/..astrofor interactive/data-driven,.mdxfor long-form prose. URL = filename (architecture.mdx→/architecture/). - Astro frontmatter for
.astropages goes in the---script block at the top — typedAstro.props. - MDX pages: minimal frontmatter (none in Aspire’s example — title is just an
<h1>in the content). Layout is wrapped by importingBaseLayoutand using<BaseLayout title="…" description="…">. - Proposals uses a different pattern —
import.meta.glob('../../../*/proposal.md', { eager: true })walks markdown files outside the website folder (sibling project folders), each with a frontmatter shape:
---
client: …
topic: …
date: 2026-04-15
status: evaluating | quoting | submitted | won | lost
deadline: 2026-05-01
contacts: ['Name <email>']
---
That pattern is excellent for the Swasti KB — keep prose markdown next to the work it describes (under kickoff/notes/*.md, etc.) and have the Astro site glob it in.
Routing
- Static routing via filename — no router config.
- Dynamic routes via
[slug].astro+getStaticPaths()(proposals does this). - Nested folders work (Aspire has
pages/modules/scoring.mdx→/modules/scoring/).
8. Navigation pattern
- Left sidebar always. No top nav (Aspire’s
Nav.astroexists but isn’t used in the layout shell — it’s a leftover). - Sidebar is grouped into sections with
.sidebar-section-titleheaders. Each link has a single-character mono-font icon badge (ASCII or letter). - No auto-generated TOC inside pages — pages structure themselves with
h2markers (the crimson uppercase letterspaced ones). - Active-link styling based on
Astro.url.pathnamematching. - Cross-linking: plain
<a href="/some-page/">— no special component. The CSS gives them crimson + underline-on-hover.
9. Editorial feel — what makes it distinctive
When porting to a new project, preserve these or you lose the look:
- Cream paper background (
#FDFCFA) — not pure white. Sidebar is one shade warmer (#F2EDE7). - Crimson + ochre + leaf-green palette — never substitute “professional blues” or generic teals. The Dhwani crimson
#8B1A1Ais the brand. h2as a tiny uppercase mono-spaced crimson micro-label, not a big bold title. This is the signature — anyone copying the system who skips this loses 60% of the look.- Generous body line-height (1.75) with 15px Space Grotesk — lets long-form prose breathe.
- Mono numbers everywhere stats appear (JetBrains Mono on
.stat-value,.score-num,.matrix td.mono,.step-num,.card-num). Numbers are the interface. - Hairline rules between everything —
1px solid rgba(0,0,0,0.05-0.08). No heavy borders. - Dark hero / scorecard surfaces with radial gold + crimson glows for emphasis blocks (
.hero-banner::before/::after,.score-overall::after). Use sparingly — top-of-page or one big metric. - Code blocks are dark terminal-ish (
#1E1E1Ebg,#E8A435gold fg) — code feels like a CRT, not a notebook. - Inline code is a tiny crimson chip (
primary-bgbackground,primarytext, mono). - Left-bordered tinted callouts (
.callout— primary by default,.accent,.greenvariants) instead of admonition icons. The micro.labelinside (mono, uppercase, 1.5px tracked) is the signal.
10. Brand assets
- Favicon:
/favicon.svg(Aspire); none in proposals viewer (acceptable for local-only sites). - Logo: No image logo — the sidebar header is a
<div class="sidebar-title">text logo +.sidebar-subtagline. Project name doubles as logo. - Color usage rule:
--primary(crimson) for emphasis, links, active states, and h2;--accent(ochre) for hero highlights and “live” badges;--green(leaf) for positive/done states. Never invert (e.g., don’t use crimson for “danger” — use it for primary/brand). - Dark mode: No dark-mode support today. The cream-paper aesthetic is light-only by design.
11. Build & deploy
- Local dev:
npm install && npm run dev(Aspire port 4321, proposals port 4322). - Build:
npm run build→dist/(static). - Deploy: No CI workflow committed in either repo today (
/.github/workflows/empty for Aspire). Built locally and presumably uploaded to GitHub Pages / Netlify per project. Open task if you want it automated.
12. Authoring workflow
- Add a page: drop a new
.mdx(long-form) or.astro(interactive) intosrc/pages/. Wrap content in<BaseLayout title="..." description="...">. - Link it from the sidebar: edit
src/components/Sidebar.astro’ssectionsarray (Aspire pattern) or rely on the auto-glob (proposals pattern, if you adopt it). - No CMS, no scripts, no template generator — just files in
src/pages/. The simplicity is the feature. - Long-form prose lives in MDX with
<div class="prose-dhwani">…</div>wrapper for the article body.
Reusable starter — for the Swasti KB (and every project after)
Recommended directory placement
~/Dhwani/swasti-mform-migration/
└── kb/ # the website lives here
├── astro.config.mjs
├── tailwind.config.cjs
├── tsconfig.json
├── package.json
├── public/
│ └── favicon.svg
└── src/
├── styles/global.css # COPIED VERBATIM from Aspire
├── layouts/BaseLayout.astro
├── components/Sidebar.astro
└── pages/
├── index.astro # landing — links to all kickoff notes
├── brief.mdx # mirrors notes/01-kickoff-brief.md
├── pm-design.mdx # mirrors notes/05-pm-design-doc.md
├── json-analysis.mdx
├── conversion-skill.mdx
├── reliance-context.mdx
├── design-system.mdx (this doc)
└── ... etc.
Files to copy verbatim (do not modify)
| File | Source path | Destination |
|---|---|---|
src/styles/global.css | ~/Dhwani/Aspire-Impact/website/src/styles/global.css | kb/src/styles/global.css |
tailwind.config.cjs | ~/Dhwani/Aspire-Impact/website/tailwind.config.cjs | kb/tailwind.config.cjs |
tsconfig.json | ~/Dhwani/Aspire-Impact/website/tsconfig.json | kb/tsconfig.json |
Files to copy and lightly adapt
| File | Adapt | Notes |
|---|---|---|
astro.config.mjs | site: URL, drop sitemap() for local-only sites | Keep tailwind({applyBaseStyles:false}) and mdx() |
package.json | rename, drop playwright and rehype-mermaid if not using diagrams | Keep astro/mdx/tailwind deps + script set |
BaseLayout.astro | change <title> suffix and the .mobile-header <span> text | Keep the entire shell structure |
Sidebar.astro | replace the sections data with Swasti-specific nav | Aspire’s hardcoded-list pattern is simpler than proposals’ auto-glob — start with hardcoded |
Conventions when writing pages
- Page title as
<h1>inside a.page-headerdiv with a<div class="breadcrumb">above it. - Section headers are
<h2>— they will auto-render as the crimson uppercase mini-label thanks to global.css. Don’t override. - Long prose wraps in
<div class="prose-dhwani">for consistent type rhythm. - Stats / counts use
.stat-row > .stat-itemwith mono numbers in.stat-value. Colored variants by intent: default (crimson) for primary,.accentfor emphasis,.greenfor done/positive,.inkfor neutral. - Decisions / notes / risks use
.calloutwith a tiny mono.label. Pick variant by tone: default (crimson) = decision,.accent(ochre) = note/heads-up,.green(leaf) = success/done. - Sequential lists / phases use
.stepblocks with.step-nummono badges. - Tabular data uses
<table class="matrix">not raw<table>. Usetd.monofor numeric cells. - External vs internal links are visually identical — both are crimson + underline-on-hover. No special “external link” indicator (yet).
- One signature emphasis block per page max —
.hero-banneror.score-overall. More than one and they cancel each other out.
Things NOT to copy
- Aspire’s mermaid loader — only copy if your project actually uses mermaid diagrams. Adds a CDN fetch + ~150KB of JS.
zoom-overlayblock in BaseLayout — only copy if the KB will have screenshot/diagram zooming. The Swasti KB will at least want this for the 22 kickoff screenshots, so probably yes.Nav.astrocomponent — exists in Aspire’scomponents/but is unused. Skip it.@astrojs/sitemap— only useful if the site is publicly hosted and you want crawlable URLs. Drop for local viewers.- Aspire-specific page content (architecture.mdx, scoring.mdx, etc.) — these are project-specific, not template.
Bootstrap commands
mkdir -p ~/Dhwani/swasti-mform-migration/kb/src/{styles,layouts,components,pages}
cp ~/Dhwani/Aspire-Impact/website/src/styles/global.css \
~/Dhwani/swasti-mform-migration/kb/src/styles/global.css
cp ~/Dhwani/Aspire-Impact/website/tailwind.config.cjs \
~/Dhwani/swasti-mform-migration/kb/tailwind.config.cjs
cp ~/Dhwani/Aspire-Impact/website/tsconfig.json \
~/Dhwani/swasti-mform-migration/kb/tsconfig.json
# Then author astro.config.mjs, package.json, BaseLayout.astro, Sidebar.astro,
# index.astro by adapting the references above.
cd ~/Dhwani/swasti-mform-migration/kb && npm install && npm run dev
13. References
- Canonical:
~/Dhwani/Aspire-Impact/website/ - Reference impl:
~/Dhwani/proposals/_website/ - Memory:
~/.claude/projects/-Users-abhijitnair-Dhwani/memory/reference_aspire_design.md - Companion docs (Swasti KB content sources):
01-kickoff-brief.md,02-reliance-frappe-context.md,03-mform-json-analysis.md,04-mform-to-frappe-skill.md,04a-validation-source-resolution.md,05-pm-design-doc.md