diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b417a9d..eb2c406 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,7 +24,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' cache: 'npm' - name: Install dependencies diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index c2708cc..56918d6 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '22' cache: 'npm' - name: Install dependencies diff --git a/CLAUDE.md b/CLAUDE.md index c66cc02..6401753 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -33,21 +33,26 @@ source under `src/` (see "Current design system" below) rather than trusting the ## Architecture -**Stack**: Astro 4 (static output, no SSR adapter), TailwindCSS 3, TypeScript (strict, via -`astro/tsconfigs/strict`), MDX for blog content. Content is entirely in Vietnamese -(`` in `BaseLayout.astro`); English blog posts exist but there is no live -language switcher — the navbar's EN/VI control (`Navbar.astro`) only shows a toast saying -translation is planned, it does not route anywhere. +**Stack**: Astro 7 (static output, no SSR adapter), TailwindCSS 4 (via the `@tailwindcss/vite` +plugin, not an Astro integration), TypeScript (strict, via `astro/tsconfigs/strict`), MDX for blog +content. Astro 7 defaults to a new Rust-based Markdown processor ("Sätteri"); this project opts +back into the classic `unified()`/remark pipeline by installing `@astrojs/markdown-remark` +explicitly, since `astro.config.mjs`'s `markdown.remarkPlugins`/`shikiConfig` keys depend on it. +Content is entirely in Vietnamese (`` in `BaseLayout.astro`); English pages exist +under a manually-duplicated `/en/` page tree (not Astro's i18n-routing-generated pages) — the +navbar's EN/VI control routes between the two trees directly. **Import aliases** (`tsconfig.json`): `@/*` → `src/*`, `@components/*`, `@layouts/*`, `@utils/*`, `@styles/*`. -### Content collections (`src/content/config.ts`) +### Content collections (`src/content.config.ts`) -Two collections, both consumed via `getCollection()`: +Two collections defined with the Content Layer API (`loader: glob({...})`), both consumed via +`getCollection()`: - `blog` — MDX files in `src/content/blog/`. Frontmatter: `title`, `description`, `date`, `updated?`, `author` (defaults to "Hieu Nguyen"), `language` (`en`|`vi`, default `en`), - `category`, `tags[]`, `image?`, `draft`. Slug comes from the filename. + `category`, `tags[]`, `image?`, `draft`. The entry's `id` (filename-derived) is used as the + slug — not `.slug`, which no longer exists on Content Layer entries. - `projects` — JSON files in `src/content/projects/`. Schema includes `category` (`Product`|`Research`|`Tutorial`|`Tool`|`Fun`), `tech[]`, `image?`/`images[]`, `links` (`website`/`github`/`blog`), `featured`, `order`, `status` (`open-source`|`private`), @@ -55,6 +60,9 @@ Two collections, both consumed via `getCollection()`: (`src/pages/index.astro`) looks it up via `allProjects.find(p => p.data.flagship)` and renders it as the `CaseStudySpotlight`. +Rendering a blog post uses the Content Layer's `render(entry)` function imported from +`astro:content` (not an `entry.render()` method) — see `src/pages/blog/[...slug].astro`. + ### Page/layout structure - `BaseLayout.astro` — the shared HTML shell: `` head tags, Google Fonts (Archivo), GTM @@ -72,17 +80,22 @@ Two collections, both consumed via `getCollection()`: ### Current design system -The palette, type scale, and component conventions live in `tailwind.config.mjs` and -`src/styles/global.css`, not in the root markdown docs. Key facts a future edit should respect: -- Brutalist/"Modernist" terminal aesthetic: sharp corners everywhere (`borderRadius` is zeroed - out except `full`), heavy 2px `border-ink` borders, monospace (`font-mono`) used for +The palette, type scale, and component conventions live entirely in `src/styles/global.css` (there +is no `tailwind.config.mjs` — Tailwind 4 uses CSS-based config). Key facts a future edit should +respect: +- Brutalist/"Modernist" terminal aesthetic: sharp corners everywhere (`--radius-*` tokens are + zeroed out except `full`), heavy 2px `border-ink` borders, monospace (`font-mono`) used for labels/kickers/nav, `font-heading`/`font-sans` both map to Archivo. -- Color tokens: `bg`, `surface`, `ink` (near-black text), `border`/`border-light`, `accent` - (`#ec3013` red/orange) with a full 100–900 ramp, `accent-2`, and a `neutral` ramp. There is no - dark-mode variant of these tokens (no `dark:` classes exist in `src/`). -- Reusable utility classes are defined in `global.css` `@layer components`: `.btn`/`.btn-primary`/ - `.btn-secondary`, `.card`, `.tag-pill`, `.kicker` (mono accent-colored section labels like - `01 /`), `.blink-cursor` (terminal cursor animation), `.section`, `.container`. +- Color tokens live in the `@theme` block at the top of `global.css`: `bg`, `surface`, `ink` + (near-black text), `border`/`border-light`, `accent` (`#ec3013` red/orange) with a full 100–900 + ramp, `accent-2`, and a `neutral` ramp. There is no dark-mode variant of these tokens (no `dark:` + classes exist in `src/`). +- Reusable utility classes are defined in `global.css` as `@utility` blocks (Tailwind 4's + replacement for `@layer components`): `.btn`/`.btn-primary`/`.btn-secondary`, `.card`, + `.tag-pill`, `.kicker` (mono accent-colored section labels like `01 /`), `.blink-cursor` + (terminal cursor animation), `.section`, `.container`. Any scoped `