๐ŸŽจ ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ดํ„ฐ ์ปค๋ฏธ์…˜ ์ฃผ๋ฌธ์„œ ํ”Œ๋žซํผ - ์˜๋ขฐ์„œ ์ž‘์„ฑ๋ถ€ํ„ฐ ํฌํŠธํด๋ฆฌ์˜ค๊นŒ์ง€ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ค‘๊ธ‰ PHP + JavaScript Laravel + React + Inertia.js AI: Claude

๐ŸŽจ ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ดํ„ฐ ์ปค๋ฏธ์…˜ ์ฃผ๋ฌธ์„œ ํ”Œ๋žซํผ - ์˜๋ขฐ์„œ ์ž‘์„ฑ๋ถ€ํ„ฐ ํฌํŠธํด๋ฆฌ์˜ค๊นŒ์ง€

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… illustration-commission-order-platform
์กฐํšŒ์ˆ˜ 47 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

๐ŸŽฏ ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ดํ„ฐ๊ฐ€ ๊ณ ๊ฐ์˜ ์ปค๋ฏธ์…˜ ์˜๋ขฐ๋ฅผ ์ฒด๊ณ„์ ์œผ๋กœ ๋ฐ›๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ํ’€์Šคํƒ ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ๊ณ ๊ฐ์€ ์›ํ•˜๋Š” ์Šคํƒ€์ผ, ์บ๋ฆญํ„ฐ ํŠน์ง•, ์šฉ๋„ ๋“ฑ์„ ์ƒ์„ธํžˆ ์ž‘์„ฑํ•˜์—ฌ ์˜๋ขฐ์„œ๋ฅผ ์ œ์ถœํ•˜๊ณ , ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ดํ„ฐ๋Š” ์˜๋ขฐ ํ˜„ํ™ฉ์„ ํ™•์ธํ•˜๊ณ  ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค. ์™„์„ฑ๋œ ์ž‘ํ’ˆ์€ ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ์— ์ž๋™์œผ๋กœ ์ถ”๊ฐ€๋˜์–ด ์‹ ๊ทœ ๊ณ ๊ฐ ์œ ์น˜์— ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค.

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

  • ์ปค๋ฏธ์…˜ ์˜๋ขฐ์„œ ์ž‘์„ฑ ํผ: ๊ณ ๊ฐ์ด ์ž‘์„ฑ ๋ชฉ์ (ํ”„๋กœํ•„, SNS, ๊ตฟ์ฆˆ ๋“ฑ), ์บ๋ฆญํ„ฐ ์„ค๋ช…, ๋ฐฐ๊ฒฝ ์š”์ฒญ์‚ฌํ•ญ, ์ฐธ๊ณ  ์ด๋ฏธ์ง€ URL, ์˜ˆ์‚ฐ ๋ฒ”์œ„๋ฅผ ์ž…๋ ฅ
  • ์˜๋ขฐ ๋ชฉ๋ก ํŽ˜์ด์ง€: ์ œ์ถœ๋œ ๋ชจ๋“  ์˜๋ขฐ๋ฅผ ์นด๋“œ ํ˜•ํƒœ๋กœ ๋‚˜์—ด, ์ƒํƒœ๋ณ„ ํ•„ํ„ฐ๋ง(๋Œ€๊ธฐ์ค‘/์ง„ํ–‰์ค‘/์™„๋ฃŒ)
  • ์˜๋ขฐ ์ƒ์„ธ ํŽ˜์ด์ง€: ๊ณ ๊ฐ์ด ์ž‘์„ฑํ•œ ๋ชจ๋“  ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๊ณ , ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ดํ„ฐ๊ฐ€ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ๋“œ๋กญ๋‹ค์šด ์ œ๊ณต
  • ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ: ์™„๋ฃŒ๋œ ์ปค๋ฏธ์…˜ ์ž‘ํ’ˆ์„ ์ธ๋„ค์ผ ๊ทธ๋ฆฌ๋“œ๋กœ ํ‘œ์‹œ, ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ๋กœ ์ƒ์„ธ ์ •๋ณด ํ™•์ธ
  • ๋ฐ˜์‘ํ˜• ๋””์ž์ธ: Tailwind CSS๋กœ ๋ชจ๋ฐ”์ผ/๋ฐ์Šคํฌํƒ‘ ๋ชจ๋‘ ์ตœ์ ํ™”
  • ๋ถ€๋“œ๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜: ์นด๋“œ hover ํšจ๊ณผ, ๋ชจ๋‹ฌ fade-in, ์ƒํƒœ ๋ณ€๊ฒฝ ์‹œ ํ† ์ŠคํŠธ ์•Œ๋ฆผ

