git:20261009.4ed6af1 to git:20261012.6342afe

38 added, 473 removed. Audit B to A.

- # AGENTS.md — Bub Website
-
- > Guidelines for AI agents (Copilot, Claude Code, Cursor, etc.) working on the `website/` directory.
-
- ---
-
- ## Project Overview
-
- This is the **Bub marketing & docs site** — a static Astro site combining:
-
- - **Landing pages** (home, 404) with custom components — i18n via `src/i18n/`.
- - **Starlight docs** (`/docs/getting-started/…`, `/zh-cn/docs/getting-started/…`) auto-generated from `src/content/docs/` — i18n via Starlight built-in.
- - **Blog** (`/posts/…`, `/zh-cn/posts/…`) powered by Astro content collections — i18n via `src/i18n/`.
-
- Site URL: `https://bub.build`
-
- ---
-
- ## Tech Stack
-
- | Layer | Tool |
- |---------------|----------------------------------------|
- | Framework | **Astro 7** (static output, SSR landing pages via Cloudflare) |
- | Docs | **@astrojs/starlight** 0.42 |
- | Styling | **Tailwind CSS v4** via `@tailwindcss/vite` + `@astrojs/starlight-tailwind` |
- | Component lib | shadcn/ui conventions (base-vega style) |
- | Animations | `motion` (formerly Framer Motion) |
- | Code blocks | `astro-expressive-code` |
- | Fonts | Outfit Variable (sans), JetBrains Mono Variable (mono) |
- | Icons | **@lucide/astro** — see Icon section below |
- | Type checking | TypeScript strict mode |
-
- ---
-
- ## Package Manager
-
- **pnpm** — always use `pnpm` to install packages and run scripts.
-
- Use the version pinned in `package.json#packageManager`; CI reads the same field.
- `website/.node-version` is the single Node pin for this repository. Use Node
- 24.21.0 for development and deployment; `package.json#engines` and `engineStrict`
- enforce the dependency minimum (Node 22.19+ on the 22.x line, or Node 24+). Older
- Node versions can silently omit optional WASM dependencies before the build fails.
- Keep dependency overrides and build-script permissions in `pnpm-workspace.yaml`.
- Astro 7, `@astrojs/cloudflare` 14, and Starlight 0.42 use Vite 8 and the default
- Sätteri Markdown processor. Upgrade these packages together and do not restore
- the old Vite 7 override. `astro-mermaid` 2.1 supports this processor but requires
- Mermaid 10/11; keep Mermaid on 11 until the integration supports a newer major.
- Register Mermaid before Starlight. Preserve `compressHTML: true` to retain the
- site's HTML-aware spacing between inline elements.
- Keep both `current` and `wasm32` in `supportedArchitectures.cpu`: Starlight's
- HTML processors need Sätteri's WASI package when bundled for Cloudflare, while
- Node prerendering uses the native binding. Keep strict peer checks enabled.
- Keep `session: false` while the site has no server-side user state. The generated
- Cloudflare configuration should contain only `ASSETS`, without a `SESSION` KV binding.
-
- Check the [Astro migration guide](https://docs.astro.build/en/guides/upgrade-to/v7/)
- and [Starlight changelog](https://github.com/withastro/starlight/blob/main/packages/starlight/CHANGELOG.md)
- when updating this stack. Starlight 0.42's mobile menu uses `.sl-menu-button` and
- `sl-sidebar-pane:popover-open`; it requires Chromium 116+, Safari 17+, or Firefox 125+.
-
- ```bash
- pnpm install # install deps
- pnpm dev # dev server
- pnpm build # production build
- pnpm preview # preview production build
- ```
-
- ## Image Handling
-
- - Prefer the standard `Image` component from `astro:assets` for local raster assets used by pages and shared UI.
- - Do not replace local logo or illustration components with raw `<img>` just to work around Cloudflare adapter behavior.
- - The repository intentionally splits image handling in `astro.config.mjs`:
- - `BUB_ASTRO_IMAGE_MODE=dev`: Cloudflare image service, so `make docs` works under Node without `cloudflare:workers` import failures.
- - `BUB_ASTRO_IMAGE_MODE=build`: compile-time optimization with passthrough runtime, so `make docs-test`, `make docs-preview`, and production serve stable built asset URLs.
- - `Makefile` is the source of truth for that mode selection. `process.argv` command detection only exists as a fallback for direct `pnpm dev` / `pnpm build` usage inside `website/`.
- - If you change adapter image settings, re-verify all three paths: `make docs`, `make docs-test`, and `make docs-preview`.
-
- ---
-
- ## Directory Structure
-
- ```
- website/
- ├── astro.config.mjs # Astro + Starlight + Tailwind config (locales, sidebar translations)
- ├── components.json # shadcn/ui settings (base-vega style)
- ├── package.json
- ├── tsconfig.json # strict, path alias @/* → src/*
- ├── public/ # static assets (logos, favicon)
- ├── src/
- │ ├── content.config.ts # Content collections: docs (Starlight), i18n (Starlight), posts (blog)
- │ ├── components/ # shared Astro components
- │ │ ├── ui/ # primitives (Icon, SectionHeading)
- │ │ ├── NavBar.astro
- │ │ ├── Footer.astro
- │ │ ├── Hero.astro
- │ │ ├── Features.astro
- │ │ ├── HookIntro.astro
- │ │ ├── TapeModel.astro
- │ │ ├── Testimonials.astro
- │ │ ├── Contributors.astro
- │ │ ├── PostCard.astro
- │ │ └── ThemeToggle.astro
- │ ├── content/
- │ │ ├── docs/ # [Zone 1] Starlight content collection
- │ │ │ ├── docs/ # EN docs — maps to /docs/… routes
- │ │ │ │ ├── getting-started/
- │ │ │ │ ├── concepts/
- │ │ │ │ ├── guides/
- │ │ │ │ └── extending/
- │ │ │ └── zh-cn/ # Chinese translations
- │ │ │ └── docs/
- │ │ │ ├── getting-started/
- │ │ │ ├── concepts/
- │ │ │ └── …
- │ │ ├── i18n/ # [Zone 1] Starlight UI string overrides — BCP-47 filenames
- │ │ │ └── zh-CN.json # ⚠️ ONLY for Starlight UI — never duplicate into src/i18n/
- │ │ └── posts/ # [Zone 3] Blog posts
- │ │ ├── en/ # English posts
- │ │ └── zh-cn/ # Chinese posts
- │ ├── i18n/ # [Zone 2] Custom page i18n module (NOT for Starlight docs)
- │ │ ├── ui.ts # Flat-key UI strings: nav, footer, 404, posts, site meta
- │ │ ├── utils.ts # getLangFromUrl, useTranslations, getNavProps, etc.
- │ │ └── landing-page.ts # Single source of truth for all landing-page copy (both locales)
- │ ├── layouts/
- │ │ ├── BaseLayout.astro # Shared HTML shell (head, nav, footer, scripts)
- │ │ ├── LandingLayout.astro
- │ │ ├── PostLayout.astro
- │ │ └── PostListLayout.astro
- │ ├── pages/
- │ │ ├── 404.astro # 404 page (custom page, uses Zone 2 i18n)
- │ │ └── [...locale]/ # Dynamic locale routing — generates EN (root) + ZH-CN variants
- │ │ ├── index.astro # Landing page (both locales via getStaticPaths)
- │ │ └── posts/
- │ │ ├── index.astro # Post list (both locales via getStaticPaths)
- │ │ └── [slug].astro # Single post (both locales via getStaticPaths)
- │ └── styles/
- │ └── global.css # Tailwind v4 + Starlight bridge + CSS custom properties
- └── DESIGN.md # Visual design guide
- ```
-
- ---
-
- ## i18n Architecture
-
- > **Design reference**: This architecture follows patterns from the [Starlight docs site](https://github.com/withastro/starlight/tree/main/docs) and the [Astro docs site](https://github.com/withastro/docs). Both use Starlight for documentation and separate systems for non-doc pages. Study those repos when in doubt.
-
- ### Three zones, clear boundaries
-
- The site has **three distinct i18n zones**. Each zone has its own translation mechanism. **Never mix them.**
-
- | Zone | Pages | i18n mechanism | String source |
- |------|-------|---------------|---------------|
- | **1 — Starlight docs** | `/docs/…`, `/zh-cn/docs/…` (all under `src/content/docs/`) | Starlight built-in i18n | `src/content/i18n/{locale}.json` + sidebar `translations` in config |
- | **2 — Custom pages** | Landing (`/`, `/zh-cn/`), 404 | Project's own `src/i18n/` module | `src/i18n/ui.ts` + `src/i18n/landing-page.ts` |
- | **3 — Blog** | `/posts/…`, `/zh-cn/posts/…` | Content collection + project `src/i18n/` | Post markdown in `src/content/posts/{locale}/`, UI strings in `ui.ts` |
-
- ### Zone 1 — Starlight docs i18n
-
- Follows the **exact pattern** from [`withastro/starlight/docs`](https://github.com/withastro/starlight/tree/main/docs).
-
- **Locale configuration** — `astro.config.mjs`:
- - English is the **`root` locale** — content files live under `src/content/docs/docs/` (e.g., `docs/getting-started/run-bub-locally.mdx`).
- - Other locales get **subdirectories**: `src/content/docs/zh-cn/docs/getting-started/run-bub-locally.mdx`.
- - The inner `docs/` directory creates the `/docs/` URL prefix — this nesting namespaces docs routes.
- - This keeps the URL scheme consistent: `/docs/…` for docs, `/posts/…` for blog.
-
- ```js
- // astro.config.mjs
- locales: {
- root: { label: 'English', lang: 'en' }, // ← root = no prefix for EN docs
- 'zh-cn': { label: '简体中文', lang: 'zh-CN' },
- },
- ```
-
- **Starlight UI string overrides** — `src/content/i18n/{locale}.json`:
- - File names use **BCP-47 language tags** (e.g., `zh-CN.json`, not `zh-cn.json`).
- - Override Starlight's built-in UI strings (theme toggle, nav labels, pagination, 404 text, etc.).
- - Extend with custom keys via `i18nSchema({ extend: z.object({...}) })` in `content.config.ts` (see [Starlight docs `content.config.ts`](https://github.com/withastro/starlight/blob/main/docs/src/content.config.ts) and [Astro docs `i18n-schema.ts`](https://github.com/withastro/docs/blob/main/src/content/i18n-schema.ts) for examples).
- - **Do NOT** put these strings in `src/i18n/ui.ts` — that's Zone 2 only.
-
- **Sidebar translations** — inline in `astro.config.mjs`:
- - Uses the `translations` property on each sidebar group/item.
- - Keys are **BCP-47 language tags** (e.g., `'zh-CN'`), NOT URL-slug locale keys.
- - This matches the Starlight docs pattern where sidebar labels carry translation maps inline.
-
- ```js
- sidebar: [{
- label: 'Getting Started',
- translations: { 'zh-CN': '快速开始' },
- items: [{ autogenerate: { directory: 'docs/getting-started' } }],
- }]
- ```
-
- Starlight 0.39+ requires autogenerated entries inside `items`. Keep the group
- label and its translations on the parent; set `autogenerate.collapsed` explicitly
- if generated subgroups should start collapsed.
-
- **Content schema** — `src/content.config.ts`:
- - Uses `docsLoader()` + `docsSchema()` from `@astrojs/starlight/loaders` and `@astrojs/starlight/schema`.
- - Uses `i18nLoader()` + `i18nSchema()` for the i18n collection.
- - Import Zod from `astro/zod`, using `z.url()` for URL fields.
- - Extend the i18n schema with a Zod object if custom Starlight component overrides need translated keys.
- - Extend the docs schema if docs need custom frontmatter fields.
-
- ### Zone 2 — Custom page i18n (`src/i18n/`)
-
- For pages **outside Starlight** (landing page, 404). Starlight has no control over these pages.
-
- **`[...locale]` rest-param routing** — All custom pages live under `src/pages/[...locale]/`. A single page file generates both locales via `getStaticPaths()`:
-
- ```ts
- // In any page under src/pages/[...locale]/
- export function getStaticPaths() {
- return Object.keys(languages).map((lang) => ({
- params: { locale: lang === defaultLang ? undefined : lang },
- props: { locale: lang as Locale },
- }));
- }
- // → generates / (en, root)
- // → generates /zh-cn/ (zh-cn)
- ```
-
- **Never duplicate a page file per locale** — use this pattern instead. The 404 page is the only exception (lives at `src/pages/404.astro` root because Astro requires it there).
-
- | File | Purpose |
- |--------------------|---------|
- | `ui.ts` | **Flat-key** UI string dictionary for shared UI: site meta, nav, footer, 404, post list, language switcher. English is source of truth; other locales override selectively. |
- | `utils.ts` | `getLangFromUrl()`, `useTranslations()`, `useTranslatedPath()`, `getNavProps()`, `getAlternateLocaleHref()` helpers. |
- | `landing-page.ts` | **Single source of truth for all landing-page copy** — both locales. Structured data (feature arrays with icons/colors, testimonials, hook stages, hero text, CTAs). |
-
- > **Convention** (from Astro docs pattern): Landing-page components (Hero, Features, HookIntro, TapeModel, Testimonials) are **pure presentation** — they receive all text/content via props from `landing-page.ts`. They have no hardcoded text defaults. Non-landing UI strings (nav, footer, 404, posts) go in `ui.ts` and are accessed via `t()`.
-
- ### Zone 3 — Blog i18n
-
- Blog posts are a **content collection** (`src/content/posts/{locale}/`). The collection schema includes a `locale` field to identify the language. Blog UI strings (page titles, back-links, etc.) live in `src/i18n/ui.ts` (Zone 2), not in Starlight's i18n.
-
- ### Boundary rules
-
- | ✅ Do | ❌ Don't |
- |-------|----------|
- | Put Starlight UI overrides in `src/content/i18n/` | Duplicate Starlight keys into `src/i18n/ui.ts` |
- | Put nav/footer/404/post-list strings in `src/i18n/ui.ts` | Put custom page strings in `src/content/i18n/` |
- | Put landing-page structured copy in `src/i18n/landing-page.ts` | Put landing text in `ui.ts` or Starlight i18n |
- | Use `[...locale]` rest-param pages for custom pages | Duplicate page files per locale (e.g., `zh-cn/posts/`) |
- | Use `translations` on sidebar items in `astro.config.mjs` | Create separate sidebar translation files for 2 locales |
- | Use BCP-47 tags (`zh-CN`) in Starlight config/i18n files | Use URL slugs (`zh-cn`) in Starlight i18n JSON filenames |
- | Use URL slugs (`zh-cn`) in directory paths and page routes | Use BCP-47 tags in directory/route paths |
-
- ### Adding a UI string (non-landing pages)
-
- 1. Add the English string to `ui.ts` under the `en` object.
- 2. Add the translation under the target locale.
- 3. Use `const t = useTranslations(locale); t('your.key')` in any `.astro` file.
-
- ### Adding landing-page content
-
- 1. Add the English content to `landing-page.ts` under the `en` section.
- 2. Add the translation under the target locale section.
- 3. Pass as props from the page to the component via `getLandingPageCopy(locale)`.
-
- ### Adding a Starlight UI string override
-
- 1. Add the translated string to `src/content/i18n/zh-CN.json` (or the target locale file).
- 2. If the key is custom (not a built-in Starlight key), define it in the Zod extension inside `content.config.ts` via `i18nSchema({ extend: z.object({ 'your.key': z.string().optional() }) })`.
- 3. Reference: [Starlight i18n docs](https://starlight.astro.build/guides/i18n/).
-
- ### Adding a new locale
-
- 1. **Starlight zone**: Add the locale to `astro.config.mjs` `locales`. Create `src/content/docs/<locale>/docs/` with translated docs. Create `src/content/i18n/<BCP-47>.json` for UI overrides.
- 2. **Custom page zone**: Add the locale key to `languages` in `ui.ts`. Add translations in `ui.ts` and `landing-page.ts`. The `[...locale]` pages auto-generate routes via `getStaticPaths()`.
- 3. **Blog zone**: Create `src/content/posts/<locale>/` with translated posts.
-
- ### URL scheme
-
- - English (default): **no prefix** — `/`, `/posts/`, `/docs/getting-started/`, etc.
- - Chinese: `/zh-cn/`, `/zh-cn/posts/`, `/zh-cn/docs/getting-started/`, etc.
- - Starlight docs have a `/docs/` prefix via the `docs/` content subdirectory.
- - Custom pages (landing, blog) use `[...locale]` rest-param routing — one page file generates both `/` and `/zh-cn/` variants.
-
- ### Locale key conventions
-
- | Context | Key format | Example |
- |---------|-----------|---------|
- | URL paths & directory names | lowercase slug | `zh-cn` |
- | `astro.config.mjs` locale keys | lowercase slug | `'zh-cn': { ... }` |
- | `astro.config.mjs` sidebar `translations` keys | BCP-47 | `'zh-CN': '快速开始'` |
- | `src/content/i18n/` filenames | BCP-47 | `zh-CN.json` |
- | HTML `lang` attribute | BCP-47 | `zh-CN` |
- | `src/i18n/ui.ts` locale keys | lowercase slug | `'zh-cn': { ... }` |
-
- ### Adding a new page
-
- **Custom page** (landing/blog style):
- 1. Create `src/pages/[...locale]/<path>.astro` with `getStaticPaths()` generating all locales.
- 2. Import and wrap with the appropriate layout (BaseLayout, PostLayout, etc.).
- 3. Extract text into `ui.ts` (Zone 2) with appropriate flat keys.
- 4. Run `pnpm build` to verify.
-
- **Starlight doc page**:
- 1. Create the English file in `src/content/docs/docs/<section>/<slug>.md`.
- 2. Create the Chinese translation in `src/content/docs/zh-cn/docs/<section>/<slug>.md`.
- 3. Sidebar item is auto-generated if the parent group uses `autogenerate`. Otherwise add to `sidebar` in `astro.config.mjs` with `translations`.
- 4. Run `pnpm build` to verify.
-
- ### Adding a new component
-
- 1. Create in `src/components/`.
- 2. **No hardcoded text defaults** — components are pure presentation. Accept all text/content via props.
- 3. Let the caller pass content via `getLandingPageCopy()` or `t()` from the page level.
- 4. Add `data-reveal` if the component should animate on scroll.
-
- ### Common mistakes
-
- | Mistake | Fix |
- |---------|-----|
- | Writing `<!doctype html>` in a page | Use BaseLayout |
- | Hardcoding nav/footer props in a page | Use `getNavProps()` |
- | Adding nested objects to `ui.ts` | Use flat `'section.key'` format |
- | Using non-Lucide icon libraries | Use `@lucide/astro` via `Icon.astro` |
- | Adding `dark:text-*` for themed colors | Use semantic tokens (`text-foreground`) that auto-switch |
- | Duplicating scroll-reveal `<script>` | BaseLayout includes it |
- | Putting landing text in `ui.ts` | Use `landing-page.ts` — `ui.ts` is for shared UI only |
- | Hardcoding English in component defaults | Components are pure presentation; all text from i18n |
- | Duplicating a page file per locale | Use `[...locale]` rest-param routing with `getStaticPaths()` |
- | Putting custom page strings in `src/content/i18n/` | Zone 1 only. Custom pages use `src/i18n/` (Zone 2) |
- | Putting Starlight overrides in `src/i18n/ui.ts` | Zone 2 only. Starlight uses `src/content/i18n/` (Zone 1) |
- | Naming i18n JSON with URL slugs (`zh-cn.json`) | Use BCP-47 (`zh-CN.json`) for Starlight i18n files |
- | Using BCP-47 in directory paths (`docs/zh-CN/`) | Use lowercase slugs (`docs/zh-cn/`) for directories |
- | Using `'zh-cn'` in sidebar `translations` keys | Use BCP-47 (`'zh-CN'`) |
- | Adding hue/chroma to Starlight color overrides | Site is monochrome — keep oklch with 0 chroma |
- | Adding new `--sl-color-*` overrides without checking the existing unlayered block | Edit the existing `:root { --sl-color-* }` / `.dark, [data-theme="dark"] { --sl-color-* }` blocks — they override the bridge for correct monochrome contrast |
- | Importing `@import "tailwindcss"` in `global.css` | Use `tailwindcss/theme.css` + `tailwindcss/utilities.css` in proper layers — full import brings Preflight that breaks Starlight/EC |
-
- ---
-
- ## Icons — @lucide/astro
-
- All icons come from **Lucide** via `@lucide/astro`. The project wraps them in `src/components/ui/Icon.astro` which handles camelCase → PascalCase name mapping.
-
- ```astro
- <Icon name="arrowUpRight" size={15} />
- <Icon name="github" size={14} /> <!-- special-cased brand icon -->
- ```
-
- - Use **camelCase** icon names (e.g., `arrowUpRight`, `radioTower`, `fileSearch`).
- - Browse icons at https://lucide.dev/icons.
- - The GitHub brand icon is manually defined inside `Icon.astro` (not a Lucide built-in).
-
- ---
-
- ## Theme System — CSS Custom Properties
-
- The design system uses **oklch** color tokens defined in `src/styles/global.css`.
-
- ### Light / Dark
-
- - Light tokens in `:root { }`.
- - Dark tokens in `.dark, [data-theme="dark"] { }` — both selectors are needed to cover landing pages (`.dark` class) and Starlight docs (`[data-theme]` attribute). `ThemeToggle.astro` sets both.
- - Theme toggle logic lives in `ThemeToggle.astro` (in `NavBar.astro`), with an inline init script in `BaseLayout.astro` `<head>` to prevent FOUC.
- - Stored in `localStorage` under key `bub-theme`.
-
- ### Core tokens (selection)
-
- | Token | Usage |
- |----------------------|------------------------------------|
- | `--background` | Page background |
- | `--foreground` | Primary text |
- | `--primary` | CTA buttons, active states |
- | `--primary-foreground` | Text on primary backgrounds |
- | `--secondary` | Hover backgrounds, cards |
- | `--muted-foreground` | Secondary text, meta info |
- | `--border` | All borders |
- | `--card` / `--card-foreground` | Card surfaces |
- | `--chart-1` … `--chart-5` | Accent colors for charts/tags |
-
- ### Fonts
-
- | CSS var | Font | Usage |
- |-----------------|--------------------------|----------------------|
- | `--font-sans` | Outfit Variable | Body, headings |
- | `--font-mono` | JetBrains Mono Variable | Code, badges, labels |
-
- ### Starlight + Tailwind v4 integration
+ # Website
- Starlight ships with a blue accent (hue 224/234) and blue-tinted grays. The main site is **monochrome** (all oklch colors have `0` chroma). The bridge is `@astrojs/starlight-tailwind` — it reads `--color-accent-*` and `--color-gray-*` from the `@theme` block and generates `--sl-color-*` variables in `@layer utilities`. However, the bridge's mapping (e.g. `--sl-color-accent` ← `--color-accent-600`) does not produce the right contrast for a monochrome theme, so **manual unlayered `--sl-*` overrides are still required** for precise control. Unlayered CSS always beats `@layer utilities`.
+ Astro builds this site. Starlight serves the docs. The landing page, the blog, and the 404 page are site pages. The package manager is pnpm.
- **Reference:** [Starlight CSS + Tailwind guide](https://starlight.astro.build/guides/css-and-tailwind/)
+ In `website/`, run `pnpm install`, `pnpm dev`, `pnpm build`, or `pnpm preview`. From the repository root, `make docs`, `make docs-test`, and `make docs-preview` call these scripts.
- **How `global.css` is structured (follow this order):**
+ ## Files
- ```
- 1. Font imports (@import "@fontsource-variable/…")
- 2. Cascade layer ordering: @layer base, starlight, theme, components, utilities;
- 3. Starlight bridge: @import '@astrojs/starlight-tailwind';
- 4. Tailwind layers: @import 'tailwindcss/theme.css' layer(theme);
- @import 'tailwindcss/utilities.css' layer(utilities);
- 5. @theme inline { … } — fonts, Starlight color scales, site design tokens, radius
- 6. :root { … } — raw light tokens (unlayered)
- 7. .dark, [data-theme="dark"] { … } — raw dark tokens (unlayered)
- 8. :root { --sl-font/color overrides } — unlayered to beat bridge @layer utilities
- 9. .dark, [data-theme="dark"] { --sl-color-* overrides }
- 10. @layer base { … } — Tailwind preflight + site base resets (lowest priority)
- ```
+ English URLs have no prefix. Chinese URLs use the prefix `/zh-cn/`.
- **Why this order matters:**
+ Starlight uses a root locale for English. A root locale requires `lang`. Do not add an `en/` directory for the docs.
- | Layer | Contains | Priority |
- |---|---|---|
- | `@layer base` | Site resets (`* { border-border }`, body bg/text, etc.) | Lowest |
- | `@layer starlight` | Starlight + expressive-code styles | Overrides `base` |
- | `@layer theme` | Tailwind theme variables | Overrides `starlight` |
- | `@layer utilities` | Tailwind utilities | Overrides `theme` |
- | Unlayered CSS | Raw tokens, `--sl-font-*` / `--sl-color-*` overrides, `@media` queries | Highest |
+ The docs use the `/docs/` subpath. Put English docs in `src/content/docs/docs/`. Put Chinese docs in `src/content/docs/zh-cn/docs/`. Use the same path under each locale. Starlight serves the default locale when a translation is missing.
- **Key rules:**
+ | Content | Path |
+ |---|---|
+ | Blog posts | `src/content/posts/en/` and `src/content/posts/zh-cn/` |
+ | Landing page and blog routes | `src/pages/[...locale]/` |
+ | 404 page | `src/pages/404.astro` |
+ | Starlight UI strings | `src/content/i18n/zh-CN.json` |
+ | Nav, footer, 404, posts, site meta | `src/i18n/ui.ts` |
+ | Landing sections | `src/data/landing-page.ts` |
- - **NEVER use `@import "tailwindcss"`** — it brings in the full Preflight reset that conflicts with Starlight and astro-expressive-code. Only import `tailwindcss/theme.css` and `tailwindcss/utilities.css` in their proper layers.
- - **Define Starlight colors via `@theme` scales** — `--color-accent-50` through `--color-accent-950` and `--color-gray-50` through `--color-gray-950`. The bridge reads these and generates `--sl-color-*` in `@layer utilities`.
- - **Override `--sl-*` colors and fonts manually (unlayered)** — the bridge's auto-mapped values don't produce the right contrast for the monochrome theme. Unlayered `:root` / `.dark, [data-theme="dark"]` blocks with explicit `--sl-color-*` values win over the bridge's `@layer utilities` output.
- - **Import `tailwindcss/preflight.css` in `@layer base`** — restores box-sizing, link resets, and other base styles that the split Tailwind import omits. Because `base` is the lowest layer, EC and Starlight styles still override it.
- - **The `@layer base` `*` reset is safe** — because EC styles live in `@layer starlight.components` (higher priority), they always win.
+ Site pages take their prefix from `src/i18n/utils.ts`. Do not put landing text in `ui.ts`. `ui.ts` uses flat keys (`section.key`). A missing key uses the English text.
- **Starlight color scales (in `@theme`):**
+ `Astro.locals.t` reads Starlight UI strings. `Astro.locals.starlightRoute` throws on a site page. On site pages, call `useTranslations()`.
- Both scales use pure neutral oklch values (0 chroma) to match the site's monochrome identity:
+ The landing page sets `prerender` to `false`. The blog routes are static. Starlight docs stay pre-rendered. Keep `disable404Route: true`.
- | Scale | Example values | Starlight usage |
- |---|---|---|
- | `--color-accent-50` … `--color-accent-950` | oklch(0.985 0 0) → oklch(0.145 0 0) | Links, highlights, active nav items |
- | `--color-gray-50` … `--color-gray-950` | oklch(0.985 0 0) → oklch(0.145 0 0) | Backgrounds, text, borders |
+ A new locale needs all of these items:
- > **Do NOT** add hue or chroma to these scales — the entire site identity is neutral/monochrome. Aside callout colors (orange, green, blue, purple, red) are intentionally left at Starlight defaults for semantic clarity.
+ - a Starlight `locales` entry with a BCP-47 `lang`
+ - a `src/content/docs/<slug>/docs/` tree with the same paths as English
+ - a BCP-47 file in `src/content/i18n/`
+ - a `languages` entry and its strings in `ui.ts`
+ - a record in `landing-page.ts`
+ - a `src/content/posts/<slug>/` directory
- **How to customize Starlight appearance going forward:**
+ ## Limits
- 1. To change Starlight's gray palette → update `--color-gray-*` values in `@theme inline`.
- 2. To change Starlight's accent color → update `--color-accent-*` values in `@theme inline`.
- 3. To change fonts → update `--font-sans` / `--font-mono` in `@theme inline` AND `--sl-font` / `--sl-font-mono` in the unlayered `:root` block.
- 4. To add site-specific overrides that should beat Starlight → put them **unlayered** (outside any `@layer`).
- 5. To add base resets that Starlight/EC can override → put them in `@layer base`.
+ Do not set Astro `i18n` in `astro.config.mjs`. This project sets Starlight `locales`. Starlight rejects both configurations. Starlight rejects Astro `routing: "manual"`.
- **Overriding expressive-code on custom pages (e.g., Hero):**
+ Keep the `docs` collection on `docsLoader()` and `docsSchema()`. Keep the `i18n` collection on `i18nLoader()` and `i18nSchema()`.
- When using `<Code />` from `astro-expressive-code` outside Starlight docs, override EC's CSS custom properties in the component's scoped `<style>` block:
+ In `src/styles/global.css`, keep this setup:
```css
- .hero-terminal :global(.expressive-code) {
- --ec-codeBg: var(--card);
- --ec-frm-edTabBarBg: var(--secondary);
- /* … etc. — see Hero.astro for the full list */
- }
- ```
-
- Scoped Astro styles are unlayered, so they beat both `@layer base` and `@layer starlight`.
-
- ### Radius
-
- Radius tokens follow a scale: `--radius-sm` through `--radius-4xl`, all computed from `--radius: 0.625rem`.
-
- ---
-
- ## Layout Hierarchy
-
- ```
- BaseLayout.astro ← HTML shell, <head>, NavBar, Footer, scroll-reveal, back-to-top
- ├── LandingLayout.astro ← Hero + section components
- ├── PostLayout.astro ← Single blog post with article styles
- ├── PostListLayout.astro ← Blog listing page
- └── (404.astro uses BaseLayout directly)
+ @layer base, starlight, theme, components, utilities;
+ @import "@astrojs/starlight-tailwind";
+ @import "tailwindcss/theme.css" layer(theme);
+ @import "tailwindcss/utilities.css" layer(utilities);
```
- **All pages go through BaseLayout.** Never duplicate `<!doctype>`, `<head>`, `NavBar`, `Footer`, or scroll-reveal scripts.
-
- ---
-
- ## Coding Conventions
+ Do not import `tailwindcss` in that file. That import adds Preflight. Preflight overrides Starlight styles.
- - **Astro components** for static content; avoid client-side JS unless interactive.
- - **Tailwind utility classes** for styling; avoid custom CSS unless for animations.
- - `class:list` for conditional classes.
- - Props interfaces at the top of the frontmatter.
- - `data-reveal` attribute on elements that should animate on scroll.
- - Keep line length ≤ 120 chars.
+ Docs colors are the unlayered `--sl-color-*` block in `src/styles/global.css`. The `--color-gray-*` and `--color-accent-*` scales do not change those colors.
- ---
+ Astro 7 sets the default of `compressHTML` to `'jsx'`. This config sets `compressHTML` to `true`. That value keeps one space between inline elements. Do not remove it.
- ## Before Committing
+ Close every non-void tag. Do not add `src/fetch.ts`. Astro reserves that file name.
- 1. `pnpm build` — must pass with no errors.
- 2. Check for hardcoded nav props — use `getNavProps()` from `i18n/utils.ts`.
- 3. Check for duplicated HTML shell — compose on `BaseLayout`.
- 4. Ensure all user-visible strings use the correct zone:
- - **Zone 1** (Starlight docs): UI overrides in `src/content/i18n/`, sidebar translations inline in config.
- - **Zone 2** (Custom pages): Landing copy → `landing-page.ts`, shared UI strings → `ui.ts`.
- - **Zone 3** (Blog): Post content in `src/content/posts/{locale}/`, UI strings in `ui.ts`.
- - Never duplicate text across zones.
+ Build site pages with `BaseLayout`. Do not copy the document shell, the nav, or the footer.