# Birthday Roast Gallery Implementation Plan > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Build a birthday roast gallery website with photos, Vietnamese captions, confetti animation, and Kubernetes deployment manifests. **Architecture:** Vite + React frontend with Tailwind CSS, served by Nginx in a minimal Alpine container. Multi-stage Docker build for small image size. Kubernetes manifests for Traefik ingress deployment. **Tech Stack:** React 18, Vite, Tailwind CSS, canvas-confetti, nginx:alpine, Kubernetes --- ### Task 1: Project Scaffold and Dependencies **Files:** - Create: `package.json` - Create: `vite.config.js` - Create: `tailwind.config.js` - Create: `postcss.config.js` - Create: `index.html` - Create: `src/main.jsx` - Create: `src/index.css` - Create: `.gitignore` **Step 1: Initialize npm project and install dependencies** ```bash npm init -y npm install react react-dom npm install -D vite @vitejs/plugin-react tailwindcss postcss autoprefixer canvas-confetti ``` **Step 2: Create vite.config.js** ```javascript import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], }) ``` **Step 3: Create tailwind.config.js** ```bash npx tailwindcss init -p ``` Then update `tailwind.config.js`: ```javascript /** @type {import('tailwindcss').Config} */ export default { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], } ``` **Step 4: Create postcss.config.js** ```javascript export default { plugins: { tailwindcss: {}, autoprefixer: {}, }, } ``` **Step 5: Create index.html** ```html Happy Birthday Linh! 🎂
``` **Step 6: Create src/main.jsx** ```javascript import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.jsx' import './index.css' ReactDOM.createRoot(document.getElementById('root')).render( , ) ``` **Step 7: Create src/index.css** ```css @tailwind base; @tailwind components; @tailwind utilities; ``` **Step 8: Create .gitignore** ``` node_modules dist .DS_Store *.local ``` **Step 9: Update package.json scripts** Add these scripts to `package.json`: ```json "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" } ``` **Step 10: Test the setup** ```bash npm run dev ``` Expected: Dev server starts at http://localhost:5173 (will show error since App.jsx doesn't exist yet - that's ok) **Step 11: Commit** ```bash git init git add . git commit -m "chore: initialize Vite + React + Tailwind project" ``` --- ### Task 2: Create RoastCard Component **Files:** - Create: `src/components/RoastCard.jsx` **Step 1: Create the RoastCard component** ```jsx export default function RoastCard({ image, caption }) { return (
{caption}