๐ŸŽ ๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ

  • ์˜๋ขฐ์„œ ์ž‘์„ฑ ์‹œ ์‹ค์‹œ๊ฐ„ ๊ธ€์ž ์ˆ˜ ์นด์šดํ„ฐ
  • ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ์— ํƒœ๊ทธ ํ•„ํ„ฐ๋ง(์บ๋ฆญํ„ฐ, ๋ฐฐ๊ฒฝ, ์Šคํƒ€์ผ ๋“ฑ)
  • ์™„๋ฃŒ๋œ ์ž‘ํ’ˆ์— ๊ณ ๊ฐ ํ›„๊ธฐ ๋‚จ๊ธฐ๊ธฐ ๊ธฐ๋Šฅ
  • ์˜๋ขฐ ๋ชฉ๋ก ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋˜๋Š” ๋ฌดํ•œ ์Šคํฌ๋กค
  • ๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€

๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

  • Backend: Laravel 13.x (์ปจํŠธ๋กค๋Ÿฌ, Eloquent ORM, ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜)
  • Frontend: React 19 (ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ, hooks)
  • Bridge: Inertia.js (SPA ๊ฒฝํ—˜ ์ œ๊ณต, ๋ณ„๋„ API ๋ถˆํ•„์š”)
  • Database: SQLite (๊ฐœ๋ฐœ ํŽธ์˜์„ฑ)
  • Styling: Tailwind CSS 3.x
  • Validation: Laravel Form Requests

๐Ÿ“š ํ•™์Šต ํฌ์ธํŠธ

  • Laravel ์ปจํŠธ๋กค๋Ÿฌ์—์„œ Inertia::render()๋กœ React ์ปดํฌ๋„ŒํŠธ์— ๋ฐ์ดํ„ฐ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ๋ฒ•
  • Eloquent ๊ด€๊ณ„ ์ •์˜ ์—†์ด ๋‹จ์ผ ๋ชจ๋ธ๋กœ CRUD ๊ตฌํ˜„
  • React์—์„œ useForm hook์œผ๋กœ ํผ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐ ์ œ์ถœ
  • Tailwind๋กœ ์นด๋“œ ๋ ˆ์ด์•„์›ƒ, ๋ชจ๋‹ฌ, ํ† ์ŠคํŠธ ์•Œ๋ฆผ ๊ตฌํ˜„
  • ํŒŒ์ผ ์—…๋กœ๋“œ ์—†์ด URL ๊ธฐ๋ฐ˜ ์ด๋ฏธ์ง€ ์ฐธ์กฐ๋กœ ๊ฐ„์†Œํ™”

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ํ’€์Šคํƒ ๊ตฌ์กฐ (๋ณ„๋„ API ์—”๋“œํฌ์ธํŠธ ๊ธˆ์ง€)
  • โ€ข Laravel Breeze (React + Inertia) starter kit ๋˜๋Š” `npx create laravel` ์‚ฌ์šฉ
  • โ€ข SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (MySQL/PostgreSQL ๊ธˆ์ง€)
  • โ€ข Tailwind CSS 3.x ์ด์ƒ ์‚ฌ์šฉ (Bootstrap ๊ธˆ์ง€)
  • โ€ข ํŒŒ์ผ ์—…๋กœ๋“œ ๊ธˆ์ง€ (์ด๋ฏธ์ง€๋Š” ์™ธ๋ถ€ URL ์ฐธ์กฐ๋กœ ๋Œ€์ฒด)
  • โ€ข ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…/WebSocket ๊ธˆ์ง€ (HTTP ๊ธฐ๋ฐ˜ CRUD๋งŒ ์‚ฌ์šฉ)
  • โ€ข ๋ณต์žกํ•œ ์ƒํƒœ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Redux, Zustand ๋“ฑ) ๊ธˆ์ง€ (useState/useReducer๋งŒ ์‚ฌ์šฉ)
  • โ€ข ์ธ์ฆ/๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ์ œ์™ธ (์ปค๋ฏธ์…˜ ์˜๋ขฐ๋Š” ๋ˆ„๊ตฌ๋‚˜ ์ž‘์„ฑ ๊ฐ€๋Šฅ)
  • โ€ข ์™ธ๋ถ€ API ์—ฐ๋™ ๊ธˆ์ง€
  • โ€ข ๋ชจ๋“  ํ…์ŠคํŠธ๋Š” ํ•œ๊ตญ์–ด๋กœ ์ž‘์„ฑ

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

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

1
Step 1
mkdir illustration-commission-order-platform && cd illustration-commission-order-platform

์ด ํด๋”์—์„œ Laravel 13.x + Inertia.js + React 19 ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜์—ฌ Laravel Breeze (React + Inertia) starter kit์„ ์„ค์น˜ํ•˜์„ธ์š”:

```bash
composer create-project laravel/laravel .
composer require laravel/breeze --dev
php artisan breeze:install react
npm install
npm run build
```

ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ค์ •์„ ์™„๋ฃŒํ•œ ํ›„, `.env` ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ SQLite๋กœ ๋ณ€๊ฒฝํ•˜์„ธ์š”:

