Karan6933 commited on
Commit
c85705b
·
verified ·
1 Parent(s): 3feee3a

Upload 93 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +0 -0
  2. .env.example +21 -0
  3. .gitignore +8 -0
  4. Dockerfile +61 -0
  5. README.md +1 -11
  6. data/projects.db +0 -0
  7. deploy.sh +38 -0
  8. docker-compose.yml +47 -0
  9. ecosystem.config.js +29 -0
  10. fly-deploy.sh +57 -0
  11. fly.toml +69 -0
  12. node.Dockerfile +39 -0
  13. nodemon.json +7 -0
  14. package-lock.json +0 -0
  15. package.json +49 -0
  16. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/index.html +15 -0
  17. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/package.json +34 -0
  18. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/postcss.config.js +6 -0
  19. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/App.tsx +7 -0
  20. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/components/TodoInput.tsx +44 -0
  21. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/components/TodoItem.tsx +49 -0
  22. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/components/TodoList.tsx +50 -0
  23. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/index.css +29 -0
  24. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/main.tsx +10 -0
  25. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/pages/TodoPage.tsx +62 -0
  26. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/routes.tsx +15 -0
  27. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/types.ts +5 -0
  28. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/utils/animations.ts +16 -0
  29. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/utils/storage.ts +16 -0
  30. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/tailwind.config.js +23 -0
  31. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/tsconfig.json +22 -0
  32. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/tsconfig.node.json +9 -0
  33. projects/060c1fce-28f4-472a-b360-97eb9dd5065d/vite.config.ts +11 -0
  34. projects/706236f9-12ab-40ac-989e-75c18042cdb2/index.html +15 -0
  35. projects/706236f9-12ab-40ac-989e-75c18042cdb2/package.json +34 -0
  36. projects/706236f9-12ab-40ac-989e-75c18042cdb2/postcss.config.js +6 -0
  37. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/App.tsx +7 -0
  38. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/components/TodoInput.tsx +44 -0
  39. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/components/TodoItem.tsx +49 -0
  40. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/components/TodoList.tsx +50 -0
  41. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/index.css +29 -0
  42. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/main.tsx +10 -0
  43. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/pages/TodoPage.tsx +62 -0
  44. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/routes.tsx +15 -0
  45. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/types.ts +5 -0
  46. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/utils/animations.ts +16 -0
  47. projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/utils/storage.ts +16 -0
  48. projects/706236f9-12ab-40ac-989e-75c18042cdb2/tailwind.config.js +23 -0
  49. projects/706236f9-12ab-40ac-989e-75c18042cdb2/tsconfig.json +22 -0
  50. projects/706236f9-12ab-40ac-989e-75c18042cdb2/tsconfig.node.json +9 -0
