๐ŸŽฏ ์ง‘์ค‘๋ ฅ ํƒ€์ด๋จธ & ์ผ์ผ ๋ชฉํ‘œ ํŠธ๋ž˜์ปค - ํฌ๋ชจ๋„๋กœ ๊ธฐ๋ฒ• with ์„ฑ์ทจ ๋ฆฌ์›Œ๋“œ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ค‘๊ธ‰ PHP Vanilla PHP AI: Claude

๐ŸŽฏ ์ง‘์ค‘๋ ฅ ํƒ€์ด๋จธ & ์ผ์ผ ๋ชฉํ‘œ ํŠธ๋ž˜์ปค - ํฌ๋ชจ๋„๋กœ ๊ธฐ๋ฒ• with ์„ฑ์ทจ ๋ฆฌ์›Œ๋“œ

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… focus-timer-daily-goal-tracker
์กฐํšŒ์ˆ˜ 44 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

ํฌ๋ชจ๋„๋กœ ๊ธฐ๋ฒ•(25๋ถ„ ์ง‘์ค‘ + 5๋ถ„ ํœด์‹)์„ ํ™œ์šฉํ•œ ์ง‘์ค‘๋ ฅ ํƒ€์ด๋จธ์™€ ์ผ์ผ ๋ชฉํ‘œ ๋‹ฌ์„ฑ ๊ธฐ๋ก์„ ํ†ตํ•ฉํ•œ ๊ฐœ์ธ ์ƒ์‚ฐ์„ฑ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ์˜ค๋Š˜ ํ•ด์•ผ ํ•  ์ž‘์—…์„ ๋“ฑ๋กํ•˜๊ณ , ํƒ€์ด๋จธ๋ฅผ ๋Œ๋ ค ์ง‘์ค‘ ์„ธ์…˜์„ ์™„๋ฃŒํ•˜๋ฉฐ, ์™„๋ฃŒํ•œ ์„ธ์…˜ ์ˆ˜์— ๋”ฐ๋ผ ์„ฑ์ทจ ๋ฐฐ์ง€๋ฅผ ํš๋“ํ•ฉ๋‹ˆ๋‹ค.

ํ•„์ˆ˜ ๊ธฐ๋Šฅ

  1. ์ผ์ผ ๋ชฉํ‘œ ๊ด€๋ฆฌ

    • ์˜ค๋Š˜ ํ•  ์ž‘์—…(ํƒœ์Šคํฌ) ๋“ฑ๋ก/์ˆ˜์ •/์‚ญ์ œ (SQLite ์ €์žฅ)
    • ๊ฐ ํƒœ์Šคํฌ๋ณ„ ์˜ˆ์ƒ ํฌ๋ชจ๋„๋กœ ๊ฐœ์ˆ˜ ์„ค์ •
    • ์™„๋ฃŒ ์ฒดํฌ๋ฐ•์Šค๋กœ ํƒœ์Šคํฌ ์™„๋ฃŒ ์ฒ˜๋ฆฌ
  2. ํฌ๋ชจ๋„๋กœ ํƒ€์ด๋จธ

    • 25๋ถ„ ์ง‘์ค‘ ๋ชจ๋“œ ํƒ€์ด๋จธ (JavaScript setInterval)
    • 5๋ถ„ ํœด์‹ ๋ชจ๋“œ ์ž๋™ ์ „ํ™˜
    • ํƒ€์ด๋จธ ์‹œ์ž‘/์ผ์‹œ์ •์ง€/๋ฆฌ์…‹ ๋ฒ„ํŠผ
    • ๋‚จ์€ ์‹œ๊ฐ„ ์‹ค์‹œ๊ฐ„ ํ‘œ์‹œ (๋ถ„:์ดˆ ํ˜•์‹)
    • ํƒ€์ด๋จธ ์ข…๋ฃŒ ์‹œ ๋ธŒ๋ผ์šฐ์ € ์•Œ๋ฆผ ๋˜๋Š” ์‚ฌ์šด๋“œ ์žฌ์ƒ
  3. ์„ธ์…˜ ๊ธฐ๋ก & ํ†ต๊ณ„

    • ์™„๋ฃŒํ•œ ํฌ๋ชจ๋„๋กœ ์„ธ์…˜ DB ์ €์žฅ (๋‚ ์งœ, ํƒœ์Šคํฌ๋ช…, ์™„๋ฃŒ ์‹œ๊ฐ)
    • ์˜ค๋Š˜ ์™„๋ฃŒํ•œ ์ด ์„ธ์…˜ ์ˆ˜ ํ‘œ์‹œ
    • ์ฃผ๊ฐ„/์›”๊ฐ„ ์„ธ์…˜ ํ†ต๊ณ„ (๊ฐ„๋‹จํ•œ ์ฐจํŠธ ๋˜๋Š” ์ˆซ์ž)
  4. ์„ฑ์ทจ ๋ฐฐ์ง€ ์‹œ์Šคํ…œ

    • ์„ธ์…˜ ์ˆ˜ ๊ธฐ์ค€ ๋ฐฐ์ง€ (์˜ˆ: 5์„ธ์…˜, 10์„ธ์…˜, 20์„ธ์…˜)
    • ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜ ๋ฐฐ์ง€ (์˜ˆ: 3์ผ ์—ฐ์†, 7์ผ ์—ฐ์†)
    • ๋ฐฐ์ง€ ๊ฐค๋Ÿฌ๋ฆฌ ํŽ˜์ด์ง€์—์„œ ํš๋“ํ•œ ๋ฐฐ์ง€ ํ™•์ธ
  5. UI/UX

    • ํƒ€์ด๋จธ ํ™”๋ฉด: ํฐ ์ˆซ์ž๋กœ ๋‚จ์€ ์‹œ๊ฐ„ ํ‘œ์‹œ, ์ง„ํ–‰ ๋ฐ” ๋˜๋Š” ์›ํ˜• ํ”„๋กœ๊ทธ๋ ˆ์Šค
    • ํƒœ์Šคํฌ ๋ชฉ๋ก: ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ ์šฐ์„ ์ˆœ์œ„ ๋ณ€๊ฒฝ (์„ ํƒ)
    • ๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€ (CSS ๋ณ€์ˆ˜ ํ™œ์šฉ)

