From c0f9ca207ce6470fbb58d1ae21a8b185f823ac3d Mon Sep 17 00:00:00 2001 From: Ronald Tse Date: Thu, 23 Jul 2026 14:25:16 +0800 Subject: [PATCH 1/3] feat: migrate site to Astro with interactive 3D wireframe logo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the VitePress site with an Astro + Vue + Tailwind v4 build, and introduce a redesigned homepage centered on a real 3D wireframe of the UnitsML mark. Highlights - 3D wireframe logo (Logo3DWireframe.vue) — manual SVG 3D projection of the polygon mark, edges colored by Y position through the 7 official BIPM SI Brochure base-unit colors. Cursor parallax (rotateX/Y) reads zero rotation when the cursor is at logo center, then tilts away. - Strummable strings — each of the 48 wireframe edges is a string tuned to a C major scale across 1.5 octaves (C5 A4 G4 F4 E4 D4 C4). Pluck on click, strum on drag, with three synth voices (piano, bell, electronic) via Web Audio. - The Units Song — three recognizable public-domain melodies (Ode to Joy, Amazing Grace, Twinkle Twinkle Little Star) triggerable by word, button, or console. - Sounds-have-units educational readout — live panel showing each note's frequency (Hz = s-1), period (ms), wavelength (cm), and MIDI number, tied back to the SI base units. Toggled by a music-note emoji. - Dark mode toggle with no-flash bootstrap and localStorage persistence. - Hero dim-symbol collection game (L M T I Θ N J -> Master of Dimensions). - Word-trigger easter eggs: nist, oasis, calconnect, bipm, iec, ocx, wappi, units, si, 2003, hello, plus SI base unit names -> fact cards. - Count-up UnitsDB stat counters (IntersectionObserver + rAF). - Chrome-particle logo click burst + 5-click combo cascade. Content refresh - Frame UnitsML as "since 2003 - 23 strong years and counting" throughout (about, history, who-uses-unitsml, WappiMascot facts). - Front-page gutter fix (max-width + auto margins on home-hero and section). - Replace GitHub text link in nav with a GitHub mark SVG button. Build and infra - astro.config.mjs, src/ tree, src/content collections, src/layouts, src/components/islands/* (Vue 3 islands via client:load and client:idle). - .astro/ build cache and kimi-export-session_*.md added to .gitignore. --- .gitignore | 4 + .vitepress/config.ts | 181 - .vitepress/plugins/asciidoc.ts | 29 - .vitepress/posts.data.ts | 38 - .vitepress/theme/components/BlogByline.vue | 151 - .vitepress/theme/components/BlogIndex.vue | 292 - .vitepress/theme/components/HomePage.vue | 736 - .../theme/components/UnitsDBBrowser.vue | 592 - .../theme/components/UnitsDBEntityDetail.vue | 848 - .vitepress/theme/custom.css | 789 - .vitepress/theme/index.ts | 39 - CLAUDE.md | 1 + astro.config.mjs | 27 + .../plans/2026-07-19-astro-migration.md | 939 + ...26-07-18-unitsml-site-enrichment-design.md | 92 + .../2026-07-19-astro-migration-design.md | 74 + .../specs/2026-07-19-ia-redesign-design.md | 103 + index.md | 5 - package-lock.json | 8497 ++- package.json | 30 +- public/logos/bipm-logo.svg | 1 + public/logos/calconnect-logo.svg | 1 + public/logos/iec-logo.svg | 1 + public/logos/nist-logo.svg | 4 + public/logos/oasis-logo.svg | 11 + public/logos/ocx-logo.pdf | Bin 0 -> 63326 bytes public/logos/ocx-logo.png | Bin 0 -> 5552 bytes public/logos/ocx-logo.svg | 1 + public/ref-docs/UnitsML-CIPM-2022.pdf | Bin 0 -> 1427189 bytes public/ref-docs/UnitsML-NDRs-v1.0.pdf | Bin 0 -> 133038 bytes .../UnitsML-OASIS-TC-first-meeting.pdf | Bin 0 -> 477435 bytes public/ref-docs/UnitsML-OASIS-flyer.pdf | Bin 0 -> 177182 bytes public/ref-docs/UnitsML-SCC20.pdf | Bin 0 -> 386910 bytes public/symbol.svg | 2 +- scripts/generate-unitsdb.ts | 8 +- software/index.md | 40 - specs/guidelines.md | 12 - src/components/Footer.astro | 48 + src/components/Nav.astro | 264 + src/components/islands/CursorTrail.vue | 119 + .../components/islands}/DataModelDiagram.vue | 0 src/components/islands/EasterEggs.vue | 1304 + .../components/islands}/EcosystemDiagram.vue | 5 +- .../components/islands}/FAQAccordion.vue | 0 src/components/islands/HistoryTimeline.vue | 657 + src/components/islands/HomePage.vue | 1686 + src/components/islands/LiquidLogo.vue | 370 + src/components/islands/Logo3DWireframe.vue | 555 + .../components/islands}/NavScrollHandler.vue | 10 +- .../components/islands}/PeopleGrid.vue | 6 + .../components/islands}/TimelineSection.vue | 36 +- .../components/islands}/UnitsDBComposite.vue | 0 .../components/islands}/UnitsDBDiagram.vue | 0 src/components/islands/WappiMascot.vue | 168 + src/content.config.mjs | 41 + src/content/adopters/iec-cdd.md | 59 + src/content/adopters/ocx.md | 64 + .../2022-06-20-scidatacon-units-summit.md | 0 ...2-09-11-calconnect-tc-units-established.md | 0 .../content/blog}/2025-03-15-unitsdb-1.0.md | 0 .../content/blog}/2025-05-21-unitsdb-2.0.md | 0 .../content/blog}/2026-04-28-site-redesign.md | 0 {blog => src/content/blog}/index.md | 0 {learn => src/content/learn}/guide.md | 49 - .../content/learn/how-it-works.mdx | 64 +- .../content/learn}/incorporating-unitsml.md | 0 .../content/learn}/what-is-unitsml.md | 103 +- {learn => src/content/learn}/who-is-it-for.md | 67 +- about.md => src/content/pages/about.mdx | 241 +- src/content/pages/bipm-evaluation.md | 544 + faq.md => src/content/pages/faq.md | 0 .../content/pages/get-started.md | 122 - src/content/pages/history.mdx | 29 + src/content/pages/playground.md | 293 + privacy.md => src/content/pages/privacy.md | 4 + .../content/pages/resources.md | 177 +- schemas.md => src/content/pages/schemas.md | 34 +- .../content/pages/supporters.md | 122 +- tos.md => src/content/pages/tos.md | 4 + src/content/pages/who-uses-unitsml.md | 69 + .../content/software}/unitsdb-ruby.md | 25 - {software => src/content/software}/unitsdb.md | 0 .../content/software}/unitsml-ruby.md | 25 - {.vitepress => src}/data/projects.ts | 16 + src/data/unitsdb/dimensions.json | 4913 ++ src/data/unitsdb/index.json | 55250 ++++++++++++++++ src/data/unitsdb/prefixes.json | 1697 + src/data/unitsdb/quantities.json | 13637 ++++ src/data/unitsdb/scales.json | 132 + src/data/unitsdb/stats.json | 23 + src/data/unitsdb/unit_systems.json | 135 + src/data/unitsdb/units.json | 34713 ++++++++++ src/env.d.ts | 2 + src/layouts/BaseLayout.astro | 52 + src/layouts/ContentLayout.astro | 388 + src/pages/[...slug].astro | 41 + src/pages/about.astro | 35 + src/pages/adopters/[slug].astro | 35 + src/pages/blog/[slug].astro | 36 + src/pages/blog/index.astro | 73 + src/pages/history.astro | 32 + src/pages/index.astro | 10 + src/pages/learn/[slug].astro | 41 + src/pages/learn/how-it-works.astro | 32 + src/pages/search.astro | 28 + src/pages/software/[slug].astro | 35 + src/pages/software/index.astro | 62 + src/pages/unitsdb/[type]/[id].astro | 51 + src/pages/unitsdb/[type]/index.astro | 91 + src/pages/unitsdb/index.astro | 76 + src/styles/global.css | 2191 + src/styles/tailwind.css | 56 + tsconfig.json | 20 +- unitsdb/[type]/[id].md | 7 - unitsdb/[type]/[id].paths.ts | 39 - unitsdb/dimensions.md | 25 - unitsdb/index.md | 25 - unitsdb/prefixes.md | 25 - unitsdb/quantities.md | 25 - unitsdb/scales.md | 25 - unitsdb/systems.md | 25 - unitsdb/units.md | 25 - 122 files changed, 128236 insertions(+), 6800 deletions(-) delete mode 100644 .vitepress/config.ts delete mode 100644 .vitepress/plugins/asciidoc.ts delete mode 100644 .vitepress/posts.data.ts delete mode 100644 .vitepress/theme/components/BlogByline.vue delete mode 100644 .vitepress/theme/components/BlogIndex.vue delete mode 100644 .vitepress/theme/components/HomePage.vue delete mode 100644 .vitepress/theme/components/UnitsDBBrowser.vue delete mode 100644 .vitepress/theme/components/UnitsDBEntityDetail.vue delete mode 100644 .vitepress/theme/custom.css delete mode 100644 .vitepress/theme/index.ts create mode 100644 astro.config.mjs create mode 100644 docs/superpowers/plans/2026-07-19-astro-migration.md create mode 100644 docs/superpowers/specs/2026-07-18-unitsml-site-enrichment-design.md create mode 100644 docs/superpowers/specs/2026-07-19-astro-migration-design.md create mode 100644 docs/superpowers/specs/2026-07-19-ia-redesign-design.md delete mode 100644 index.md create mode 100644 public/logos/bipm-logo.svg create mode 100644 public/logos/calconnect-logo.svg create mode 100644 public/logos/iec-logo.svg create mode 100644 public/logos/nist-logo.svg create mode 100644 public/logos/oasis-logo.svg create mode 100644 public/logos/ocx-logo.pdf create mode 100644 public/logos/ocx-logo.png create mode 100644 public/logos/ocx-logo.svg create mode 100644 public/ref-docs/UnitsML-CIPM-2022.pdf create mode 100644 public/ref-docs/UnitsML-NDRs-v1.0.pdf create mode 100644 public/ref-docs/UnitsML-OASIS-TC-first-meeting.pdf create mode 100644 public/ref-docs/UnitsML-OASIS-flyer.pdf create mode 100644 public/ref-docs/UnitsML-SCC20.pdf delete mode 100644 software/index.md delete mode 100644 specs/guidelines.md create mode 100644 src/components/Footer.astro create mode 100644 src/components/Nav.astro create mode 100644 src/components/islands/CursorTrail.vue rename {.vitepress/theme/components => src/components/islands}/DataModelDiagram.vue (100%) create mode 100644 src/components/islands/EasterEggs.vue rename {.vitepress/theme/components => src/components/islands}/EcosystemDiagram.vue (99%) rename {.vitepress/theme/components => src/components/islands}/FAQAccordion.vue (100%) create mode 100644 src/components/islands/HistoryTimeline.vue create mode 100644 src/components/islands/HomePage.vue create mode 100644 src/components/islands/LiquidLogo.vue create mode 100644 src/components/islands/Logo3DWireframe.vue rename {.vitepress/theme/components => src/components/islands}/NavScrollHandler.vue (93%) rename {.vitepress/theme/components => src/components/islands}/PeopleGrid.vue (84%) rename {.vitepress/theme/components => src/components/islands}/TimelineSection.vue (77%) rename {.vitepress/theme/components => src/components/islands}/UnitsDBComposite.vue (100%) rename {.vitepress/theme/components => src/components/islands}/UnitsDBDiagram.vue (100%) create mode 100644 src/components/islands/WappiMascot.vue create mode 100644 src/content.config.mjs create mode 100644 src/content/adopters/iec-cdd.md create mode 100644 src/content/adopters/ocx.md rename {blog => src/content/blog}/2022-06-20-scidatacon-units-summit.md (100%) rename {blog => src/content/blog}/2022-09-11-calconnect-tc-units-established.md (100%) rename {blog => src/content/blog}/2025-03-15-unitsdb-1.0.md (100%) rename {blog => src/content/blog}/2025-05-21-unitsdb-2.0.md (100%) rename {blog => src/content/blog}/2026-04-28-site-redesign.md (100%) rename {blog => src/content/blog}/index.md (100%) rename {learn => src/content/learn}/guide.md (92%) rename learn/how-it-works.md => src/content/learn/how-it-works.mdx (93%) rename {learn => src/content/learn}/incorporating-unitsml.md (100%) rename {learn => src/content/learn}/what-is-unitsml.md (86%) rename {learn => src/content/learn}/who-is-it-for.md (91%) rename about.md => src/content/pages/about.mdx (60%) create mode 100644 src/content/pages/bipm-evaluation.md rename faq.md => src/content/pages/faq.md (100%) rename get-started.md => src/content/pages/get-started.md (64%) create mode 100644 src/content/pages/history.mdx create mode 100644 src/content/pages/playground.md rename privacy.md => src/content/pages/privacy.md (52%) rename resources.md => src/content/pages/resources.md (60%) rename schemas.md => src/content/pages/schemas.md (81%) rename supporters.md => src/content/pages/supporters.md (71%) rename tos.md => src/content/pages/tos.md (52%) create mode 100644 src/content/pages/who-uses-unitsml.md rename {software => src/content/software}/unitsdb-ruby.md (68%) rename {software => src/content/software}/unitsdb.md (100%) rename {software => src/content/software}/unitsml-ruby.md (66%) rename {.vitepress => src}/data/projects.ts (57%) create mode 100644 src/data/unitsdb/dimensions.json create mode 100644 src/data/unitsdb/index.json create mode 100644 src/data/unitsdb/prefixes.json create mode 100644 src/data/unitsdb/quantities.json create mode 100644 src/data/unitsdb/scales.json create mode 100644 src/data/unitsdb/stats.json create mode 100644 src/data/unitsdb/unit_systems.json create mode 100644 src/data/unitsdb/units.json create mode 100644 src/env.d.ts create mode 100644 src/layouts/BaseLayout.astro create mode 100644 src/layouts/ContentLayout.astro create mode 100644 src/pages/[...slug].astro create mode 100644 src/pages/about.astro create mode 100644 src/pages/adopters/[slug].astro create mode 100644 src/pages/blog/[slug].astro create mode 100644 src/pages/blog/index.astro create mode 100644 src/pages/history.astro create mode 100644 src/pages/index.astro create mode 100644 src/pages/learn/[slug].astro create mode 100644 src/pages/learn/how-it-works.astro create mode 100644 src/pages/search.astro create mode 100644 src/pages/software/[slug].astro create mode 100644 src/pages/software/index.astro create mode 100644 src/pages/unitsdb/[type]/[id].astro create mode 100644 src/pages/unitsdb/[type]/index.astro create mode 100644 src/pages/unitsdb/index.astro create mode 100644 src/styles/global.css create mode 100644 src/styles/tailwind.css delete mode 100644 unitsdb/[type]/[id].md delete mode 100644 unitsdb/[type]/[id].paths.ts delete mode 100644 unitsdb/dimensions.md delete mode 100644 unitsdb/index.md delete mode 100644 unitsdb/prefixes.md delete mode 100644 unitsdb/quantities.md delete mode 100644 unitsdb/scales.md delete mode 100644 unitsdb/systems.md delete mode 100644 unitsdb/units.md diff --git a/.gitignore b/.gitignore index b286963..aa188cb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules/ dist/ .vitepress/dist/ .vitepress/cache/ +.astro/ # Ruby / Jekyll (legacy) _site/ @@ -32,3 +33,6 @@ artifacts/ # Claude Code memory (local only) .claude/ + +# Local AI assistant session exports (not for source control) +kimi-export-session_*.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts deleted file mode 100644 index b60463a..0000000 --- a/.vitepress/config.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { defineConfig } from 'vitepress' -import { asciidocPlugin } from './plugins/asciidoc' -import { readFileSync } from 'node:fs' -import { resolve, dirname } from 'node:path' -import { fileURLToPath } from 'node:url' - -const __dirname = dirname(fileURLToPath(import.meta.url)) -const root = resolve(__dirname, '..') - -const TYPE_FILE: Record = { - units: 'units', quantities: 'quantities', dimensions: 'dimensions', - prefixes: 'prefixes', scales: 'scales', systems: 'unit_systems', -} -const TYPE_LABEL: Record = { - units: 'Unit', quantities: 'Quantity', dimensions: 'Dimension', - prefixes: 'Prefix', scales: 'Scale', systems: 'Unit System', -} - -// Build a lookup: slug → entity name, loaded once at build -const slugToName: Record> = {} -for (const [urlType, fileKey] of Object.entries(TYPE_FILE)) { - const raw = readFileSync(resolve(root, `public/unitsdb/${fileKey}.json`), 'utf-8') - const items: any[] = JSON.parse(raw) - slugToName[urlType] = {} - for (const item of items) { - const slug = item.unitsml_id.replace(/[^a-zA-Z0-9._-]/g, '_') - slugToName[urlType][slug] = item.name - } -} - -export default defineConfig({ - title: 'UnitsML', - description: 'A set of models for unambiguously encoding and identifying scientific units of measure and quantities', - lang: 'en-US', - lastUpdated: true, - srcExclude: ['CLAUDE.md'], - - transformPageData(pageData) { - const params = (pageData as any).params - if (params?.type && params?.id) { - const label = TYPE_LABEL[params.type] || params.type - const name = slugToName[params.type]?.[params.id] || params.id.replace(/_/g, ' ') - pageData.title = `${name} — ${label}` - pageData.description = `${label} ${name} in UnitsDB` - } - }, - - head: [ - ['link', { rel: 'icon', type: 'image/png', href: '/favicon-32x32.png', sizes: '32x32' }], - ['link', { rel: 'icon', type: 'image/svg+xml', href: '/favicon.svg' }], - ['link', { rel: 'shortcut icon', href: '/favicon.ico' }], - ['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }], - ['link', { rel: 'manifest', href: '/site.webmanifest' }], - ['meta', { name: 'theme-color', content: '#2d2c69' }], - ], - - vite: { - plugins: [asciidocPlugin()] - }, - - themeConfig: { - logo: { src: '/symbol.svg', height: 28 }, - siteTitle: 'UnitsML', - - nav: [ - { text: 'UnitsDB', link: '/unitsdb/' }, - { text: 'Schemas', link: '/schemas' }, - { - text: 'Learn', - items: [ - { text: 'What is UnitsML', link: '/learn/what-is-unitsml' }, - { text: 'Who is it for', link: '/learn/who-is-it-for' }, - { text: 'How it works', link: '/learn/how-it-works' }, - { text: 'Incorporating UnitsML', link: '/learn/incorporating-unitsml' }, - { text: 'Guide', link: '/learn/guide' }, - { text: 'Get Started', link: '/get-started' }, - ] - }, - { text: 'Software', link: '/software/' }, - { text: 'Resources', link: '/resources' }, - { text: 'Blog', link: '/blog/' }, - { text: 'About', link: '/about' } - ], - - socialLinks: [ - { icon: 'github', link: 'https://github.com/unitsml' } - ], - - footer: { - message: `UnitsDB · Schemas · Learn · Get Started · Software · GitHub`, - copyright: 'Copyright © 2026 UnitsML Group' - }, - - search: { - provider: 'local' - }, - - sidebar: { - '/learn/': [ - { - text: 'Learn', - items: [ - { text: 'What is UnitsML', link: '/learn/what-is-unitsml' }, - { text: 'Who is it for', link: '/learn/who-is-it-for' }, - { text: 'How it works', link: '/learn/how-it-works' }, - { text: 'Incorporating UnitsML', link: '/learn/incorporating-unitsml' }, - { text: 'Guide', link: '/learn/guide' }, - ] - }, - { - text: 'Resources', - items: [ - { text: 'Get Started', link: '/get-started' }, - { text: 'Schemas', link: '/schemas' }, - { text: 'Reference Materials', link: '/resources' }, - ] - } - ], - '/software/': [ - { - text: 'Software', - items: [ - { text: 'Overview', link: '/software/' }, - { text: 'unitsdb-ruby', link: '/software/unitsdb-ruby' }, - { text: 'unitsml-ruby', link: '/software/unitsml-ruby' }, - ] - } - ], - '/schemas': [ - { - text: 'Schemas', - items: [ - { text: 'Overview', link: '/schemas' }, - { text: 'UnitsML XML Schemas', link: '/schemas#unitsml-xml-schemas' }, - { text: 'UnitsDB YAML Schemas', link: '/schemas#unitsdb-yaml-schemas' }, - { text: 'Schema Browser', link: '/schemas#schema-browser' }, - ] - } - ], - '/get-started': [ - { - text: 'Getting Started', - items: [ - { text: 'Get Started', link: '/get-started' }, - ] - } - ], - '/about': [ - { - text: 'About', - items: [ - { text: 'About UnitsML', link: '/about' }, - { text: 'Supporters', link: '/supporters' }, - { text: 'Privacy Policy', link: '/privacy' }, - { text: 'Terms of Service', link: '/tos' }, - ] - } - ], - '/supporters': [ - { - text: 'About', - items: [ - { text: 'About UnitsML', link: '/about' }, - { text: 'Supporters', link: '/supporters' }, - { text: 'Privacy Policy', link: '/privacy' }, - { text: 'Terms of Service', link: '/tos' }, - ] - } - ], - }, - - outline: { - level: [2, 3] - }, - - editLink: { - pattern: 'https://github.com/unitsml/unitsml.github.io/edit/main/:path', - text: 'Edit this page on GitHub' - } - } -}) diff --git a/.vitepress/plugins/asciidoc.ts b/.vitepress/plugins/asciidoc.ts deleted file mode 100644 index 2ae4dde..0000000 --- a/.vitepress/plugins/asciidoc.ts +++ /dev/null @@ -1,29 +0,0 @@ -import Asciidoctor from '@asciidoctor/core' -import type { Plugin } from 'vite' - -const asciidoctor = Asciidoctor() - -export function asciidocPlugin(): Plugin { - return { - name: 'vite-plugin-asciidoc', - transform(code, id) { - if (!id.endsWith('.md')) return null - if (!code.includes('asciidoc: true')) return null - - const match = code.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/) - if (!match) return null - - const [, frontmatter, body] = match - - const html = asciidoctor.convert(body, { - standalone: false, - attributes: { showtitle: false } - }) - - return { - code: `---\n${frontmatter}\n---\n\n${html}`, - map: null - } - } - } -} diff --git a/.vitepress/posts.data.ts b/.vitepress/posts.data.ts deleted file mode 100644 index 233449b..0000000 --- a/.vitepress/posts.data.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { createContentLoader } from 'vitepress' - -interface Post { - title: string - url: string - date: string - authors: string[] - description: string - lastUpdated: number | undefined - readingTime: number -} - -declare const data: Post[] -export { data } - -export default createContentLoader('blog/*.md', { - transform(raw): Post[] { - return raw - .filter((page) => !page.url.endsWith('/blog/')) - .map((page) => { - const wordCount = page.src ? page.src.split(/\s+/).length : 0 - return { - title: page.frontmatter.title, - url: page.url, - date: page.frontmatter.date, - authors: page.frontmatter.authors || [], - description: page.frontmatter.description || '', - lastUpdated: page.lastUpdated, - readingTime: Math.max(1, Math.round(wordCount / 200)), - } - }) - .sort((a, b) => { - const dateA = new Date(a.date).getTime() - const dateB = new Date(b.date).getTime() - return dateB - dateA - }) - }, -}) diff --git a/.vitepress/theme/components/BlogByline.vue b/.vitepress/theme/components/BlogByline.vue deleted file mode 100644 index b692a97..0000000 --- a/.vitepress/theme/components/BlogByline.vue +++ /dev/null @@ -1,151 +0,0 @@ - - - - - diff --git a/.vitepress/theme/components/BlogIndex.vue b/.vitepress/theme/components/BlogIndex.vue deleted file mode 100644 index c3ce834..0000000 --- a/.vitepress/theme/components/BlogIndex.vue +++ /dev/null @@ -1,292 +0,0 @@ - - - - - diff --git a/.vitepress/theme/components/HomePage.vue b/.vitepress/theme/components/HomePage.vue deleted file mode 100644 index 58a98b4..0000000 --- a/.vitepress/theme/components/HomePage.vue +++ /dev/null @@ -1,736 +0,0 @@ - - - - - diff --git a/.vitepress/theme/components/UnitsDBBrowser.vue b/.vitepress/theme/components/UnitsDBBrowser.vue deleted file mode 100644 index b7d276b..0000000 --- a/.vitepress/theme/components/UnitsDBBrowser.vue +++ /dev/null @@ -1,592 +0,0 @@ - - - - - diff --git a/.vitepress/theme/components/UnitsDBEntityDetail.vue b/.vitepress/theme/components/UnitsDBEntityDetail.vue deleted file mode 100644 index 50587b2..0000000 --- a/.vitepress/theme/components/UnitsDBEntityDetail.vue +++ /dev/null @@ -1,848 +0,0 @@ - - - - - diff --git a/.vitepress/theme/custom.css b/.vitepress/theme/custom.css deleted file mode 100644 index 3355596..0000000 --- a/.vitepress/theme/custom.css +++ /dev/null @@ -1,789 +0,0 @@ -/* ========================================================================== - UnitsML Brand Theme - Navy: #2d2c69 | Teal: #30dfc0 | Blue: #57a0fe - ========================================================================== */ - -:root { - /* UnitsML Brand Colors */ - --unitsml-navy: #2d2c69; - --unitsml-navy-light: #46457a; - --unitsml-navy-dark: #1f1e4a; - --unitsml-teal: #30dfc0; - --unitsml-teal-light: #5eead4; - --unitsml-teal-dark: #14b8a6; - --unitsml-blue: #57a0fe; - - /* VitePress Brand Overrides */ - --vp-c-brand-1: var(--unitsml-navy); - --vp-c-brand-2: var(--unitsml-navy-light); - --vp-c-brand-3: var(--unitsml-navy-dark); - --vp-c-brand-soft: rgba(45, 44, 105, 0.10); - - /* Button */ - --vp-button-brand-border: transparent; - --vp-button-brand-text: #fff; - --vp-button-brand-bg: var(--unitsml-navy); - --vp-button-brand-hover-border: transparent; - --vp-button-brand-hover-text: #fff; - --vp-button-brand-hover-bg: var(--unitsml-navy-light); - --vp-button-brand-active-border: transparent; - --vp-button-brand-active-text: #fff; - --vp-button-brand-active-bg: var(--unitsml-navy-dark); - - /* Nav */ - --vp-nav-logo-height: 32px; -} - -/* Dark Mode */ -.dark { - --unitsml-navy: #6e6dba; - --unitsml-navy-light: #8a8acf; - --unitsml-navy-dark: #2d2c69; - - --vp-c-brand-1: #6e6dba; - --vp-c-brand-2: #8a8acf; - --vp-c-brand-3: #2d2c69; - --vp-c-brand-soft: rgba(110, 109, 186, 0.14); - - --vp-button-brand-bg: #6e6dba; - --vp-button-brand-hover-bg: #8a8acf; -} - -/* ========================================================================== - Typography - ========================================================================== */ - -:root { - --vp-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', - Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', - 'Helvetica Neue', sans-serif; -} - -/* ========================================================================== - Home Hero - ========================================================================== */ - -.home-hero { - padding: 6rem 2rem 4rem; - text-align: center; - position: relative; - overflow: hidden; -} - -.home-hero-logo { - max-width: 260px; - margin: 0 auto 3rem; - position: relative; - filter: drop-shadow(0 4px 12px rgba(45, 44, 105, 0.12)); -} - -.home-hero h1 { - font-size: 3.5rem; - font-weight: 700; - margin-bottom: 1.25rem; - letter-spacing: -0.03em; - line-height: 1.08; - color: var(--vp-c-text-1); - position: relative; -} - -.home-hero h1 .accent { - background: linear-gradient(135deg, var(--unitsml-navy) 0%, var(--unitsml-teal-dark) 50%, var(--unitsml-blue) 100%); - background-size: 200% 200%; - animation: gradient-shift 8s ease infinite; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -@keyframes gradient-shift { - 0%, 100% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } -} - -.home-hero .tagline { - font-size: 1.25rem; - color: var(--vp-c-text-2); - max-width: 640px; - margin: 0 auto 2.5rem; - line-height: 1.7; - position: relative; -} - -.home-hero-actions { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; - position: relative; -} - -/* ========================================================================== - Buttons - ========================================================================== */ - -.btn { - display: inline-flex; - align-items: center; - gap: 0.5rem; - border: 1px solid transparent; - text-align: center; - font-weight: 600; - border-radius: 24px; - padding: 0.625rem 1.5rem; - font-size: 0.9375rem; - text-decoration: none; - transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); - cursor: pointer; - line-height: 1.5; -} - -.btn-brand { - background: var(--unitsml-navy); - color: white !important; - box-shadow: 0 1px 3px rgba(45, 44, 105, 0.2); -} - -.btn-brand:hover { - background: var(--unitsml-navy-light); - box-shadow: 0 6px 20px rgba(45, 44, 105, 0.3); - transform: translateY(-1px); - color: white !important; -} - -.btn-brand svg { - stroke: white; -} - -.btn-teal { - background: var(--unitsml-teal); - color: var(--unitsml-navy-dark); - box-shadow: 0 1px 3px rgba(48, 223, 192, 0.2); -} - -.btn-teal:hover { - background: var(--unitsml-teal-light); - box-shadow: 0 6px 20px rgba(48, 223, 192, 0.3); - transform: translateY(-1px); - color: var(--unitsml-navy-dark); -} - -.btn-outline { - border-color: var(--vp-c-divider); - background: var(--vp-c-bg-soft); - color: var(--vp-c-text-1); -} - -.btn-outline:hover { - border-color: var(--unitsml-navy); - box-shadow: 0 2px 8px rgba(45, 44, 105, 0.1); - transform: translateY(-1px); -} - -/* ========================================================================== - Sections - ========================================================================== */ - -.section { - margin: 6rem 0; -} - -.section-header { - margin-bottom: 2.5rem; -} - -.section-header h2 { - font-size: 2rem; - font-weight: 700; - letter-spacing: -0.025em; - color: var(--vp-c-text-1); - margin-bottom: 0.5rem; -} - -.section-header p { - color: var(--vp-c-text-2); - font-size: 1.0625rem; - line-height: 1.6; -} - -.section-divider { - width: 48px; - height: 3px; - background: linear-gradient(90deg, var(--unitsml-teal), var(--unitsml-blue)); - border-radius: 2px; - margin-top: 1rem; -} - -/* ========================================================================== - Project Cards - ========================================================================== */ - -.projects-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); - gap: 1.5rem; - margin: 2rem 0; -} - -.project-card { - background: var(--vp-c-bg-soft); - border: 1px solid var(--vp-c-divider); - border-radius: 16px; - padding: 2rem; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; -} - -.project-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 3px; - background: linear-gradient(90deg, var(--unitsml-teal), var(--unitsml-blue)); - opacity: 0; - transition: opacity 0.3s ease; -} - -.project-card:hover { - border-color: var(--unitsml-navy); - box-shadow: 0 8px 30px rgba(45, 44, 105, 0.10); - transform: translateY(-2px); -} - -.project-card:hover::before { - opacity: 1; -} - -.project-card.featured { - border-color: transparent; - background: linear-gradient( - 135deg, - var(--vp-c-bg-soft) 0%, - rgba(45, 44, 105, 0.03) 100% - ); - border: 1px solid rgba(45, 44, 105, 0.12); -} - -.project-card .card-icon { - width: 44px; - height: 44px; - border-radius: 12px; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.375rem; - margin-bottom: 1.125rem; - background: linear-gradient(135deg, rgba(45, 44, 105, 0.08), rgba(87, 160, 254, 0.08)); - color: var(--unitsml-navy); -} - -.project-card h3 { - display: flex; - align-items: center; - gap: 0.5rem; - margin-bottom: 0.75rem; - color: var(--vp-c-text-1); - font-size: 1.125rem; - font-weight: 600; -} - -.project-card h3 .version { - font-size: 0.75rem; - font-weight: 500; - color: var(--vp-c-text-3); - background: var(--vp-c-default-soft); - padding: 0.125rem 0.5rem; - border-radius: 4px; -} - -.project-card .description { - color: var(--vp-c-text-2); - font-size: 0.9375rem; - margin-bottom: 1.5rem; - line-height: 1.7; -} - -.project-card .links { - display: flex; - gap: 0.75rem; - flex-wrap: wrap; -} - -.project-card .links a { - font-size: 0.875rem; - font-weight: 500; - padding: 0.375rem 1rem; - border-radius: 8px; - text-decoration: none; - transition: all 0.2s ease; -} - -.project-card .links a.primary { - background: var(--unitsml-navy); - color: white; -} - -.project-card .links a.primary:hover { - background: var(--unitsml-navy-light); -} - -.project-card .links a.secondary { - background: var(--vp-c-default-soft); - color: var(--vp-c-text-1); -} - -.project-card .links a.secondary:hover { - background: var(--vp-c-default-2); -} - -.project-card .links a.teal-link { - color: var(--unitsml-teal-dark); - background: rgba(48, 223, 192, 0.1); -} - -.project-card .links a.teal-link:hover { - background: rgba(48, 223, 192, 0.2); -} - -/* ========================================================================== - Features Grid - ========================================================================== */ - -.features-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 1.5rem; - margin: 2rem 0; -} - -.feature-card { - padding: 2rem; - background: var(--vp-c-bg-soft); - border-radius: 16px; - border: 1px solid var(--vp-c-divider); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -.feature-card:hover { - border-color: var(--unitsml-navy); - box-shadow: 0 8px 30px rgba(45, 44, 105, 0.08); - transform: translateY(-2px); -} - -.feature-card .feature-icon { - width: 48px; - height: 48px; - border-radius: 14px; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.375rem; - margin-bottom: 1.25rem; - background: linear-gradient(135deg, rgba(48, 223, 192, 0.1), rgba(87, 160, 254, 0.1)); -} - -.feature-card h4 { - color: var(--vp-c-text-1); - margin-bottom: 0.5rem; - font-weight: 600; - font-size: 1.0625rem; -} - -.feature-card p { - color: var(--vp-c-text-2); - font-size: 0.9375rem; - line-height: 1.65; -} - -/* ========================================================================== - Schema Link Card - ========================================================================== */ - -.schema-card { - background: var(--unitsml-navy-dark); - border-radius: 20px; - padding: 2.5rem; - color: white; - position: relative; - overflow: hidden; -} - -.schema-card::after { - content: ''; - position: absolute; - top: -40%; - right: -15%; - width: 350px; - height: 350px; - background: radial-gradient(circle, rgba(48, 223, 192, 0.12) 0%, transparent 70%); - pointer-events: none; -} - -.schema-card::before { - content: ''; - position: absolute; - bottom: -30%; - left: -10%; - width: 250px; - height: 250px; - background: radial-gradient(circle, rgba(87, 160, 254, 0.08) 0%, transparent 70%); - pointer-events: none; -} - -.schema-card h3 { - color: white; - font-size: 1.5rem; - margin-bottom: 0.75rem; - font-weight: 700; -} - -.schema-card p { - color: rgba(255, 255, 255, 0.7); - font-size: 1rem; - line-height: 1.65; - margin-bottom: 1.5rem; -} - -.schema-card code { - color: var(--unitsml-teal-light); - background: rgba(255, 255, 255, 0.08); - padding: 0.125rem 0.5rem; - border-radius: 4px; - font-size: 0.875rem; -} - -.schema-card .schema-url { - background: rgba(255, 255, 255, 0.06); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 10px; - padding: 0.75rem 1rem; - display: inline-flex; - align-items: center; - gap: 0.5rem; -} - -/* ========================================================================== - Responsive - ========================================================================== */ - -@media (max-width: 768px) { - .home-hero { - padding: 4rem 1.5rem 3rem; - } - - .home-hero h1 { - font-size: 2.25rem; - } - - .home-hero .tagline { - font-size: 1.0625rem; - } - - .home-hero-logo { - max-width: 180px; - } - - .projects-grid { - grid-template-columns: 1fr; - } - - .section-header h2 { - font-size: 1.5rem; - } - - .schema-card { - padding: 1.5rem; - } -} - -@media (max-width: 640px) { - .home-hero-actions { - flex-direction: column; - align-items: center; - } - - .btn { - width: 100%; - justify-content: center; - } -} - -/* ========================================================================== - Animations & Motion - ========================================================================== */ - -@keyframes pulse-glow { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 0.8; } -} - -@keyframes fade-in-up { - from { opacity: 0; transform: translateY(20px); } - to { opacity: 1; transform: translateY(0); } -} - -@keyframes dash-flow { - to { stroke-dashoffset: -24; } -} - -@keyframes shimmer { - 0% { background-position: -200% 0; } - 100% { background-position: 200% 0; } -} - -@keyframes gentle-spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -/* Smooth scrolling */ -html { - scroll-behavior: smooth; -} - -body { - overflow-x: hidden; -} - -/* Transition for dark mode toggle */ -.vp-doc, .VPSidebar, .VPNav, .VPFooter { - transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; -} - -/* ========================================================================== - VitePress Overrides - ========================================================================== */ - -.vp-doc .custom-block.tip { - border-color: var(--unitsml-teal); -} - -.vp-doc h1 { - letter-spacing: -0.025em; -} - -.vp-doc h2 { - letter-spacing: -0.02em; - border-top: 1px solid var(--vp-c-divider); - padding-top: 2rem; - margin-top: 3rem; -} - -.vp-doc h2:first-of-type { - border-top: none; - padding-top: 0; - margin-top: 2rem; -} - -.VPFooter { - border-top: 1px solid var(--vp-c-divider) !important; -} - -/* ========================================================================== - Nav Logo - ========================================================================== */ - -.VPNavBarTitle .title { - font-size: 1rem; - font-weight: 700; - letter-spacing: -0.02em; -} - -.VPNavBarTitle .VPImage { - transition: transform 0.3s ease; -} - -.VPNavBarTitle:hover .VPImage { - transform: scale(1.05); -} - -/* Nav logo visibility transitions (controlled by NavScrollHandler) */ -/* The hero logo scrolls up naturally; the nav logo slides up from - below into its resting position as the hero scrolls past. */ - -/* Always define transitions so they apply on the first class change */ -.VPNavBarTitle { - transition: opacity 0.5s cubic-bezier(0.4, 0, 0.2, 1), - transform 0.5s cubic-bezier(0.4, 0, 0.2, 1) !important; - will-change: opacity, transform; -} - -/* Disable transitions during initial paint to prevent flash */ -.no-transition .VPNavBarTitle { - transition: none !important; -} - -.home-logo-hidden .VPNavBarTitle { - opacity: 0 !important; - transform: translateY(12px) !important; - pointer-events: none; -} - -.home-logo-visible .VPNavBarTitle { - opacity: 1 !important; - transform: translateY(0) !important; - pointer-events: auto; -} - -/* ========================================================================== - Subtle Page-level Enhancements - ========================================================================== */ - -/* Top-level doc pages: add a faint left accent line */ -.vp-doc > div > h1::before { - content: ''; - display: inline-block; - width: 4px; - height: 1.5em; - background: linear-gradient(180deg, var(--unitsml-teal), var(--unitsml-blue)); - border-radius: 2px; - margin-right: 0.75rem; - vertical-align: middle; -} - -/* ========================================================================== - Section Reveal Animation - ========================================================================== */ - -.section[data-revealed] { - animation: fade-in-up 0.6s ease-out both; -} - -.section[data-reveal-delay="1"] { animation-delay: 0.1s; } -.section[data-reveal-delay="2"] { animation-delay: 0.2s; } -.section[data-reveal-delay="3"] { animation-delay: 0.3s; } - -/* ========================================================================== - Interactive Tooltip - ========================================================================== */ - -.tooltip { - position: relative; -} - -.tooltip::after { - content: attr(data-tooltip); - position: absolute; - bottom: calc(100% + 8px); - left: 50%; - transform: translateX(-50%) scale(0.95); - background: var(--unitsml-navy-dark); - color: white; - padding: 0.5rem 0.75rem; - border-radius: 8px; - font-size: 0.75rem; - line-height: 1.4; - white-space: nowrap; - opacity: 0; - pointer-events: none; - transition: all 0.2s ease; - z-index: 10; - max-width: 240px; - white-space: normal; - text-align: center; - box-shadow: 0 4px 12px rgba(0,0,0,0.15); -} - -.tooltip:hover::after { - opacity: 1; - transform: translateX(-50%) scale(1); -} - -/* ========================================================================== - UnitsDB Full-Width App Page - ========================================================================== */ - -.unitsdb-full .VPSidebar { - display: none !important; -} - -.unitsdb-full .VPContent { - padding-left: 0 !important; -} - -.unitsdb-full .VPDoc .container { - max-width: unset !important; -} - -.unitsdb-full .VPDoc .content-container { - max-width: unset !important; - padding: 0 !important; -} - -.unitsdb-full .VPDoc .aside { - display: none !important; -} - -.unitsdb-full .VPDoc > .content { - padding: 0 !important; -} - -.unitsdb-full .VPDoc { - padding: 0 !important; -} - -.unitsdb-full .vp-doc > div > h1 { - display: none; -} - -/* ========================================================================== - Scroll Reveal - ========================================================================== */ - -.reveal { - opacity: 0; - transform: translateY(20px); - transition: opacity 0.5s ease-out, transform 0.5s ease-out; -} - -.reveal.visible { - opacity: 1; - transform: translateY(0); -} - -/* ========================================================================== - Focus Indicators - ========================================================================== */ - -.btn:focus-visible, -.step-tab:focus-visible, -.tab-btn:focus-visible, -.cta-strip-card:focus-visible { - outline: 2px solid var(--vp-c-brand-1); - outline-offset: 2px; -} - -/* ========================================================================== - Reduced Motion - ========================================================================== */ - -@media (prefers-reduced-motion: reduce) { - .home-hero h1 .accent, - .hero-dim, - .step-tab.active .step-tab-num, - .stat-card, - .feature-card, - .project-card, - .section[data-revealed], - .ticker-track { - animation: none !important; - } - - .btn, - .step-tab, - .cta-strip-card, - .project-card, - .feature-card { - transition: none !important; - } - - .stat-card, - .section[data-revealed], - .reveal { - opacity: 1 !important; - transform: none !important; - } -} diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts deleted file mode 100644 index 373290f..0000000 --- a/.vitepress/theme/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -import DefaultTheme from 'vitepress/theme' -import './custom.css' -import HomePage from './components/HomePage.vue' -import BlogIndex from './components/BlogIndex.vue' -import BlogByline from './components/BlogByline.vue' -import EcosystemDiagram from './components/EcosystemDiagram.vue' -import DataModelDiagram from './components/DataModelDiagram.vue' -import UnitsDBDiagram from './components/UnitsDBDiagram.vue' -import UnitsDBComposite from './components/UnitsDBComposite.vue' -import UnitsDBBrowser from './components/UnitsDBBrowser.vue' -import UnitsDBEntityDetail from './components/UnitsDBEntityDetail.vue' -import TimelineSection from './components/TimelineSection.vue' -import PeopleGrid from './components/PeopleGrid.vue' -import FAQAccordion from './components/FAQAccordion.vue' -import NavScrollHandler from './components/NavScrollHandler.vue' -import { h } from 'vue' - -export default { - extends: DefaultTheme, - Layout() { - return h(DefaultTheme.Layout, null, { - 'layout-top': () => h(NavScrollHandler) - }) - }, - enhanceApp({ app }) { - app.component('HomePage', HomePage) - app.component('BlogIndex', BlogIndex) - app.component('BlogByline', BlogByline) - app.component('EcosystemDiagram', EcosystemDiagram) - app.component('DataModelDiagram', DataModelDiagram) - app.component('UnitsDBDiagram', UnitsDBDiagram) - app.component('UnitsDBComposite', UnitsDBComposite) - app.component('UnitsDBBrowser', UnitsDBBrowser) - app.component('UnitsDBEntityDetail', UnitsDBEntityDetail) - app.component('TimelineSection', TimelineSection) - app.component('PeopleGrid', PeopleGrid) - app.component('FAQAccordion', FAQAccordion) - } -} diff --git a/CLAUDE.md b/CLAUDE.md index 8d5ba76..dc98928 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -53,6 +53,7 @@ npm run preview - `about.md` — Rich about page with ecosystem diagram, governance, timeline, people grid, organizations - `faq.md` — FAQ with accordion component - `privacy.md`, `tos.md` — standalone pages +- `brand.md` — Brand & press assets page (logos in `public/brand/`, brand colors, usage guidelines) - `software/index.md` — Software listing with ecosystem diagram and project cards - `software/schemas.md`, `software/unitsdb.md`, `software/unitsml-ruby.md` — individual software pages - `specs/guidelines.md` — specifications page diff --git a/astro.config.mjs b/astro.config.mjs new file mode 100644 index 0000000..6f661d7 --- /dev/null +++ b/astro.config.mjs @@ -0,0 +1,27 @@ +import { defineConfig } from 'astro/config'; +import vue from '@astrojs/vue'; +import mdx from '@astrojs/mdx'; +import tailwindcss from '@tailwindcss/vite'; + +export default defineConfig({ + integrations: [vue(), mdx()], + site: 'https://unitsml.org', + base: '/', + outDir: './dist', + publicDir: './public', + markdown: { + shikiConfig: { + theme: 'github-dark', + wrap: true + } + }, + legacy: { + collectionsBackwardsCompat: true + }, + vite: { + plugins: [tailwindcss()], + optimizeDeps: { + exclude: ['fsevents'] + } + } +}); diff --git a/docs/superpowers/plans/2026-07-19-astro-migration.md b/docs/superpowers/plans/2026-07-19-astro-migration.md new file mode 100644 index 0000000..b495b85 --- /dev/null +++ b/docs/superpowers/plans/2026-07-19-astro-migration.md @@ -0,0 +1,939 @@ +# Astro Migration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Migrate UnitsML.org from VitePress to Astro 7 with Vite 8 and Vue islands, preserving all content, components, and dynamic UnitsDB functionality. + +**Architecture:** Fresh Astro project in `astro/` directory alongside existing VitePress site. Content collections for markdown, Vue islands for interactive components, Astro static routes for pages, `getStaticPaths()` for UnitsDB dynamic routes. Cut over after full verification. + +**Tech Stack:** Astro 7, Vite 8, Vue 3, TypeScript, MDX (optional), Pagefind (search) + +## Global Constraints +- Astro 7.x, Vite 8.x, Vue 3.x +- All 35+ pages must render correctly +- All 13 Vue components must work as islands +- UnitsDB browser and dynamic routes must be fully functional +- Dark mode, search, and navigation must work +- Build must pass with zero errors +- No VitePress dependencies in final output + +--- + +### Phase 1: Scaffold & Foundation + +#### Task 1: Create Astro project skeleton + +**Files:** +- Create: `astro/package.json` +- Create: `astro/astro.config.mjs` +- Create: `astro/tsconfig.json` +- Create: `astro/src/env.d.ts` + +**Interfaces:** +- Produces: Astro project root with config and TypeScript setup + +- [ ] **Step 1: Create astro/package.json** + +```json +{ + "name": "unitsml-org-astro", + "type": "module", + "version": "1.0.0", + "scripts": { + "dev": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "astro": "^5.0.0", + "@astrojs/vue": "^5.0.0", + "@astrojs/mdx": "^4.0.0", + "vue": "^3.5.0", + "typescript": "^5.6.0" + }, + "devDependencies": { + "@types/node": "^22.0.0" + } +} +``` + +- [ ] **Step 2: Create astro/astro.config.mjs** + +```js +import { defineConfig } from 'astro/config'; +import vue from '@astrojs/vue'; +import mdx from '@astrojs/mdx'; + +export default defineConfig({ + integrations: [vue(), mdx()], + site: 'https://unitsml.org', + base: '/', + outDir: './dist', + publicDir: './public', + markdown: { + shikiConfig: { + theme: 'github-dark', + wrap: true + } + } +}); +``` + +- [ ] **Step 3: Create astro/tsconfig.json** + +```json +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@components/*": ["src/components/*"], + "@layouts/*": ["src/layouts/*"], + "@data/*": ["src/data/*"] + } + } +} +``` + +- [ ] **Step 4: Create astro/src/env.d.ts** + +```ts +/// +/// +``` + +- [ ] **Step 5: Install dependencies** + +```bash +cd astro && npm install +``` + +Expected: No errors. + +- [ ] **Step 6: Commit** + +```bash +git add astro/ +git commit -m "chore: scaffold Astro project" +``` + +--- + +#### Task 2: Migrate global styles and static assets + +**Files:** +- Create: `astro/public/` (copy from root `public/`) +- Create: `astro/src/styles/global.css` +- Modify: `astro/astro.config.mjs` (add global CSS) + +**Interfaces:** +- Consumes: Existing `public/` assets and `.vitepress/theme/custom.css` +- Produces: Global styles and static assets available in Astro + +- [ ] **Step 1: Copy public assets** + +```bash +cp -r public/* astro/public/ +``` + +- [ ] **Step 2: Create astro/src/styles/global.css** + +Copy the entire contents of `.vitepress/theme/custom.css` to `astro/src/styles/global.css`. Add these Astro-specific overrides at the end: + +```css +/* Astro-specific overrides */ +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + font-family: var(--vp-font-family-base); + background: var(--vp-c-bg); + color: var(--vp-c-text-1); +} + +.dark body { + background: var(--vp-c-bg); + color: var(--vp-c-text-1); +} +``` + +- [ ] **Step 3: Update astro/astro.config.mjs to include global CSS** + +```js +export default defineConfig({ + integrations: [vue(), mdx()], + site: 'https://unitsml.org', + base: '/', + outDir: './dist', + publicDir: './public', + markdown: { + shikiConfig: { + theme: 'github-dark', + wrap: true + } + }, + vite: { + css: { + preprocessorOptions: {} + } + } +}); +``` + +- [ ] **Step 4: Commit** + +```bash +git add astro/public astro/src/styles astro/astro.config.mjs +git commit -m "feat: migrate global styles and static assets" +``` + +--- + +#### Task 3: Create BaseLayout with nav and footer + +**Files:** +- Create: `astro/src/layouts/BaseLayout.astro` +- Create: `astro/src/components/Nav.astro` +- Create: `astro/src/components/Footer.astro` + +**Interfaces:** +- Produces: Base layout used by all pages + +- [ ] **Step 1: Create astro/src/layouts/BaseLayout.astro** + +```astro +--- +import Nav from '@components/Nav.astro'; +import Footer from '@components/Footer.astro'; +import '../styles/global.css'; + +interface Props { + title: string; + description?: string; +} + +const { title, description } = Astro.props; +--- + + + + + + + {title} + {description && } + + + + + + +