back to garden

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

  1. add a .md file to src/content/entries/ — filename in kebab-case
  2. complete all required frontmatter fields (see template below)
  3. write growing notes in the markdown body
  4. 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 builddist/
  • domain: tramhay.com
  • trigger: push to main
  • integrations: @astrojs/sitemap (auto-generates sitemap-index.xml)