# 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`:

```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)

```dockerfile
# 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
```