์„ ํƒ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ (๋ณด๋„ˆ์Šค)

  • ์ปค์Šคํ…€ ํƒ€์ด๋จธ ๊ธธ์ด ์„ค์ • (25๋ถ„ ์™ธ ๋‹ค๋ฅธ ์‹œ๊ฐ„)
  • ๋ฐฐ๊ฒฝ์Œ์•… ๋˜๋Š” ๋ฐฑ์ƒ‰์†Œ์Œ ์žฌ์ƒ ๊ธฐ๋Šฅ
  • ์ฃผ๊ฐ„ ๋ฆฌํฌํŠธ PDF ๋‹ค์šด๋กœ๋“œ
  • ํƒœ์Šคํฌ๋ณ„ ๋ˆ„์  ํฌ๋ชจ๋„๋กœ ์‹œ๊ฐ„ ์‹œ๊ฐํ™” (ํŒŒ์ด ์ฐจํŠธ)
  • ์นœ๊ตฌ์™€ ์„ธ์…˜ ์ˆ˜ ๊ฒฝ์Ÿ (๊ฐ„๋‹จํ•œ ๋ฆฌ๋”๋ณด๋“œ)

๊ธฐ์ˆ  ์Šคํƒ

  • ๋ฐฑ์—”๋“œ: ์ผ๋ฐ˜ PHP 8.3+ (ํ”„๋ ˆ์ž„์›Œํฌ ์—†์Œ)
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: PDO + SQLite
  • ํ”„๋ก ํŠธ์—”๋“œ: ์ˆœ์ˆ˜ HTML/CSS/JavaScript (๋ฐ”๋‹๋ผ JS)
  • ๋ณด์•ˆ: prepared statement, XSS ๋ฐฉ์–ด (htmlspecialchars), CSRF ํ† ํฐ

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค๊ณ„

  • tasks ํ…Œ์ด๋ธ”: id, title, estimated_pomodoros, completed, created_at
  • sessions ํ…Œ์ด๋ธ”: id, task_id, completed_at, duration_minutes
  • badges ํ…Œ์ด๋ธ”: id, user_id, badge_type, earned_at (์„ ํƒ)

ํ•™์Šต ๋ชฉํ‘œ

  • PHP ์„ธ์…˜๊ณผ SQLite๋ฅผ ํ™œ์šฉํ•œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ ์˜์†ํ™”
  • JavaScript ํƒ€์ด๋จธ ๋กœ์ง๊ณผ ๋น„๋™๊ธฐ UI ์—…๋ฐ์ดํŠธ
  • AJAX(fetch API)๋ฅผ ํ™œ์šฉํ•œ ์„œ๋ฒ„-ํด๋ผ์ด์–ธํŠธ ํ†ต์‹ 
  • ๊ฐ„๋‹จํ•œ ๊ฒŒ์ด๋ฏธํ”ผ์ผ€์ด์…˜ ์š”์†Œ ๊ตฌํ˜„

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข ์ผ๋ฐ˜ PHP 8.3+ ์‚ฌ์šฉ (Laravel, Symfony ๋“ฑ ํ”„๋ ˆ์ž„์›Œํฌ ๊ธˆ์ง€)
  • โ€ข PDO + SQLite๋กœ ๋ฐ์ดํ„ฐ ์ €์žฅ
  • โ€ข ์ˆœ์ˆ˜ HTML/CSS/JavaScript (React, Vue ๋“ฑ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธˆ์ง€)
  • โ€ข php -S localhost:8000์œผ๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด์•ผ ํ•จ
  • โ€ข Prepared statement๋กœ SQL Injection ๋ฐฉ์–ด
  • โ€ข htmlspecialchars()๋กœ XSS ๋ฐฉ์–ด
  • โ€ข CSRF ํ† ํฐ์œผ๋กœ ํผ ๋ณดํ˜ธ
  • โ€ข ํƒ€์ด๋จธ๋Š” ํด๋ผ์ด์–ธํŠธ ์ธก JavaScript๋กœ ๊ตฌํ˜„ (์„œ๋ฒ„ ํด๋ง ๊ธˆ์ง€)
  • โ€ข ๋ฐ˜์‘ํ˜• ๋””์ž์ธ (๋ชจ๋ฐ”์ผ์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)

ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ

์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ AI์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
mkdir focus-timer-daily-goal-tracker && cd focus-timer-daily-goal-tracker

์ด ํด๋”์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

# 1๋‹จ๊ณ„: ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๋ฐ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ดˆ๊ธฐํ™”

์ผ๋ฐ˜ PHP 8.3+ ๊ธฐ๋ฐ˜ ํฌ๋ชจ๋„๋กœ ํƒ€์ด๋จธ & ์ผ์ผ ๋ชฉํ‘œ ํŠธ๋ž˜์ปค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ˆœ์ˆ˜ PHP๋กœ ๊ตฌํ˜„ํ•˜๋ฉฐ, SQLite๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.

## ํŒŒ์ผ ๊ตฌ์กฐ

```
focus-timer-daily-goal-tracker/
โ”œโ”€โ”€ index.php              # ๋ฉ”์ธ ํŽ˜์ด์ง€ (ํƒ€์ด๋จธ + ํƒœ์Šคํฌ ๋ชฉ๋ก)
โ”œโ”€โ”€ tasks.php              # ํƒœ์Šคํฌ CRUD API ์—”๋“œํฌ์ธํŠธ
โ”œโ”€โ”€ sessions.php           # ์„ธ์…˜ ๊ธฐ๋ก API ์—”๋“œํฌ์ธํŠธ
โ”œโ”€โ”€ stats.php              # ํ†ต๊ณ„ ํŽ˜์ด์ง€
โ”œโ”€โ”€ badges.php             # ๋ฐฐ์ง€ ๊ฐค๋Ÿฌ๋ฆฌ ํŽ˜์ด์ง€
โ”œโ”€โ”€ db.php                 # ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๋ฐ ์ดˆ๊ธฐํ™”
โ”œโ”€โ”€ config.php             # ์„ค์ • ํŒŒ์ผ (DB ๊ฒฝ๋กœ, CSRF ํ† ํฐ ๋“ฑ)
โ”œโ”€โ”€ style.css              # ์Šคํƒ€์ผ์‹œํŠธ
โ”œโ”€โ”€ app.js                 # ํƒ€์ด๋จธ & AJAX ๋กœ์ง
โ””โ”€โ”€ data/
    โ””โ”€โ”€ pomodoro.db        # SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŒŒ์ผ
```

## ์š”๊ตฌ์‚ฌํ•ญ

1. `config.php`: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ฒฝ๋กœ, ์„ธ์…˜ ์„ค์ •, CSRF ํ† ํฐ ์ƒ์„ฑ ํ•จ์ˆ˜ ํฌํ•จ
2. `db.php`: SQLite ์—ฐ๊ฒฐ ๋ฐ ํ…Œ์ด๋ธ” ์ƒ์„ฑ (tasks, sessions, badges)
   - `tasks`: id, title, estimated_pomodoros, completed, created_at
   - `sessions`: id, task_id, completed_at, duration_minutes
   - `badges`: id, badge_type, earned_at (์„ ํƒ)
3. `index.php`: ํƒ€์ด๋จธ UI + ์˜ค๋Š˜์˜ ํƒœ์Šคํฌ ๋ชฉ๋ก ํ‘œ์‹œ. PHP ์„ธ์…˜ ์‹œ์ž‘.
4. `tasks.php`: RESTful ์Šคํƒ€์ผ API (GET: ๋ชฉ๋ก, POST: ์ƒ์„ฑ, PUT: ์ˆ˜์ •, DELETE: ์‚ญ์ œ)
5. `sessions.php`: ์„ธ์…˜ ๊ธฐ๋ก ์ €์žฅ API (POST)
6. ๋ชจ๋“  ์ž…๋ ฅ์€ prepared statement๋กœ ์ฒ˜๋ฆฌ
7. ์ถœ๋ ฅ์€ htmlspecialchars()๋กœ ์ด์Šค์ผ€์ดํ”„

๊ฐ ํŒŒ์ผ์— ์ฃผ์„์œผ๋กœ ์—ญํ• ์„ ๋ช…์‹œํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์ด ์—†์œผ๋ฉด ์ž๋™ ์ƒ์„ฑ๋˜๋„๋ก ๊ตฌํ˜„ํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: `config.php`, `db.php`, `index.php`, `tasks.php`, `sessions.php`๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์ดˆ๊ธฐํ™”๋ฉ๋‹ˆ๋‹ค. `php -S localhost:8000`์œผ๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜๋ฉด `index.php`์—์„œ ํƒ€์ด๋จธ์™€ ํƒœ์Šคํฌ ๋ชฉ๋ก UI๊ฐ€ ๋ณด์ž…๋‹ˆ๋‹ค (์•„์ง ๊ธฐ๋Šฅ์€ ๋ฏธ๊ตฌํ˜„).

2
Step 2
# 2๋‹จ๊ณ„: ํƒœ์Šคํฌ CRUD ๊ตฌํ˜„ ๋ฐ ๋ชฉ๋ก ํ‘œ์‹œ

`tasks.php`์—์„œ ํƒœ์Šคํฌ ์ƒ์„ฑ/์กฐํšŒ/์ˆ˜์ •/์‚ญ์ œ API๋ฅผ ์™„์„ฑํ•˜๊ณ , `index.php`์—์„œ AJAX๋กœ ํƒœ์Šคํฌ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์™€ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