```
DB_CONNECTION=sqlite
# DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD ์ฃผ์„ ์ฒ˜๋ฆฌ
```

`database/database.sqlite` ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜์„ธ์š”:

```bash
touch database/database.sqlite
php artisan migrate
```

Tailwind CSS ์„ค์ •์€ ์ด๋ฏธ ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ถ”๊ฐ€ ์„ค์ • ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๊ธฐ๋ณธ ํ™”๋ฉด์ด ์ •์ƒ์ ์œผ๋กœ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”:

```bash
php artisan serve
npm run dev
```

๋ธŒ๋ผ์šฐ์ €์—์„œ `http://localhost:8000`์— ์ ‘์†ํ•˜์—ฌ Laravel Breeze ๊ธฐ๋ณธ ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋˜๋ฉด ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel 13.x + Inertia.js + React 19 ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์„ค์ •๋˜๋ฉฐ, Tailwind CSS๊ฐ€ ์ ์šฉ๋œ ๊ธฐ๋ณธ ํŽ˜์ด์ง€๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

2
Step 2
์ปค๋ฏธ์…˜ ์˜๋ขฐ์„œ๋ฅผ ์ €์žฅํ•  `commissions` ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ช…๋ น์–ด๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

```bash
php artisan make:migration create_commissions_table
```

์ƒ์„ฑ๋œ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ(`database/migrations/YYYY_MM_DD_HHMMSS_create_commissions_table.php`)์„ ์—ด๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์„ธ์š”:

```php
public function up()
{
    Schema::create('commissions', function (Blueprint $table) {
        $table->id();
        $table->string('client_name'); // ๊ณ ๊ฐ ์ด๋ฆ„
        $table->string('client_email'); // ๊ณ ๊ฐ ์ด๋ฉ”์ผ
        $table->string('purpose'); // ์ž‘์„ฑ ๋ชฉ์  (ํ”„๋กœํ•„, SNS, ๊ตฟ์ฆˆ ๋“ฑ)
        $table->text('character_description'); // ์บ๋ฆญํ„ฐ ์„ค๋ช…
        $table->text('background_request')->nullable(); // ๋ฐฐ๊ฒฝ ์š”์ฒญ์‚ฌํ•ญ
        $table->string('reference_image_url')->nullable(); // ์ฐธ๊ณ  ์ด๋ฏธ์ง€ URL
        $table->string('budget_range'); // ์˜ˆ์‚ฐ ๋ฒ”์œ„ (์˜ˆ: 10-20๋งŒ์›)
        $table->enum('status', ['pending', 'in_progress', 'completed'])->default('pending'); // ์ƒํƒœ
        $table->string('completed_image_url')->nullable(); // ์™„์„ฑ ์ด๋ฏธ์ง€ URL (์™„๋ฃŒ ์‹œ)
        $table->timestamps();
    });
}
```

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‹คํ–‰ํ•˜์„ธ์š”:

```bash
php artisan migrate
```

๋‹ค์Œ์œผ๋กœ Commission ๋ชจ๋ธ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

```bash
php artisan make:model Commission
```

`app/Models/Commission.php`๋ฅผ ์—ด๊ณ  fillable ํ•„๋“œ๋ฅผ ์ •์˜ํ•˜์„ธ์š”:

```php
protected $fillable = [
    'client_name',
    'client_email',
    'purpose',
    'character_description',
    'background_request',
    'reference_image_url',
    'budget_range',
    'status',
    'completed_image_url',
];
```

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: `commissions` ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜๊ณ , Commission ๋ชจ๋ธ์ด ์ •์˜๋ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ํ…Œ์ด๋ธ”์ด ์ •์ƒ์ ์œผ๋กœ ์ถ”๊ฐ€๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

3
Step 3
์ปค๋ฏธ์…˜ ์˜๋ขฐ์„œ ์ž‘์„ฑ ํผ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € CommissionController๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”:

```bash
php artisan make:controller CommissionController
```

`app/Http/Controllers/CommissionController.php`๋ฅผ ์—ด๊ณ  ๋‹ค์Œ ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”:

```php
use App\Models\Commission;
use Inertia\Inertia;
use Illuminate\Http\Request;

public function create()
{
    return Inertia::render('Commissions/Create');
}

public function store(Request $request)
{
    $validated = $request->validate([
        'client_name' => 'required|string|max:255',
        'client_email' => 'required|email',
        'purpose' => 'required|string|max:255',
        'character_description' => 'required|string',
        'background_request' => 'nullable|string',
        'reference_image_url' => 'nullable|url',
        'budget_range' => 'required|string|max:255',
    ]);

    Commission::create($validated);

    return redirect()->route('commissions.index')->with('success', '์˜๋ขฐ์„œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์ œ์ถœ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
}
```

