# 03 — Architecture ## Stack - **Astro** (latest stable) with TypeScript in strict mode. Static output. - **MDX** for full articles, via Astro content collections. - **Plain CSS** with custom properties for tokens. One global stylesheet for tokens and base type; component-scoped styles for the rest. No Tailwind, so the design stays its own. - **Pagefind** for static full-text search (runs after `astro build`). - **D3** (only `d3-force`, `d3-selection`, `d3-zoom`) for the Connections graph, loaded as a client island on that page only. - **sharp** for generating PNG icons and the OG image from the SVGs in `brand/`. - Fonts: Literata and IBM Plex Sans, self-hosted via Fontsource (no Google Fonts requests at runtime). - Hosting: any static host (Cloudflare Pages, Netlify or Vercel). Why Astro: the site is mostly reading content that should load fast on phones and rank well in search. Astro ships zero JS by default and adds interactivity only where needed (search, arcs, graph). ## Folder structure ``` / ├── CLAUDE.md ├── docs/ # These specs ├── brand/ # Source SVGs ├── content/source/ # question-index.md exported from the owner's doc ├── scripts/ │ ├── import-index.mjs # Markdown index → src/data/questions.json │ └── build-icons.mjs # brand SVGs → public/ icons and OG image ├── public/ # Generated icons, robots.txt └── src/ ├── site.config.ts # Site name, tagline, URL, nav ├── data/questions.json # Generated. Do not hand-edit. ├── content/ │ └── articles/ # One MDX file per full article, e.g. 3-4.mdx ├── lib/ │ ├── questions.ts # Typed loaders, lookups, related resolution │ └── graph.ts # Builds nodes/edges for arcs and graph ├── components/ ├── layouts/ ├── styles/ │ ├── tokens.css │ └── base.css └── pages/ ├── index.astro ├── questions/index.astro ├── questions/[slug].astro ├── groups/[group].astro ├── connections.astro ├── about.astro # Explains the status system and method └── 404.astro ``` ## Data model `src/data/questions.json` is generated by `npm run import`. ```ts type Status = "green" | "yellow" | "red"; type Domain = | "Scripture" | "History" | "Archaeology" | "Science" | "Philosophy" | "Textual Criticism" | "Context" | "Theology"; interface Group { id: string; // "3", "3B", "12" slug: string; // "the-canon", "missing-books" title: string; // "The Canon" intro?: string; // Paragraph under the heading, if any order: number; } interface Question { id: string; // "3.4", "3B.10" slug: string; // "3-4-why-isnt-enoch-in-the-bible" groupId: string; // "3" title: string; // Question text, or the book name for 3B rows status: Status; domains: Domain[]; answer: string; // Short answer (markdown allowed: *italics*) related: string[]; // Question ids, e.g. ["3.5", "3.11", "3B.10"] bookGroup?: string; // 3B only: "Apocrypha", "Pseudepigrapha", ... authorNotes?: string;// Private. Never rendered outside dev mode. hasArticle: boolean; // True when src/content/articles/.mdx exists } ``` Related links in the source look like "3B.1–3B.9 Apocrypha books" (ranges) or "3B Missing books" (a whole group). The importer expands ranges into each id and maps a bare group id to the group page. ## Routes | Route | Content | | --- | --- | | `/` | Arc diagram, headline, search, status counts, group list | | `/questions/` | Full filterable list (`?status=green&domain=History`) | | `/questions/[slug]/` | Question page: short answer, article if present, marginalia | | `/groups/[slug]/` | One group's questions with its intro | | `/connections/` | Force-directed graph plus accessible list fallback | | `/about/` | The status system, the domains, the method, who's behind it | Question pages without a full article still render: title, seal, domains, short answer, related questions, and a quiet "Full article coming" line. ## Dev-only owner notes When `import.meta.env.DEV` is true, question pages show `authorNotes` in a clearly styled "Owner notes (dev only)" box. Production builds must not include the notes in HTML, JSON, or the search index. Add a build-time check that fails if any note text appears in `dist/`. ## SEO - Per-page ``, meta description (the short answer), canonical URL, Open Graph tags. - JSON-LD `FAQPage` on question pages (question plus short answer). - `sitemap.xml` via `@astrojs/sitemap`, plus `robots.txt`.