## ์š”๊ตฌ์‚ฌํ•ญ

1. **tasks.php**:
   - `GET /tasks.php`: ์˜ค๋Š˜ ์ƒ์„ฑ๋œ ํƒœ์Šคํฌ ๋ชฉ๋ก JSON ๋ฐ˜ํ™˜ (completed ์ƒํƒœ ํฌํ•จ)
   - `POST /tasks.php`: ์ƒˆ ํƒœ์Šคํฌ ์ƒ์„ฑ (title, estimated_pomodoros ๋ฐ›๊ธฐ)
   - `PUT /tasks.php?id={id}`: ํƒœ์Šคํฌ ์ˆ˜์ • (title, estimated_pomodoros, completed ์—…๋ฐ์ดํŠธ)
   - `DELETE /tasks.php?id={id}`: ํƒœ์Šคํฌ ์‚ญ์ œ
   - CSRF ํ† ํฐ ๊ฒ€์ฆ (POST/PUT/DELETE ์š”์ฒญ ์‹œ)
   - JSON ์‘๋‹ต: `{"success": true, "data": ...}` ๋˜๋Š” `{"success": false, "error": "..."}`

2. **index.php**:
   - ํƒœ์Šคํฌ ์ถ”๊ฐ€ ํผ (์ œ๋ชฉ, ์˜ˆ์ƒ ํฌ๋ชจ๋„๋กœ ๊ฐœ์ˆ˜ ์ž…๋ ฅ)
   - ํƒœ์Šคํฌ ๋ชฉ๋ก์„ `<ul>` ๋˜๋Š” `<div>` ๋ฆฌ์ŠคํŠธ๋กœ ํ‘œ์‹œ
   - ๊ฐ ํƒœ์Šคํฌ์— ์™„๋ฃŒ ์ฒดํฌ๋ฐ•์Šค, ์ˆ˜์ •/์‚ญ์ œ ๋ฒ„ํŠผ ์ถ”๊ฐ€
   - ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ `app.js`์—์„œ `fetch('/tasks.php')`๋กœ ๋ชฉ๋ก ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

3. **app.js**:
   - `loadTasks()`: GET ์š”์ฒญ์œผ๋กœ ํƒœ์Šคํฌ ๋ชฉ๋ก ๊ฐ€์ ธ์™€ DOM์— ๋ Œ๋”๋ง
   - `addTask(title, estimatedPomodoros)`: POST ์š”์ฒญ์œผ๋กœ ํƒœ์Šคํฌ ์ƒ์„ฑ
   - `updateTask(id, data)`: PUT ์š”์ฒญ์œผ๋กœ ํƒœ์Šคํฌ ์ˆ˜์ •
   - `deleteTask(id)`: DELETE ์š”์ฒญ์œผ๋กœ ํƒœ์Šคํฌ ์‚ญ์ œ
   - CSRF ํ† ํฐ์„ ํ—ค๋” ๋˜๋Š” ๋ฐ”๋””์— ํฌํ•จ

4. **style.css**:
   - ํƒœ์Šคํฌ ๋ชฉ๋ก ์Šคํƒ€์ผ๋ง (์ฒดํฌ๋ฐ•์Šค, ๋ฒ„ํŠผ ์ •๋ ฌ)
   - ์™„๋ฃŒ๋œ ํƒœ์Šคํฌ๋Š” ํ…์ŠคํŠธ ์ทจ์†Œ์„  ๋˜๋Š” ํšŒ์ƒ‰ ์ฒ˜๋ฆฌ

๋ชจ๋“  API ์‘๋‹ต์€ JSON ํ˜•์‹์ด๋ฉฐ, ์—๋Ÿฌ ์‹œ ์ ์ ˆํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ(400, 500 ๋“ฑ)๋ฅผ ๋ฐ˜ํ™˜ํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: `index.php`์—์„œ ํƒœ์Šคํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ชฉ๋ก์— ์ฆ‰์‹œ ํ‘œ์‹œ๋˜๊ณ , ์™„๋ฃŒ ์ฒดํฌ๋ฐ•์Šค๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ทจ์†Œ์„ ์ด ๊ทธ์–ด์ง‘๋‹ˆ๋‹ค. ์ˆ˜์ •/์‚ญ์ œ ๋ฒ„ํŠผ๋„ ์ •์ƒ ์ž‘๋™ํ•˜๋ฉฐ, ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค.

3
Step 3
# 3๋‹จ๊ณ„: ํฌ๋ชจ๋„๋กœ ํƒ€์ด๋จธ ๊ตฌํ˜„

`index.php`์— ํƒ€์ด๋จธ UI๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , `app.js`์—์„œ 25๋ถ„ ์ง‘์ค‘ ๋ชจ๋“œ์™€ 5๋ถ„ ํœด์‹ ๋ชจ๋“œ๋ฅผ ์ž๋™ ์ „ํ™˜ํ•˜๋Š” ํƒ€์ด๋จธ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

## ์š”๊ตฌ์‚ฌํ•ญ