`routes/web.php`์— ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
use App\Http\Controllers\CommissionController;

Route::get('/commissions/create', [CommissionController::class, 'create'])->name('commissions.create');
Route::post('/commissions', [CommissionController::class, 'store'])->name('commissions.store');
```

์ด์ œ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. `resources/js/Pages/Commissions/Create.jsx` ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์„ธ์š”:

```jsx
import { useForm } from '@inertiajs/react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';

export default function Create() {
    const { data, setData, post, processing, errors } = useForm({
        client_name: '',
        client_email: '',
        purpose: '',
        character_description: '',
        background_request: '',
        reference_image_url: '',
        budget_range: '',
    });

    const handleSubmit = (e) => {
        e.preventDefault();
        post(route('commissions.store'));
    };

    return (
        <AuthenticatedLayout>
            <div className="max-w-2xl mx-auto p-6">
                <h1 className="text-3xl font-bold mb-6">์ปค๋ฏธ์…˜ ์˜๋ขฐ์„œ ์ž‘์„ฑ</h1>
                <form onSubmit={handleSubmit} className="space-y-4">
                    <div>
                        <label className="block text-sm font-medium mb-1">์ด๋ฆ„</label>
                        <input
                            type="text"
                            value={data.client_name}
                            onChange={(e) => setData('client_name', e.target.value)}
                            className="w-full border rounded px-3 py-2"
                        />
                        {errors.client_name && <p className="text-red-500 text-sm">{errors.client_name}</p>}
                    </div>
                    <div>
                        <label className="block text-sm font-medium mb-1">์ด๋ฉ”์ผ</label>
                        <input
                            type="email"
                            value={data.client_email}
                            onChange={(e) => setData('client_email', e.target.value)}
                            className="w-full border rounded px-3 py-2"
                        />
                        {errors.client_email && <p className="text-red-500 text-sm">{errors.client_email}</p>}
                    </div>
                    <div>
                        <label className="block text-sm font-medium mb-1">์ž‘์„ฑ ๋ชฉ์ </label>
                        <select
                            value={data.purpose}
                            onChange={(e) => setData('purpose', e.target.value)}
                            className="w-full border rounded px-3 py-2"
                        >
                            <option value="">์„ ํƒํ•˜์„ธ์š”</option>
                            <option value="ํ”„๋กœํ•„">ํ”„๋กœํ•„</option>
                            <option value="SNS">SNS</option>
                            <option value="๊ตฟ์ฆˆ">๊ตฟ์ฆˆ</option>
                            <option value="๊ธฐํƒ€">๊ธฐํƒ€</option>
                        </select>
                        {errors.purpose && <p className="text-red-500 text-sm">{errors.purpose}</p>}
                    </div>
                    <div>
                        <label className="block text-sm font-medium mb-1">์บ๋ฆญํ„ฐ ์„ค๋ช…</label>
                        <textarea
                            value={data.character_description}
                            onChange={(e) => setData('character_description', e.target.value)}
                            className="w-full border rounded px-3 py-2 h-32"
                            placeholder="์บ๋ฆญํ„ฐ์˜ ์™ธ๋ชจ, ์„ฑ๊ฒฉ, ํฌ์ฆˆ ๋“ฑ์„ ์ƒ์„ธํžˆ ์ž‘์„ฑํ•ด์ฃผ์„ธ์š”"
                        />
                        {errors.character_description && <p className="text-red-500 text-sm">{errors.character_description}</p>}
                    </div>
                    <div>
                        <label className="block text-sm font-medium mb-1">๋ฐฐ๊ฒฝ ์š”์ฒญ์‚ฌํ•ญ (์„ ํƒ)</label>
                        <textarea
                            value={data.background_request}
                            onChange={(e) => setData('background_request', e.target.value)}
                            className="w-full border rounded px-3 py-2 h-20"
                        />
                    </div>
                    <div>
                        <label className="block text-sm font-medium mb-1">์ฐธ๊ณ  ์ด๋ฏธ์ง€ URL (์„ ํƒ)</label>
                        <input
                            type="url"
                            value={data.reference_image_url}
                            onChange={(e) => setData('reference_image_url', e.target.value)}
                            className="w-full border rounded px-3 py-2"
                        />
                    </div>
                    <div>
                        <label className="block text-sm font-medium mb-1">์˜ˆ์‚ฐ ๋ฒ”์œ„</label>
                        <input
                            type="text"
                            value={data.budget_range}
                            onChange={(e) => setData('budget_range', e.target.value)}
                            className="w-full border rounded px-3 py-2"
                            placeholder="์˜ˆ: 10-20๋งŒ์›"
                        />
                        {errors.budget_range && <p className="text-red-500 text-sm">{errors.budget_range}</p>}
                    </div>
                    <button
                        type="submit"
                        disabled={processing}
                        className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 transition"
                    >
                        ์˜๋ขฐ์„œ ์ œ์ถœ
                    </button>
                </form>
            </div>
        </AuthenticatedLayout>
    );
}
```

๋ธŒ๋ผ์šฐ์ €์—์„œ `/commissions/create`์— ์ ‘์†ํ•˜์—ฌ ํผ์ด ์ •์ƒ์ ์œผ๋กœ ํ‘œ์‹œ๋˜๊ณ  ์ œ์ถœ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ปค๋ฏธ์…˜ ์˜๋ขฐ์„œ ์ž‘์„ฑ ํผ์ด ํ‘œ์‹œ๋˜๊ณ , ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•œ ํ›„ ์ œ์ถœํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํผ ์•„๋ž˜์— ๋นจ๊ฐ„์ƒ‰์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

4
Step 4
์ œ์ถœ๋œ ์ปค๋ฏธ์…˜ ์˜๋ขฐ ๋ชฉ๋ก ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

`CommissionController`์— `index` ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
public function index(Request $request)
{
    $status = $request->query('status', 'all');

    $query = Commission::query()->orderBy('created_at', 'desc');

    if ($status !== 'all') {
        $query->where('status', $status);
    }

    $commissions = $query->paginate(12);

    return Inertia::render('Commissions/Index', [
        'commissions' => $commissions,
        'currentStatus' => $status,
    ]);
}
```

