Files
cmsn-b-linh/docs/plans/2026-03-11-birthday-roast-gallery-design.md

4.7 KiB

Birthday Roast Gallery - Design Document

Date: 2026-03-11 Project: Birthday meme website for Linh Domain: cmsn-b-linh.duylai.duckdns.org

Overview

A static birthday roast gallery website featuring embarrassing photos with funny Vietnamese captions. Built with Vite + React + Tailwind, containerized with Nginx, and deployed to Kubernetes.

Architecture

┌─────────────────────────────────────────────────────────┐
│                    Build Stage                          │
│  Node.js image → npm install → Vite build → dist/      │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                   Runtime Stage                         │
│  Nginx:alpine → copy dist/ → serve on port 80          │
└─────────────────────────────────────────────────────────┘
                          │
                          ▼
┌─────────────────────────────────────────────────────────┐
│                   Kubernetes                            │
│  Deployment → Service → Ingress (Traefik)              │
│  cmsn-b-linh.duylai.duckdns.org                        │
└─────────────────────────────────────────────────────────┘

Tech Stack

Layer Technology
Frontend Vite + React 18
Styling Tailwind CSS
Animation canvas-confetti
Container Multi-stage Dockerfile → nginx:alpine
Orchestration Kubernetes with Traefik ingress

Frontend Components

App.jsx
├── BirthdayBanner.jsx     # Hero with name + roast message + confetti
└── RoastGallery.jsx       # Grid of photos with captions
    └── RoastCard.jsx      # Individual photo + caption

BirthdayBanner

  • Large "Happy Birthday [Name]!" heading
  • Roast subtitle message
  • Confetti animation on page load
  • Click-to-trigger more confetti

RoastGallery

  • Responsive CSS grid (2-3 cols mobile, 4-5 cols desktop)
  • Cards with hover effects

RoastCard

  • Image with aspect ratio
  • Caption below or overlay
  • Hover animation (scale/shadow)

Static Assets

Images stored in public/images/ with captions configured in public/roasts.json:

[
  { "image": "roast-1.jpg", "caption": "Khoảnh khắc huyền thoại 😂" },
  { "image": "roast-2.jpg", "caption": "Gương mặt khi quên đóng bài tập" }
]
  • UTF-8 encoding for Vietnamese captions
  • Add photos: drop file → add JSON entry → rebuild

Container Setup

Dockerfile (multi-stage)

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# Runtime stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

nginx.conf

  • Static file serving
  • Gzip compression
  • SPA fallback

Kubernetes Deployment

k8s/
├── deployment.yaml      # Nginx pod, 1-2 replicas
├── service.yaml         # ClusterIP on port 80
└── ingress.yaml         # Traefik ingress for cmsn-b-linh.duylai.duckdns.org

Deployment

  • Image: registry/cmsn-b-linh:latest
  • Replicas: 1
  • Resources: minimal (50m CPU, 64Mi RAM)

Ingress (Traefik)

  • Host: cmsn-b-linh.duylai.duckdns.org
  • Traefik-compatible annotations
  • Optional: TLS via cert-manager

File Structure

cmsn/
├── src/
│   ├── App.jsx
│   ├── main.jsx
│   ├── components/
│   │   ├── BirthdayBanner.jsx
│   │   ├── RoastGallery.jsx
│   │   └── RoastCard.jsx
│   └── index.css
├── public/
│   ├── images/           # Add photos here
│   └── roasts.json       # Caption config
├── index.html
├── package.json
├── vite.config.js
├── tailwind.config.js
├── postcss.config.js
├── Dockerfile
├── nginx.conf
└── k8s/
    ├── deployment.yaml
    ├── service.yaml
    └── ingress.yaml