1. **index.php ํƒ€์ด๋จธ UI**:
   - ํฐ ์ˆซ์ž๋กœ ๋‚จ์€ ์‹œ๊ฐ„ ํ‘œ์‹œ (`<div id="timer">25:00</div>`)
   - ์‹œ์ž‘/์ผ์‹œ์ •์ง€/๋ฆฌ์…‹ ๋ฒ„ํŠผ
   - ํ˜„์žฌ ๋ชจ๋“œ ํ‘œ์‹œ ("์ง‘์ค‘ ๋ชจ๋“œ" ๋˜๋Š” "ํœด์‹ ๋ชจ๋“œ")
   - ์ง„ํ–‰ ๋ฐ” ๋˜๋Š” ์›ํ˜• ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” (์„ ํƒ)
   - ํƒœ์Šคํฌ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด (ํƒ€์ด๋จธ๋ฅผ ์–ด๋–ค ํƒœ์Šคํฌ์— ์—ฐ๊ฒฐํ• ์ง€ ์„ ํƒ)

2. **app.js ํƒ€์ด๋จธ ๋กœ์ง**:
   - `startTimer()`: ํƒ€์ด๋จธ ์‹œ์ž‘ (setInterval 1์ดˆ๋งˆ๋‹ค ์นด์šดํŠธ๋‹ค์šด)
   - `pauseTimer()`: ํƒ€์ด๋จธ ์ผ์‹œ์ •์ง€ (clearInterval)
   - `resetTimer()`: ํƒ€์ด๋จธ ์ดˆ๊ธฐํ™” (25:00์œผ๋กœ ๋ฆฌ์…‹)
   - 25๋ถ„ ์™„๋ฃŒ ์‹œ:
     - ๋ธŒ๋ผ์šฐ์ € ์•Œ๋ฆผ ๋˜๋Š” ์˜ค๋””์˜ค ์žฌ์ƒ ("์ง‘์ค‘ ์„ธ์…˜ ์™„๋ฃŒ!")
     - ์ž๋™์œผ๋กœ 5๋ถ„ ํœด์‹ ๋ชจ๋“œ๋กœ ์ „ํ™˜
   - 5๋ถ„ ์™„๋ฃŒ ์‹œ:
     - ์•Œ๋ฆผ ("ํœด์‹ ๋! ๋‹ค์‹œ ์ง‘์ค‘ํ•˜์„ธ์š”")
     - ์ž๋™์œผ๋กœ 25๋ถ„ ์ง‘์ค‘ ๋ชจ๋“œ๋กœ ์ „ํ™˜
   - ํƒ€์ด๋จธ ์ข…๋ฃŒ ์‹œ ์„ ํƒ๋œ ํƒœ์Šคํฌ์™€ ํ•จ๊ป˜ ์„ธ์…˜ ๊ธฐ๋ก ์ €์žฅ (POST `/sessions.php`)

3. **sessions.php**:
   - `POST /sessions.php`: ์„ธ์…˜ ๊ธฐ๋ก ์ €์žฅ (task_id, completed_at, duration_minutes)
   - JSON ์‘๋‹ต: `{"success": true, "session_id": ...}`

4. **style.css**:
   - ํƒ€์ด๋จธ ์ˆซ์ž๋ฅผ ํ™”๋ฉด ์ค‘์•™์— ํฌ๊ฒŒ ํ‘œ์‹œ (font-size: 4rem ์ด์ƒ)
   - ์ง‘์ค‘ ๋ชจ๋“œ๋Š” ํŒŒ๋ž€์ƒ‰, ํœด์‹ ๋ชจ๋“œ๋Š” ์ดˆ๋ก์ƒ‰ ๋ฐฐ๊ฒฝ (์„ ํƒ)
   - ๋ฒ„ํŠผ ์Šคํƒ€์ผ๋ง (์‹œ์ž‘: ์ดˆ๋ก์ƒ‰, ์ผ์‹œ์ •์ง€: ๋…ธ๋ž€์ƒ‰, ๋ฆฌ์…‹: ๋นจ๊ฐ„์ƒ‰)

๋ธŒ๋ผ์šฐ์ € ์•Œ๋ฆผ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด `Notification.requestPermission()`์œผ๋กœ ๊ถŒํ•œ ์š”์ฒญ ํ›„ `new Notification(...)`์œผ๋กœ ์•Œ๋ฆผ์„ ํ‘œ์‹œํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ํƒ€์ด๋จธ๋ฅผ ์‹œ์ž‘ํ•˜๋ฉด 25:00์—์„œ ์นด์šดํŠธ๋‹ค์šด์ด ์‹œ์ž‘๋˜๊ณ , 0:00์ด ๋˜๋ฉด ์•Œ๋ฆผ์ด ๋œจ๋ฉฐ ์ž๋™์œผ๋กœ 5๋ถ„ ํœด์‹ ๋ชจ๋“œ๋กœ ์ „ํ™˜๋ฉ๋‹ˆ๋‹ค. ํœด์‹ ์ข…๋ฃŒ ํ›„ ๋‹ค์‹œ 25๋ถ„ ์ง‘์ค‘ ๋ชจ๋“œ๋กœ ๋Œ์•„์˜ต๋‹ˆ๋‹ค. ์„ธ์…˜ ์™„๋ฃŒ ์‹œ DB์— ๊ธฐ๋ก์ด ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

