quick reference
npm run dev # start dev server at localhost:4321
npm run build # type-check and build static site
npm run preview # preview dist/ locally
content authoring
creating an entry
- add a
.md file to src/content/entries/ — filename in kebab-case
- complete all required frontmatter fields (see template below)
- write growing notes in the markdown body
- the site rebuilds on push to
main
frontmatter template
---
title: "the name of the thing"
url: "https://example.com"
description: "one sentence about what it is"
tags: ["tag1", "tag2"]
topic: "sách" # one of: sách, công cụ, đồ vật, thương hiệu, bài viết, nghe, xem, ý tưởng
added: 2025-05-26 # date first planted
tended: 2025-05-26 # last updated (optional, defaults to added)
status: "seedling" # seedling | growing | blooming | evergreen
via: "friend recommended" # optional attribution
image: "https://example.com/pic.jpg" # optional preview image
display: "default" # image | text | default (card layout)
---
content rules
- titles: lowercase, descriptive, can include english brand names
- descriptions: one sentence, informal, tell why it matters
- tags: lowercase, no special characters, kebab-case for multi-word (
open-source not open source)
- body: markdown, informal voice, write like talking to a friend
- status by confidence:
- seedling — just found it, no strong opinion yet - growing — actively using/evaluating, forming opinions - blooming — confidently recommend, well-tested - evergreen — timeless value, proven over 6+ months
topic-specific card behavior
| topic |
card style on homepage |
| ------- |
---------------------- |
| sách |
CSS-generated 3D book cover from title |
| công cụ |
browser chrome + screenshot preview + domain |
| đồ vật |
product image centered on clean background |
| thương hiệu |
logo in rounded container + brand name |
| everything else |
image + title + optional description |
topic-specific detail page behavior
| topic |
entry page features |
| ------- |
-------------------- |
| sách |
large CSS book cover + side metadata |
| công cụ |
browser chrome screenshot + domain link |
| đồ vật |
centered product image on light bg |
| thương hiệu |
centered logo, brand description |
| xem |
auto-detects youtube/vimeo and embeds player |
| nghe |
album art with fallback icon + listen link |
| bài viết / ý tưởng |
clean reading layout |
code conventions
naming
- files & dirs: kebab-case (
garden-card.astro, src/content/entries/)
- components: descriptive noun (
garden-card, tag-chip, video-embed)
- typescript: interfaces and types in PascalCase, functions in camelCase
- css classes: tailwind utilities only, no custom class names (except prose-garden)
style
- all user-facing text in lowercase (titles, labels, nav, buttons)
- vietnamese-first content, english technical terms are fine
- no comments unless explaining a non-obvious constraint or workaround
- prefer
const over let, arrow functions for callbacks
astro patterns
- content collections:
type: 'content' with zod schema in src/content/config.ts
- all dynamic routes use
getStaticPaths() with full pre-rendering
entry.render() called inside getStaticPaths, <Content /> passed as prop
- component props typed via TypeScript interfaces inline
frontend
- tailwind css 4 with
@tailwindcss/vite plugin (no postcss config needed)
- theme tokens defined in
@theme block in src/styles/global.css
- all components hand-written in
.astro files — no ui library
- css grid for card and page layouts, flexbox for inline/alignment needs
project files
src/
├── content/
│ ├── config.ts # zod schema + collection definition
│ └── entries/*.md # one markdown file per garden entry
├── components/
│ ├── icon.astro # ibm carbon icon wrapper (40 icons)
│ ├── book-cover.astro # css-generated book cover from title
│ ├── video-embed.astro # youtube/vimeo embed detector
│ ├── garden-card.astro # entry card with topic-specific layout
│ ├── garden-grid.astro # grid container (deprecated, now inline)
│ ├── tag-chip.astro # tag pill with icon
│ ├── nav.astro # sticky nav + mobile hamburger
│ └── footer.astro # 4-column footer with colophon
├── layouts/
│ ├── base.astro # html shell, font loading, meta tags
│ └── entry.astro # type-specific detail page layout
├── pages/
│ ├── index.astro # homepage — grouped by topic, bento grid
│ ├── about.astro # about the garden
│ ├── submit.astro # how to suggest content
│ ├── terms.astro # terms of use
│ ├── system.astro # live design system / brand guide
│ ├── tags/index.astro # all tags with counts
│ ├── tags/[tag].astro # entries filtered by tag
│ ├── topics/[topic].astro # entries filtered by topic
│ └── entries/[...slug].astro # entry detail — gets Content from render()
├── lib/
│ └── garden.ts # getAllEntries, groupByTopic, getAllTags, sortEntries, tendedAgo
└── styles/
└── global.css # tailwind import + @theme tokens + prose-garden
git
- commit messages in lowercase, present-tense verb
- format:
[verb] [what] — add notion entry, fix tag sorting, tend darktable
- push to
main triggers cloudflare pages auto-deploy
- no
--force on main, no --no-verify
deployment
- platform: cloudflare pages (static)
- build:
npm run build → dist/
- domain: tramhay.com
- trigger: push to
main
- integrations:
@astrojs/sitemap (auto-generates sitemap-index.xml)