github-actions[bot] commited on
Commit
4cb7ab8
ยท
0 Parent(s):

deploy: sync to hugging face

Browse files
This view is limited to 50 files because it contains too many changes. ย  See raw diff
Files changed (50) hide show
  1. .github/workflows/ci.yml +47 -0
  2. .github/workflows/deploy_hf.yml +36 -0
  3. .gitignore +31 -0
  4. CHAT_HISTORY.md +42 -0
  5. Dockerfile +53 -0
  6. README.md +72 -0
  7. backend/main.py +163 -0
  8. backend/requirements.txt +7 -0
  9. backend/stress_test.py +47 -0
  10. backend/test_main.py +112 -0
  11. check_ports.sh +79 -0
  12. deploy.sh +35 -0
  13. docs/API.md +69 -0
  14. docs/MONITORING.md +59 -0
  15. docs/ROADMAP_AND_IDEAS.md +125 -0
  16. frontend/.gitignore +41 -0
  17. frontend/README.md +36 -0
  18. frontend/eslint.config.mjs +18 -0
  19. frontend/next.config.ts +15 -0
  20. frontend/package-lock.json +0 -0
  21. frontend/package.json +33 -0
  22. frontend/postcss.config.mjs +7 -0
  23. frontend/public/ads.txt +1 -0
  24. frontend/public/file.svg +1 -0
  25. frontend/public/globe.svg +1 -0
  26. frontend/public/manifest.json +16 -0
  27. frontend/public/next.svg +1 -0
  28. frontend/public/robots.txt +4 -0
  29. frontend/public/sitemap.xml +22 -0
  30. frontend/public/vercel.svg +1 -0
  31. frontend/public/window.svg +1 -0
  32. frontend/src/app/about/page.tsx +50 -0
  33. frontend/src/app/de/page.tsx +87 -0
  34. frontend/src/app/de/visa-photo/page.tsx +31 -0
  35. frontend/src/app/echo/page.tsx +35 -0
  36. frontend/src/app/echo/privacy/page.tsx +49 -0
  37. frontend/src/app/en/echo/page.tsx +35 -0
  38. frontend/src/app/en/page.tsx +93 -0
  39. frontend/src/app/en/visa-photo/page.tsx +41 -0
  40. frontend/src/app/es/page.tsx +75 -0
  41. frontend/src/app/es/visa-photo/page.tsx +32 -0
  42. frontend/src/app/favicon.ico +0 -0
  43. frontend/src/app/fr/page.tsx +84 -0
  44. frontend/src/app/fr/visa-photo/page.tsx +32 -0
  45. frontend/src/app/globals.css +26 -0
  46. frontend/src/app/ja/page.tsx +84 -0
  47. frontend/src/app/ja/visa-photo/page.tsx +32 -0
  48. frontend/src/app/ko/page.tsx +75 -0
  49. frontend/src/app/ko/visa-photo/page.tsx +32 -0
  50. frontend/src/app/layout.tsx +68 -0