4
Step 4
# 4๋‹จ๊ณ„: ํ†ต๊ณ„ ํŽ˜์ด์ง€ ๊ตฌํ˜„

`stats.php` ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ค์–ด ์˜ค๋Š˜/์ด๋ฒˆ ์ฃผ/์ด๋ฒˆ ๋‹ฌ ์™„๋ฃŒํ•œ ํฌ๋ชจ๋„๋กœ ์„ธ์…˜ ํ†ต๊ณ„๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

## ์š”๊ตฌ์‚ฌํ•ญ

1. **stats.php**:
   - ํŽ˜์ด์ง€ ์ƒ๋‹จ์— "ํ†ต๊ณ„" ์ œ๋ชฉ
   - ์˜ค๋Š˜ ์™„๋ฃŒํ•œ ์„ธ์…˜ ์ˆ˜ ํ‘œ์‹œ
   - ์ด๋ฒˆ ์ฃผ ์™„๋ฃŒํ•œ ์„ธ์…˜ ์ˆ˜ ํ‘œ์‹œ
   - ์ด๋ฒˆ ๋‹ฌ ์™„๋ฃŒํ•œ ์„ธ์…˜ ์ˆ˜ ํ‘œ์‹œ
   - ํƒœ์Šคํฌ๋ณ„ ๋ˆ„์  ์„ธ์…˜ ์‹œ๊ฐ„ ํ‘œ์‹œ (ํ…Œ์ด๋ธ” ํ˜•์‹)
   - ์ฃผ๊ฐ„ ์„ธ์…˜ ์ถ”์ด ์ฐจํŠธ (์„ ํƒ, ๊ฐ„๋‹จํ•œ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„)

2. **๋ฐ์ดํ„ฐ ์กฐํšŒ**:
   - `sessions` ํ…Œ์ด๋ธ”์—์„œ `completed_at` ๊ธฐ์ค€์œผ๋กœ ์˜ค๋Š˜/์ด๋ฒˆ ์ฃผ/์ด๋ฒˆ ๋‹ฌ ํ•„ํ„ฐ๋ง
   - `GROUP BY task_id`๋กœ ํƒœ์Šคํฌ๋ณ„ ํ•ฉ๊ณ„ ๊ณ„์‚ฐ
   - PHP์—์„œ SQL ์ฟผ๋ฆฌ ์‹คํ–‰ ํ›„ ๊ฒฐ๊ณผ๋ฅผ HTML ํ…Œ์ด๋ธ”๋กœ ๋ Œ๋”๋ง

3. **์ฐจํŠธ (์„ ํƒ)**:
   - ์ตœ๊ทผ 7์ผ๊ฐ„ ์ผ๋ณ„ ์„ธ์…˜ ์ˆ˜๋ฅผ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„๋กœ ํ‘œ์‹œ
   - Chart.js ๋˜๋Š” ์ˆœ์ˆ˜ CSS๋กœ ๊ฐ„๋‹จํ•œ ๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„ ๊ตฌํ˜„

4. **๋„ค๋น„๊ฒŒ์ด์…˜**:
   - `index.php`์— "ํ†ต๊ณ„ ๋ณด๊ธฐ" ๋งํฌ ์ถ”๊ฐ€
   - `stats.php`์— "ํƒ€์ด๋จธ๋กœ ๋Œ์•„๊ฐ€๊ธฐ" ๋งํฌ ์ถ”๊ฐ€

5. **style.css**:
   - ํ†ต๊ณ„ ์ˆซ์ž๋ฅผ ๊ฐ•์กฐ (ํฐ ํฐํŠธ, ์ปฌ๋Ÿฌ ๋ฐ•์Šค)
   - ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ๋ง (ํ—ค๋”, ํ–‰ ๊ตฌ๋ถ„)

๋ชจ๋“  ๋‚ ์งœ ๊ณ„์‚ฐ์€ PHP `strtotime()` ๋˜๋Š” `DateTime` ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: `stats.php`์—์„œ ์˜ค๋Š˜/์ด๋ฒˆ ์ฃผ/์ด๋ฒˆ ๋‹ฌ ์™„๋ฃŒํ•œ ์„ธ์…˜ ์ˆ˜๊ฐ€ ํ‘œ์‹œ๋˜๊ณ , ํƒœ์Šคํฌ๋ณ„ ๋ˆ„์  ์‹œ๊ฐ„ ํ…Œ์ด๋ธ”์ด ๋ณด์ž…๋‹ˆ๋‹ค. ์ฐจํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ์ตœ๊ทผ 7์ผ๊ฐ„ ์„ธ์…˜ ์ถ”์ด๊ฐ€ ์‹œ๊ฐํ™”๋ฉ๋‹ˆ๋‹ค.

5
Step 5
# 5๋‹จ๊ณ„: ๋ฐฐ์ง€ ์‹œ์Šคํ…œ ๊ตฌํ˜„

์„ธ์…˜ ์ˆ˜์™€ ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜์— ๋”ฐ๋ผ ๋ฐฐ์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋ถ€์—ฌํ•˜๊ณ , `badges.php` ํŽ˜์ด์ง€์—์„œ ํš๋“ํ•œ ๋ฐฐ์ง€๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