`routes/web.php`์— ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
Route::get('/commissions', [CommissionController::class, 'index'])->name('commissions.index');
```

`resources/js/Pages/Commissions/Index.jsx` ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์„ธ์š”:

```jsx
import { Link } from '@inertiajs/react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';

export default function Index({ commissions, currentStatus }) {
    const statusLabels = {
        all: '์ „์ฒด',
        pending: '๋Œ€๊ธฐ์ค‘',
        in_progress: '์ง„ํ–‰์ค‘',
        completed: '์™„๋ฃŒ',
    };

    const statusColors = {
        pending: 'bg-yellow-100 text-yellow-800',
        in_progress: 'bg-blue-100 text-blue-800',
        completed: 'bg-green-100 text-green-800',
    };

    return (
        <AuthenticatedLayout>
            <div className="max-w-6xl mx-auto p-6">
                <div className="flex justify-between items-center mb-6">
                    <h1 className="text-3xl font-bold">์ปค๋ฏธ์…˜ ์˜๋ขฐ ๋ชฉ๋ก</h1>
                    <Link
                        href={route('commissions.create')}
                        className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600 transition"
                    >
                        ์ƒˆ ์˜๋ขฐ ์ž‘์„ฑ
                    </Link>
                </div>

                <div className="flex gap-2 mb-6">
                    {Object.entries(statusLabels).map(([key, label]) => (
                        <Link
                            key={key}
                            href={route('commissions.index', { status: key })}
                            className={`px-4 py-2 rounded transition ${
                                currentStatus === key
                                    ? 'bg-blue-500 text-white'
                                    : 'bg-gray-200 hover:bg-gray-300'
                            }`}
                        >
                            {label}
                        </Link>
                    ))}
                </div>

                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
                    {commissions.data.map((commission) => (
                        <Link
                            key={commission.id}
                            href={route('commissions.show', commission.id)}
                            className="border rounded-lg p-4 hover:shadow-lg transition transform hover:-translate-y-1"
                        >
                            <div className="flex justify-between items-start mb-2">
                                <h3 className="font-bold text-lg">{commission.client_name}</h3>
                                <span className={`px-2 py-1 rounded text-xs font-semibold ${statusColors[commission.status]}`}>
                                    {statusLabels[commission.status]}
                                </span>
                            </div>
                            <p className="text-sm text-gray-600 mb-2">{commission.purpose}</p>
                            <p className="text-sm text-gray-700 line-clamp-3">{commission.character_description}</p>
                            <p className="text-xs text-gray-500 mt-2">์˜ˆ์‚ฐ: {commission.budget_range}</p>
                        </Link>
                    ))}
                </div>

                {commissions.links.length > 3 && (
                    <div className="flex justify-center gap-2 mt-6">
                        {commissions.links.map((link, index) => (
                            <Link
                                key={index}
                                href={link.url || '#'}
                                className={`px-3 py-1 rounded ${
                                    link.active
                                        ? 'bg-blue-500 text-white'
                                        : 'bg-gray-200 hover:bg-gray-300'
                                } ${!link.url && 'opacity-50 cursor-not-allowed'}`}
                                dangerouslySetInnerHTML={{ __html: link.label }}
                            />
                        ))}
                    </div>
                )}
            </div>
        </AuthenticatedLayout>
    );
}
```

๋ธŒ๋ผ์šฐ์ €์—์„œ `/commissions`์— ์ ‘์†ํ•˜์—ฌ ์˜๋ขฐ ๋ชฉ๋ก์ด ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œ๋˜๊ณ , ์ƒํƒœ๋ณ„ ํ•„ํ„ฐ๋ง์ด ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ปค๋ฏธ์…˜ ์˜๋ขฐ ๋ชฉ๋ก์ด ์นด๋“œ ๊ทธ๋ฆฌ๋“œ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, ์ƒํƒœ๋ณ„ ํ•„ํ„ฐ๋ง ๋ฒ„ํŠผ์ด ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค. ์นด๋“œ์— hover ์‹œ ๊ทธ๋ฆผ์ž์™€ ์•ฝ๊ฐ„์˜ ์ƒ์Šน ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋งํฌ๊ฐ€ ํ•˜๋‹จ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

5
Step 5
์ปค๋ฏธ์…˜ ์˜๋ขฐ ์ƒ์„ธ ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ , ์ผ๋Ÿฌ์ŠคํŠธ๋ ˆ์ดํ„ฐ๊ฐ€ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

`CommissionController`์— `show`์™€ `update` ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
public function show(Commission $commission)
{
    return Inertia::render('Commissions/Show', [
        'commission' => $commission,
    ]);
}

public function update(Request $request, Commission $commission)
{
    $validated = $request->validate([
        'status' => 'required|in:pending,in_progress,completed',
        'completed_image_url' => 'nullable|url',
    ]);

    $commission->update($validated);

    return redirect()->route('commissions.show', $commission->id)
        ->with('success', '์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
}
```

