Swasti · mForm V2→V3

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

ConcernChoiceNotes
FrameworkAstro 5.14package.json"astro": "^5.14.0"
StylingTailwind 3.4 via @astrojs/tailwindapplyBaseStyles: false — global.css owns base
MarkdownMDX via @astrojs/mdxMix .astro and .mdx pages freely
Diagrams (optional)rehype-mermaid + client-side mermaid loaderAspire uses; proposals doesn’t
Sitemap (optional)@astrojs/sitemapAspire-only; drop for local viewer
Image render (Aspire-only)playwrightUsed as a build-time tool for screenshot generation
Node@types/node ^22.10 (devDep)Node 20+ recommended
DeployStatic astro builddist/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.jsondev, 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)

ElementSizeWeightTrackingColorNotes
body15px / 1.75400--text-webkit-font-smoothing: antialiased
h126px / 1.3700-0.5px--textdense
h211px / —7002.5px--primaryuppercase, mono-feeling micro-section header
h315px / —600--textinline-section subhead
h413px / —600--texttight
code (inline)12.5px500--primary on --primary-bgcrimson chip
pre12.5px / 1.6--code-fg on --code-bgdark 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 rewrites code.language-mermaid blocks into <div class="mermaid"> — copy verbatim if you want diagrams.
  • Aspire also adds a global #zoom-overlay for click-to-zoom on diagrams/screenshots — copy if useful.
  • 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 transforms translateX(-100%), opens via .open class; main content margin resets to 0 and a .mobile-header bar appears.

Main column

  • .main { margin-left: var(--sidebar-w); padding: 32px 40px; }
  • .content { max-width: 780px; } (matches maxWidth.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.

PatternPrimary classWhere 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-bodySequential 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-bodyGrid 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-stackPer-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-dhwaniMDX page body — gives consistent type rhythm, table styles, lists
Mobile header.mobile-header + .hamburgerSticky top bar shown only <840px
Zoom overlay#zoom-overlay, .zoom-stage, .zoom-image, .zoom-hintClick-to-zoom on diagrams + screenshots

Astro components (small, in src/components/):

  • Sidebar.astro — accepts a sections prop 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/. .astro for interactive/data-driven, .mdx for long-form prose. URL = filename (architecture.mdx/architecture/).
  • Astro frontmatter for .astro pages goes in the --- script block at the top — typed Astro.props.
  • MDX pages: minimal frontmatter (none in Aspire’s example — title is just an <h1> in the content). Layout is wrapped by importing BaseLayout and 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.astro exists but isn’t used in the layout shell — it’s a leftover).
  • Sidebar is grouped into sections with .sidebar-section-title headers. Each link has a single-character mono-font icon badge (ASCII or letter).
  • No auto-generated TOC inside pages — pages structure themselves with h2 markers (the crimson uppercase letterspaced ones).
  • Active-link styling based on Astro.url.pathname matching.
  • 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:

  1. Cream paper background (#FDFCFA) — not pure white. Sidebar is one shade warmer (#F2EDE7).
  2. Crimson + ochre + leaf-green palette — never substitute “professional blues” or generic teals. The Dhwani crimson #8B1A1A is the brand.
  3. h2 as 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.
  4. Generous body line-height (1.75) with 15px Space Grotesk — lets long-form prose breathe.
  5. Mono numbers everywhere stats appear (JetBrains Mono on .stat-value, .score-num, .matrix td.mono, .step-num, .card-num). Numbers are the interface.
  6. Hairline rules between everything1px solid rgba(0,0,0,0.05-0.08). No heavy borders.
  7. 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.
  8. Code blocks are dark terminal-ish (#1E1E1E bg, #E8A435 gold fg) — code feels like a CRT, not a notebook.
  9. Inline code is a tiny crimson chip (primary-bg background, primary text, mono).
  10. Left-bordered tinted callouts (.callout — primary by default, .accent, .green variants) instead of admonition icons. The micro .label inside (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-sub tagline. 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 builddist/ (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) into src/pages/. Wrap content in <BaseLayout title="..." description="...">.
  • Link it from the sidebar: edit src/components/Sidebar.astro’s sections array (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)

~/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)

FileSource pathDestination
src/styles/global.css~/Dhwani/Aspire-Impact/website/src/styles/global.csskb/src/styles/global.css
tailwind.config.cjs~/Dhwani/Aspire-Impact/website/tailwind.config.cjskb/tailwind.config.cjs
tsconfig.json~/Dhwani/Aspire-Impact/website/tsconfig.jsonkb/tsconfig.json

Files to copy and lightly adapt

FileAdaptNotes
astro.config.mjssite: URL, drop sitemap() for local-only sitesKeep tailwind({applyBaseStyles:false}) and mdx()
package.jsonrename, drop playwright and rehype-mermaid if not using diagramsKeep astro/mdx/tailwind deps + script set
BaseLayout.astrochange <title> suffix and the .mobile-header <span> textKeep the entire shell structure
Sidebar.astroreplace the sections data with Swasti-specific navAspire’s hardcoded-list pattern is simpler than proposals’ auto-glob — start with hardcoded

Conventions when writing pages

  • Page title as <h1> inside a .page-header div 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-item with mono numbers in .stat-value. Colored variants by intent: default (crimson) for primary, .accent for emphasis, .green for done/positive, .ink for neutral.
  • Decisions / notes / risks use .callout with a tiny mono .label. Pick variant by tone: default (crimson) = decision, .accent (ochre) = note/heads-up, .green (leaf) = success/done.
  • Sequential lists / phases use .step blocks with .step-num mono badges.
  • Tabular data uses <table class="matrix"> not raw <table>. Use td.mono for 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-banner or .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-overlay block 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.astro component — exists in Aspire’s components/ 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

Last updated 2026-05-04