## ์š”๊ตฌ์‚ฌํ•ญ

1. **๋ฐฐ์ง€ ์ข…๋ฅ˜**:
   - ์„ธ์…˜ ์ˆ˜ ๋ฐฐ์ง€: "5์„ธ์…˜ ๋‹ฌ์„ฑ", "10์„ธ์…˜ ๋‹ฌ์„ฑ", "20์„ธ์…˜ ๋‹ฌ์„ฑ", "50์„ธ์…˜ ๋‹ฌ์„ฑ"
   - ์—ฐ์† ๋‹ฌ์„ฑ ๋ฐฐ์ง€: "3์ผ ์—ฐ์†", "7์ผ ์—ฐ์†", "30์ผ ์—ฐ์†"
   - ํŠน๋ณ„ ๋ฐฐ์ง€: "์ฒซ ์„ธ์…˜ ์™„๋ฃŒ", "์ƒˆ๋ฒฝ ์˜ฌ๋นผ๋ฏธ (์ƒˆ๋ฒฝ 2~5์‹œ ์„ธ์…˜)"

2. **๋ฐฐ์ง€ ๋ถ€์—ฌ ๋กœ์ง**:
   - `sessions.php`์—์„œ ์„ธ์…˜ ์ €์žฅ ํ›„, ์ด ์„ธ์…˜ ์ˆ˜๋ฅผ ํ™•์ธํ•˜์—ฌ ๋ฐฐ์ง€ ์กฐ๊ฑด ์ถฉ์กฑ ์‹œ `badges` ํ…Œ์ด๋ธ”์— ์‚ฝ์ž…
   - ์—ฐ์† ๋‹ฌ์„ฑ ์ผ์ˆ˜๋Š” `sessions` ํ…Œ์ด๋ธ”์—์„œ ์ตœ๊ทผ ๋‚ ์งœ ์—ฐ์†์„ฑ์„ ์ฒดํฌ
   - ์ด๋ฏธ ํš๋“ํ•œ ๋ฐฐ์ง€๋Š” ์ค‘๋ณต ๋ถ€์—ฌํ•˜์ง€ ์•Š์Œ (UNIQUE ์ œ์•ฝ ๋˜๋Š” SELECT ํ›„ ์ฒดํฌ)

3. **badges.php**:
   - ํš๋“ํ•œ ๋ฐฐ์ง€ ๋ชฉ๋ก์„ ์นด๋“œ ํ˜•์‹์œผ๋กœ ํ‘œ์‹œ (๋ฐฐ์ง€ ์•„์ด์ฝ˜ + ์ด๋ฆ„ + ํš๋“ ๋‚ ์งœ)
   - ์•„์ง ํš๋“ํ•˜์ง€ ๋ชปํ•œ ๋ฐฐ์ง€๋Š” ํšŒ์ƒ‰ ๋˜๋Š” ์ž ๊ธˆ ์•„์ด์ฝ˜์œผ๋กœ ํ‘œ์‹œ (์„ ํƒ)
   - ๋ฐฐ์ง€๋ณ„ ์„ค๋ช… ํˆดํŒ (hover ์‹œ ํ‘œ์‹œ)

4. **index.php**:
   - ์ƒ๋‹จ์— "ํš๋“ํ•œ ๋ฐฐ์ง€ ์ˆ˜" ํ‘œ์‹œ (`count(badges)` ์ฟผ๋ฆฌ)
   - "๋ฐฐ์ง€ ๊ฐค๋Ÿฌ๋ฆฌ" ๋งํฌ ์ถ”๊ฐ€

5. **style.css**:
   - ๋ฐฐ์ง€ ์นด๋“œ ์Šคํƒ€์ผ๋ง (์•„์ด์ฝ˜, ๊ทธ๋ฆผ์ž, ํ˜ธ๋ฒ„ ํšจ๊ณผ)
   - ํš๋“ํ•œ ๋ฐฐ์ง€๋Š” ์ปฌ๋Ÿฌํ’€ํ•˜๊ฒŒ, ๋ฏธํš๋“ ๋ฐฐ์ง€๋Š” ํšŒ์ƒ‰ ์ฒ˜๋ฆฌ

๋ฐฐ์ง€ ์•„์ด์ฝ˜์€ ์ด๋ชจ์ง€(๐Ÿ†, ๐ŸŽฏ, ๐Ÿ”ฅ ๋“ฑ) ๋˜๋Š” Font Awesome ์•„์ด์ฝ˜์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์„ธ์…˜์„ ์™„๋ฃŒํ•˜๋ฉด ์กฐ๊ฑด์— ๋”ฐ๋ผ ๋ฐฐ์ง€๊ฐ€ ์ž๋™์œผ๋กœ ๋ถ€์—ฌ๋˜๊ณ , `badges.php`์—์„œ ํš๋“ํ•œ ๋ฐฐ์ง€๊ฐ€ ์นด๋“œ ํ˜•์‹์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์•„์ง ํš๋“ํ•˜์ง€ ๋ชปํ•œ ๋ฐฐ์ง€๋Š” ์ž ๊ธˆ ์ƒํƒœ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

6
Step 6
# 6๋‹จ๊ณ„: UI/UX ๊ฐœ์„  ๋ฐ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ

์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด UI๋ฅผ ๋‹ค๋“ฌ๊ณ , ๋ชจ๋ฐ”์ผ์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก ๋ฐ˜์‘ํ˜• ๋””์ž์ธ์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

## ์š”๊ตฌ์‚ฌํ•ญ

1. **๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€**:
   - `index.php` ์ƒ๋‹จ์— ๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€ ๋ฒ„ํŠผ ์ถ”๊ฐ€
   - JavaScript๋กœ `<body>`์— `dark-mode` ํด๋ž˜์Šค ์ถ”๊ฐ€/์ œ๊ฑฐ
   - CSS ๋ณ€์ˆ˜๋กœ ์ƒ‰์ƒ ์ •์˜ (`:root`์™€ `.dark-mode`)
   - ๋‹คํฌ ๋ชจ๋“œ ์ƒํƒœ๋ฅผ localStorage์— ์ €์žฅํ•˜์—ฌ ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ์œ ์ง€

2. **๋ฐ˜์‘ํ˜• ๋””์ž์ธ**:
   - ๋ชจ๋ฐ”์ผ(< 768px)์—์„œ ํƒ€์ด๋จธ์™€ ํƒœ์Šคํฌ ๋ชฉ๋ก์ด ์„ธ๋กœ๋กœ ๋ฐฐ์น˜
   - ํƒœ๋ธ”๋ฆฟ/๋ฐ์Šคํฌํƒ‘์—์„œ๋Š” ํƒ€์ด๋จธ ์™ผ์ชฝ, ํƒœ์Šคํฌ ๋ชฉ๋ก ์˜ค๋ฅธ์ชฝ ๋ฐฐ์น˜
   - ๋ฒ„ํŠผ ํฌ๊ธฐ๋ฅผ ํ„ฐ์น˜ํ•˜๊ธฐ ํŽธํ•˜๊ฒŒ ์กฐ์ • (์ตœ์†Œ 44x44px)

3. **์• ๋‹ˆ๋ฉ”์ด์…˜ & ์ธํ„ฐ๋ž™์…˜**:
   - ํƒœ์Šคํฌ ์ถ”๊ฐ€/์‚ญ์ œ ์‹œ fade-in/fade-out ์• ๋‹ˆ๋ฉ”์ด์…˜
   - ํƒ€์ด๋จธ ์ˆซ์ž ๋ณ€๊ฒฝ ์‹œ ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜ ํšจ๊ณผ
   - ์™„๋ฃŒ ์ฒดํฌ๋ฐ•์Šค ํด๋ฆญ ์‹œ ์ฒดํฌ ์• ๋‹ˆ๋ฉ”์ด์…˜
   - ๋ฒ„ํŠผ ํ˜ธ๋ฒ„ ์‹œ ์ƒ‰์ƒ ๋ณ€ํ™” ๋˜๋Š” ํ™•๋Œ€ ํšจ๊ณผ

4. **์ง„ํ–‰ ๋ฐ” ๊ฐœ์„ **:
   - ํƒ€์ด๋จธ ์•„๋ž˜์— ์›ํ˜• ๋˜๋Š” ์„ ํ˜• ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ์ถ”๊ฐ€
   - CSS `stroke-dashoffset` ๋˜๋Š” `width` ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ

5. **์ ‘๊ทผ์„ฑ ๊ฐœ์„ **:
   - ๋ฒ„ํŠผ์— `aria-label` ์ถ”๊ฐ€
   - ํผ ์ž…๋ ฅ ํ•„๋“œ์— `<label>` ์—ฐ๊ฒฐ
   - ํ‚ค๋ณด๋“œ๋กœ ๋ชจ๋“  ๊ธฐ๋Šฅ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก `tabindex` ์„ค์ •

6. **์—๋Ÿฌ ๋ฉ”์‹œ์ง€ & ๋กœ๋”ฉ ์ƒํƒœ**:
   - AJAX ์š”์ฒญ ์‹คํŒจ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
   - ๋กœ๋”ฉ ์ค‘์ผ ๋•Œ ์Šคํ”ผ๋„ˆ ๋˜๋Š” "๋กœ๋”ฉ ์ค‘..." ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ

๋ชจ๋“  ์• ๋‹ˆ๋ฉ”์ด์…˜์€ CSS transition ๋˜๋Š” @keyframes๋กœ ๊ตฌํ˜„ํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€์ด ์ž‘๋™ํ•˜๊ณ , ๋ชจ๋ฐ”์ผ์—์„œ๋„ UI๊ฐ€ ๊นจ์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํƒœ์Šคํฌ ์ถ”๊ฐ€/์‚ญ์ œ ์‹œ ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋˜๊ณ , ํƒ€์ด๋จธ ์ง„ํ–‰ ๋ฐ”๊ฐ€ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ฉ๋‹ˆ๋‹ค. ํ‚ค๋ณด๋“œ๋งŒ์œผ๋กœ๋„ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ œ์ถœ๋œ ์ž‘ํ’ˆ (0)

๋กœ๊ทธ์ธ ํ›„ ์ œ์ถœ

์•„์ง ์ œ์ถœ๋œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋„์ „์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!

๋Œ“๊ธ€ (0)

๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ๋ณด์„ธ์š”!