.env ADDED
File without changes
.env.example ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ── Server ────────────────────────────────────────────────────────────────────
2
+ PORT=4000
3
+ NODE_ENV=production
4
+
5
+ # ── Dynamic Container Port Range ──────────────────────────────────────────────
6
+ # Each user project gets a unique host port in this range.
7
+ # Make sure the same range is exposed in docker-compose.yml ports section.
8
+ BASE_HOST_PORT=3100
9
+ MAX_HOST_PORT=3300
10
+
11
+ # ── Docker ────────────────────────────────────────────────────────────────────
12
+ # Path to Docker socket (default for Linux; use 'npipe:////./pipe/docker_engine' on Windows)
13
+ DOCKER_SOCKET=/var/run/docker.sock
14
+
15
+ # ── Resource Limits per Container ─────────────────────────────────────────────
16
+ CONTAINER_MEMORY_MB=512
17
+ CONTAINER_CPU=0.5
18
+
19
+ # ── Cleanup ───────────────────────────────────────────────────────────────────
20
+ # How long (ms) a project can be idle before its container is stopped
21
+ CONTAINER_IDLE_TIMEOUT_MS=3600000
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ data/*.db
5
+ data/*.db-wal
6
+ data/*.db-shm
7
+ logs/
8
+ projects/
Dockerfile ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ─── Stage 1: Build TypeScript ───────────────────────────────────────────────
2
+ FROM node:20-slim AS builder
3
+
4
+ WORKDIR /app
5
+ COPY package*.json ./
6
+ RUN npm ci
7
+ COPY tsconfig.json ./
8
+ COPY src/ ./src/
9
+ RUN npm run build
10
+
11
+ # ─── Stage 2: Production Image ───────────────────────────────────────────────
12
+ # We use a full Debian image (not slim) so we can install Docker CLI.
13
+ # The Docker daemon itself runs on the HOST (Fly machine in privileged mode).
14
+ # This container only needs the Docker CLI + socket to talk to it.
15
+ FROM node:20-bookworm AS runner
16
+
17
+ WORKDIR /app
18
+
19
+ # ── Install Docker CLI (client only, not the daemon) ──────────────────────────
20
+ RUN apt-get update && apt-get install -y --no-install-recommends \
21
+ ca-certificates \
22
+ curl \
23
+ gnupg \
24
+ lsb-release \
25
+ && install -m 0755 -d /etc/apt/keyrings \
26
+ && curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \
27
+ && chmod a+r /etc/apt/keyrings/docker.gpg \
28
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" \
29
+ > /etc/apt/sources.list.d/docker.list \
30
+ && apt-get update \
31
+ && apt-get install -y --no-install-recommends docker-ce-cli \
32
+ && rm -rf /var/lib/apt/lists/*
33
+
34
+ # ── Install native Node deps (better-sqlite3 needs build tools) ───────────────
35
+ RUN apt-get update && apt-get install -y --no-install-recommends \
36
+ python3 make g++ \
37
+ && rm -rf /var/lib/apt/lists/*
38
+
39
+ COPY package*.json ./
40
+ RUN npm ci --omit=dev
41
+
42
+ # Copy compiled output from builder
43
+ COPY --from=builder /app/dist ./dist
44
+
45
+ # Worker Dockerfiles needed at runtime for building project images
46
+ COPY node.Dockerfile ./node.Dockerfile
47
+ COPY python.Dockerfile ./python.Dockerfile
48
+
49
+ # Create directories for persistent data
50
+ RUN mkdir -p /app/projects /app/data /app/logs
51
+
52
+ EXPOSE 4000
53
+
54
+ ENV NODE_ENV=production
55
+ ENV PORT=4000
56
+
57
+ # Healthcheck
58
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
59
+ CMD curl -f http://localhost:4000/health || exit 1
60
+
61
+ CMD ["node", "dist/index.js"]
README.md CHANGED
@@ -1,11 +1 @@
1
- ---
2
- title: Docker Backend
3
- emoji: 🐠
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # docker-container-manager
 
 
 
 
 
 
 
 
 
 
data/projects.db ADDED
Binary file (28.7 kB). View file
 
deploy.sh ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
5
+ echo " Coder AI — Deploy Script"
6
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
7
+
8
+ # ── 1. Pre-build worker images (if not already built) ──
9
+ echo ""
10
+ echo "▶ Building worker images..."
11
+
12
+ if ! docker image inspect coder-node &>/dev/null; then
13
+ echo " Building coder-node..."
14
+ docker build -f node.Dockerfile -t coder-node .
15
+ else
16
+ echo " coder-node already exists — skipping"
17
+ fi
18
+
19
+ if ! docker image inspect coder-python &>/dev/null; then
20
+ echo " Building coder-python..."
21
+ docker build -f python.Dockerfile -t coder-python .
22
+ else
23
+ echo " coder-python already exists — skipping"
24
+ fi
25
+
26
+ # ── 2. Pull/build orchestrator image ──
27
+ echo ""
28
+ echo "▶ Building orchestrator image..."
29
+ docker-compose build --no-cache orchestrator
30
+
31
+ # ── 3. Start (or restart) via docker-compose ──
32
+ echo ""
33
+ echo "▶ Starting orchestrator..."
34
+ docker-compose up -d --remove-orphans
35
+
36
+ echo ""
37
+ echo "✅ Deployed! Orchestrator running on port ${PORT:-4000}"
38
+ echo " Logs: docker-compose logs -f orchestrator"
docker-compose.yml ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.9'
2
+
3
+ services:
4
+ # ── Coder AI Orchestrator ────────────────────────────────────────────────────
5
+ orchestrator:
6
+ build:
7
+ context: .
8
+ dockerfile: Dockerfile
9
+ container_name: coderai-orchestrator
10
+ restart: unless-stopped
11
+ ports:
12
+ # Main API + proxy port
13
+ - "${PORT:-4000}:4000"
14
+ # Dynamic project preview ports range (3100–3300)
15
+ # Extend this range as you onboard more concurrent users
16
+ - "3100-3300:3100-3300"
17
+ volumes:
18
+ # Allow orchestrator to spawn sibling Docker containers
19
+ - /var/run/docker.sock:/var/run/docker.sock
20
+ # Persist SQLite DB across restarts
21
+ - orchestrator_data:/app/data
22
+ # Persist project source files across restarts
23
+ - orchestrator_projects:/app/projects
24
+ environment:
25
+ - NODE_ENV=production
26
+ - PORT=4000
27
+ - BASE_HOST_PORT=${BASE_HOST_PORT:-3100}
28
+ - MAX_HOST_PORT=${MAX_HOST_PORT:-3300}
29
+ env_file:
30
+ - .env
31
+ logging:
32
+ driver: "json-file"
33
+ options:
34
+ max-size: "10m"
35
+ max-file: "3"
36
+ healthcheck:
37
+ test: ["CMD", "curl", "-f", "http://localhost:4000/health"]
38
+ interval: 30s
39
+ timeout: 10s
40
+ retries: 3
41
+ start_period: 20s
42
+
43
+ volumes:
44
+ orchestrator_data:
45
+ driver: local
46
+ orchestrator_projects:
47
+ driver: local
ecosystem.config.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ apps: [
3
+ {
4
+ name: 'coderai-server',
5
+ script: 'dist/index.js',
6
+ instances: 1, // Must be 1 — Docker socket is not safe to share across workers
7
+ exec_mode: 'fork',
8
+ watch: false,
9
+ max_memory_restart: '512M',
10
+ env: {
11
+ NODE_ENV: 'development',
12
+ PORT: 4000,
13
+ },
14
+ env_production: {
15
+ NODE_ENV: 'production',
16
+ PORT: 4000,
17
+ },
18
+ // Log settings
19
+ out_file: './logs/out.log',
20
+ error_file: './logs/error.log',
21
+ log_date_format: 'YYYY-MM-DD HH:mm:ss',
22
+ merge_logs: true,
23
+ // Auto-restart on crash with exponential backoff
24
+ autorestart: true,
25
+ restart_delay: 2000,
26
+ max_restarts: 10,
27
+ },
28
+ ],
29
+ };
fly-deploy.sh ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # fly-deploy.sh — Full Fly.io deploy + Docker-in-Docker setup
3
+ # Run this once for initial deploy, then just `fly deploy` for updates.
4
+ set -euo pipefail
5
+
6
+ APP=${FLY_APP_NAME:-"coderai-orchestrator"}
7
+
8
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
9
+ echo " Coder AI → Fly.io Deploy"
10
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
11
+
12
+ # ── Check flyctl is installed ──────────────────────────────────────────────
13
+ if ! command -v flyctl &>/dev/null; then
14
+ echo "Installing flyctl..."
15
+ curl -L https://fly.io/install.sh | sh
16
+ export PATH="$HOME/.fly/bin:$PATH"
17
+ fi
18
+
19
+ # ── Create app if it doesn't exist ────────────────────────────────────────
20
+ flyctl apps list | grep -q "$APP" || {
21
+ echo "▶ Creating Fly app: $APP"
22
+ flyctl apps create "$APP" --machines
23
+ }
24
+
25
+ # ── Create persistent volume (only first time) ────────────────────────────
26
+ flyctl volumes list -a "$APP" | grep -q "coderai_data" || {
27
+ echo "▶ Creating persistent volume (1GB)..."
28
+ flyctl volumes create coderai_data --size 1 --region sin -a "$APP"
29
+ }
30
+
31
+ # ── Set secrets from .env ─────────────────────────────────────────────────
32
+ if [ -f .env ]; then
33
+ echo "▶ Setting secrets from .env..."
34
+ # Filter out comments and empty lines, set as Fly secrets
35
+ grep -v '^\s*#' .env | grep -v '^\s*$' | flyctl secrets import -a "$APP"
36
+ fi
37
+
38
+ # ── Deploy ────────────────────────────────────────────────────────────────
39
+ echo "▶ Deploying..."
40
+ flyctl deploy --remote-only -a "$APP"
41
+
42
+ # ── Enable Docker-in-Docker (privileged mode) ─────────────────────────────
43
+ echo ""
44
+ echo "▶ Enabling Docker socket access (privileged machine)..."
45
+ MACHINE_ID=$(flyctl machines list -a "$APP" --json | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])")
46
+
47
+ flyctl machines update "$MACHINE_ID" \
48
+ --privileged \
49
+ -a "$APP" \
50
+ --yes
51
+
52
+ echo ""
53
+ echo "✅ Done! App running at: https://${APP}.fly.dev"
54
+ echo " Health: https://${APP}.fly.dev/health"
55
+ echo ""
56
+ echo " For updates: flyctl deploy -a $APP"
57
+ echo " For logs: flyctl logs -a $APP"
fly.toml ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # fly.toml — Fly.io deployment config for Coder AI Orchestrator
2
+ # Deploy: fly deploy
3
+
4
+ app = "coderai-orchestrator" # Change to your unique app name
5
+ primary_region = "sin" # Singapore — closest to India; or try "bom" if available
6
+
7
+ [build]
8
+ dockerfile = "Dockerfile"
9
+
10
+ [env]
11
+ NODE_ENV = "production"
12
+ PORT = "4000"
13
+ # Port range for user project containers (must match [[services]] TCP)
14
+ BASE_HOST_PORT = "3100"
15
+ MAX_HOST_PORT = "3200"
16
+
17
+ # ── Main HTTP service ──────────────────────────────────────────────────────────
18
+ [[services]]
19
+ protocol = "tcp"
20
+ internal_port = 4000
21
+
22
+ [[services.ports]]
23
+ port = 80
24
+ handlers = ["http"]
25
+
26
+ [[services.ports]]
27
+ port = 443
28
+ handlers = ["tls", "http"]
29
+
30
+ [services.concurrency]
31
+ type = "requests"
32
+ soft_limit = 50
33
+ hard_limit = 100
34
+
35
+ [[services.http_checks]]
36
+ interval = "15s"
37
+ timeout = "5s"
38
+ grace_period = "30s"
39
+ method = "GET"
40
+ path = "/health"
41
+
42
+ # ── Project preview port range (TCP passthrough) ──────────────────────────────
43
+ # Each user project gets a unique port in 3100-3200 range
44
+ # Fly.io exposes these as TCP services so users can hit preview URLs
45
+ [[services]]
46
+ protocol = "tcp"
47
+ internal_port = 3100
48
+
49
+ [[services.ports]]
50
+ port = 3100
51
+ handlers = ["tcp"]
52
+
53
+ # ── Machine/VM settings ───────────────────────────────────────────────────────
54
+ [vm]
55
+ cpu_kind = "shared"
56
+ cpus = 2
57
+ memory_mb = 2048 # 2GB — adjust based on concurrent users
58
+
59
+ # ── Persistent volumes (SQLite DB + project files) ────────────────────────────
60
+ [mounts]
61
+ source = "coderai_data"
62
+ destination = "/app/data"
63
+
64
+ # NOTE: Fly.io does NOT support Docker-in-Docker by default.
65
+ # To enable Docker socket access (required to spawn project containers),
66
+ # you must use a Fly Machine with --privileged flag.
67
+ # Run this once after initial deploy:
68
+ # fly machine update <machine-id> --privileged
69
+ # Or use the fly-machines.sh helper script included in this repo.
node.Dockerfile ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ─── specialized Node.js Worker Image ──────────────────────────────────────────
2
+ FROM node:20-slim
3
+
4
+ # Avoid interactive prompts
5
+ ENV DEBIAN_FRONTEND=noninteractive
6
+
7
+ RUN apt-get update && apt-get install -y \
8
+ curl \
9
+ git \
10
+ unzip \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Install Bun for speed
14
+ RUN curl -fsSL https://bun.sh/install | bash
15
+ ENV PATH="/root/.bun/bin:${PATH}"
16
+
17
+ # ─── Pre-cache common packages ────────────────────────────────────────────────
18
+ WORKDIR /tmp/precache
19
+ RUN echo '{\
20
+ "name":"precache",\
21
+ "version":"1.0.0",\
22
+ "dependencies":{\
23
+ "next":"14.2.0",\
24
+ "react":"^18.3.1",\
25
+ "react-dom":"^18.3.1",\
26
+ "typescript":"^5.5.3",\
27
+ "tailwindcss":"^3.4.1",\
28
+ "postcss":"^8.4.38",\
29
+ "vite":"^5.4.0",\
30
+ "@vitejs/plugin-react":"^4.3.1",\
31
+ "framer-motion":"^11.3.8",\
32
+ "lucide-react":"^0.395.0"\
33
+ }\
34
+ }' > package.json
35
+ RUN bun install
36
+
37
+ WORKDIR /app
38
+ EXPOSE 3000 5173 8080 4173
39
+ CMD ["tail", "-f", "/dev/null"]
nodemon.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "watch": ["src"],
3
+ "ignore": ["projects/**", "node_modules/**"],
4
+ "ext": "ts,json",
5
+ "exec": "ts-node src/index.ts",
6
+ "env": {}
7
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "server-orchestrator",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "dev": "nodemon",
8
+ "build": "tsc",
9
+ "start": "node dist/index.js",
10
+ "start:dev": "ts-node src/index.ts",
11
+ "start:prod": "npm run build && node dist/index.js",
12
+ "pm2:start": "npm run build && pm2 start ecosystem.config.js --env production",
13
+ "pm2:stop": "pm2 stop coderai-server",
14
+ "pm2:logs": "pm2 logs coderai-server",
15
+ "docker:build": "bash deploy.sh",
16
+ "test": "echo \"Error: no test specified\" && exit 1"
17
+ },
18
+ "keywords": [],
19
+ "author": "",
20
+ "license": "ISC",
21
+ "type": "commonjs",
22
+ "dependencies": {
23
+ "@types/better-sqlite3": "^7.6.13",
24
+ "@types/fs-extra": "^11.0.4",
25
+ "@types/http-proxy": "^1.17.17",
26
+ "@types/tar-fs": "^2.0.4",
27
+ "archiver": "^7.0.1",
28
+ "better-sqlite3": "^12.6.2",
29
+ "cors": "^2.8.6",
30
+ "dockerode": "^4.0.9",
31
+ "dotenv": "^17.3.1",
32
+ "express": "^5.2.1",
33
+ "fs-extra": "^11.3.4",
34
+ "http-proxy": "^1.18.1",
35
+ "tar-fs": "^3.1.2",
36
+ "uuid": "^13.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/archiver": "^7.0.0",
40
+ "@types/cors": "^2.8.19",
41
+ "@types/dockerode": "^4.0.1",
42
+ "@types/express": "^5.0.6",
43
+ "@types/node": "^25.4.0",
44
+ "@types/uuid": "^10.0.0",
45
+ "nodemon": "^3.1.14",
46
+ "ts-node": "^10.9.2",
47
+ "typescript": "^5.9.3"
48
+ }
49
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/index.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Neon Task</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&display=swap" rel="stylesheet">
10
+ </head>
11
+ <body class="bg-background text-text font-['Syne']">
12
+ <div id="root"></div>
13
+ <script type="module" src="/src/main.tsx"></script>
14
+ </body>
15
+ </html>
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/package.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "neon-tasks",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "framer-motion": "^11.3.8",
14
+ "lucide-react": "^0.395.0",
15
+ "react": "^18.3.1",
16
+ "react-dom": "^18.3.1",
17
+ "react-router-dom": "^6.26.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^18.3.3",
21
+ "@types/react-dom": "^18.3.0",
22
+ "@typescript-eslint/eslint-plugin": "^6.10.0",
23
+ "@typescript-eslint/parser": "^6.10.0",
24
+ "@vitejs/plugin-react": "^4.3.1",
25
+ "autoprefixer": "^10.4.19",
26
+ "eslint": "^8.53.0",
27
+ "eslint-plugin-react-hooks": "^4.6.0",
28
+ "eslint-plugin-react-refresh": "^0.4.4",
29
+ "postcss": "^8.4.38",
30
+ "tailwindcss": "^3.4.1",
31
+ "typescript": "^5.5.3",
32
+ "vite": "^5.4.0"
33
+ }
34
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/App.tsx ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import Routes from './routes';
2
+
3
+ function App() {
4
+ return <Routes />;
5
+ }
6
+
7
+ export default App;
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/components/TodoInput.tsx ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import { motion } from 'framer-motion';
3
+
4
+ type TodoInputProps = {
5
+ onAdd: (text: string) => void;
6
+ };
7
+
8
+ export default function TodoInput({ onAdd }: TodoInputProps) {
9
+ const [text, setText] = useState('');
10
+
11
+ const handleSubmit = (e: React.FormEvent) => {
12
+ e.preventDefault();
13
+ if (text.trim()) {
14
+ onAdd(text);
15
+ setText('');
16
+ }
17
+ };
18
+
19
+ return (
20
+ <motion.form
21
+ onSubmit={handleSubmit}
22
+ initial={{ opacity: 0, y: 20 }}
23
+ animate={{ opacity: 1, y: 0 }}
24
+ transition={{ duration: 0.5 }}
25
+ className="flex gap-4 mb-8"
26
+ >
27
+ <input
28
+ type="text"
29
+ value={text}
30
+ onChange={(e) => setText(e.target.value)}
31
+ placeholder="What needs to be done?"
32
+ className="flex-1 px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-gray-100 placeholder-gray-400 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/50 transition-all"
33
+ />
34
+ <motion.button
35
+ whileHover={{ scale: 1.05 }}
36
+ whileTap={{ scale: 0.95 }}
37
+ type="submit"
38
+ className="px-6 py-3 bg-primary text-gray-900 font-bold rounded-lg hover:bg-primary/90 transition-all"
39
+ >
40
+ Add
41
+ </motion.button>
42
+ </motion.form>
43
+ );
44
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/components/TodoItem.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { Todo } from '../types';
3
+ import { Check, X } from 'lucide-react';
4
+
5
+ interface TodoItemProps {
6
+ todo: Todo;
7
+ onToggle: (id: number) => void;
8
+ onDelete: (id: number) => void;
9
+ index: number;
10
+ }
11
+
12
+ export default function TodoItem({ todo, onToggle, onDelete, index }: TodoItemProps) {
13
+ return (
14
+ <motion.li
15
+ initial={{ opacity: 0, y: 20 }}
16
+ animate={{ opacity: 1, y: 0 }}
17
+ exit={{ opacity: 0, x: -100 }}
18
+ transition={{ duration: 0.3, delay: index * 0.1 }}
19
+ className="flex items-center justify-between p-4 rounded-lg bg-black/30 border border-white/10 mb-3 last:mb-0 hover:bg-black/50 transition-all duration-300"
20
+ >
21
+ <div className="flex items-center gap-4">
22
+ <button
23
+ onClick={() => onToggle(todo.id)}
24
+ className={`w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all duration-300 ${
25
+ todo.completed
26
+ ? 'border-primary bg-primary/20'
27
+ : 'border-white/30 hover:border-primary'
28
+ }`}
29
+ >
30
+ {todo.completed && <Check size={16} className="text-primary" />}
31
+ </button>
32
+ <span
33
+ className={`text-lg transition-all duration-300 ${
34
+ todo.completed ? 'text-white/50 line-through' : 'text-text'
35
+ }`}
36
+ >
37
+ {todo.text}
38
+ </span>
39
+ </div>
40
+
41
+ <button
42
+ onClick={() => onDelete(todo.id)}
43
+ className="text-white/30 hover:text-red-400 transition-colors duration-300 p-1 rounded-full hover:bg-red-400/10"
44
+ >
45
+ <X size={20} />
46
+ </button>
47
+ </motion.li>
48
+ );
49
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/components/TodoList.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import TodoItem from './TodoItem';
3
+ import { Todo } from '../types';
4
+
5
+ interface TodoListProps {
6
+ todos: Todo[];
7
+ onToggle: (id: number) => void;
8
+ onDelete: (id: number) => void;
9
+ }
10
+
11
+ export default function TodoList({ todos, onToggle, onDelete }: TodoListProps) {
12
+ if (todos.length === 0) {
13
+ return (
14
+ <motion.div
15
+ initial={{ opacity: 0 }}
16
+ animate={{ opacity: 1 }}
17
+ className="text-center text-white/50 py-8"
18
+ >
19
+ No tasks yet. Add one above!
20
+ </motion.div>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <motion.ul
26
+ initial="hidden"
27
+ animate="visible"
28
+ variants={{
29
+ hidden: { opacity: 0 },
30
+ visible: {
31
+ opacity: 1,
32
+ transition: {
33
+ staggerChildren: 0.1
34
+ }
35
+ }
36
+ }}
37
+ className="mt-6"
38
+ >
39
+ {todos.map((todo, index) => (
40
+ <TodoItem
41
+ key={todo.id}
42
+ todo={todo}
43
+ onToggle={onToggle}
44
+ onDelete={onDelete}
45
+ index={index}
46
+ />
47
+ ))}
48
+ </motion.ul>
49
+ );
50
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/index.css ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ html {
7
+ background: radial-gradient(ellipse at 50% 30%, rgba(0, 245, 255, 0.1) 0%, transparent 70%),
8
+ linear-gradient(135deg, rgba(10, 10, 26, 0.8) 0%, rgba(20, 20, 40, 0.8) 100%);
9
+ min-height: 100vh;
10
+ }
11
+
12
+ body {
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+ }
16
+ }
17
+
18
+ @keyframes glow {
19
+ 0%, 100% {
20
+ box-shadow: 0 0 10px rgba(0, 245, 255, 0.3);
21
+ }
22
+ 50% {
23
+ box-shadow: 0 0 20px rgba(0, 245, 255, 0.6);
24
+ }
25
+ }
26
+
27
+ .glow {
28
+ animation: glow 3s ease-in-out infinite;
29
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/pages/TodoPage.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+ import { Todo } from '../types';
3
+ import TodoInput from '../components/TodoInput';
4
+ import TodoList from '../components/TodoList';
5
+ import { loadTodos, saveTodos } from '../utils/storage';
6
+ import { AnimatePresence } from 'framer-motion';
7
+
8
+ export default function TodoPage() {
9
+ const [todos, setTodos] = useState<Todo[]>([]);
10
+ const [animationKey, setAnimationKey] = useState(0);
11
+
12
+ useEffect(() => {
13
+ setTodos(loadTodos());
14
+ }, []);
15
+
16
+ useEffect(() => {
17
+ saveTodos(todos);
18
+ }, [todos]);
19
+
20
+ const addTodo = (text: string) => {
21
+ const newTodo: Todo = {
22
+ id: Date.now(),
23
+ text,
24
+ completed: false,
25
+ };
26
+ setTodos([...todos, newTodo]);
27
+ setAnimationKey(prev => prev + 1);
28
+ };
29
+
30
+ const toggleTodo = (id: number) => {
31
+ setTodos(todos.map(todo =>
32
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo
33
+ ));
34
+ };
35
+
36
+ const deleteTodo = (id: number) => {
37
+ setTodos(todos.filter(todo => todo.id !== id));
38
+ };
39
+
40
+ return (
41
+ <div className="min-h-screen bg-background text-text font-syne">
42
+ <div className="container mx-auto px-4 py-16 max-w-2xl">
43
+ <h1 className="text-5xl font-bold text-center mb-12 text-primary glow">
44
+ Neon Tasks
45
+ </h1>
46
+
47
+ <div className="bg-black/20 backdrop-blur-sm rounded-2xl p-8 border border-white/10 shadow-2xl shadow-primary/10">
48
+ <TodoInput onAdd={addTodo} />
49
+
50
+ <AnimatePresence mode="wait">
51
+ <TodoList
52
+ key={animationKey}
53
+ todos={todos}
54
+ onToggle={toggleTodo}
55
+ onDelete={deleteTodo}
56
+ />
57
+ </AnimatePresence>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ );
62
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/routes.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
2
+ import TodoPage from './pages/TodoPage';
3
+
4
+ const router = createBrowserRouter([
5
+ {
6
+ path: '/',
7
+ element: <TodoPage />,
8
+ },
9
+ ]);
10
+
11
+ const Routes = () => {
12
+ return <RouterProvider router={router} />;
13
+ };
14
+
15
+ export default Routes;
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/types.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export interface Todo {
2
+ id: number
3
+ text: string
4
+ completed: boolean
5
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/utils/animations.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Variants } from 'framer-motion';
2
+
3
+ export const fadeInUp: Variants = {
4
+ hidden: { opacity: 0, y: 20 },
5
+ visible: { opacity: 1, y: 0 },
6
+ };
7
+
8
+ export const staggerContainer = {
9
+ hidden: { opacity: 0 },
10
+ visible: {
11
+ opacity: 1,
12
+ transition: {
13
+ staggerChildren: 0.1,
14
+ },
15
+ },
16
+ };
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/src/utils/storage.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Todo } from '../types';
2
+
3
+ const STORAGE_KEY = 'neon-tasks';
4
+
5
+ export const loadTodos = (): Todo[] => {
6
+ try {
7
+ const stored = localStorage.getItem(STORAGE_KEY);
8
+ return stored ? JSON.parse(stored) : [];
9
+ } catch {
10
+ return [];
11
+ }
12
+ };
13
+
14
+ export const saveTodos = (todos: Todo[]): void => {
15
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
16
+ };
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/tailwind.config.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ primary: '#00f5ff',
11
+ background: '#0a0a1a',
12
+ text: '#e0e0e0',
13
+ },
14
+ fontFamily: {
15
+ sans: ['Syne', 'sans-serif'],
16
+ },
17
+ animation: {
18
+ 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
19
+ }
20
+ },
21
+ },
22
+ plugins: [],
23
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/tsconfig.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Node",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+ "types": ["vite/client"]
19
+ },
20
+ "include": ["src"],
21
+ "references": [{ "path": "./tsconfig.node.json" }]
22
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/tsconfig.node.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": ["vite.config.ts"]
9
+ }
projects/060c1fce-28f4-472a-b360-97eb9dd5065d/vite.config.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ host: '0.0.0.0',
8
+ port: 3000,
9
+ strictPort: true
10
+ }
11
+ })
projects/706236f9-12ab-40ac-989e-75c18042cdb2/index.html ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Neon Task</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&display=swap" rel="stylesheet">
10
+ </head>
11
+ <body class="bg-background text-text font-['Syne']">
12
+ <div id="root"></div>
13
+ <script type="module" src="/src/main.tsx"></script>
14
+ </body>
15
+ </html>
projects/706236f9-12ab-40ac-989e-75c18042cdb2/package.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "neon-tasks",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc && vite build",
9
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "framer-motion": "^11.3.8",
14
+ "lucide-react": "^0.395.0",
15
+ "react": "^18.3.1",
16
+ "react-dom": "^18.3.1",
17
+ "react-router-dom": "^6.26.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^18.3.3",
21
+ "@types/react-dom": "^18.3.0",
22
+ "@typescript-eslint/eslint-plugin": "^6.10.0",
23
+ "@typescript-eslint/parser": "^6.10.0",
24
+ "@vitejs/plugin-react": "^4.3.1",
25
+ "autoprefixer": "^10.4.19",
26
+ "eslint": "^8.53.0",
27
+ "eslint-plugin-react-hooks": "^4.6.0",
28
+ "eslint-plugin-react-refresh": "^0.4.4",
29
+ "postcss": "^8.4.38",
30
+ "tailwindcss": "^3.4.1",
31
+ "typescript": "^5.5.3",
32
+ "vite": "^5.4.0"
33
+ }
34
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/App.tsx ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import Routes from './routes';
2
+
3
+ function App() {
4
+ return <Routes />;
5
+ }
6
+
7
+ export default App;
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/components/TodoInput.tsx ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import { motion } from 'framer-motion';
3
+
4
+ type TodoInputProps = {
5
+ onAdd: (text: string) => void;
6
+ };
7
+
8
+ export default function TodoInput({ onAdd }: TodoInputProps) {
9
+ const [text, setText] = useState('');
10
+
11
+ const handleSubmit = (e: React.FormEvent) => {
12
+ e.preventDefault();
13
+ if (text.trim()) {
14
+ onAdd(text);
15
+ setText('');
16
+ }
17
+ };
18
+
19
+ return (
20
+ <motion.form
21
+ onSubmit={handleSubmit}
22
+ initial={{ opacity: 0, y: 20 }}
23
+ animate={{ opacity: 1, y: 0 }}
24
+ transition={{ duration: 0.5 }}
25
+ className="flex gap-4 mb-8"
26
+ >
27
+ <input
28
+ type="text"
29
+ value={text}
30
+ onChange={(e) => setText(e.target.value)}
31
+ placeholder="What needs to be done?"
32
+ className="flex-1 px-4 py-3 bg-gray-800/50 border border-gray-700 rounded-lg text-gray-100 placeholder-gray-400 focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/50 transition-all"
33
+ />
34
+ <motion.button
35
+ whileHover={{ scale: 1.05 }}
36
+ whileTap={{ scale: 0.95 }}
37
+ type="submit"
38
+ className="px-6 py-3 bg-primary text-gray-900 font-bold rounded-lg hover:bg-primary/90 transition-all"
39
+ >
40
+ Add
41
+ </motion.button>
42
+ </motion.form>
43
+ );
44
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/components/TodoItem.tsx ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import { Todo } from '../types';
3
+ import { Check, X } from 'lucide-react';
4
+
5
+ interface TodoItemProps {
6
+ todo: Todo;
7
+ onToggle: (id: number) => void;
8
+ onDelete: (id: number) => void;
9
+ index: number;
10
+ }
11
+
12
+ export default function TodoItem({ todo, onToggle, onDelete, index }: TodoItemProps) {
13
+ return (
14
+ <motion.li
15
+ initial={{ opacity: 0, y: 20 }}
16
+ animate={{ opacity: 1, y: 0 }}
17
+ exit={{ opacity: 0, x: -100 }}
18
+ transition={{ duration: 0.3, delay: index * 0.1 }}
19
+ className="flex items-center justify-between p-4 rounded-lg bg-black/30 border border-white/10 mb-3 last:mb-0 hover:bg-black/50 transition-all duration-300"
20
+ >
21
+ <div className="flex items-center gap-4">
22
+ <button
23
+ onClick={() => onToggle(todo.id)}
24
+ className={`w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all duration-300 ${
25
+ todo.completed
26
+ ? 'border-primary bg-primary/20'
27
+ : 'border-white/30 hover:border-primary'
28
+ }`}
29
+ >
30
+ {todo.completed && <Check size={16} className="text-primary" />}
31
+ </button>
32
+ <span
33
+ className={`text-lg transition-all duration-300 ${
34
+ todo.completed ? 'text-white/50 line-through' : 'text-text'
35
+ }`}
36
+ >
37
+ {todo.text}
38
+ </span>
39
+ </div>
40
+
41
+ <button
42
+ onClick={() => onDelete(todo.id)}
43
+ className="text-white/30 hover:text-red-400 transition-colors duration-300 p-1 rounded-full hover:bg-red-400/10"
44
+ >
45
+ <X size={20} />
46
+ </button>
47
+ </motion.li>
48
+ );
49
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/components/TodoList.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { motion } from 'framer-motion';
2
+ import TodoItem from './TodoItem';
3
+ import { Todo } from '../types';
4
+
5
+ interface TodoListProps {
6
+ todos: Todo[];
7
+ onToggle: (id: number) => void;
8
+ onDelete: (id: number) => void;
9
+ }
10
+
11
+ export default function TodoList({ todos, onToggle, onDelete }: TodoListProps) {
12
+ if (todos.length === 0) {
13
+ return (
14
+ <motion.div
15
+ initial={{ opacity: 0 }}
16
+ animate={{ opacity: 1 }}
17
+ className="text-center text-white/50 py-8"
18
+ >
19
+ No tasks yet. Add one above!
20
+ </motion.div>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <motion.ul
26
+ initial="hidden"
27
+ animate="visible"
28
+ variants={{
29
+ hidden: { opacity: 0 },
30
+ visible: {
31
+ opacity: 1,
32
+ transition: {
33
+ staggerChildren: 0.1
34
+ }
35
+ }
36
+ }}
37
+ className="mt-6"
38
+ >
39
+ {todos.map((todo, index) => (
40
+ <TodoItem
41
+ key={todo.id}
42
+ todo={todo}
43
+ onToggle={onToggle}
44
+ onDelete={onDelete}
45
+ index={index}
46
+ />
47
+ ))}
48
+ </motion.ul>
49
+ );
50
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/index.css ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ html {
7
+ background: radial-gradient(ellipse at 50% 30%, rgba(0, 245, 255, 0.1) 0%, transparent 70%),
8
+ linear-gradient(135deg, rgba(10, 10, 26, 0.8) 0%, rgba(20, 20, 40, 0.8) 100%);
9
+ min-height: 100vh;
10
+ }
11
+
12
+ body {
13
+ -webkit-font-smoothing: antialiased;
14
+ -moz-osx-font-smoothing: grayscale;
15
+ }
16
+ }
17
+
18
+ @keyframes glow {
19
+ 0%, 100% {
20
+ box-shadow: 0 0 10px rgba(0, 245, 255, 0.3);
21
+ }
22
+ 50% {
23
+ box-shadow: 0 0 20px rgba(0, 245, 255, 0.6);
24
+ }
25
+ }
26
+
27
+ .glow {
28
+ animation: glow 3s ease-in-out infinite;
29
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/pages/TodoPage.tsx ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect } from 'react';
2
+ import { Todo } from '../types';
3
+ import TodoInput from '../components/TodoInput';
4
+ import TodoList from '../components/TodoList';
5
+ import { loadTodos, saveTodos } from '../utils/storage';
6
+ import { AnimatePresence } from 'framer-motion';
7
+
8
+ export default function TodoPage() {
9
+ const [todos, setTodos] = useState<Todo[]>([]);
10
+ const [animationKey, setAnimationKey] = useState(0);
11
+
12
+ useEffect(() => {
13
+ setTodos(loadTodos());
14
+ }, []);
15
+
16
+ useEffect(() => {
17
+ saveTodos(todos);
18
+ }, [todos]);
19
+
20
+ const addTodo = (text: string) => {
21
+ const newTodo: Todo = {
22
+ id: Date.now(),
23
+ text,
24
+ completed: false,
25
+ };
26
+ setTodos([...todos, newTodo]);
27
+ setAnimationKey(prev => prev + 1);
28
+ };
29
+
30
+ const toggleTodo = (id: number) => {
31
+ setTodos(todos.map(todo =>
32
+ todo.id === id ? { ...todo, completed: !todo.completed } : todo
33
+ ));
34
+ };
35
+
36
+ const deleteTodo = (id: number) => {
37
+ setTodos(todos.filter(todo => todo.id !== id));
38
+ };
39
+
40
+ return (
41
+ <div className="min-h-screen bg-background text-text font-syne">
42
+ <div className="container mx-auto px-4 py-16 max-w-2xl">
43
+ <h1 className="text-5xl font-bold text-center mb-12 text-primary glow">
44
+ Neon Tasks
45
+ </h1>
46
+
47
+ <div className="bg-black/20 backdrop-blur-sm rounded-2xl p-8 border border-white/10 shadow-2xl shadow-primary/10">
48
+ <TodoInput onAdd={addTodo} />
49
+
50
+ <AnimatePresence mode="wait">
51
+ <TodoList
52
+ key={animationKey}
53
+ todos={todos}
54
+ onToggle={toggleTodo}
55
+ onDelete={deleteTodo}
56
+ />
57
+ </AnimatePresence>
58
+ </div>
59
+ </div>
60
+ </div>
61
+ );
62
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/routes.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createBrowserRouter, RouterProvider } from 'react-router-dom';
2
+ import TodoPage from './pages/TodoPage';
3
+
4
+ const router = createBrowserRouter([
5
+ {
6
+ path: '/',
7
+ element: <TodoPage />,
8
+ },
9
+ ]);
10
+
11
+ const Routes = () => {
12
+ return <RouterProvider router={router} />;
13
+ };
14
+
15
+ export default Routes;
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/types.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ export interface Todo {
2
+ id: number
3
+ text: string
4
+ completed: boolean
5
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/utils/animations.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Variants } from 'framer-motion';
2
+
3
+ export const fadeInUp: Variants = {
4
+ hidden: { opacity: 0, y: 20 },
5
+ visible: { opacity: 1, y: 0 },
6
+ };
7
+
8
+ export const staggerContainer = {
9
+ hidden: { opacity: 0 },
10
+ visible: {
11
+ opacity: 1,
12
+ transition: {
13
+ staggerChildren: 0.1,
14
+ },
15
+ },
16
+ };
projects/706236f9-12ab-40ac-989e-75c18042cdb2/src/utils/storage.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Todo } from '../types';
2
+
3
+ const STORAGE_KEY = 'neon-tasks';
4
+
5
+ export const loadTodos = (): Todo[] => {
6
+ try {
7
+ const stored = localStorage.getItem(STORAGE_KEY);
8
+ return stored ? JSON.parse(stored) : [];
9
+ } catch {
10
+ return [];
11
+ }
12
+ };
13
+
14
+ export const saveTodos = (todos: Todo[]): void => {
15
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
16
+ };
projects/706236f9-12ab-40ac-989e-75c18042cdb2/tailwind.config.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {
9
+ colors: {
10
+ primary: '#00f5ff',
11
+ background: '#0a0a1a',
12
+ text: '#e0e0e0',
13
+ },
14
+ fontFamily: {
15
+ sans: ['Syne', 'sans-serif'],
16
+ },
17
+ animation: {
18
+ 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
19
+ }
20
+ },
21
+ },
22
+ plugins: [],
23
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/tsconfig.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Node",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+ "types": ["vite/client"]
19
+ },
20
+ "include": ["src"],
21
+ "references": [{ "path": "./tsconfig.node.json" }]
22
+ }
projects/706236f9-12ab-40ac-989e-75c18042cdb2/tsconfig.node.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "ESNext",
5
+ "moduleResolution": "Node",
6
+ "allowSyntheticDefaultImports": true
7
+ },
8
+ "include": ["vite.config.ts"]
9
+ }