{caption}

) } ``` **Step 2: Verify syntax** ```bash npm run build ``` Expected: Build succeeds (may warn about unused component) **Step 3: Commit** ```bash git add src/components/RoastCard.jsx git commit -m "feat: add RoastCard component with hover effects" ``` --- ### Task 3: Create RoastGallery Component **Files:** - Create: `src/components/RoastGallery.jsx` - Create: `public/roasts.json` **Step 1: Create sample roasts.json** ```json [ { "image": "placeholder-1.jpg", "caption": "Thêm ảnh vào thư mục public/images/ nhé! 😄" }, { "image": "placeholder-2.jpg", "caption": "Chỉnh caption trong public/roasts.json" } ] ``` **Step 2: Create RoastGallery component** ```jsx import { useState, useEffect } from 'react' import RoastCard from './RoastCard' export default function RoastGallery() { const [roasts, setRoasts] = useState([]) useEffect(() => { fetch('/roasts.json') .then((res) => res.json()) .then((data) => setRoasts(data)) .catch((err) => console.error('Failed to load roasts:', err)) }, []) return (

📸 Bộ Sưu Tập "Huyền Thoại"

{roasts.map((roast, index) => ( ))}
) } ``` **Step 3: Test the build** ```bash npm run build ``` Expected: Build succeeds **Step 4: Commit** ```bash git add src/components/RoastGallery.jsx public/roasts.json git commit -m "feat: add RoastGallery component with JSON config" ``` --- ### Task 4: Create BirthdayBanner Component with Confetti **Files:** - Create: `src/components/BirthdayBanner.jsx` **Step 1: Create BirthdayBanner component** ```jsx import { useEffect } from 'react' import confetti from 'canvas-confetti' export default function BirthdayBanner({ name = "Linh" }) { useEffect(() => { // Fire confetti on load const duration = 3 * 1000 const animationEnd = Date.now() + duration const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 } function randomInRange(min, max) { return Math.random() * (max - min) + min } const interval = setInterval(function() { const timeLeft = animationEnd - Date.now() if (timeLeft <= 0) { return clearInterval(interval) } const particleCount = 50 * (timeLeft / duration) confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 }, }) confetti({ ...defaults, particleCount, origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 }, }) }, 250) return () => clearInterval(interval) }, []) const handleClick = () => { confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } }) } return (

🎂 Happy Birthday {name}! 🎉

Chúc mừng sinh nhật! Click để ăn confetti! ✨

(Click anywhere for more confetti 🎊)

) } ``` **Step 2: Test the build** ```bash npm run build ``` Expected: Build succeeds **Step 3: Commit** ```bash git add src/components/BirthdayBanner.jsx git commit -m "feat: add BirthdayBanner with confetti animation" ``` --- ### Task 5: Create App Component and Integrate **Files:** - Create: `src/App.jsx` **Step 1: Create App.jsx** ```jsx import BirthdayBanner from './components/BirthdayBanner' import RoastGallery from './components/RoastGallery' function App() { return (
) } export default App ``` **Step 2: Test locally** ```bash npm run dev ``` Expected: Open http://localhost:5173, see banner with confetti, gallery placeholder **Step 3: Build for production** ```bash npm run build ``` Expected: Build succeeds, creates `dist/` folder **Step 4: Commit** ```bash git add src/App.jsx git commit -m "feat: integrate all components in App" ``` --- ### Task 6: Create Docker Configuration **Files:** - Create: `Dockerfile` - Create: `nginx.conf` - Create: `.dockerignore` **Step 1: Create .dockerignore** ``` node_modules dist .git *.md .env* ``` **Step 2: Create nginx.conf** ```nginx server { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; # Gzip compression gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; gzip_min_length 1000; # Cache static assets location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ { expires 1y; add_header Cache-Control "public, immutable"; } # SPA fallback location / { try_files $uri $uri/ /index.html; } } ``` **Step 3: Create Dockerfile** ```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 CMD ["nginx", "-g", "daemon off;"] ``` **Step 4: Test Docker build** ```bash docker build -t cmsn-b-linh:test . ``` Expected: Build succeeds **Step 5: Test Docker run** ```bash docker run -p 8080:80 cmsn-b-linh:test ``` Expected: Open http://localhost:8080, see the app working **Step 6: Commit** ```bash git add Dockerfile nginx.conf .dockerignore git commit -m "feat: add multi-stage Docker build with Nginx" ``` --- ### Task 7: Create Kubernetes Manifests **Files:** - Create: `k8s/deployment.yaml` - Create: `k8s/service.yaml` - Create: `k8s/ingress.yaml` **Step 1: Create k8s directory** ```bash mkdir -p k8s ``` **Step 2: Create k8s/deployment.yaml** ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: cmsn-b-linh labels: app: cmsn-b-linh spec: replicas: 1 selector: matchLabels: app: cmsn-b-linh template: metadata: labels: app: cmsn-b-linh spec: containers: - name: nginx image: cmsn-b-linh:latest # Change to your registry imagePullPolicy: IfNotPresent ports: - containerPort: 80 resources: requests: cpu: "50m" memory: "64Mi" limits: cpu: "100m" memory: "128Mi" ``` **Step 3: Create k8s/service.yaml** ```yaml apiVersion: v1 kind: Service metadata: name: cmsn-b-linh spec: selector: app: cmsn-b-linh ports: - port: 80 targetPort: 80 type: ClusterIP ``` **Step 4: Create k8s/ingress.yaml** ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: cmsn-b-linh annotations: traefik.ingress.kubernetes.io/router.entrypoints: web spec: rules: - host: cmsn-b-linh.duylai.duckdns.org http: paths: - path: / pathType: Prefix backend: service: name: cmsn-b-linh port: number: 80 ``` **Step 5: Commit** ```bash git add k8s/ git commit -m "feat: add Kubernetes manifests for Traefik ingress" ``` --- ### Task 8: Add Placeholder Images and Final Testing **Files:** - Create: `public/images/.gitkeep` **Step 1: Create images directory** ```bash mkdir -p public/images touch public/images/.gitkeep ``` **Step 2: Update roasts.json with instructions** ```json [ { "image": "your-photo-1.jpg", "caption": "Thêm ảnh vào thư mục public/images/ và chỉnh caption tại đây! 😄" }, { "image": "your-photo-2.jpg", "caption": "Xóa placeholder này sau khi thêm ảnh thật" } ] ``` **Step 3: Final build test** ```bash npm run build ``` Expected: Build succeeds **Step 4: Final Docker test** ```bash docker build -t cmsn-b-linh:test . && docker run -p 8080:80 cmsn-b-linh:test ``` Expected: App works at http://localhost:8080 **Step 5: Commit** ```bash git add public/images/.gitkeep public/roasts.json git commit -m "chore: add placeholder for images" ``` --- ### Task 9: Add README with Deployment Instructions **Files:** - Create: `README.md` **Step 1: Create README.md** ```markdown # 🎂 Birthday Roast Gallery A fun birthday meme website for roasting your friends with their best (worst) moments. ## Development ```bash npm install npm run dev ``` ## Adding Photos 1. Add images to `public/images/` folder 2. Update `public/roasts.json` with image filenames and Vietnamese captions 3. Rebuild and redeploy ## Docker Build ```bash docker build -t cmsn-b-linh:latest . ``` ## Kubernetes Deployment 1. Push image to your registry: ```bash docker tag cmsn-b-linh:latest your-registry/cmsn-b-linh:latest docker push your-registry/cmsn-b-linh:latest ``` 2. Update `k8s/deployment.yaml` with your registry image 3. Apply manifests: ```bash kubectl apply -f k8s/ ``` 4. Access at: https://cmsn-b-linh.duylai.duckdns.org ``` **Step 2: Commit** ```bash git add README.md git commit -m "docs: add README with deployment instructions" ``` --- ## Summary | Task | Description | |------|-------------| | 1 | Project scaffold with Vite + React + Tailwind | | 2 | RoastCard component | | 3 | RoastGallery component with JSON config | | 4 | BirthdayBanner with confetti | | 5 | App integration | | 6 | Docker multi-stage build | | 7 | Kubernetes manifests (Traefik) | | 8 | Placeholder images and final testing | | 9 | README documentation | **Total: 9 tasks, ~20-30 minutes**