.github/workflows/ci.yml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: CI - Backend Testing
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+
9
+ jobs:
10
+ test-backend:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Set up Python 3.12
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.12'
19
+
20
+ - name: Install dependencies
21
+ run: |
22
+ cd backend
23
+ python -m pip install --upgrade pip
24
+ pip install -r requirements.txt
25
+ pip install pytest httpx
26
+
27
+ - name: Run Pytest
28
+ run: |
29
+ cd backend
30
+ pytest test_main.py
31
+
32
+ - name: Start server for Stress Test
33
+ run: |
34
+ cd backend
35
+ # Install uvicorn if not present (it should be in requirements.txt)
36
+ nohup python -m uvicorn main:app --host 0.0.0.0 --port 13002 > server.log 2>&1 &
37
+ # Wait for the AI model to download and load (might take a while in CI)
38
+ timeout 120 bash -c 'until curl -s http://localhost:13002/ > /dev/null; do sleep 5; done'
39
+
40
+ - name: Run Stress Test
41
+ run: |
42
+ cd backend
43
+ python stress_test.py
44
+
45
+ - name: Show server logs on failure
46
+ if: failure()
47
+ run: cat backend/server.log
.github/workflows/deploy_hf.yml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face Spaces
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0
15
+
16
+ - name: Push to HF (Clean History)
17
+ env:
18
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
19
+ run: |
20
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
21
+ git config --global user.name "github-actions[bot]"
22
+
23
+ # Prepare a clean directory for HF (avoiding .git history)
24
+ mkdir -p ../hf_space
25
+ cp -R . ../hf_space/
26
+ cd ../hf_space
27
+ rm -rf .git
28
+
29
+ # Initialize new repo for HF Space (Stateless)
30
+ git init -b main
31
+ git add .
32
+ git commit -m "deploy: sync to hugging face"
33
+
34
+ # Force push to HF Space
35
+ git remote add space https://winterandchaiyun:$HF_TOKEN@huggingface.co/spaces/winterandchaiyun/quicktools
36
+ git push --force space main
.gitignore ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ backend/venv/
3
+ frontend/node_modules/
4
+
5
+ # Next.js build output
6
+ frontend/.next/
7
+ frontend/out/
8
+
9
+ # Logs
10
+ *.log
11
+ npm-debug.log*
12
+ yarn-debug.log*
13
+ yarn-error.log*
14
+ pnpm-debug.log*
15
+
16
+ # OS generated files
17
+ .DS_Store
18
+ Thumbs.db
19
+
20
+ # Environment variables
21
+ .env
22
+ .env.local
23
+ .env.development.local
24
+ .env.test.local
25
+ .env.production.local
26
+
27
+ # Python
28
+ __pycache__/
29
+ *.py[cod]
30
+ *$py.class
31
+ indexing_key.json
CHAT_HISTORY.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Visa Photo Maker ้กน็›ฎๅฏน่ฏๅ…จ่ฎฐๅฝ•
2
+
3
+ > ๆญคๆ–‡ไปถ็”ฑ chat_recorder.py ่‡ชๅŠจ็”Ÿๆˆ
4
+
5
+
6
+ # --- ๅฏน่ฏๅฏผๅ‡บ @ 2025-12-31 09:27:59 ---
7
+
8
+ ## 1. ๅˆๅง‹ๆž„ๆƒณ
9
+ ็”จๆˆทๆๅ‡บๅšไธ€ไธช Visa Photo Maker๏ผŒ่ฆๆฑ‚่ƒฝๅŽป่ƒŒใ€่ฎพไธบ็™ฝ่‰ฒ่ƒŒๆ™ฏใ€่ฐƒๆ•ดๅฐบๅฏธๅนถไธ‹่ฝฝใ€‚
10
+
11
+ ## 2. ๆ–นๆกˆ็กฎๅฎš
12
+ - **ๅ‰็ซฏ**: Next.js (React) + Tailwind CSSใ€‚
13
+ - **ๅŽ็ซฏ**: FastAPI (Python)ใ€‚
14
+ - **ๆ ธๅฟƒๅบ“**: rembg (AIๅŽป่ƒŒ), Pillow (ๅ›พๅƒๅค„็†)ใ€‚
15
+ - **ๆต็จ‹**: ไธŠไผ  -> ่‡ชๅŠจๅŽป่ƒŒ -> ๅˆๆˆ็™ฝๅบ• -> ่ฃๅ‰ช(600x600px) -> ไธ‹่ฝฝใ€‚
16
+
17
+ ## 3. ๅผ€ๅ‘ๅทฅๅ…ทๅ‡†ๅค‡
18
+ - ๅˆ›ๅปบไบ† : ็”จไบŽไฟๅญ˜ๅ•ๆฌก่ฎจ่ฎบ็‰‡ๆฎตใ€‚
19
+ - ๅˆ›ๅปบไบ† : ็”จไบŽๅฏผๅ‡บๆ•ดๆฎตๅฏน่ฏๅކๅฒใ€‚
20
+
21
+ # --- ๅฏผๅ‡บ็ป“ๆŸ ---
22
+
23
+ # --- ๅฏน่ฏๅฏผๅ‡บ @ 2026-02-08 17:00:00 ---
24
+
25
+ ## 4. ๆ‰ฉๅฑ•ๅทฅๅ…ท๏ผšEcho Reddit Assistant
26
+ ็”จๆˆทๆๅ‡บๅขžๅŠ ็ฌฌไบŒไธชๅทฅๅ…ท "Echo"๏ผŒๅฎšไฝไธบๅ…่ดน็š„ Reddit ๅ›žๅคๅŠฉๆ‰‹๏ผˆๆต่งˆๅ™จๆ‰ฉๅฑ•๏ผ‰ใ€‚
27
+
28
+ ### ๆŠ€ๆœฏๅฎž็Žฐ
29
+ - **ๆžถๆž„ๆผ”่ฟ›**: ๅฐ†ไธป้กตไปŽๅ•ไธ€ๅทฅๅ…ท้—จๆˆทๅ‡็บงไธบๅคšๅทฅๅ…ท Grid ๅธƒๅฑ€๏ผŒๆ”ฏๆŒๆฐดๅนณๆ‰ฉๅฑ•ใ€‚
30
+ - **ๅคš่ฏญ่จ€ๆ”ฏๆŒ**: ๅฎž็Žฐไบ† Echo ็š„ไธญ่‹ฑๆ–‡ๅŒ่ฏญ่ฝๅœฐ้กต (`/echo`, `/en/echo`)ใ€‚
31
+ - **่ฎพ่ฎก้ฃŽๆ ผ**: ๅปถ็ปญไบ† VisaBerry ็š„ๆž็ฎ€ใ€ไธ“ไธšใ€Material Design ้ฃŽๆ ผ๏ผŒไฝฟ็”จ Emoji ไฝœไธบๅ›พๆ ‡ไปฅๅฎž็Žฐ้›ถๅปถ่ฟŸๅŠ ่ฝฝใ€‚
32
+ - **ๅˆ่ง„ๆ€ง**: ๅปบ็ซ‹ไบ†ไธ“้—จ็š„้š็งๆ”ฟ็ญ–้กต้ข (`/echo/privacy`)๏ผŒ่ฏฆ็ป†่ฏดๆ˜Žไบ†๏ผš
33
+ - ไฝฟ็”จ Firebase Authentication ็ฎก็†็™ปๅฝ•็Šถๆ€๏ผˆไธๅญ˜ๅ‚จ Reddit ๅฏ†็ ๏ผ‰ใ€‚
34
+ - ๆ”ถ้›†ๅŒฟๅๆดปๅŠจๆ—ฅๅฟ—๏ผˆ็‚นๅ‡ปใ€ไบคไบ’๏ผ‰็”จไบŽๆ€ง่ƒฝไผ˜ๅŒ–ใ€‚
35
+ - ๅญ˜ๅ‚จ AI ็”Ÿๆˆ็š„ๅ›žๅคไปฅๆŒ็ปญไผ˜ๅŒ–ๆจกๅž‹ไธŠไธ‹ๆ–‡ๆ„Ÿๅบ”่ƒฝๅŠ›ใ€‚
36
+
37
+ ### ้ƒจ็ฝฒไธŽๅ‘ๅธƒ
38
+ - **ๅคš่ฏญ่จ€้—จๆˆทๆ›ดๆ–ฐ**: ๅŒๆญฅๆ›ดๆ–ฐไบ† ZH, EN, DE, JA, FR ็ญ‰ๅคšไธช่ฏญ่จ€็‰ˆๆœฌ็š„้—จๆˆทไธป้กต Gridใ€‚
39
+ - **CI/CD**: ้€š่ฟ‡ GitHub Actions ่‡ชๅŠจๅŒๆญฅ่‡ณ Hugging Face Spaces ็”Ÿไบง็Žฏๅขƒใ€‚
40
+ - **้กต่„š้›†ๆˆ**: ๆ›ดๆ–ฐไบ†ๅ…จๅฑ€ Footer๏ผŒๅฐ† Echo ๅŠ ๅ…ฅๅทฅๅ…ทๅฏผ่ˆชใ€‚
41
+
42
+ # --- ๅฏผๅ‡บ็ป“ๆŸ ---
Dockerfile ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stage 1: Build Frontend
2
+ FROM node:20-slim AS frontend-builder
3
+ WORKDIR /app/frontend
4
+ COPY frontend/package*.json ./
5
+ RUN npm install
6
+ COPY frontend/ ./
7
+ RUN npm run build
8
+
9
+ # Stage 2: Final Image
10
+ FROM python:3.11-slim
11
+
12
+ # Create a non-root user
13
+ RUN useradd -m -u 1000 user
14
+
15
+ # Install Node.js
16
+ RUN apt-get update && apt-get install -y curl && \
17
+ curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
18
+ apt-get install -y nodejs && \
19
+ rm -rf /var/lib/apt/lists/*
20
+
21
+ WORKDIR /app
22
+
23
+ # Install Python dependencies
24
+ COPY backend/requirements.txt ./backend/
25
+ RUN pip install --no-cache-dir -r backend/requirements.txt
26
+
27
+ # Pre-download rembg models
28
+ ENV U2NET_HOME=/app/.u2net
29
+ RUN mkdir -p /app/.u2net && \
30
+ python3 -c "from rembg import new_session; new_session('u2net')" && \
31
+ chown -R user:user /app/.u2net
32
+
33
+ # Copy backend code
34
+ COPY backend/ ./backend/
35
+
36
+ # Copy built frontend
37
+ COPY --from=frontend-builder /app/frontend/.next ./frontend/.next
38
+ COPY --from=frontend-builder /app/frontend/public ./frontend/public
39
+ COPY --from=frontend-builder /app/frontend/package*.json ./frontend/
40
+ COPY --from=frontend-builder /app/frontend/next.config.ts ./frontend/
41
+ COPY --from=frontend-builder /app/frontend/node_modules ./frontend/node_modules
42
+
43
+ # Copy startup script
44
+ COPY scripts/start_hf.sh ./scripts/start_hf.sh
45
+ RUN chmod +x ./scripts/start_hf.sh && chown user:user ./scripts/start_hf.sh
46
+
47
+ # Ensure permissions
48
+ RUN chown -R user:user /app
49
+
50
+ USER user
51
+ EXPOSE 7860
52
+
53
+ CMD ["./scripts/start_hf.sh"]
README.md ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: QuickTools
3
+ emoji: ๐Ÿ“ธ
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 7860
9
+ ---
10
+
11
+ # Visa Photo Maker
12
+
13
+ A professional, multilingual, privacy-first web application for generating standard visa and passport photos.
14
+
15
+ ## Features
16
+ - **AI-Powered**: Automatic background removal and biometric cropping.
17
+ - **Privacy-First**: RAM-only processing, zero disk storage for user images.
18
+ - **Multilingual**: Supports ZH, EN, ES, FR, JA, KO.
19
+ - **Global Standards**: Supports US, EU, UK, China, Japan, Korea, Australia, Canada.
20
+ - **SEO Optimized**: Pre-rendered routes and Schema.org integration.
21
+
22
+ ## Tech Stack
23
+ - **Frontend**: Next.js 15+, Tailwind CSS, TypeScript.
24
+ - **Backend**: FastAPI (Python 3.12), Rembg, Pillow, NumPy.
25
+ - **DevOps**: PM2 for process management.
26
+
27
+ ## Getting Started
28
+
29
+ ### Backend
30
+ 1. `cd backend`
31
+ 2. `python -m venv venv`
32
+ 3. `source venv/bin/activate`
33
+ 4. `pip install -r requirements.txt`
34
+ 5. `python main.py` (Runs on port 13002)
35
+
36
+ ### Frontend
37
+ 1. `cd frontend`
38
+ 2. `npm install`
39
+ 3. `npm run dev` (Runs on port 3000 in dev, 13001 in production)
40
+
41
+ ## Monitoring & Maintenance
42
+
43
+ We use a watchdog script to ensure services stay online.
44
+
45
+ - **Port Checker**: `./check_ports.sh` scans for active services.
46
+ - **Auto-Recovery**: `scripts/watchdog.sh` (Runs via cron every minute).
47
+ - **Guide**: See [docs/MONITORING.md](docs/MONITORING.md) for setup instructions.
48
+
49
+ ## Testing
50
+
51
+ ### Backend Unit Tests
52
+ We use `pytest` to ensure image processing and API logic remain correct.
53
+ ```bash
54
+ cd backend
55
+ venv/bin/pytest test_main.py
56
+ ```
57
+
58
+ ## Deployment
59
+ 1. Run the safe deployment script:
60
+ ```bash
61
+ ./deploy.sh
62
+ ```
63
+ (This script automatically runs unit tests before building and reloading.)
64
+ Processes are managed by PM2:
65
+ ```bash
66
+ pm2 list
67
+ pm2 restart visa-backend
68
+ pm2 restart visa-frontend
69
+ ```
70
+
71
+ ## License
72
+ MIT
backend/main.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form
3
+ from fastapi.responses import StreamingResponse
4
+ from rembg import remove
5
+ from PIL import Image
6
+ import io
7
+ import uvicorn
8
+ import asyncio
9
+ import numpy as np
10
+ from fastapi.middleware.cors import CORSMiddleware
11
+
12
+ # Configure logging
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
16
+ datefmt='%Y-%m-%d %H:%M:%S'
17
+ )
18
+ logger = logging.getLogger("visa-backend")
19
+
20
+ app = FastAPI(title="Visa Photo Maker API")
21
+
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"],
25
+ allow_credentials=True,
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ @app.get("/")
31
+ def read_root():
32
+ return {"status": "ok", "message": "Visa Photo Maker Backend is running"}
33
+
34
+ def add_white_background(
35
+ image: Image.Image,
36
+ size: tuple = (600, 600),
37
+ crop_params: dict = None
38
+ ) -> Image.Image:
39
+ """
40
+ 1. ่ฃๅ‰ชๆމ้€ๆ˜Ž่พน็ผ˜ (ๅฆ‚ๆžœๆฒกๆœ‰ๆไพ›ๆ‰‹ๅŠจ่ฃๅ‰ชๅ‚ๆ•ฐ)
41
+ 2. ๅˆ›ๅปบ็™ฝ่‰ฒ่ƒŒๆ™ฏ
42
+ 3. ๅฐ†ไบบ็‰ฉไธปไฝ“็ผฉๆ”พๅนถๅฑ…ไธญ
43
+ """
44
+ # ็กฎไฟๅ›พ็‰‡ๆ˜ฏ RGBA ๆจกๅผ
45
+ image = image.convert("RGBA")
46
+ logger.info(f"Original image size: {image.size}, Target size: {size}")
47
+
48
+ if crop_params and all(k in crop_params for k in ('x', 'y', 'w', 'h')):
49
+ # --- ไฝฟ็”จๆ‰‹ๅŠจ่ฃๅ‰ชๅ‚ๆ•ฐ ---
50
+ logger.info(f"Using manual crop params: {crop_params}")
51
+ x, y, w, h = crop_params['x'], crop_params['y'], crop_params['w'], crop_params['h']
52
+ # ่ฃๅ‰ชๅŽŸๅ›พ (crop_params ๅบ”่ฏฅๆ˜ฏ็›ธๅฏนไบŽๅŽŸๅ›พ็š„ๅƒ็ด ๅๆ ‡)
53
+ image = image.crop((x, y, x + w, y + h))
54
+ else:
55
+ # --- ่‡ชๅŠจๅผบๅŠ›่ฃๅ‰ช้€ป่พ‘ๅผ€ๅง‹ ---
56
+ # ๅฐ† PIL Image ่ฝฌไธบ numpy ๆ•ฐ็ป„ไปฅๅค„็† Alpha ้€š้“
57
+ img_np = np.array(image)
58
+
59
+ # ่Žทๅ– Alpha ้€š้“ (็ฌฌ4ไธช้€š้“)
60
+ alpha = img_np[:, :, 3]
61
+
62
+ # ่ฎพๅฎš้˜ˆๅ€ผ๏ผšAlpha ๅ€ผๅฐไบŽ 50 ็š„ๅƒ็ด ่ง†ไธบๅฎŒๅ…จ้€ๆ˜Ž
63
+ threshold = 50
64
+ mask = alpha > threshold
65
+
66
+ # ๅฆ‚ๆžœๅ…จๅ›พ้ƒฝๆ˜ฏ้€ๆ˜Ž็š„๏ผˆๆฒกๆœ‰ๆฃ€ๆต‹ๅˆฐไบบ๏ผ‰๏ผŒๅฐฑ็›ดๆŽฅ่ฟ”ๅ›ž็™ฝๅ›พ
67
+ if not np.any(mask):
68
+ logger.warning("No subject found (image is empty)")
69
+ return Image.new("RGB", size, (255, 255, 255))
70
+
71
+ # ่Žทๅ–้ž้›ถๅŒบๅŸŸ็š„ๅๆ ‡ (่กŒ, ๅˆ—)
72
+ rows = np.any(mask, axis=1)
73
+ cols = np.any(mask, axis=0)
74
+
75
+ y_min, y_max = np.where(rows)[0][[0, -1]]
76
+ x_min, x_max = np.where(cols)[0][[0, -1]]
77
+
78
+ # ่ฃๅ‰ชๅ›พ็‰‡
79
+ image = image.crop((x_min, y_min, x_max + 1, y_max + 1))
80
+ logger.info(f"Cropped size (tight): {image.size}")
81
+
82
+ # --- ๆ™บ่ƒฝๅŠ่บซ่ฃๅ‰ช (Smart Body Crop) ---
83
+ target_ratio = size[1] / size[0]
84
+ max_allowed_ratio = target_ratio + 0.15
85
+
86
+ w, h = image.size
87
+ current_ratio = h / w
88
+
89
+ if current_ratio > max_allowed_ratio:
90
+ logger.info(f"Image is too tall (ratio {current_ratio:.2f} > {max_allowed_ratio:.2f}), cropping bottom...")
91
+ new_h = int(w * max_allowed_ratio)
92
+ image = image.crop((0, 0, w, new_h))
93
+ logger.info(f"New size after smart crop: {image.size}")
94
+ # ------------------------------------
95
+
96
+ # 3. ่ฎก็ฎ—็ผฉๆ”พๆฏ”ไพ‹๏ผšCover ๆจกๅผ๏ผŒไฝ†็จๅพฎ็•™ไธ€็‚น่พน่ท (95% ่ฆ†็›–)
97
+ scale_width = (size[0] * 0.95) / image.width
98
+ scale_height = (size[1] * 0.95) / image.height
99
+
100
+ scale_factor = max(scale_width, scale_height)
101
+
102
+ new_width = int(image.width * scale_factor)
103
+ new_height = int(image.height * scale_factor)
104
+
105
+ logger.info(f"Resizing: {new_width}x{new_height}")
106
+ resized_img = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
107
+
108
+ final_image = Image.new("RGB", size, (255, 255, 255))
109
+ x_offset = (size[0] - new_width) // 2
110
+
111
+ top_padding = int(size[1] * 0.10)
112
+ y_offset = top_padding
113
+
114
+ if y_offset + new_height < size[1]:
115
+ y_offset = (size[1] - new_height) // 2
116
+ elif y_offset + new_height > size[1] + 100:
117
+ y_offset = int(size[1] * 0.05)
118
+
119
+ final_image.paste(resized_img, (x_offset, y_offset), resized_img)
120
+
121
+ return final_image.convert("RGB")
122
+
123
+ # Global lock to prevent CPU overload
124
+ processing_lock = asyncio.Lock()
125
+
126
+ @app.post("/process-image")
127
+ async def process_image_endpoint(
128
+ file: UploadFile = File(...),
129
+ width: int = Form(600),
130
+ height: int = Form(600),
131
+ crop_x: int = Form(None),
132
+ crop_y: int = Form(None),
133
+ crop_w: int = Form(None),
134
+ crop_h: int = Form(None)
135
+ ):
136
+ # Wait for the lock before processing
137
+ async with processing_lock:
138
+ try:
139
+ input_image_bytes = await file.read()
140
+ logger.info(f"Processing image for file: {file.filename}")
141
+
142
+ output_image_bytes = await asyncio.to_thread(remove, input_image_bytes)
143
+ img_no_bg = Image.open(io.BytesIO(output_image_bytes))
144
+
145
+ crop_params = None
146
+ if crop_x is not None and crop_y is not None:
147
+ crop_params = {'x': crop_x, 'y': crop_y, 'w': crop_w, 'h': crop_h}
148
+
149
+ final_image = add_white_background(img_no_bg, size=(width, height), crop_params=crop_params)
150
+
151
+ img_io = io.BytesIO()
152
+ final_image.save(img_io, format="JPEG", quality=95)
153
+ img_io.seek(0)
154
+
155
+ logger.info(f"Successfully processed image: {file.filename}")
156
+ return StreamingResponse(img_io, media_type="image/jpeg")
157
+
158
+ except Exception as e:
159
+ logger.error(f"Error processing image: {str(e)}", exc_info=True)
160
+ raise HTTPException(status_code=500, detail=str(e))
161
+
162
+ if __name__ == "__main__":
163
+ uvicorn.run(app, host="0.0.0.0", port=13002)
backend/requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ rembg
4
+ pillow
5
+ python-multipart
6
+ numpy
7
+ onnxruntime
backend/stress_test.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import httpx
3
+ import time
4
+ import io
5
+ from PIL import Image
6
+
7
+ # ้…็ฝฎๅŽ็ซฏ API ๅœฐๅ€
8
+ URL = "http://localhost:13002/process-image"
9
+
10
+ async def send_request(client, task_id):
11
+ # Create a unique dummy image for each request
12
+ file_io = io.BytesIO()
13
+ image = Image.new('RGBA', size=(800, 800), color=(task_id * 50, 0, 0, 255))
14
+ image.save(file_io, 'png')
15
+ file_io.seek(0)
16
+
17
+ print(f"[Task {task_id}] Sending request...")
18
+ start_time = time.time()
19
+
20
+ try:
21
+ response = await client.post(
22
+ URL,
23
+ files={"file": ("test.png", file_io, "image/png")},
24
+ data={"width": "600", "height": "600"},
25
+ timeout=60.0
26
+ )
27
+ end_time = time.time()
28
+ print(f"[Task {task_id}] Completed in {end_time - start_time:.2f}s with status {response.status_code}")
29
+ return response.status_code
30
+ except Exception as e:
31
+ print(f"[Task {task_id}] Failed: {str(e)}")
32
+ return None
33
+
34
+ async def run_stress_test():
35
+ async with httpx.AsyncClient() as client:
36
+ # Fire 3 requests simultaneously
37
+ tasks = [send_request(client, i) for i in range(1, 4)]
38
+ print(f"--- Starting Stress Test: 3 Concurrent Requests ---")
39
+ start_total = time.time()
40
+ results = await asyncio.gather(*tasks)
41
+ end_total = time.time()
42
+ print(f"--- Stress Test Finished ---")
43
+ print(f"Total time for all tasks: {end_total - start_total:.2f}s")
44
+ print(f"Success count: {results.count(200)}/3")
45
+
46
+ if __name__ == "__main__":
47
+ asyncio.run(run_stress_test())
backend/test_main.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from fastapi.testclient import TestClient
3
+ from main import app
4
+ import io
5
+ from PIL import Image
6
+
7
+ client = TestClient(app)
8
+
9
+ def test_read_root():
10
+ response = client.get("/")
11
+ assert response.status_code == 200
12
+ assert "running" in response.json()["message"]
13
+
14
+ @pytest.mark.parametrize("width, height", [
15
+ (600, 600), # US
16
+ (413, 531), # EU
17
+ (354, 472), # JP
18
+ (591, 827), # CA
19
+ ])
20
+ def test_process_image_various_sizes(width, height):
21
+ # Create a dummy RGBA image (red square)
22
+ file = io.BytesIO()
23
+ # Use a larger source image to test cropping/resizing
24
+ image = Image.new('RGBA', size=(1000, 1500), color=(255, 0, 0, 255))
25
+ image.save(file, 'png')
26
+ file.seek(0)
27
+
28
+ response = client.post(
29
+ "/process-image",
30
+ files={"file": ("test.png", file, "image/png")},
31
+ data={"width": str(width), "height": str(height)}
32
+ )
33
+
34
+ assert response.status_code == 200
35
+ assert response.headers["content-type"] == "image/jpeg"
36
+
37
+ output_image = Image.open(io.BytesIO(response.content))
38
+ assert output_image.size == (width, height)
39
+
40
+ def test_manual_crop():
41
+ # Create a 500x500 source image
42
+ file = io.BytesIO()
43
+ image = Image.new('RGBA', size=(500, 500), color=(0, 0, 255, 255))
44
+ image.save(file, 'png')
45
+ file.seek(0)
46
+
47
+ # Define a crop in the middle (100, 100 to 300, 300)
48
+ response = client.post(
49
+ "/process-image",
50
+ files={"file": ("manual.png", file, "image/png")},
51
+ data={
52
+ "width": "600",
53
+ "height": "600",
54
+ "crop_x": "100",
55
+ "crop_y": "100",
56
+ "crop_w": "200",
57
+ "crop_h": "200"
58
+ }
59
+ )
60
+
61
+ assert response.status_code == 200
62
+ output_image = Image.open(io.BytesIO(response.content))
63
+ # Output should still be normalized to the requested 600x600
64
+ assert output_image.size == (600, 600)
65
+
66
+ import xml.etree.ElementTree as ET
67
+ import os
68
+
69
+ def test_sitemap_validity():
70
+ # Path to the sitemap file in frontend/public
71
+ sitemap_path = os.path.join(os.path.dirname(__file__), "..", "frontend", "public", "sitemap.xml")
72
+
73
+ # 1. Check if file exists
74
+ assert os.path.exists(sitemap_path), "sitemap.xml does not exist in frontend/public"
75
+
76
+ # 2. Try to parse XML
77
+ try:
78
+ tree = ET.parse(sitemap_path)
79
+ root = tree.getroot()
80
+ except ET.ParseError as e:
81
+ pytest.fail(f"sitemap.xml is not valid XML: {e}")
82
+
83
+ # 3. Check Namespace
84
+ assert "sitemaps.org" in root.tag, "Sitemap namespace missing or incorrect"
85
+
86
+ # 4. Check URLs
87
+ urls = root.findall(".//{http://www.sitemaps.org/schemas/sitemap/0.9}loc")
88
+ assert len(urls) >= 16, f"Expected at least 16 URLs (8 portals + 8 tools), found {len(urls)}"
89
+
90
+ for loc in urls:
91
+ url_text = loc.text.strip()
92
+ # Verify no whitespace inside the URL
93
+ assert " " not in url_text, f"Sitemap URL contains spaces: '{url_text}'"
94
+ assert url_text.startswith("https://quicktools.dpdns.org"), f"Invalid URL domain in sitemap: {url_text}"
95
+ assert not url_text.endswith("/undefined"), f"Sitemap contains undefined path: {url_text}"
96
+ # Check for typical malformed patterns
97
+ assert "\n" not in url_text, f"Sitemap URL contains newline: '{url_text}'"
98
+
99
+ def test_processing_lock():
100
+ # This is hard to test with a simple TestClient because it's synchronous,
101
+ # but we can verify it doesn't crash.
102
+ file = io.BytesIO()
103
+ image = Image.new('RGBA', size=(10, 10), color=(0, 255, 0, 255))
104
+ image.save(file, 'png')
105
+ file.seek(0)
106
+
107
+ response = client.post(
108
+ "/process-image",
109
+ files={"file": ("test.png", file, "image/png")},
110
+ data={"width": "100", "height": "100"}
111
+ )
112
+ assert response.status_code == 200
check_ports.sh ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Define colors
4
+ RED='\033[0;31m'
5
+ GREEN='\033[0;32m'
6
+ YELLOW='\033[1;33m'
7
+ BLUE='\033[0;34m'
8
+ NC='\033[0m' # No Color
9
+
10
+ # Get optional port argument
11
+ TARGET_PORT=$1
12
+
13
+ echo -e "${BLUE}๐Ÿ” Scanning for open ports...${NC}\n"
14
+
15
+ # Print Header
16
+ printf "% -8s % -65s % -15s %b\n" "PORT" "WORKING DIRECTORY [COMMAND]" "PID" "NOTE"
17
+ echo "--------------------------------------------------------------------------------------------------------------------------------"
18
+
19
+ # Use ss to get listening ports
20
+ ss -tunlp | grep LISTEN | while read -r line; do
21
+ local_addr=$(echo $line | awk '{print $5}')
22
+ port=${local_addr##*:}
23
+
24
+ if [ -n "$TARGET_PORT" ] && [ "$port" != "$TARGET_PORT" ]; then
25
+ continue
26
+ fi
27
+
28
+ proc_raw=$(echo $line | grep -o 'users:(.*)')
29
+
30
+ if [ -n "$proc_raw" ]; then
31
+ pid=$(echo $proc_raw | grep -o 'pid=[0-9]*' | head -n 1 | cut -d'=' -f2)
32
+
33
+ # Get Current Working Directory
34
+ cwd=$(readlink -f /proc/$pid/cwd 2>/dev/null)
35
+ [ -z "$cwd" ] && cwd="unknown"
36
+
37
+ # Get Command
38
+ cmd_full=$(cat /proc/$pid/cmdline 2>/dev/null | tr '\0' ' ' | sed 's/ $//')
39
+
40
+ # Child process handling
41
+ if [[ "$cmd_full" == *"spawn_main"* ]]; then
42
+ ppid=$(ps -o ppid= -p $pid 2>/dev/null | tr -d ' ')
43
+ [ -n "$ppid" ] && cmd_full=$(cat /proc/$ppid/cmdline 2>/dev/null | tr '\0' ' ' | sed 's/ $//')
44
+ fi
45
+
46
+ # Extract Brief Command
47
+ if [[ "$cmd_full" == *"python"* ]]; then
48
+ # Extract script name or module
49
+ cmd_brief=$(echo "$cmd_full" | grep -oE "([^ ]+\.py|-m [^ ]+)" | head -1)
50
+ [ -z "$cmd_brief" ] && cmd_brief="python"
51
+ elif [[ "$cmd_full" == *"node"* ]]; then
52
+ # Just take the first argument after node if it exists
53
+ cmd_brief=$(echo "$cmd_full" | awk '{print $2}')
54
+ [ -z "$cmd_brief" ] || [[ "$cmd_brief" == -* ]] && cmd_brief="node"
55
+ cmd_brief=$(basename "$cmd_brief")
56
+ else
57
+ cmd_brief=$(echo "$cmd_full" | awk '{print $1}')
58
+ cmd_brief=$(basename "$cmd_brief")
59
+ fi
60
+
61
+ cwd_short=$(echo "$cwd" | sed "s|/home/$USER|~|g")
62
+ info_display="${cwd_short} [${cmd_brief}]"
63
+ else
64
+ pid="-"
65
+ info_display="-"
66
+ fi
67
+
68
+ note=""
69
+ if [ "$port" == "13002" ]; then note="${GREEN}Visa-Backend${NC}"
70
+ elif [ "$port" == "13001" ]; then note="${GREEN}Visa-Frontend${NC}"
71
+ elif [ "$port" == "10318" ]; then note="${BLUE}n8n${NC}"
72
+ elif [ "$port" == "20241" ]; then note="${BLUE}Cloudflare${NC}"
73
+ elif [ "$port" == "8501" ]; then note="${YELLOW}Streamlit${NC}"
74
+ fi
75
+
76
+ printf "% -8s % -65s % -15s %b\n" "$port" "${info_display:0:64}" "$pid" "$note"
77
+ done | sort -n
78
+
79
+ echo -e "\n${BLUE}๐Ÿ’ก Format: WORKING_DIR [SCRIPT/COMMAND]${NC}"
deploy.sh ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Safe Deployment Script for VisaBerry / QuickTools
4
+
5
+ echo "๐Ÿš€ Starting Safe Deployment Process..."
6
+
7
+ # 1. Run Backend Unit Tests
8
+ echo "๐Ÿงช Running Backend Tests..."
9
+ cd backend
10
+ if venv/bin/pytest test_main.py; then
11
+ echo "โœ… Tests Passed!"
12
+ else
13
+ echo "โŒ Tests Failed! Deployment Aborted."
14
+ exit 1
15
+ fi
16
+ cd ..
17
+
18
+ # 2. Build Frontend
19
+ echo "๐Ÿ—๏ธ Building Frontend..."
20
+ cd frontend
21
+ rm -rf .next
22
+ if npm run build; then
23
+ echo "โœ… Build Successful!"
24
+ else
25
+ echo "โŒ Build Failed! Deployment Aborted."
26
+ exit 1
27
+ fi
28
+
29
+ # 3. Zero-Downtime Reload via PM2
30
+ echo "๐Ÿ”„ Reloading Services..."
31
+ pm2 reload visa-frontend
32
+ # Backend is usually fast, restart is fine, or also reload if using gunicorn
33
+ pm2 restart visa-backend
34
+
35
+ echo "โœจ Deployment Complete! VisaBerry is live and stable."
docs/API.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Visa Photo Maker Documentation
2
+
3
+ ## Overview
4
+ Visa Photo Maker is a privacy-first, bilingual web application that automatically processes user-uploaded photos into standard visa/passport photo formats. It uses AI to remove backgrounds and intelligently crops the image to meet specific aspect ratios and composition requirements.
5
+
6
+ ## Privacy & Security
7
+ **Does the server keep my photos?**
8
+ **NO.**
9
+ - Images are processed in **RAM (Memory)** only.
10
+ - The backend receives the file -> Removes Background -> Crops -> Returns the result immediately.
11
+ - No `save()` to disk operations are performed for user images.
12
+ - Once the request is finished, the data is discarded by the server's garbage collector.
13
+
14
+ ## Features
15
+ - **AI Background Removal:** Uses `rembg` (U2-Net) to remove complex backgrounds.
16
+ - **Smart Cropping:**
17
+ - Automatically detects the subject.
18
+ - Cleans up semi-transparent noise.
19
+ - Crops excess body parts for correct head-to-body ratios.
20
+ - Ensures no white borders ("Cover Mode").
21
+ - **Internationalization (i18n):**
22
+ - Full support for 6 languages:
23
+ - ๐Ÿ‡จ๐Ÿ‡ณ Chinese (ZH)
24
+ - ๐Ÿ‡บ๐Ÿ‡ธ English (EN)
25
+ - ๐Ÿ‡ช๐Ÿ‡ธ Spanish (ES)
26
+ - ๐Ÿ‡ซ๐Ÿ‡ท French (FR)
27
+ - ๐Ÿ‡ฏ๐Ÿ‡ต Japanese (JA)
28
+ - ๐Ÿ‡ฐ๐Ÿ‡ท Korean (KO)
29
+ - Dedicated SEO-friendly routes (e.g., `/es`, `/ja`).
30
+ - **Multi-Standard Support:**
31
+ - US/India: 2x2 inch (600x600 px)
32
+ - Europe/UK/China: 35x45 mm
33
+ - Japan: 30x40 mm
34
+ - Canada: 50x70 mm
35
+ - **Queue System:** Strict "One at a time" processing to protect server resources.
36
+
37
+ ## API Reference
38
+
39
+ ### `POST /process-image`
40
+
41
+ Uploads an image and returns the processed JPEG.
42
+
43
+ **Parameters (Form Data):**
44
+ - `file`: The image file (JPG/PNG).
45
+ - `width`: Target width in pixels (e.g., 600).
46
+ - `height`: Target height in pixels (e.g., 600).
47
+
48
+ **Response:**
49
+ - Returns a binary JPEG stream of the processed image.
50
+
51
+ **Concurrency:**
52
+ - The server implements a **Global Lock**.
53
+ - Only **1 request** is processed at a time.
54
+ - Subsequent requests will wait in a queue until the lock is released.
55
+
56
+ ## Architecture
57
+ - **Frontend:** Next.js (React) on Port 3000.
58
+ - Components: `src/components/VisaPhotoMaker.tsx` (Core Logic).
59
+ - Routes: `src/app/[lang]/page.tsx`.
60
+ - **Backend:** FastAPI (Python) on Port 13002.
61
+ - **Process Management:** PM2.
62
+
63
+ ## Deployment
64
+ Managed via PM2:
65
+ ```bash
66
+ pm2 list
67
+ pm2 restart visa-backend
68
+ pm2 restart visa-frontend
69
+ ```
docs/MONITORING.md ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Service Monitoring & Auto-Recovery Guide
2
+
3
+ This guide explains how to ensure the Visa Photo Maker services (Frontend & Backend) remain online and automatically recover from crashes or stops.
4
+
5
+ ## 1. Watchdog Script
6
+
7
+ A simple bash script `scripts/watchdog.sh` has been created to check the health of your services every minute.
8
+
9
+ - **Location**: `scripts/watchdog.sh`
10
+ - **What it does**:
11
+ 1. Checks if **Port 13002** (Backend) is open.
12
+ 2. Checks if **Port 13001** (Frontend) is open.
13
+ 3. If a port is closed, it triggers `pm2 restart <app_name>`.
14
+ 4. Logs all incidents to `watchdog.log` in the project root.
15
+
16
+ ## 2. Setup Instructions
17
+
18
+ ### Step 1: Make Script Executable
19
+ Run this in your terminal:
20
+ ```bash
21
+ chmod +x scripts/watchdog.sh
22
+ ```
23
+
24
+ ### Step 2: Test the Script
25
+ Run it manually once to make sure it works (it should be silent if everything is OK):
26
+ ```bash
27
+ ./scripts/watchdog.sh
28
+ ```
29
+
30
+ ### Step 3: Enable Auto-Run (Crontab)
31
+ To make it run every minute automatically:
32
+
33
+ 1. Open your crontab editor:
34
+ ```bash
35
+ crontab -e
36
+ ```
37
+
38
+ 2. Add the following line at the bottom of the file:
39
+ *(Update the path if you moved the project)*
40
+ ```bash
41
+ * * * * * /home/ubuntu/code/visa_photo_maker/scripts/watchdog.sh
42
+ ```
43
+
44
+ 3. Save and exit (usually `Ctrl+O`, `Enter`, `Ctrl+X` if using nano).
45
+
46
+ ## 3. Viewing Logs
47
+
48
+ To see if the watchdog has restarted anything, check the log file:
49
+ ```bash
50
+ tail -f watchdog.log
51
+ ```
52
+
53
+ ## 4. Other Protections Enabled
54
+
55
+ Besides this script, the following PM2 protections are already active:
56
+
57
+ 1. **Memory Limit**: Backend restarts if it exceeds **2GB** RAM.
58
+ 2. **Backoff Restart**: If the app crashes repeatedly, PM2 waits 100ms+ before restarting to prevent CPU spikes.
59
+ 3. **4 Workers**: The backend runs 4 parallel instances to utilize the quad-core CPU.
docs/ROADMAP_AND_IDEAS.md ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Visa Photo Maker: Roadmap & Monetization Ideas
2
+
3
+ This document tracks the current progress of the project and outlines future possibilities for growth and revenue.
4
+
5
+ ## ๐Ÿš€ Current Progress (Phase 1: Foundation)
6
+
7
+ ### Technical Implementation
8
+ - **AI Core**: Integrated `rembg` (U2-Net) for precise background removal in RAM.
9
+ - **Biometric Logic**: Smart cropping that prioritizes head/shoulder ratios according to international standards.
10
+ - **Manual Control**: Added `react-easy-crop` for user-driven fine-tuning.
11
+ - **Print Engine**: Pure-frontend Canvas logic to generate 4x6 inch (1800x1200px) printable grids.
12
+ - **Concurrency**: `asyncio.Lock` implemented to protect CPU from overloading.
13
+
14
+ ### SEO & GEO (Growth)
15
+ - **Multilingual**: Full localization for **ZH, EN, ES, FR, JA, KO** with dedicated routes.
16
+ - **GEO-friendly**: `hreflang` tags, `sitemap.xml`, and `robots.txt` implemented.
17
+ - **Schema.org**: `SoftwareApplication` and `FAQPage` JSON-LD injected for AI search engines.
18
+ - **Trust Building**: Compliance Checklists and Trust Badges integrated into the UI.
19
+
20
+ ### Quality Assurance
21
+ - **Unit Testing**: `pytest` suite covering root health, dynamic sizing, manual cropping, and locking.
22
+ - **CI/CD**: GitHub Actions pipeline for automated testing on every push.
23
+
24
+ ### ๐Ÿ“Š Analytics & Tracking
25
+ - **Umami Analytics**: Implemented for privacy-first traffic monitoring.
26
+ - *Website ID*: `45fc6a7a-d84d-4db9-adfd-e5346a9de470`
27
+ - **Microsoft Clarity**: Implemented for behavioral analysis and heatmaps.
28
+ - *Project ID*: `uu7gwujo4s`
29
+
30
+ ---
31
+
32
+ ## ๐Ÿง  GEO & Advanced SEO Insights (The Strategy)
33
+
34
+ ### 1. GEO (Generative Engine Optimization)
35
+ - **Concept**: Optimizing for AI crawlers (ChatGPT, Claude, Gemini) rather than just traditional search bots.
36
+ - **Implementation**:
37
+ - Reclaimed control of `robots.txt` from Cloudflare ("Self-Managed" mode) to explicitly allow **GPTBot**, **ClaudeBot**, and **Bytespider**.
38
+ - Structured content using JSON-LD Schema to help LLMs understand the tool's utility and privacy promises.
39
+ - **Insight**: AI engines value **Unique Facts** (like RAM-only processing) and **Technical Precision**.
40
+
41
+ ### 2. High-Performance UX (Lighthouse 100)
42
+ - **Contrast & Accessibility**: Darkened gray text across all UI components to meet WCAG 2.1 standards, ensuring 100/100 Accessibility score.
43
+ - **Semantic Hierarchy**: Fixed heading levels (H1 -> H2 -> H3) to ensure a logical document flow for crawlers and screen readers.
44
+ - **Speed Index**: Minimized render-blocking assets. Using Emojis as icons provides zero-latency visual feedback.
45
+
46
+ ### 3. Portal Architecture
47
+ - **Domain**: `quicktools.dpdns.org`
48
+ - **Hub & Spoke Strategy**: The root `/` acts as a multi-language portal, distributing authority to specialized sub-tools.
49
+ - **Multi-Tool Expansion (Feb 2026)**: Successfully transitioned from a single-tool site to a collection platform with the launch of **Echo: Reddit Assistant**.
50
+
51
+ ---
52
+
53
+ ## ๐Ÿš€ Current Tools
54
+
55
+ ### ๐Ÿ“ธ VisaBerry (Passport Photo)
56
+ - **Status**: Stable / Production
57
+ - **Core**: AI background removal and biometric cropping.
58
+
59
+ ### ๐Ÿ“ฃ Echo (Reddit Assistant)
60
+ - **Status**: Beta / Production
61
+ - **Concept**: A Chrome extension helper for high-quality Reddit engagement.
62
+ - **Privacy Focus**: Firebase Auth for stateless session management; no storage of sensitive Reddit credentials.
63
+ - **Refinement**: AI-generated responses are stored anonymously to improve context-aware suggestions.
64
+
65
+ ---
66
+
67
+ ## ๐Ÿง  Commercial Philosophy (The "Numbers Game")
68
+
69
+ ### 1. Value is Just the Entry Ticket
70
+ Building a high-value tool like VisaBerry is the foundation, but **Transaction = Value ร— Probability**. In a crowded market, the cost of "being found" and "building trust" is significantly higher than the cost of development.
71
+
72
+ ### 2. High Distribution / "Cast a Wide Net"
73
+ The strategy of implementing 8 languages and granular SEO routes is not just about reachโ€”it's about reducing the **Transaction Cost** by meeting the user exactly where they are (their local language and specific search intent).
74
+
75
+ ### 3. Business as a Gravity Field
76
+ Instead of chasing individual customers, we build a platform that acts as a "gravity field," attracting high-intent users at the exact moment they need a solution.
77
+
78
+ ---
79
+
80
+ ## ๐Ÿ’ฐ Monetization Ideas (Phase 2: Revenue)
81
+
82
+ ### 1. The "Convenience" Model (Freemium)
83
+ - **Current**: Single photo download is 100% free.
84
+ - **Upsell**: Charge a small fee ($0.99 - $1.99) to unlock the **4x6 Printable Sheet**.
85
+
86
+ ### 2. Affiliate Marketing (The "Travel Toolkit" Matrix)
87
+ - **Fintech (High Trust)**: **Wise / Revolut** referral programs. (Essential for currency exchange).
88
+ - **Connectivity (High Velocity)**: **Airalo / Holafly** (eSIM cards). Perfect for instant conversion on the download page.
89
+ - **Security (Recurring)**: **SafetyWing** (Nomad/Travel Insurance). High lifetime value.
90
+ - **Concierge (High Margin)**: **iVisa / VisaHQ**. For users who find the manual process too difficult.
91
+ - **Destination (Upsell)**: **Klook / GetYourGuide**. For tour and ticket bookings.
92
+
93
+ ---
94
+
95
+ ## ๐Ÿ›  Production Transition & Deployment
96
+
97
+ ### 1. Mode Switch (Dev to Prod)
98
+ To optimize performance and minimize memory usage, the frontend has been transitioned to production mode:
99
+ - **Build**: `npm run build` was executed to generate a minimized, optimized bundle.
100
+ - **Run**: Switched PM2 command from `npm run dev` to `npm run start`.
101
+ - **Result**: Faster response times and significantly lower RAM overhead.
102
+
103
+ ### 2. Environment Variables & Analytics
104
+ - **Loading**: Next.js automatically detects `.env.local`.
105
+ - **Injection**: Variables starting with `NEXT_PUBLIC_` are injected into the client-side code during the **build** phase.
106
+ - **Maintenance**: If `NEXT_PUBLIC_` variables are changed, a **re-build** is required:
107
+ ```bash
108
+ cd frontend
109
+ # Update .env.local
110
+ npm run build
111
+ pm2 restart visa-frontend
112
+ ```
113
+
114
+ ### 3. Process Management (PM2)
115
+ Current production processes:
116
+ - `visa-frontend`: Next.js production server (Port 3000).
117
+ - `visa-backend`: FastAPI Uvicorn server (Port 13002).
118
+
119
+ ---
120
+
121
+ ## ๐Ÿ›  Maintenance Reminders
122
+ - Keep the `rembg` model updated.
123
+ - Monitor `pm2` memory usage (currently ~200-400MB).
124
+ - Ensure `sitemap.xml` is updated when new languages or routes are added.
125
+ - **Safety First**: NEVER add `save()` to disk for user images to keep the privacy promise.
frontend/.gitignore ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/versions
12
+
13
+ # testing
14
+ /coverage
15
+
16
+ # next.js
17
+ /.next/
18
+ /out/
19
+
20
+ # production
21
+ /build
22
+
23
+ # misc
24
+ .DS_Store
25
+ *.pem
26
+
27
+ # debug
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log*
31
+ .pnpm-debug.log*
32
+
33
+ # env files (can opt-in for committing if needed)
34
+ .env*
35
+
36
+ # vercel
37
+ .vercel
38
+
39
+ # typescript
40
+ *.tsbuildinfo
41
+ next-env.d.ts
frontend/README.md ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
2
+
3
+ ## Getting Started
4
+
5
+ First, run the development server:
6
+
7
+ ```bash
8
+ npm run dev
9
+ # or
10
+ yarn dev
11
+ # or
12
+ pnpm dev
13
+ # or
14
+ bun dev
15
+ ```
16
+
17
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
+
19
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20
+
21
+ This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
+
23
+ ## Learn More
24
+
25
+ To learn more about Next.js, take a look at the following resources:
26
+
27
+ - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
+ - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
+
30
+ You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
+
32
+ ## Deploy on Vercel
33
+
34
+ The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
+
36
+ Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
frontend/eslint.config.mjs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, globalIgnores } from "eslint/config";
2
+ import nextVitals from "eslint-config-next/core-web-vitals";
3
+ import nextTs from "eslint-config-next/typescript";
4
+
5
+ const eslintConfig = defineConfig([
6
+ ...nextVitals,
7
+ ...nextTs,
8
+ // Override default ignores of eslint-config-next.
9
+ globalIgnores([
10
+ // Default ignores of eslint-config-next:
11
+ ".next/**",
12
+ "out/**",
13
+ "build/**",
14
+ "next-env.d.ts",
15
+ ]),
16
+ ]);
17
+
18
+ export default eslintConfig;
frontend/next.config.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ async rewrites() {
5
+ return [
6
+ {
7
+ source: '/api/:path*',
8
+ destination: 'http://localhost:13002/:path*',
9
+ },
10
+ ]
11
+ },
12
+
13
+ };
14
+
15
+ export default nextConfig;
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start -p 13001",
9
+ "lint": "eslint"
10
+ },
11
+ "dependencies": {
12
+ "next": "16.1.1",
13
+ "react": "19.2.3",
14
+ "react-dom": "19.2.3",
15
+ "react-easy-crop": "^5.5.6"
16
+ },
17
+ "devDependencies": {
18
+ "@tailwindcss/postcss": "^4",
19
+ "@types/node": "^20",
20
+ "@types/react": "^19",
21
+ "@types/react-dom": "^19",
22
+ "eslint": "^9",
23
+ "eslint-config-next": "16.1.1",
24
+ "tailwindcss": "^4",
25
+ "typescript": "^5"
26
+ },
27
+ "browserslist": [
28
+ "chrome >= 87",
29
+ "firefox >= 78",
30
+ "edge >= 88",
31
+ "safari >= 14"
32
+ ]
33
+ }
frontend/postcss.config.mjs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
frontend/public/ads.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ google.com, pub-8465309813507611, DIRECT, f08c47fec0942fa0
frontend/public/file.svg ADDED
frontend/public/globe.svg ADDED
frontend/public/manifest.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "QuickTools - VisaBerry",
3
+ "short_name": "VisaBerry",
4
+ "description": "Professional Visa & Passport Photo Maker",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#ffffff",
8
+ "theme_color": "#2563eb",
9
+ "icons": [
10
+ {
11
+ "src": "https://quicktools.dpdns.org/file.svg",
12
+ "sizes": "192x192",
13
+ "type": "image/svg+xml"
14
+ }
15
+ ]
16
+ }
frontend/public/next.svg ADDED
frontend/public/robots.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ User-agent: *
2
+ Allow: /
3
+
4
+ Sitemap: https://quicktools.dpdns.org/sitemap.xml
frontend/public/sitemap.xml ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3
+ <!-- Portals -->
4
+ <url><loc>https://quicktools.dpdns.org/</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>1.0</priority></url>
5
+ <url><loc>https://quicktools.dpdns.org/en</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
6
+ <url><loc>https://quicktools.dpdns.org/de</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
7
+ <url><loc>https://quicktools.dpdns.org/es</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
8
+ <url><loc>https://quicktools.dpdns.org/fr</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
9
+ <url><loc>https://quicktools.dpdns.org/ru</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
10
+ <url><loc>https://quicktools.dpdns.org/ja</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
11
+ <url><loc>https://quicktools.dpdns.org/ko</loc><lastmod>2026-01-20</lastmod><changefreq>weekly</changefreq><priority>0.8</priority></url>
12
+
13
+ <!-- VisaBerry Tools -->
14
+ <url><loc>https://quicktools.dpdns.org/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
15
+ <url><loc>https://quicktools.dpdns.org/en/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
16
+ <url><loc>https://quicktools.dpdns.org/de/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
17
+ <url><loc>https://quicktools.dpdns.org/es/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
18
+ <url><loc>https://quicktools.dpdns.org/fr/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
19
+ <url><loc>https://quicktools.dpdns.org/ru/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
20
+ <url><loc>https://quicktools.dpdns.org/ja/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
21
+ <url><loc>https://quicktools.dpdns.org/ko/visa-photo</loc><lastmod>2026-01-20</lastmod><changefreq>daily</changefreq><priority>1.0</priority></url>
22
+ </urlset>
frontend/public/vercel.svg ADDED
frontend/public/window.svg ADDED
frontend/src/app/about/page.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Link from 'next/link';
2
+
3
+ export default function AboutUs() {
4
+ return (
5
+ <div className="min-h-screen bg-white text-gray-900 py-16 px-6 lg:px-8">
6
+ <div className="max-w-3xl mx-auto">
7
+ <Link href="/" className="text-blue-600 hover:underline mb-8 inline-block">&larr; Back to Home</Link>
8
+ <h1 className="text-4xl font-bold mb-8">About QuickTools</h1>
9
+
10
+ <p className="text-lg mb-6 leading-relaxed">
11
+ QuickTools was born from a simple observation: <strong>Getting a standard visa or passport photo should not be a difficult or expensive process.</strong>
12
+ </p>
13
+
14
+ <section className="mb-12">
15
+ <h2 className="text-2xl font-semibold mb-4 text-blue-600">Our Mission</h2>
16
+ <p className="mb-4">
17
+ We aim to provide professional-grade utility tools that are free, fast, and privacy-first. We believe that technology should be accessible to everyone without the need for complex registrations or hidden fees.
18
+ </p>
19
+ </section>
20
+
21
+ <section className="mb-12">
22
+ <h2 className="text-2xl font-semibold mb-4 text-blue-600">Why VisaBerry?</h2>
23
+ <p className="mb-4">
24
+ VisaBerry is our flagship tool designed to solve the common headache of visa photo compliance. Embassies have strict requirements for background color, face position, and image dimensions.
25
+ </p>
26
+ <p className="mb-4">
27
+ Our AI-powered tool automates the background removal and biometric cropping, ensuring that anyone can create a compliant photo from their home using just a smartphone.
28
+ </p>
29
+ </section>
30
+
31
+ <section className="mb-12">
32
+ <h2 className="text-2xl font-semibold mb-4 text-blue-600">Privacy at Core</h2>
33
+ <p className="mb-4">
34
+ Unlike many other "free" tools, we do not monetize your data. Your photos never touch a hard drive; they exist only in the temporary memory of our server while they are being processed.
35
+ </p>
36
+ </section>
37
+
38
+ <div className="bg-gray-50 p-8 rounded-2xl border border-gray-100">
39
+ <h3 className="text-xl font-bold mb-2">Support the Project</h3>
40
+ <p className="mb-4 text-sm text-gray-600">
41
+ QuickTools is maintained by a small team of independent developers. If you find our tools helpful, consider supporting our work to keep the servers running and the tools free for everyone.
42
+ </p>
43
+ <Link href="https://buymeacoffee.com/realberry" target="_blank" className="text-blue-600 font-semibold hover:underline">
44
+ Buy us a coffee &rarr;
45
+ </Link>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ );
50
+ }
frontend/src/app/de/page.tsx ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "QuickTools - Professionelle Online-Tool-Sammlung",
6
+ description: "Einfache, schnelle und datenschutzfreundliche Online-Tools. Entdecken Sie VisaBerry fรผr Visum- und Passfotos.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/de',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org',
11
+ 'en-US': 'https://quicktools.dpdns.org/en',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr',
15
+ 'ja-JP': 'https://quicktools.dpdns.org/ja',
16
+ 'ko-KR': 'https://quicktools.dpdns.org/ko',
17
+ },
18
+ },
19
+ openGraph: {
20
+ title: "QuickTools - Professionelle Online-Tool-Sammlung",
21
+ description: "Einfache, schnelle und datenschutzfreundliche Online-Tools.",
22
+ url: 'https://quicktools.dpdns.org/de',
23
+ locale: 'de_DE',
24
+ images: ['https://quicktools.dpdns.org/next.svg'],
25
+ },
26
+ };
27
+
28
+ const LANGS = [
29
+ { code: 'zh', label: 'ไธญๆ–‡', path: '/' },
30
+ { code: 'en', label: 'English', path: '/en' },
31
+ { code: 'de', label: 'Deutsch', path: '/de' },
32
+ { code: 'es', label: 'Espaรฑol', path: '/es' },
33
+ { code: 'fr', label: 'Franรงais', path: '/fr' },
34
+ { code: 'ja', label: 'ๆ—ฅๆœฌ่ชž', path: '/ja' },
35
+ { code: 'ko', label: 'ํ•œ๊ตญ์–ด', path: '/ko' }
36
+ ];
37
+
38
+ export default function GermanPortalHome() {
39
+ return (
40
+ <div className="min-h-screen bg-white text-gray-900 font-sans">
41
+ <div className="absolute top-4 right-4 z-10 flex flex-wrap gap-2 justify-end max-w-xs">
42
+ {LANGS.map((l) => (
43
+ <Link key={l.code} href={l.path} className={`px-3 py-1 rounded-full text-xs font-medium border ${l.code === 'de' ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-600 border-gray-200 hover:bg-gray-100'}`}>{l.label}</Link>
44
+ ))}
45
+ </div>
46
+ <header className="relative overflow-hidden bg-gray-50 pt-12 pb-16 sm:pt-16 sm:pb-20 text-center">
47
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
48
+ <div className="mx-auto max-w-2xl text-center">
49
+ <h1 className="text-5xl font-extrabold tracking-tight text-blue-600 sm:text-6xl mb-4">QuickTools</h1>
50
+ <p className="text-xl leading-8 text-gray-600">Einfach ยท Schnell ยท Datenschutz First<br/>Eine wachsende Sammlung nรผtzlicher Tools fรผr alle.</p>
51
+ </div>
52
+ </div>
53
+ </header>
54
+ <main className="max-w-7xl mx-auto px-6 py-16 lg:px-8">
55
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12">
56
+ <Link href="/de/visa-photo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
57
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ธ</div>
58
+ <h2 className="text-2xl font-bold mb-3">VisaBerry</h2>
59
+ <p className="text-gray-600 text-sm text-balance">
60
+ <strong>100% Kostenlos</strong>. Professioneller Generator fรผr Visum- und Passfotos. Automatischer Hintergrund und intelligentes Zuschneiden.
61
+ </p>
62
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Tool starten <span>&rarr;</span></div>
63
+ </Link>
64
+
65
+ <Link href="/en/echo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
66
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ฃ</div>
67
+ <h2 className="text-2xl font-bold mb-3">Echo</h2>
68
+ <p className="text-gray-600 text-center text-sm leading-relaxed text-balance">
69
+ <strong>Free Reddit Assistant</strong>. AI-powered reply assistant to help you boost engagement and karma with high-quality responses on Reddit.
70
+ </p>
71
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Launch Tool <span>&rarr;</span></div>
72
+ </Link>
73
+ <div className="flex flex-col items-center justify-center p-8 bg-gray-50 rounded-3xl border border-dashed border-gray-300 opacity-60 text-center">
74
+ <div className="w-16 h-16 bg-gray-200 text-gray-500 rounded-2xl flex items-center justify-center text-3xl mb-6">๐Ÿ“„</div>
75
+ <h2 className="text-2xl font-bold mb-3 text-gray-400">Demnรคchst</h2>
76
+ <p className="text-gray-400 text-center text-sm">Weitere leistungsstarke Tools befinden sich in der Entwicklung. Bleiben Sie dran!</p>
77
+ </div>
78
+ </div>
79
+ </main>
80
+ <footer className="bg-white border-t border-gray-100 py-12">
81
+ <div className="mx-auto max-w-7xl px-6 lg:px-8 text-center">
82
+ <p className="text-sm text-gray-500">&copy; 2025 QuickTools. Alle Rechte vorbehalten.<br/>Datenschutz garantiert, keine Registrierung erforderlich.</p>
83
+ </div>
84
+ </footer>
85
+ </div>
86
+ );
87
+ }
frontend/src/app/de/visa-photo/page.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import VisaPhotoMaker from '@/components/VisaPhotoMaker';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "VisaBerry - Kostenloser Visa-Foto-Generator | Passfoto Online Erstellen",
6
+ description: "Erstellen Sie kostenlos biometrische Visa- und Passfotos online. Automatische Hintergrunderstellung und intelligentes Zuschneiden.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/de/visa-photo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/visa-photo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/visa-photo',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de/visa-photo',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es/visa-photo',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr/visa-photo',
15
+ 'ja-JP': 'https://quicktools.dpdns.org/ja/visa-photo',
16
+ 'ko-KR': 'https://quicktools.dpdns.org/ko/visa-photo',
17
+ },
18
+ },
19
+ openGraph: {
20
+ title: "VisaBerry - Kostenloser Visa-Foto-Generator",
21
+ description: "Erstellen Sie kostenlos biometrische Visa- und Passfotos online.",
22
+ url: 'https://quicktools.dpdns.org/de/visa-photo',
23
+ locale: 'de_DE',
24
+ type: 'article',
25
+ images: ['https://quicktools.dpdns.org/next.svg'],
26
+ },
27
+ };
28
+
29
+ export default function GermanHome() {
30
+ return <VisaPhotoMaker locale="de" />;
31
+ }
frontend/src/app/echo/page.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import EchoAssistant from '@/components/EchoAssistant';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "Echo - ๅ…่ดน Reddit ๅ›žๅคๅŠฉๆ‰‹ | Reddit ่ฟ่ฅๆๆ•ˆๅทฅๅ…ท",
6
+ description: "ไฝฟ็”จ Echo ๆ™บ่ƒฝๅŠฉๆ‰‹ๆๅ‡ๆ‚จ็š„ Reddit ไบ’ๅŠจใ€‚AI ้ฉฑๅŠจ็š„่ฏญๅขƒๆ„Ÿๅบ”ๅ›žๅค๏ผŒๅธฎๅŠฉๆ‚จ่ฝปๆพๅปบ็ซ‹็คพๅŒบๅฝฑๅ“ๅŠ›ๅนถ่Žทๅพ— Karmaใ€‚",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/echo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/echo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/echo',
12
+ },
13
+ },
14
+ openGraph: {
15
+ title: "Echo - ๅ…่ดน Reddit AI ๅŠฉๆ‰‹",
16
+ description: "็”ฑ AI ้ฉฑๅŠจ็š„ Reddit ไบ’ๅŠจๅขžๅผบๅทฅๅ…ทใ€‚ๆ’ฐๅ†™้ซ˜่ดจ้‡ๅ›žๅค๏ผŒ่ฝปๆพๅปบ็ซ‹็คพๅŒบๅฃฐๆœ›ใ€‚",
17
+ url: 'https://quicktools.dpdns.org/echo',
18
+ locale: 'zh_CN',
19
+ type: 'website',
20
+ images: ['https://quicktools.dpdns.org/next.svg'],
21
+ },
22
+ };
23
+
24
+ export default function ChineseEchoPage() {
25
+ return (
26
+ <main>
27
+ <EchoAssistant locale="zh" />
28
+ <div className="max-w-4xl mx-auto px-4 pb-12 text-center">
29
+ <p className="text-xs text-gray-400 leading-relaxed border-t border-gray-100 pt-8">
30
+ Echo ๆ˜ฏไธ€ๆฌพ่‡ดๅŠ›ไบŽๆๅ‡ Reddit ไบ’ๅŠจ่ดจ้‡็š„ๅ…่ดนๅทฅๅ…ทใ€‚ๆˆ‘ไปฌๅˆฉ็”จ AI ๆŠ€ๆœฏๅธฎๅŠฉ็”จๆˆทๆ›ด้ซ˜ๆ•ˆๅœฐๅ‚ไธŽ็คพๅŒบ่ฎจ่ฎบใ€‚
31
+ </p>
32
+ </div>
33
+ </main>
34
+ );
35
+ }
frontend/src/app/echo/privacy/page.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Link from 'next/link';
2
+
3
+ export default function EchoPrivacyPage() {
4
+ return (
5
+ <div className="min-h-screen bg-white text-gray-900 font-sans py-16 px-6 lg:px-8">
6
+ <div className="max-w-3xl mx-auto">
7
+ <Link href="/echo" className="text-blue-600 hover:underline mb-8 inline-block">โ† Back to Echo</Link>
8
+ <h1 className="text-4xl font-bold mb-8">Echo Privacy Policy</h1>
9
+
10
+ <div className="prose prose-blue max-w-none text-gray-600">
11
+ <p className="mb-4">Last updated: February 8, 2026</p>
12
+
13
+ <h2 className="text-2xl font-bold text-gray-900 mt-8 mb-4">1. Introduction</h2>
14
+ <p className="mb-4">
15
+ Echo ("we", "our", or "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, and safeguard your information when you use the Echo browser extension and website.
16
+ </p>
17
+
18
+ <h2 className="text-2xl font-bold text-gray-900 mt-8 mb-4">2. Data Collection</h2>
19
+ <p className="mb-4">
20
+ <strong>Personal Data:</strong> We use Firebase Authentication to manage user login states. While we do not store your Reddit password, we may process unique user identifiers to provide personalized features. We do not collect personal identification information such as your name or email address.
21
+ </p>
22
+ <p className="mb-4">
23
+ <strong>Usage Data:</strong> We collect activity logs (such as clicks and feature interactions) to improve performance and debug errors. We may also collect anonymous usage statistics and store AI-generated responses as well as the final responses provided by users to improve our AI models and service quality.
24
+ </p>
25
+
26
+ <h2 className="text-2xl font-bold text-gray-900 mt-8 mb-4">3. Data Processing</h2>
27
+ <p className="mb-4">
28
+ When you request a reply suggestion, the content of the post you are viewing is processed by our AI servers to generate a relevant response. We store the final AI-generated responses to monitor the quality of our service and to further refine our AI models for better context-aware suggestions. However, this data is not linked to your personal identity.
29
+ </p>
30
+
31
+ <h2 className="text-2xl font-bold text-gray-900 mt-8 mb-4">4. Third-Party Services</h2>
32
+ <p className="mb-4">
33
+ We use AI models from third-party providers. No personally identifiable information is shared with these providers.
34
+ </p>
35
+
36
+ <h2 className="text-2xl font-bold text-gray-900 mt-8 mb-4">5. Your Rights</h2>
37
+ <p className="mb-4">
38
+ Since we do not store personal data, there is no data to export or delete. You can stop all data collection by simply uninstalling the browser extension.
39
+ </p>
40
+
41
+ <h2 className="text-2xl font-bold text-gray-900 mt-8 mb-4">6. Contact Us</h2>
42
+ <p className="mb-4">
43
+ If you have any questions about this Privacy Policy, please contact us through the main QuickTools contact channels.
44
+ </p>
45
+ </div>
46
+ </div>
47
+ </div>
48
+ );
49
+ }
frontend/src/app/en/echo/page.tsx ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import EchoAssistant from '@/components/EchoAssistant';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "Echo - Free Reddit Reply Assistant | AI Reddit Growth Tool",
6
+ description: "Boost your Reddit engagement with Echo. AI-powered context-aware replies to help you build community presence and karma effortlessly.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/en/echo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/echo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/echo',
12
+ },
13
+ },
14
+ openGraph: {
15
+ title: "Echo - Free AI Reddit Assistant",
16
+ description: "AI-powered Reddit engagement tool. Craft high-quality replies and build community presence effortlessly.",
17
+ url: 'https://quicktools.dpdns.org/en/echo',
18
+ locale: 'en_US',
19
+ type: 'website',
20
+ images: ['https://quicktools.dpdns.org/next.svg'],
21
+ },
22
+ };
23
+
24
+ export default function EnglishEchoPage() {
25
+ return (
26
+ <main>
27
+ <EchoAssistant locale="en" />
28
+ <div className="max-w-4xl mx-auto px-4 pb-12 text-center">
29
+ <p className="text-xs text-gray-400 leading-relaxed border-t border-gray-100 pt-8">
30
+ Echo is a free tool dedicated to improving Reddit engagement quality. We use AI technology to help users participate in community discussions more efficiently.
31
+ </p>
32
+ </div>
33
+ </main>
34
+ );
35
+ }
frontend/src/app/en/page.tsx ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "QuickTools - Professional Online Tool Collection",
6
+ description: "A collection of simple, fast, and privacy-first online tools for daily tasks. Explore VisaBerry for passport photos and more.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/en',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org',
11
+ 'en-US': 'https://quicktools.dpdns.org/en',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "QuickTools - Professional Online Tool Collection",
22
+ description: "A collection of simple, fast, and privacy-first online tools for daily tasks.",
23
+ url: 'https://quicktools.dpdns.org/en',
24
+ locale: 'en_US',
25
+ images: ['https://quicktools.dpdns.org/next.svg'],
26
+ },
27
+ };
28
+
29
+ const LANGS = [
30
+ { code: 'zh', label: 'ไธญๆ–‡', path: '/' },
31
+ { code: 'en', label: 'English', path: '/en' },
32
+ { code: 'de', label: 'Deutsch', path: '/de' },
33
+ { code: 'es', label: 'Espaรฑol', path: '/es' },
34
+ { code: 'fr', label: 'Franรงais', path: '/fr' },
35
+ { code: 'ru', label: 'ะ ัƒััะบะธะน', path: '/ru' },
36
+ { code: 'ja', label: 'ๆ—ฅๆœฌ่ชž', path: '/ja' },
37
+ { code: 'ko', label: 'ํ•œ๊ตญ์–ด', path: '/ko' }
38
+ ];
39
+
40
+ export default function EnglishPortalHome() {
41
+ return (
42
+ <div className="min-h-screen bg-white text-gray-900 font-sans">
43
+ <div className="absolute top-4 right-4 z-10 flex flex-wrap gap-2 justify-end max-w-xs">
44
+ {LANGS.map((l) => (
45
+ <Link
46
+ key={l.code}
47
+ href={l.path}
48
+ className={`px-3 py-1 rounded-full text-xs font-medium transition-colors border
49
+ ${l.code === 'en' ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-600 border-gray-200 hover:bg-gray-100'}`}
50
+ >
51
+ {l.label}
52
+ </Link>
53
+ ))}
54
+ </div>
55
+ <header className="relative overflow-hidden bg-gray-50 pt-12 pb-16 sm:pt-16 sm:pb-20">
56
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
57
+ <div className="mx-auto max-w-2xl text-center">
58
+ <h1 className="text-5xl font-extrabold tracking-tight text-blue-600 sm:text-6xl mb-4">QuickTools</h1>
59
+ <p className="text-xl leading-8 text-gray-600">Simple ยท Fast ยท Privacy-First<br/>A growing collection of useful tools for everyone.</p>
60
+ </div>
61
+ </div>
62
+ </header>
63
+ <main className="mx-auto max-w-7xl px-6 py-16 lg:px-8">
64
+ <div className="grid grid-cols-1 gap-12 sm:grid-cols-2 lg:grid-cols-3">
65
+ <Link href="/en/visa-photo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
66
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ธ</div>
67
+ <h2 className="text-2xl font-bold mb-3">VisaBerry</h2>
68
+ <p className="text-gray-600 text-center text-sm leading-relaxed text-balance">
69
+ <strong>100% Free</strong> Professional Visa & Passport photo maker. No registration required. Auto background removal and biometric cropping.
70
+ </p>
71
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Launch Tool <span>&rarr;</span></div>
72
+ </Link>
73
+
74
+ <Link href="/en/echo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
75
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ฃ</div>
76
+ <h2 className="text-2xl font-bold mb-3">Echo</h2>
77
+ <p className="text-gray-600 text-center text-sm leading-relaxed text-balance">
78
+ <strong>Free Reddit Assistant</strong>. AI-powered reply assistant to help you boost engagement and karma with high-quality responses on Reddit.
79
+ </p>
80
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Launch Tool <span>&rarr;</span></div>
81
+ </Link>
82
+
83
+ <div className="flex flex-col items-center justify-center p-8 bg-gray-50 rounded-3xl border border-dashed border-gray-300 opacity-60 text-center">
84
+ <div className="w-16 h-16 bg-gray-200 text-gray-500 rounded-2xl flex items-center justify-center text-3xl mb-6">๐Ÿ“„</div>
85
+ <h2 className="text-2xl font-bold mb-3 text-gray-400">Coming Soon</h2>
86
+ <p className="text-gray-500 text-center text-sm">More powerful tools are currently under development. Stay tuned!</p>
87
+ </div>
88
+ </div>
89
+ </main>
90
+ </div>
91
+ );
92
+ }
93
+
frontend/src/app/en/visa-photo/page.tsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import VisaPhotoMaker from '@/components/VisaPhotoMaker';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "VisaBerry - Free Visa Photo Maker | Passport Photo Generator Online",
6
+ description: "Create standard biometric visa and passport photos for free. Supports US 2x2 inch, Schengen 35x45mm, China, Japan, and more. Automatic background removal and cropping.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/en/visa-photo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/visa-photo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/visa-photo',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de/visa-photo',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es/visa-photo',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr/visa-photo',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru/visa-photo',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja/visa-photo',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko/visa-photo',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "VisaBerry - Free Visa Photo Maker Online",
22
+ description: "Create standard biometric visa and passport photos for free. AI background removal and automatic cropping.",
23
+ url: 'https://quicktools.dpdns.org/en/visa-photo',
24
+ locale: 'en_US',
25
+ type: 'article',
26
+ images: ['https://quicktools.dpdns.org/next.svg'],
27
+ },
28
+ };
29
+
30
+ export default function EnglishHome() {
31
+ return (
32
+ <main>
33
+ <VisaPhotoMaker locale="en" />
34
+ <div className="max-w-4xl mx-auto px-4 pb-12 text-center">
35
+ <p className="text-xs text-gray-400 leading-relaxed border-t border-gray-100 pt-8">
36
+ VisaBerry AI provides free online tools to create standard biometric visa and passport photos. Supports US 2x2 inch, Schengen 35x45mm, China, and more with automatic background removal and smart cropping.
37
+ </p>
38
+ </div>
39
+ </main>
40
+ );
41
+ }
frontend/src/app/es/page.tsx ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "QuickTools - Colecciรณn de Herramientas Online Profesionales",
6
+ description: "Herramientas online simples, rรกpidas y privadas. Explora VisaBerry para fotos de pasaporte y mรกs.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/es',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org',
11
+ 'en-US': 'https://quicktools.dpdns.org/en',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "QuickTools - Colecciรณn de Herramientas Online Profesionales",
22
+ description: "Herramientas online simples, rรกpidas y privadas.",
23
+ url: 'https://quicktools.dpdns.org/es',
24
+ locale: 'es_ES',
25
+ images: ['https://quicktools.dpdns.org/next.svg'],
26
+ },
27
+ };
28
+
29
+ const LANGS = [
30
+ { code: 'zh', label: 'ไธญๆ–‡', path: '/' },
31
+ { code: 'en', label: 'English', path: '/en' },
32
+ { code: 'de', label: 'Deutsch', path: '/de' },
33
+ { code: 'es', label: 'Espaรฑol', path: '/es' },
34
+ { code: 'fr', label: 'Franรงais', path: '/fr' },
35
+ { code: 'ru', label: 'ะ ัƒััะบะธะน', path: '/ru' },
36
+ { code: 'ja', label: 'ๆ—ฅๆœฌ่ชž', path: '/ja' },
37
+ { code: 'ko', label: 'ํ•œ๊ตญ์–ด', path: '/ko' }
38
+ ];
39
+
40
+ export default function SpanishPortalHome() {
41
+ return (
42
+ <div className="min-h-screen bg-white text-gray-900 font-sans">
43
+ <div className="absolute top-4 right-4 z-10 flex flex-wrap gap-2 justify-end max-w-xs">
44
+ {LANGS.map((l) => (
45
+ <Link key={l.code} href={l.path} className={`px-3 py-1 rounded-full text-xs font-medium border ${l.code === 'es' ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-600 border-gray-200 hover:bg-gray-100'}`}>{l.label}</Link>
46
+ ))}
47
+ </div>
48
+ <header className="relative overflow-hidden bg-gray-50 pt-12 pb-16 sm:pt-16 sm:pb-20 text-center">
49
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
50
+ <div className="mx-auto max-w-2xl text-center">
51
+ <h1 className="text-5xl font-extrabold tracking-tight text-blue-600 sm:text-6xl mb-4">QuickTools</h1>
52
+ <p className="text-xl leading-8 text-gray-600">Simple ยท Rรกpido ยท Privacidad Primero<br/>Una colecciรณn creciente de herramientas รบtiles para todos.</p>
53
+ </div>
54
+ </div>
55
+ </header>
56
+ <main className="max-w-7xl mx-auto px-6 py-16 lg:px-8">
57
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12">
58
+ <Link href="/es/visa-photo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
59
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ธ</div>
60
+ <h2 className="text-2xl font-bold mb-3">VisaBerry</h2>
61
+ <p className="text-gray-600 text-sm text-balance">
62
+ <strong>100% Gratis</strong>. Creador profesional de fotos para visas y pasaportes. Fondo automรกtico y recorte inteligente.
63
+ </p>
64
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Abrir Herramienta <span>&rarr;</span></div>
65
+ </Link>
66
+ <div className="flex flex-col items-center justify-center p-8 bg-gray-50 rounded-3xl border border-dashed border-gray-300 opacity-60 text-center">
67
+ <div className="w-16 h-16 bg-gray-200 text-gray-400 rounded-2xl flex items-center justify-center text-3xl mb-6">๐Ÿ“„</div>
68
+ <h2 className="text-2xl font-bold mb-3 text-gray-400">Prรณximamente</h2>
69
+ <p className="text-gray-400 text-center text-sm">Se estรกn desarrollando mรกs herramientas. ยกMantente informado!</p>
70
+ </div>
71
+ </div>
72
+ </main>
73
+ </div>
74
+ );
75
+ }
frontend/src/app/es/visa-photo/page.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import VisaPhotoMaker from '@/components/VisaPhotoMaker';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "VisaBerry - Generador de Fotos de Visa Gratis | Foto de Pasaporte en Lรญnea",
6
+ description: "Crea fotos biomรฉtricas para pasaportes y visas gratis en lรญnea. Compatible con EE. UU., Espaรฑa, Mรฉxico y mรกs. Eliminaciรณn de fondo automรกtica y recorte inteligente.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/es/visa-photo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/visa-photo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/visa-photo',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de/visa-photo',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es/visa-photo',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr/visa-photo',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru/visa-photo',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja/visa-photo',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko/visa-photo',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "VisaBerry - Generador de Fotos de Visa Gratis",
22
+ description: "Crea fotos biomรฉtricas para pasaportes y visas gratis en lรญnea.",
23
+ url: 'https://quicktools.dpdns.org/es/visa-photo',
24
+ locale: 'es_ES',
25
+ type: 'article',
26
+ images: ['https://quicktools.dpdns.org/next.svg'],
27
+ },
28
+ };
29
+
30
+ export default function SpanishHome() {
31
+ return <VisaPhotoMaker locale="es" />;
32
+ }
frontend/src/app/favicon.ico ADDED
frontend/src/app/fr/page.tsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "QuickTools - Collection d'Outils en Ligne Professionnels",
6
+ description: "Outils en ligne simples, rapides et privรฉs. Dรฉcouvrez VisaBerry pour vos photos de passeport et plus encore.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/fr',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org',
11
+ 'en-US': 'https://quicktools.dpdns.org/en',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "QuickTools - Collection d'Outils en Ligne Professionnels",
22
+ description: "Outils en ligne simples, rapides et privรฉs.",
23
+ url: 'https://quicktools.dpdns.org/fr',
24
+ locale: 'fr_FR',
25
+ images: ['https://quicktools.dpdns.org/next.svg'],
26
+ },
27
+ };
28
+
29
+ const LANGS = [
30
+ { code: 'zh', label: 'ไธญๆ–‡', path: '/' },
31
+ { code: 'en', label: 'English', path: '/en' },
32
+ { code: 'de', label: 'Deutsch', path: '/de' },
33
+ { code: 'es', label: 'Espaรฑol', path: '/es' },
34
+ { code: 'fr', label: 'Franรงais', path: '/fr' },
35
+ { code: 'ru', label: 'ะ ัƒััะบะธะน', path: '/ru' },
36
+ { code: 'ja', label: 'ๆ—ฅๆœฌ่ชž', path: '/ja' },
37
+ { code: 'ko', label: 'ํ•œ๊ตญ์–ด', path: '/ko' }
38
+ ];
39
+
40
+ export default function FrenchPortalHome() {
41
+ return (
42
+ <div className="min-h-screen bg-white text-gray-900 font-sans">
43
+ <div className="absolute top-4 right-4 z-10 flex flex-wrap gap-2 justify-end max-w-xs">
44
+ {LANGS.map((l) => (
45
+ <Link key={l.code} href={l.path} className={`px-3 py-1 rounded-full text-xs font-medium border ${l.code === 'fr' ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-600 border-gray-200 hover:bg-gray-100'}`}>{l.label}</Link>
46
+ ))}
47
+ </div>
48
+ <header className="relative overflow-hidden bg-gray-50 pt-12 pb-16 sm:pt-16 sm:pb-20 text-center">
49
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
50
+ <div className="mx-auto max-w-2xl text-center">
51
+ <h1 className="text-5xl font-extrabold tracking-tight text-blue-600 sm:text-6xl mb-4">QuickTools</h1>
52
+ <p className="text-xl leading-8 text-gray-600">Simple ยท Rapide ยท Confidentialitรฉ Avant Tout<br/>Une collection croissante d'outils utiles pour tous.</p>
53
+ </div>
54
+ </div>
55
+ </header>
56
+ <main className="max-w-7xl mx-auto px-6 py-16 lg:px-8">
57
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12">
58
+ <Link href="/fr/visa-photo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
59
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ธ</div>
60
+ <h2 className="text-2xl font-bold mb-3">VisaBerry</h2>
61
+ <p className="text-gray-600 text-sm text-balance">
62
+ <strong>100% Gratuit</strong>. Crรฉateur professionnel de photos de passeport et de visa. Suppression du fond et recadrage intelligent.
63
+ </p>
64
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Lancer l'outil <span>&rarr;</span></div>
65
+ </Link>
66
+
67
+ <Link href="/en/echo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
68
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ฃ</div>
69
+ <h2 className="text-2xl font-bold mb-3">Echo</h2>
70
+ <p className="text-gray-600 text-center text-sm leading-relaxed text-balance">
71
+ <strong>Free Reddit Assistant</strong>. AI-powered reply assistant to help you boost engagement and karma with high-quality responses on Reddit.
72
+ </p>
73
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Launch Tool <span>&rarr;</span></div>
74
+ </Link>
75
+ <div className="flex flex-col items-center justify-center p-8 bg-gray-50 rounded-3xl border border-dashed border-gray-300 opacity-60 text-center">
76
+ <div className="w-16 h-16 bg-gray-200 text-gray-400 rounded-2xl flex items-center justify-center text-3xl mb-6">๐Ÿ“„</div>
77
+ <h2 className="text-2xl font-bold mb-3 text-gray-400">Prochainement</h2>
78
+ <p className="text-gray-400 text-center text-sm">D'autres outils sont en cours de dรฉveloppement. Restez ร  l'รฉcoute !</p>
79
+ </div>
80
+ </div>
81
+ </main>
82
+ </div>
83
+ );
84
+ }
frontend/src/app/fr/visa-photo/page.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import VisaPhotoMaker from '@/components/VisaPhotoMaker';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "VisaBerry - Gรฉnรฉrateur de Photos de Visa Gratuit | Photo de Passeport en Ligne",
6
+ description: "Crรฉez des photos de passeport et de visa biomรฉtriques gratuitement en ligne. Compatible avec les ร‰tats-Unis, la France, le Canada, etc. Suppression automatique de l'arriรจre-plan.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/fr/visa-photo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/visa-photo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/visa-photo',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de/visa-photo',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es/visa-photo',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr/visa-photo',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru/visa-photo',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja/visa-photo',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko/visa-photo',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "VisaBerry - Gรฉnรฉrateur de Photos de Visa Gratuit",
22
+ description: "Crรฉez des photos de passeport et de visa biomรฉtriques gratuitement en ligne.",
23
+ url: 'https://quicktools.dpdns.org/fr/visa-photo',
24
+ locale: 'fr_FR',
25
+ type: 'article',
26
+ images: ['https://quicktools.dpdns.org/next.svg'],
27
+ },
28
+ };
29
+
30
+ export default function FrenchHome() {
31
+ return <VisaPhotoMaker locale="fr" />;
32
+ }
frontend/src/app/globals.css ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import "tailwindcss";
2
+
3
+ :root {
4
+ --background: #ffffff;
5
+ --foreground: #171717;
6
+ }
7
+
8
+ @theme inline {
9
+ --color-background: var(--background);
10
+ --color-foreground: var(--foreground);
11
+ --font-sans: var(--font-geist-sans);
12
+ --font-mono: var(--font-geist-mono);
13
+ }
14
+
15
+ @media (prefers-color-scheme: dark) {
16
+ :root {
17
+ --background: #0a0a0a;
18
+ --foreground: #ededed;
19
+ }
20
+ }
21
+
22
+ body {
23
+ background: var(--background);
24
+ color: var(--foreground);
25
+ font-family: Arial, Helvetica, sans-serif;
26
+ }
frontend/src/app/ja/page.tsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "QuickTools - ใƒ—ใƒญไป•ๆง˜ใ‚ชใƒณใƒฉใ‚คใƒณใƒ„ใƒผใƒซใ‚ณใƒฌใ‚ฏใ‚ทใƒงใƒณ",
6
+ description: "ใ‚ทใƒณใƒ—ใƒซใ€้ซ˜้€Ÿใ€ใƒ—ใƒฉใ‚คใƒใ‚ทใƒผใ‚’้‡่ฆ–ใ—ใŸใ‚ชใƒณใƒฉใ‚คใƒณใƒ„ใƒผใƒซใ€‚ใƒ“ใ‚ถใƒปใƒ‘ใ‚นใƒใƒผใƒˆๅ†™็œŸไฝœๆˆใฎVisaBerryใชใฉใ€‚",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/ja',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org',
11
+ 'en-US': 'https://quicktools.dpdns.org/en',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "QuickTools - ใƒ—ใƒญไป•ๆง˜ใ‚ชใƒณใƒฉใ‚คใƒณใƒ„ใƒผใƒซใ‚ณใƒฌใ‚ฏใ‚ทใƒงใƒณ",
22
+ description: "ใ‚ทใƒณใƒ—ใƒซใ€้ซ˜้€Ÿใ€ใƒ—ใƒฉใ‚คใƒใ‚ทใƒผใ‚’้‡่ฆ–ใ—ใŸใ‚ชใƒณใƒฉใ‚คใƒณใƒ„ใƒผใƒซใ€‚",
23
+ url: 'https://quicktools.dpdns.org/ja',
24
+ locale: 'ja_JP',
25
+ images: ['https://quicktools.dpdns.org/next.svg'],
26
+ },
27
+ };
28
+
29
+ const LANGS = [
30
+ { code: 'zh', label: 'ไธญๆ–‡', path: '/' },
31
+ { code: 'en', label: 'English', path: '/en' },
32
+ { code: 'de', label: 'Deutsch', path: '/de' },
33
+ { code: 'es', label: 'Espaรฑol', path: '/es' },
34
+ { code: 'fr', label: 'Franรงais', path: '/fr' },
35
+ { code: 'ru', label: 'ะ ัƒััะบะธะน', path: '/ru' },
36
+ { code: 'ja', label: 'ๆ—ฅๆœฌ่ชž', path: '/ja' },
37
+ { code: 'ko', label: 'ํ•œ๊ตญ์–ด', path: '/ko' }
38
+ ];
39
+
40
+ export default function JapanesePortalHome() {
41
+ return (
42
+ <div className="min-h-screen bg-white text-gray-900 font-sans">
43
+ <div className="absolute top-4 right-4 z-10 flex flex-wrap gap-2 justify-end max-w-xs">
44
+ {LANGS.map((l) => (
45
+ <Link key={l.code} href={l.path} className={`px-3 py-1 rounded-full text-xs font-medium border ${l.code === 'ja' ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-600 border-gray-200 hover:bg-gray-100'}`}>{l.label}</Link>
46
+ ))}
47
+ </div>
48
+ <header className="relative overflow-hidden bg-gray-50 pt-12 pb-16 sm:pt-16 sm:pb-20 text-center">
49
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
50
+ <div className="mx-auto max-w-2xl text-center">
51
+ <h1 className="text-5xl font-extrabold tracking-tight text-blue-600 sm:text-6xl mb-4">QuickTools</h1>
52
+ <p className="text-xl leading-8 text-gray-600">ใ‚ทใƒณใƒ—ใƒซ ใƒป ้ซ˜้€Ÿ ใƒป ใƒ—ใƒฉใ‚คใƒใ‚ทใƒผ้‡่ฆ–<br/>่ชฐใ‚‚ใŒไฝฟใˆใ‚‹ใ€้€ฒๅŒ–ใ—็ถšใ‘ใ‚‹ไพฟๅˆฉใชใƒ„ใƒผใƒซใ‚ณใƒฌใ‚ฏใ‚ทใƒงใƒณใ€‚</p>
53
+ </div>
54
+ </div>
55
+ </header>
56
+ <main className="max-w-7xl mx-auto px-6 py-16 lg:px-8">
57
+ <div className="grid grid-cols-1 gap-12 sm:grid-cols-2 lg:grid-cols-3">
58
+ <Link href="/ja/visa-photo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
59
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ธ</div>
60
+ <h2 className="text-2xl font-bold mb-3">VisaBerry</h2>
61
+ <p className="text-gray-600 text-sm text-balance">
62
+ <strong>100% ๅฎŒๅ…จ็„กๆ–™</strong>ใ€‚ใƒ—ใƒญไป•ๆง˜ of ใƒ“ใ‚ถใƒปใƒ‘ใ‚นใƒใƒผใƒˆๅ†™็œŸไฝœๆˆใƒ„ใƒผใƒซใ€‚่‡ชๅ‹•่ƒŒๆ™ฏ้™คๅŽปใจใ‚นใƒžใƒผใƒˆใƒˆใƒชใƒŸใƒณใ‚ฐใ€‚
63
+ </p>
64
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">ใƒ„ใƒผใƒซใ‚’่ตทๅ‹• <span>&rarr;</span></div>
65
+ </Link>
66
+
67
+ <Link href="/en/echo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
68
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ฃ</div>
69
+ <h2 className="text-2xl font-bold mb-3">Echo</h2>
70
+ <p className="text-gray-600 text-center text-sm leading-relaxed text-balance">
71
+ <strong>Free Reddit Assistant</strong>. AI-powered reply assistant to help you boost engagement and karma with high-quality responses on Reddit.
72
+ </p>
73
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">Launch Tool <span>&rarr;</span></div>
74
+ </Link>
75
+ <div className="flex flex-col items-center justify-center p-8 bg-gray-50 rounded-3xl border border-dashed border-gray-300 opacity-60 text-center">
76
+ <div className="w-16 h-16 bg-gray-200 text-gray-400 rounded-2xl flex items-center justify-center text-3xl mb-6">๐Ÿ“„</div>
77
+ <h2 className="text-2xl font-bold mb-3 text-gray-400">่ฟ‘ๆ—ฅๅ…ฌ้–‹</h2>
78
+ <p className="text-gray-400 text-center text-sm">ใ•ใ‚‰ใซๅผทๅŠ›ใชใƒ„ใƒผใƒซใ‚’้–‹็™บไธญใงใ™ใ€‚ใŠๆฅฝใ—ใฟใซ๏ผ</p>
79
+ </div>
80
+ </div>
81
+ </main>
82
+ </div>
83
+ );
84
+ }
frontend/src/app/ja/visa-photo/page.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import VisaPhotoMaker from '@/components/VisaPhotoMaker';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "VisaBerry - ็„กๆ–™ ่จผๆ˜Žๅ†™็œŸใƒกใƒผใ‚ซใƒผ | ใ‚ชใƒณใƒฉใ‚คใƒณใƒ“ใ‚ถใƒปใƒ‘ใ‚นใƒใƒผใƒˆๅ†™็œŸไฝœๆˆ",
6
+ description: "ใ‚ชใƒณใƒฉใ‚คใƒณใง็„กๆ–™ใงใƒ“ใ‚ถใ‚„ใƒ‘ใ‚นใƒใƒผใƒˆใฎ่จผๆ˜Žๅ†™็œŸใ‚’ไฝœๆˆใ€‚็ฑณๅ›ฝใ€ๆ—ฅๆœฌใ€ไธญๅ›ฝใชใฉใซๅฏพๅฟœใ€‚่‡ชๅ‹•่ƒŒๆ™ฏ้™คๅŽปใจใƒˆใƒชใƒŸใƒณใ‚ฐๆฉŸ่ƒฝไป˜ใใ€‚",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/ja/visa-photo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/visa-photo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/visa-photo',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de/visa-photo',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es/visa-photo',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr/visa-photo',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru/visa-photo',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja/visa-photo',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko/visa-photo',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "VisaBerry - ็„กๆ–™ ่จผๆ˜Žๅ†™็œŸใƒกใƒผใ‚ซใƒผ",
22
+ description: "ใ‚ชใƒณใƒฉใ‚คใƒณใง็„กๆ–™ใงใƒ“ใ‚ถใ‚„ใƒ‘ใ‚นใƒใƒผใƒˆใฎ่จผๆ˜Žๅ†™็œŸใ‚’ไฝœๆˆใ€‚",
23
+ url: 'https://quicktools.dpdns.org/ja/visa-photo',
24
+ locale: 'ja_JP',
25
+ type: 'article',
26
+ images: ['https://quicktools.dpdns.org/next.svg'],
27
+ },
28
+ };
29
+
30
+ export default function JapaneseHome() {
31
+ return <VisaPhotoMaker locale="ja" />;
32
+ }
frontend/src/app/ko/page.tsx ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import Link from "next/link";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "QuickTools - ์ „๋ฌธ ์˜จ๋ผ์ธ ๋„๊ตฌ ์ปฌ๋ ‰์…˜",
6
+ description: "๊ฐ„ํŽธํ•˜๊ณ  ๋น ๋ฅด๋ฉฐ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ์˜จ๋ผ์ธ ๋„๊ตฌ. ์—ฌ๊ถŒ ์‚ฌ์ง„ ์ œ์ž‘์„ ์œ„ํ•œ VisaBerry ๋“ฑ์„ ์‚ดํŽด๋ณด์„ธ์š”.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/ko',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org',
11
+ 'en-US': 'https://quicktools.dpdns.org/en',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "QuickTools - ์ „๋ฌธ ์˜จ๋ผ์ธ ๋„๊ตฌ ์ปฌ๋ ‰์…˜",
22
+ description: "๊ฐ„ํŽธํ•˜๊ณ  ๋น ๋ฅด๋ฉฐ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ์˜จ๋ผ์ธ ๋„๊ตฌ.",
23
+ url: 'https://quicktools.dpdns.org/ko',
24
+ locale: 'ko_KR',
25
+ images: ['https://quicktools.dpdns.org/next.svg'],
26
+ },
27
+ };
28
+
29
+ const LANGS = [
30
+ { code: 'zh', label: 'ไธญๆ–‡', path: '/' },
31
+ { code: 'en', label: 'English', path: '/en' },
32
+ { code: 'de', label: 'Deutsch', path: '/de' },
33
+ { code: 'es', label: 'Espaรฑol', path: '/es' },
34
+ { code: 'fr', label: 'Franรงais', path: '/fr' },
35
+ { code: 'ru', label: 'ะ ัƒััะบะธะน', path: '/ru' },
36
+ { code: 'ja', label: 'ๆ—ฅๆœฌ่ชž', path: '/ja' },
37
+ { code: 'ko', label: 'ํ•œ๊ตญ์–ด', path: '/ko' }
38
+ ];
39
+
40
+ export default function KoreanPortalHome() {
41
+ return (
42
+ <div className="min-h-screen bg-white text-gray-900 font-sans">
43
+ <div className="absolute top-4 right-4 z-10 flex flex-wrap gap-2 justify-end max-w-xs">
44
+ {LANGS.map((l) => (
45
+ <Link key={l.code} href={l.path} className={`px-3 py-1 rounded-full text-xs font-medium border ${l.code === 'ko' ? 'bg-blue-600 text-white border-blue-600' : 'bg-white text-gray-600 border-gray-200 hover:bg-gray-100'}`}>{l.label}</Link>
46
+ ))}
47
+ </div>
48
+ <header className="relative overflow-hidden bg-gray-50 pt-12 pb-16 sm:pt-16 sm:pb-20 text-center">
49
+ <div className="mx-auto max-w-7xl px-6 lg:px-8">
50
+ <div className="mx-auto max-w-2xl text-center">
51
+ <h1 className="text-5xl font-extrabold tracking-tight text-blue-600 sm:text-6xl mb-4">QuickTools</h1>
52
+ <p className="text-xl leading-8 text-gray-600">๊ฐ„ํŽธํ•จ ยท ์‹ ์†ํ•จ ยท ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ<br/>๋ชจ๋‘๋ฅผ ์œ„ํ•œ ๊ณ„์†ํ•ด์„œ ์„ฑ์žฅํ•˜๋Š” ์œ ์šฉํ•œ ๋„๊ตฌ ๋ชจ์Œใ€‚</p>
53
+ </div>
54
+ </div>
55
+ </header>
56
+ <main className="max-w-7xl mx-auto px-6 py-16 lg:px-8">
57
+ <div className="grid grid-cols-1 gap-12 sm:grid-cols-2 lg:grid-cols-3">
58
+ <Link href="/ko/visa-photo" className="group flex flex-col items-center p-8 bg-white rounded-3xl border border-gray-200 shadow-sm transition-all duration-300 hover:shadow-xl hover:-translate-y-1 group-hover:border-blue-400 text-center">
59
+ <div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-2xl flex items-center justify-center text-3xl mb-6 group-hover:bg-blue-600 group-hover:text-white transition-colors">๐Ÿ“ธ</div>
60
+ <h2 className="text-2xl font-bold mb-3">VisaBerry</h2>
61
+ <p className="text-gray-600 text-sm text-balance">
62
+ <strong>100% ๋ฌด๋ฃŒ</strong>. ์ „๋ฌธ์ ์ธ ๋น„์ž ๋ฐ ์—ฌ๊ถŒ ์‚ฌ์ง„ ์ œ์ž‘ ๋„๊ตฌ. ์ž๋™ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ๋ฐ ์Šค๋งˆํŠธ ํฌ๊ธฐ ์กฐ์ •.
63
+ </p>
64
+ <div className="mt-6 flex items-center text-blue-600 font-semibold group-hover:gap-2 transition-all">๋„๊ตฌ ์‹œ์ž‘ <span>&rarr;</span></div>
65
+ </Link>
66
+ <div className="flex flex-col items-center justify-center p-8 bg-gray-50 rounded-3xl border border-dashed border-gray-300 opacity-60 text-center">
67
+ <div className="w-16 h-16 bg-gray-200 text-gray-400 rounded-2xl flex items-center justify-center text-3xl mb-6">๐Ÿ“„</div>
68
+ <h2 className="text-2xl font-bold mb-3 text-gray-400">๊ณง ์ถœ์‹œ ์˜ˆ์ •</h2>
69
+ <p className="text-gray-400 text-center text-sm">๋”์šฑ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๋“ค์ด ๊ฐœ๋ฐœ ์ค‘์ž…๋‹ˆ๋‹ค. ๊ธฐ๋Œ€ํ•ด ์ฃผ์„ธ์š”!</p>
70
+ </div>
71
+ </div>
72
+ </main>
73
+ </div>
74
+ );
75
+ }
frontend/src/app/ko/visa-photo/page.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import VisaPhotoMaker from '@/components/VisaPhotoMaker';
3
+
4
+ export const metadata: Metadata = {
5
+ title: "VisaBerry - ๋ฌด๋ฃŒ ๋น„์ž ์‚ฌ์ง„ ๋ฉ”์ด์ปค | ์˜จ๋ผ์ธ ์—ฌ๊ถŒ ์‚ฌ์ง„ ์ƒ์„ฑ๊ธฐ",
6
+ description: "์˜จ๋ผ์ธ์—์„œ ๋ฌด๋ฃŒ๋กœ ๋น„์ž ๋ฐ ์—ฌ๊ถŒ ์‚ฌ์ง„์„ ๋งŒ๋“œ์„ธ์š”. ๋ฏธ๊ตญ, ํ•œ๊ตญ, ์ค‘๊ตญ ๋“ฑ ์ง€์›. ์ž๋™ ๋ฐฐ๊ฒฝ ์ œ๊ฑฐ ๋ฐ ํฌ๊ธฐ ์กฐ์ •.",
7
+ alternates: {
8
+ canonical: 'https://quicktools.dpdns.org/ko/visa-photo',
9
+ languages: {
10
+ 'zh-CN': 'https://quicktools.dpdns.org/visa-photo',
11
+ 'en-US': 'https://quicktools.dpdns.org/en/visa-photo',
12
+ 'de-DE': 'https://quicktools.dpdns.org/de/visa-photo',
13
+ 'es-ES': 'https://quicktools.dpdns.org/es/visa-photo',
14
+ 'fr-FR': 'https://quicktools.dpdns.org/fr/visa-photo',
15
+ 'ru-RU': 'https://quicktools.dpdns.org/ru/visa-photo',
16
+ 'ja-JP': 'https://quicktools.dpdns.org/ja/visa-photo',
17
+ 'ko-KR': 'https://quicktools.dpdns.org/ko/visa-photo',
18
+ },
19
+ },
20
+ openGraph: {
21
+ title: "VisaBerry - ๋ฌด๋ฃŒ ๋น„์ž ์‚ฌ์ง„ ๋ฉ”์ด์ปค",
22
+ description: "์˜จ๋ผ์ธ์—์„œ ๋ฌด๋ฃŒ๋กœ ๋น„์ž ๋ฐ ์—ฌ๊ถŒ ์‚ฌ์ง„์„ ๋งŒ๋“œ์„ธ์š”.",
23
+ url: 'https://quicktools.dpdns.org/ko/visa-photo',
24
+ locale: 'ko_KR',
25
+ type: 'article',
26
+ images: ['https://quicktools.dpdns.org/next.svg'],
27
+ },
28
+ };
29
+
30
+ export default function KoreanHome() {
31
+ return <VisaPhotoMaker locale="ko" />;
32
+ }
frontend/src/app/layout.tsx ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import "./globals.css";
4
+ import Analytics from "@/components/Analytics";
5
+ import Footer from "@/components/Footer";
6
+
7
+ const geistSans = Geist({
8
+ variable: "--font-geist-sans",
9
+ subsets: ["latin"],
10
+ display: 'swap',
11
+ });
12
+
13
+ const geistMono = Geist_Mono({
14
+ variable: "--font-geist-mono",
15
+ subsets: ["latin"],
16
+ display: 'swap',
17
+ });
18
+
19
+ export const metadata: Metadata = {
20
+ title: "QuickTools - Fast, Simple, Privacy-First Online Tools",
21
+ description: "A collection of professional online tools for visa photos, document processing and more. Built with privacy and speed in mind.",
22
+ openGraph: {
23
+ siteName: 'QuickTools',
24
+ type: 'website',
25
+ locale: 'en_US',
26
+ images: [{
27
+ url: 'https://quicktools.dpdns.org/next.svg',
28
+ width: 800,
29
+ height: 600,
30
+ }],
31
+ },
32
+ twitter: {
33
+ card: 'summary_large_image',
34
+ title: 'QuickTools',
35
+ description: 'Fast, Simple, Privacy-First Online Tools',
36
+ images: ['https://quicktools.dpdns.org/next.svg'],
37
+ },
38
+ icons: {
39
+ icon: 'data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>๐Ÿ“ธ</text></svg>',
40
+ },
41
+ manifest: '/manifest.json',
42
+ };
43
+
44
+ export default function RootLayout({
45
+ children,
46
+ }: Readonly<{
47
+ children: React.ReactNode;
48
+ }>) {
49
+ // Simple heuristic: in a real app we'd use usePathname but this is a Server Component.
50
+ // Next.js handles 'lang' best via i18n routing, but for this structure, we'll keep it simple.
51
+ return (
52
+ <html lang="en" suppressHydrationWarning>
53
+ <head>
54
+ <link rel="preconnect" href="https://api-gateway.umami.dev" />
55
+ </head>
56
+ <body
57
+ className={`${geistSans.variable} ${geistMono.variable} antialiased flex flex-col min-h-screen`}
58
+ suppressHydrationWarning
59
+ >
60
+ <Analytics />
61
+ <main className="flex-1">
62
+ {children}
63
+ </main>
64
+ <Footer />
65
+ </body>
66
+ </html>
67
+ );
68
+ }