`routes/web.php`์— ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
Route::get('/commissions/{commission}', [CommissionController::class, 'show'])->name('commissions.show');
Route::patch('/commissions/{commission}', [CommissionController::class, 'update'])->name('commissions.update');
```

`resources/js/Pages/Commissions/Show.jsx` ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์„ธ์š”:

```jsx
import { useForm } from '@inertiajs/react';
import { useState } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';

export default function Show({ commission }) {
    const { data, setData, patch, processing } = useForm({
        status: commission.status,
        completed_image_url: commission.completed_image_url || '',
    });

    const [showToast, setShowToast] = useState(false);

    const handleSubmit = (e) => {
        e.preventDefault();
        patch(route('commissions.update', commission.id), {
            onSuccess: () => {
                setShowToast(true);
                setTimeout(() => setShowToast(false), 3000);
            },
        });
    };

    const statusLabels = {
        pending: '๋Œ€๊ธฐ์ค‘',
        in_progress: '์ง„ํ–‰์ค‘',
        completed: '์™„๋ฃŒ',
    };

    return (
        <AuthenticatedLayout>
            <div className="max-w-4xl mx-auto p-6">
                <h1 className="text-3xl font-bold mb-6">์ปค๋ฏธ์…˜ ์˜๋ขฐ ์ƒ์„ธ</h1>

                <div className="bg-white border rounded-lg p-6 mb-6">
                    <div className="grid grid-cols-2 gap-4 mb-4">
                        <div>
                            <p className="text-sm text-gray-500">์˜๋ขฐ์ž</p>
                            <p className="font-semibold">{commission.client_name}</p>
                        </div>
                        <div>
                            <p className="text-sm text-gray-500">์ด๋ฉ”์ผ</p>
                            <p className="font-semibold">{commission.client_email}</p>
                        </div>
                        <div>
                            <p className="text-sm text-gray-500">์ž‘์„ฑ ๋ชฉ์ </p>
                            <p className="font-semibold">{commission.purpose}</p>
                        </div>
                        <div>
                            <p className="text-sm text-gray-500">์˜ˆ์‚ฐ ๋ฒ”์œ„</p>
                            <p className="font-semibold">{commission.budget_range}</p>
                        </div>
                    </div>

                    <div className="mb-4">
                        <p className="text-sm text-gray-500 mb-1">์บ๋ฆญํ„ฐ ์„ค๋ช…</p>
                        <p className="whitespace-pre-wrap">{commission.character_description}</p>
                    </div>

                    {commission.background_request && (
                        <div className="mb-4">
                            <p className="text-sm text-gray-500 mb-1">๋ฐฐ๊ฒฝ ์š”์ฒญ์‚ฌํ•ญ</p>
                            <p className="whitespace-pre-wrap">{commission.background_request}</p>
                        </div>
                    )}

                    {commission.reference_image_url && (
                        <div className="mb-4">
                            <p className="text-sm text-gray-500 mb-1">์ฐธ๊ณ  ์ด๋ฏธ์ง€</p>
                            <img
                                src={commission.reference_image_url}
                                alt="์ฐธ๊ณ  ์ด๋ฏธ์ง€"
                                className="max-w-md rounded border"
                            />
                        </div>
                    )}
                </div>

                <div className="bg-white border rounded-lg p-6">
                    <h2 className="text-xl font-bold mb-4">์ƒํƒœ ๊ด€๋ฆฌ</h2>
                    <form onSubmit={handleSubmit} className="space-y-4">
                        <div>
                            <label className="block text-sm font-medium mb-1">์ƒํƒœ</label>
                            <select
                                value={data.status}
                                onChange={(e) => setData('status', e.target.value)}
                                className="w-full border rounded px-3 py-2"
                            >
                                {Object.entries(statusLabels).map(([key, label]) => (
                                    <option key={key} value={key}>
                                        {label}
                                    </option>
                                ))}
                            </select>
                        </div>

                        {data.status === 'completed' && (
                            <div>
                                <label className="block text-sm font-medium mb-1">์™„์„ฑ ์ด๋ฏธ์ง€ URL</label>
                                <input
                                    type="url"
                                    value={data.completed_image_url}
                                    onChange={(e) => setData('completed_image_url', e.target.value)}
                                    className="w-full border rounded px-3 py-2"
                                    placeholder="์™„์„ฑ๋œ ์ž‘ํ’ˆ ์ด๋ฏธ์ง€ URL์„ ์ž…๋ ฅํ•˜์„ธ์š”"
                                />
                            </div>
                        )}

                        <button
                            type="submit"
                            disabled={processing}
                            className="bg-blue-500 text-white px-6 py-2 rounded hover:bg-blue-600 transition"
                        >
                            ์ƒํƒœ ์—…๋ฐ์ดํŠธ
                        </button>
                    </form>
                </div>

                {showToast && (
                    <div className="fixed bottom-4 right-4 bg-green-500 text-white px-6 py-3 rounded shadow-lg animate-fade-in">
                        โœ“ ์ƒํƒœ๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!
                    </div>
                )}
            </div>
        </AuthenticatedLayout>
    );
}
```

`resources/css/app.css`์— ํ† ์ŠคํŠธ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```css
@keyframes fade-in {
    from {
        opacity: 0;
        transform: translateY(1rem);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

.animate-fade-in {
    animation: fade-in 0.3s ease-out;
}
```

๋ธŒ๋ผ์šฐ์ €์—์„œ ์˜๋ขฐ ๋ชฉ๋ก์—์„œ ์นด๋“œ๋ฅผ ํด๋ฆญํ•˜์—ฌ ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ณ , ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•œ ํ›„ ์ €์žฅํ•˜๋ฉด ํ† ์ŠคํŠธ ์•Œ๋ฆผ์ด ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์ปค๋ฏธ์…˜ ์˜๋ขฐ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ํ‘œ์‹œ๋˜๊ณ , ์ƒํƒœ ๋“œ๋กญ๋‹ค์šด์—์„œ ์„ ํƒ ํ›„ ์—…๋ฐ์ดํŠธํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ์ƒํƒœ๊ฐ€ '์™„๋ฃŒ'์ผ ๋•Œ๋งŒ ์™„์„ฑ ์ด๋ฏธ์ง€ URL ์ž…๋ ฅ ํ•„๋“œ๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์—…๋ฐ์ดํŠธ ์„ฑ๊ณต ์‹œ ์šฐ์ธก ํ•˜๋‹จ์— ์ดˆ๋ก์ƒ‰ ํ† ์ŠคํŠธ ์•Œ๋ฆผ์ด ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ํ•จ๊ป˜ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค.

6
Step 6
์™„๋ฃŒ๋œ ์ปค๋ฏธ์…˜ ์ž‘ํ’ˆ์„ ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ๋กœ ํ‘œ์‹œํ•˜๋Š” ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

`CommissionController`์— `portfolio` ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
public function portfolio()
{
    $completedCommissions = Commission::where('status', 'completed')
        ->whereNotNull('completed_image_url')
        ->orderBy('updated_at', 'desc')
        ->get();

    return Inertia::render('Commissions/Portfolio', [
        'commissions' => $completedCommissions,
    ]);
}
```

`routes/web.php`์— ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
Route::get('/portfolio', [CommissionController::class, 'portfolio'])->name('commissions.portfolio');
```

`resources/js/Pages/Commissions/Portfolio.jsx` ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ  ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž‘์„ฑํ•˜์„ธ์š”:

```jsx
import { useState } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';

export default function Portfolio({ commissions }) {
    const [selectedCommission, setSelectedCommission] = useState(null);

    return (
        <AuthenticatedLayout>
            <div className="max-w-6xl mx-auto p-6">
                <h1 className="text-3xl font-bold mb-6">ํฌํŠธํด๋ฆฌ์˜ค ๊ฐค๋Ÿฌ๋ฆฌ</h1>

                {commissions.length === 0 ? (
                    <div className="text-center text-gray-500 py-12">
                        <p className="text-lg">์•„์ง ์™„๋ฃŒ๋œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
                        <p className="text-sm mt-2">์ปค๋ฏธ์…˜ ์˜๋ขฐ๋ฅผ ์™„๋ฃŒํ•˜๊ณ  ํฌํŠธํด๋ฆฌ์˜ค์— ์ถ”๊ฐ€ํ•˜์„ธ์š”!</p>
                    </div>
                ) : (
                    <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
                        {commissions.map((commission) => (
                            <div
                                key={commission.id}
                                onClick={() => setSelectedCommission(commission)}
                                className="cursor-pointer group overflow-hidden rounded-lg border hover:shadow-xl transition transform hover:scale-105"
                            >
                                <img
                                    src={commission.completed_image_url}
                                    alt={commission.purpose}
                                    className="w-full h-64 object-cover group-hover:opacity-90 transition"
                                />
                                <div className="p-2 bg-white">
                                    <p className="text-sm font-semibold truncate">{commission.purpose}</p>
                                    <p className="text-xs text-gray-500 truncate">{commission.client_name}</p>
                                </div>
                            </div>
                        ))}
                    </div>
                )}

                {selectedCommission && (
                    <div
                        className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 p-4 animate-fade-in"
                        onClick={() => setSelectedCommission(null)}
                    >
                        <div
                            className="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-auto"
                            onClick={(e) => e.stopPropagation()}
                        >
                            <div className="relative">
                                <button
                                    onClick={() => setSelectedCommission(null)}
                                    className="absolute top-4 right-4 bg-white rounded-full p-2 shadow-lg hover:bg-gray-100 transition"
                                >
                                    โœ•
                                </button>
                                <img
                                    src={selectedCommission.completed_image_url}
                                    alt={selectedCommission.purpose}
                                    className="w-full max-h-[60vh] object-contain"
                                />
                            </div>
                            <div className="p-6">
                                <h2 className="text-2xl font-bold mb-2">{selectedCommission.purpose}</h2>
                                <p className="text-gray-600 mb-4">์˜๋ขฐ์ž: {selectedCommission.client_name}</p>
                                <div className="mb-4">
                                    <p className="text-sm text-gray-500 mb-1">์บ๋ฆญํ„ฐ ์„ค๋ช…</p>
                                    <p className="whitespace-pre-wrap">{selectedCommission.character_description}</p>
                                </div>
                                {selectedCommission.background_request && (
                                    <div>
                                        <p className="text-sm text-gray-500 mb-1">๋ฐฐ๊ฒฝ ์š”์ฒญ์‚ฌํ•ญ</p>
                                        <p className="whitespace-pre-wrap">{selectedCommission.background_request}</p>
                                    </div>
                                )}
                            </div>
                        </div>
                    </div>
                )}
            </div>
        </AuthenticatedLayout>
    );
}
```

๋„ค๋น„๊ฒŒ์ด์…˜์— ํฌํŠธํด๋ฆฌ์˜ค ๋งํฌ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”. `resources/js/Layouts/AuthenticatedLayout.jsx`๋ฅผ ์—ด๊ณ  ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฉ”๋‰ด์— ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```jsx
<Link
    href={route('commissions.portfolio')}
    className="text-gray-700 hover:text-gray-900"
>
    ํฌํŠธํด๋ฆฌ์˜ค
</Link>
```

๋ธŒ๋ผ์šฐ์ €์—์„œ `/portfolio`์— ์ ‘์†ํ•˜์—ฌ ์™„๋ฃŒ๋œ ์ž‘ํ’ˆ๋“ค์ด ๊ทธ๋ฆฌ๋“œ๋กœ ํ‘œ์‹œ๋˜๊ณ , ์ธ๋„ค์ผ ํด๋ฆญ ์‹œ ๋ชจ๋‹ฌ๋กœ ์ƒ์„ธ ์ •๋ณด๊ฐ€ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ์™„๋ฃŒ๋œ ์ปค๋ฏธ์…˜ ์ž‘ํ’ˆ๋“ค์ด ๋ฐ˜์‘ํ˜• ๊ทธ๋ฆฌ๋“œ๋กœ ํ‘œ์‹œ๋˜๋ฉฐ, ์ธ๋„ค์ผ์— hover ์‹œ ํ™•๋Œ€ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ธ๋„ค์ผ ํด๋ฆญ ์‹œ ์ „์ฒด ํ™”๋ฉด ๋ชจ๋‹ฌ์ด fade-in ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ํ•จ๊ป˜ ๋‚˜ํƒ€๋‚˜๊ณ , ์ด๋ฏธ์ง€์™€ ์˜๋ขฐ ์ •๋ณด๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ๋ชจ๋‹ฌ ์™ธ๋ถ€ ํด๋ฆญ ๋˜๋Š” X ๋ฒ„ํŠผ์œผ๋กœ ๋‹ซ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

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

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

๋Œ“๊ธ€ (0)

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