# Story 4.4: Table easter_eggs et système de détection

Status: ready-for-dev

## Story

As a développeur,
I want une infrastructure pour gérer les easter eggs cachés,
so that je peux ajouter des surprises récompensant l'exploration.

## Acceptance Criteria

1. **Given** les migrations Laravel sont exécutées **When** `php artisan migrate` est lancé **Then** la table `easter_eggs` est créée (id, slug, location, trigger_type ENUM, reward_type ENUM, reward_key, difficulty, is_active, timestamps)
2. **And** les trigger_types incluent : click, hover, konami, scroll, sequence
3. **And** les reward_types incluent : snippet, anecdote, image, badge
4. **And** les seeders insèrent 5-10 easter eggs avec leurs récompenses traduites
5. **Given** l'API `/api/easter-eggs` est appelée **When** la requête est faite **Then** les métadonnées des easter eggs actifs sont retournées (slug, location, trigger_type)
6. **And** les réponses/récompenses ne sont PAS incluses (pour éviter la triche)
7. **Given** l'API `/api/easter-eggs/{slug}/validate` est appelée **When** un slug valide est fourni **Then** la récompense traduite est retournée
8. **And** l'easter egg est marqué comme trouvé côté client (store)

## Tasks / Subtasks

- [ ] **Task 1: Créer la migration table easter_eggs** (AC: #1, #2, #3)
  - [ ] Créer migration `create_easter_eggs_table`
  - [ ] Colonnes : id, slug (unique), location, trigger_type (ENUM), reward_type (ENUM), reward_key, difficulty (1-5), is_active (boolean), timestamps
  - [ ] ENUMs pour trigger_type et reward_type

- [ ] **Task 2: Créer le Model EasterEgg** (AC: #1)
  - [ ] Créer `app/Models/EasterEgg.php`
  - [ ] Définir les fillable et casts
  - [ ] Scope `active()` pour les easter eggs actifs
  - [ ] Relation avec translations pour reward_key

- [ ] **Task 3: Créer le Seeder des easter eggs** (AC: #4)
  - [ ] Créer `database/seeders/EasterEggSeeder.php`
  - [ ] 5-10 easter eggs avec variété de triggers et récompenses
  - [ ] Ajouter les traductions FR et EN pour les récompenses

- [ ] **Task 4: Créer l'endpoint liste des easter eggs** (AC: #5, #6)
  - [ ] Créer `app/Http/Controllers/Api/EasterEggController.php`
  - [ ] Méthode `index()` retournant slug, location, trigger_type
  - [ ] NE PAS inclure reward_key ou détails de la récompense

- [ ] **Task 5: Créer l'endpoint validation** (AC: #7)
  - [ ] Méthode `validate($slug)` retournant la récompense
  - [ ] Traduire selon Accept-Language
  - [ ] Retourner 404 si slug invalide

- [ ] **Task 6: Créer le store côté client** (AC: #8)
  - [ ] Ajouter `easterEggsFound: string[]` dans useProgressionStore
  - [ ] Méthode `markEasterEggFound(slug)`
  - [ ] Getter `easterEggsCount` (trouvés/total)

- [ ] **Task 7: Créer le composable useFetchEasterEggs**
  - [ ] Créer `frontend/app/composables/useFetchEasterEggs.ts`
  - [ ] Méthode `fetchList()` pour récupérer les métadonnées
  - [ ] Méthode `validate(slug)` pour valider un easter egg trouvé

- [ ] **Task 8: Tests et validation**
  - [ ] Exécuter les migrations
  - [ ] Vérifier le seeding
  - [ ] Tester l'API liste (sans récompenses)
  - [ ] Tester l'API validation (avec récompenses)

## Dev Notes

### Migration easter_eggs

```php
<?php
// database/migrations/2026_02_04_000003_create_easter_eggs_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('easter_eggs', function (Blueprint $table) {
            $table->id();
            $table->string('slug')->unique();
            $table->string('location'); // Page ou zone où se trouve l'easter egg
            $table->enum('trigger_type', ['click', 'hover', 'konami', 'scroll', 'sequence']);
            $table->enum('reward_type', ['snippet', 'anecdote', 'image', 'badge']);
            $table->string('reward_key'); // Clé de traduction pour la récompense
            $table->unsignedTinyInteger('difficulty')->default(1); // 1-5
            $table->boolean('is_active')->default(true);
            $table->timestamps();

            $table->index('is_active');
            $table->index('location');
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('easter_eggs');
    }
};
```

### Model EasterEgg

```php
<?php
// api/app/Models/EasterEgg.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class EasterEgg extends Model
{
    protected $fillable = [
        'slug',
        'location',
        'trigger_type',
        'reward_type',
        'reward_key',
        'difficulty',
        'is_active',
    ];

    protected $casts = [
        'difficulty' => 'integer',
        'is_active' => 'boolean',
    ];

    public function scopeActive($query)
    {
        return $query->where('is_active', true);
    }

    public function scopeByLocation($query, string $location)
    {
        return $query->where('location', $location);
    }

    public function getReward(string $lang = 'fr'): ?string
    {
        return Translation::getTranslation($this->reward_key, $lang);
    }
}
```

### Seeder des easter eggs

```php
<?php
// database/seeders/EasterEggSeeder.php

namespace Database\Seeders;

use App\Models\EasterEgg;
use App\Models\Translation;
use Illuminate\Database\Seeder;

class EasterEggSeeder extends Seeder
{
    public function run(): void
    {
        $easterEggs = [
            // 1. Konami code sur la landing
            [
                'slug' => 'konami-master',
                'location' => 'landing',
                'trigger_type' => 'konami',
                'reward_type' => 'badge',
                'reward_key' => 'easter.konami.reward',
                'difficulty' => 3,
            ],
            // 2. Clic sur l'araignée cachée (header)
            [
                'slug' => 'hidden-spider',
                'location' => 'header',
                'trigger_type' => 'click',
                'reward_type' => 'anecdote',
                'reward_key' => 'easter.spider.reward',
                'difficulty' => 2,
            ],
            // 3. Hover sur un caractère spécial dans le code (page projets)
            [
                'slug' => 'secret-comment',
                'location' => 'projects',
                'trigger_type' => 'hover',
                'reward_type' => 'snippet',
                'reward_key' => 'easter.comment.reward',
                'difficulty' => 2,
            ],
            // 4. Scroll jusqu'en bas de la page parcours
            [
                'slug' => 'journey-end',
                'location' => 'journey',
                'trigger_type' => 'scroll',
                'reward_type' => 'anecdote',
                'reward_key' => 'easter.journey_end.reward',
                'difficulty' => 1,
            ],
            // 5. Séquence de clics sur les compétences (Vue, Laravel, TypeScript)
            [
                'slug' => 'tech-sequence',
                'location' => 'skills',
                'trigger_type' => 'sequence',
                'reward_type' => 'snippet',
                'reward_key' => 'easter.tech_seq.reward',
                'difficulty' => 4,
            ],
            // 6. Clic sur le logo Skycel 5 fois
            [
                'slug' => 'logo-clicks',
                'location' => 'global',
                'trigger_type' => 'click',
                'reward_type' => 'image',
                'reward_key' => 'easter.logo.reward',
                'difficulty' => 2,
            ],
            // 7. Hover sur la date "2022" dans le parcours
            [
                'slug' => 'founding-date',
                'location' => 'journey',
                'trigger_type' => 'hover',
                'reward_type' => 'anecdote',
                'reward_key' => 'easter.founding.reward',
                'difficulty' => 2,
            ],
            // 8. Console.log dans les devtools
            [
                'slug' => 'dev-console',
                'location' => 'global',
                'trigger_type' => 'sequence',
                'reward_type' => 'badge',
                'reward_key' => 'easter.console.reward',
                'difficulty' => 3,
            ],
        ];

        foreach ($easterEggs as $egg) {
            EasterEgg::create($egg);
        }

        // Traductions des récompenses
        $translations = [
            // Konami
            ['key' => 'easter.konami.reward', 'fr' => "🎮 Badge 'Gamer' débloqué ! Tu connais les classiques.", 'en' => "🎮 'Gamer' badge unlocked! You know the classics."],

            // Spider
            ['key' => 'easter.spider.reward', 'fr' => "🕷️ Tu m'as trouvé ! Je me cache partout sur ce site... Le Bug te surveille toujours.", 'en' => "🕷️ You found me! I hide everywhere on this site... The Bug is always watching."],

            // Comment
            ['key' => 'easter.comment.reward', 'fr' => "// Premier code écrit : console.log('Hello World'); // Tout a commencé là...", 'en' => "// First code written: console.log('Hello World'); // It all started there..."],

            // Journey end
            ['key' => 'easter.journey_end.reward', 'fr' => "Tu as lu jusqu'au bout ? Respect. Le prochain chapitre s'écrit peut-être avec toi.", 'en' => "You read all the way? Respect. The next chapter might be written with you."],

            // Tech sequence
            ['key' => 'easter.tech_seq.reward', 'fr' => "const stack = ['Vue', 'Laravel', 'TypeScript'];\n// La sainte trinité du dev moderne ⚡", 'en' => "const stack = ['Vue', 'Laravel', 'TypeScript'];\n// The holy trinity of modern dev ⚡"],

            // Logo
            ['key' => 'easter.logo.reward', 'fr' => "🖼️ Image secrète débloquée : La première version du logo Skycel (spoiler: c'était moche)", 'en' => "🖼️ Secret image unlocked: The first version of the Skycel logo (spoiler: it was ugly)"],

            // Founding
            ['key' => 'easter.founding.reward', 'fr' => "2022 : l'année où Le Bug est né. Littéralement un bug dans le code qui m'a donné l'idée de la mascotte.", 'en' => "2022: the year The Bug was born. Literally a bug in the code that gave me the mascot idea."],

            // Console
            ['key' => 'easter.console.reward', 'fr' => "🔧 Badge 'Développeur' débloqué ! Tu as vérifié la console comme un vrai dev.", 'en' => "🔧 'Developer' badge unlocked! You checked the console like a real dev."],
        ];

        foreach ($translations as $t) {
            Translation::firstOrCreate(
                ['lang' => 'fr', 'key_name' => $t['key']],
                ['value' => $t['fr']]
            );
            Translation::firstOrCreate(
                ['lang' => 'en', 'key_name' => $t['key']],
                ['value' => $t['en']]
            );
        }
    }
}
```

### Controller API

```php
<?php
// api/app/Http/Controllers/Api/EasterEggController.php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\EasterEgg;
use Illuminate\Http\Request;

class EasterEggController extends Controller
{
    /**
     * Liste les easter eggs actifs (sans révéler les récompenses)
     */
    public function index()
    {
        $easterEggs = EasterEgg::active()
            ->select('slug', 'location', 'trigger_type', 'difficulty')
            ->get();

        return response()->json([
            'data' => $easterEggs,
            'meta' => [
                'total' => $easterEggs->count(),
            ],
        ]);
    }

    /**
     * Valide un easter egg et retourne la récompense
     */
    public function validate(Request $request, string $slug)
    {
        $easterEgg = EasterEgg::active()->where('slug', $slug)->first();

        if (!$easterEgg) {
            return response()->json([
                'error' => [
                    'code' => 'EASTER_EGG_NOT_FOUND',
                    'message' => 'Easter egg not found or inactive',
                ],
            ], 404);
        }

        $lang = $request->header('Accept-Language', 'fr');
        $reward = $easterEgg->getReward($lang);

        return response()->json([
            'data' => [
                'slug' => $easterEgg->slug,
                'reward_type' => $easterEgg->reward_type,
                'reward' => $reward,
                'difficulty' => $easterEgg->difficulty,
            ],
        ]);
    }
}
```

### Routes API

```php
// api/routes/api.php
Route::get('/easter-eggs', [EasterEggController::class, 'index']);
Route::post('/easter-eggs/{slug}/validate', [EasterEggController::class, 'validate']);
```

### Extension du store progression

```typescript
// À ajouter dans frontend/app/stores/progression.ts

// État
const easterEggsFound = ref<string[]>([])

// Actions
function markEasterEggFound(slug: string) {
  if (!easterEggsFound.value.includes(slug)) {
    easterEggsFound.value.push(slug)
  }
}

// Getters
const easterEggsFoundCount = computed(() => easterEggsFound.value.length)

// Export
return {
  // ... existing ...
  easterEggsFound,
  easterEggsFoundCount,
  markEasterEggFound,
}
```

### Composable useFetchEasterEggs

```typescript
// frontend/app/composables/useFetchEasterEggs.ts
interface EasterEggMeta {
  slug: string
  location: string
  trigger_type: 'click' | 'hover' | 'konami' | 'scroll' | 'sequence'
  difficulty: number
}

interface EasterEggReward {
  slug: string
  reward_type: 'snippet' | 'anecdote' | 'image' | 'badge'
  reward: string
  difficulty: number
}

export function useFetchEasterEggs() {
  const config = useRuntimeConfig()
  const { locale } = useI18n()

  // Cache des easter eggs disponibles
  const availableEasterEggs = ref<EasterEggMeta[]>([])
  const isLoaded = ref(false)

  async function fetchList(): Promise<EasterEggMeta[]> {
    if (isLoaded.value) return availableEasterEggs.value

    const response = await $fetch<{ data: EasterEggMeta[] }>('/easter-eggs', {
      baseURL: config.public.apiUrl,
      headers: {
        'X-API-Key': config.public.apiKey,
      },
    })

    availableEasterEggs.value = response.data
    isLoaded.value = true
    return response.data
  }

  async function validate(slug: string): Promise<EasterEggReward | null> {
    try {
      const response = await $fetch<{ data: EasterEggReward }>(`/easter-eggs/${slug}/validate`, {
        method: 'POST',
        baseURL: config.public.apiUrl,
        headers: {
          'X-API-Key': config.public.apiKey,
          'Accept-Language': locale.value,
        },
      })
      return response.data
    } catch (error) {
      console.error('Failed to validate easter egg:', error)
      return null
    }
  }

  function getByLocation(location: string): EasterEggMeta[] {
    return availableEasterEggs.value.filter(e => e.location === location || e.location === 'global')
  }

  return {
    availableEasterEggs,
    fetchList,
    validate,
    getByLocation,
  }
}
```

### Tableau des easter eggs

| Slug | Location | Trigger | Type | Difficulté |
|------|----------|---------|------|------------|
| konami-master | landing | konami | badge | 3/5 |
| hidden-spider | header | click | anecdote | 2/5 |
| secret-comment | projects | hover | snippet | 2/5 |
| journey-end | journey | scroll | anecdote | 1/5 |
| tech-sequence | skills | sequence | snippet | 4/5 |
| logo-clicks | global | click | image | 2/5 |
| founding-date | journey | hover | anecdote | 2/5 |
| dev-console | global | sequence | badge | 3/5 |

### Dépendances

**Cette story nécessite :**
- Story 1.2 : Table translations
- Story 3.5 : Store de progression

**Cette story prépare pour :**
- Story 4.5 : Implémentation UI des easter eggs

### Project Structure Notes

**Fichiers à créer :**
```
api/
├── app/Models/
│   └── EasterEgg.php                    # CRÉER
├── app/Http/Controllers/Api/
│   └── EasterEggController.php          # CRÉER
└── database/
    ├── migrations/
    │   └── 2026_02_04_000003_create_easter_eggs_table.php  # CRÉER
    └── seeders/
        └── EasterEggSeeder.php          # CRÉER

frontend/app/composables/
└── useFetchEasterEggs.ts                # CRÉER
```

**Fichiers à modifier :**
```
api/routes/api.php                       # AJOUTER routes easter-eggs
api/database/seeders/DatabaseSeeder.php  # APPELER EasterEggSeeder
frontend/app/stores/progression.ts       # AJOUTER easterEggsFound
```

### References

- [Source: docs/planning-artifacts/epics.md#Story-4.4]
- [Source: docs/planning-artifacts/ux-design-specification.md#Easter-Eggs]
- [Source: docs/brainstorming-gamification-2026-01-26.md#Easter-Eggs]

### Technical Requirements

| Requirement | Value | Source |
|-------------|-------|--------|
| Nombre d'easter eggs | 5-10 | Epics |
| Trigger types | click, hover, konami, scroll, sequence | Epics |
| Reward types | snippet, anecdote, image, badge | Epics |
| API sans spoil | Liste sans récompenses | Epics |

## Dev Agent Record

### Agent Model Used

{{agent_model_name_version}}

### Debug Log References

### Completion Notes List

### Change Log
| Date | Change | Author |
|------|--------|--------|
| 2026-02-04 | Story créée avec contexte complet | SM Agent |

### File List

