retro-portfolio-engine 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,105 @@
1
+ name: Build and Deploy Site
2
+
3
+ on:
4
+ # Manual trigger
5
+ workflow_dispatch:
6
+ inputs:
7
+ force_rebuild:
8
+ description: 'Force rebuild (ignore cache)'
9
+ required: false
10
+ type: boolean
11
+ default: false
12
+
13
+ # Scheduled rebuild (daily at midnight UTC)
14
+ schedule:
15
+ - cron: '0 0 * * *'
16
+
17
+ # Trigger on push to main
18
+ push:
19
+ branches:
20
+ - main
21
+
22
+ # Webhook from data repository (requires setup)
23
+ repository_dispatch:
24
+ types: [data-updated]
25
+
26
+ permissions:
27
+ contents: read
28
+ pages: write
29
+ id-token: write
30
+
31
+ # Prevent concurrent deployments
32
+ concurrency:
33
+ group: "pages"
34
+ cancel-in-progress: false
35
+
36
+ jobs:
37
+ build:
38
+ runs-on: ubuntu-latest
39
+
40
+ steps:
41
+ - name: 📦 Checkout Engine Repository
42
+ uses: actions/checkout@v4
43
+
44
+ - name: 🔧 Install Dependencies
45
+ run: |
46
+ sudo apt-get update
47
+ sudo apt-get install -y jq curl
48
+
49
+ - name: 🏗️ Build Site
50
+ run: |
51
+ # Set execute permission
52
+ chmod +x build.sh
53
+
54
+ # Run build script
55
+ if [ "${{ github.event.inputs.force_rebuild }}" = "true" ]; then
56
+ ./build.sh --force
57
+ else
58
+ ./build.sh
59
+ fi
60
+ env:
61
+ MANIFEST_URL: ${{ secrets.MANIFEST_URL || 'https://raw.githubusercontent.com/YOUR_USERNAME/retro-portfolio-data/main/manifest.json' }}
62
+
63
+ - name: 📊 Build Summary
64
+ run: |
65
+ echo "### 🎨 Build Summary" >> $GITHUB_STEP_SUMMARY
66
+ echo "" >> $GITHUB_STEP_SUMMARY
67
+ echo "**Build Date:** $(date -u +"%Y-%m-%d %H:%M:%S UTC")" >> $GITHUB_STEP_SUMMARY
68
+
69
+ if [ -f dist/build-info.json ]; then
70
+ echo "**Manifest Version:** $(jq -r '.version' dist/build-info.json)" >> $GITHUB_STEP_SUMMARY
71
+ echo "**Data Source:** $(jq -r '.dataSource' dist/build-info.json)" >> $GITHUB_STEP_SUMMARY
72
+ echo "**Cache Used:** $(jq -r '.cacheUsed' dist/build-info.json)" >> $GITHUB_STEP_SUMMARY
73
+ fi
74
+
75
+ echo "" >> $GITHUB_STEP_SUMMARY
76
+ echo "**Files Generated:**" >> $GITHUB_STEP_SUMMARY
77
+ echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
78
+ ls -lh dist/ >> $GITHUB_STEP_SUMMARY
79
+ echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
80
+
81
+ - name: 📤 Upload Artifact
82
+ uses: actions/upload-pages-artifact@v3
83
+ with:
84
+ path: ./dist
85
+
86
+ deploy:
87
+ needs: build
88
+ runs-on: ubuntu-latest
89
+
90
+ environment:
91
+ name: github-pages
92
+ url: ${{ steps.deployment.outputs.page_url }}
93
+
94
+ steps:
95
+ - name: 🚀 Deploy to GitHub Pages
96
+ id: deployment
97
+ uses: actions/deploy-pages@v4
98
+
99
+ - name: ✅ Deployment Complete
100
+ run: |
101
+ echo "### 🎉 Deployment Successful!" >> $GITHUB_STEP_SUMMARY
102
+ echo "" >> $GITHUB_STEP_SUMMARY
103
+ echo "**Site URL:** ${{ steps.deployment.outputs.page_url }}" >> $GITHUB_STEP_SUMMARY
104
+ echo "" >> $GITHUB_STEP_SUMMARY
105
+ echo "Your site has been successfully deployed!" >> $GITHUB_STEP_SUMMARY
package/ADMIN_SETUP.md ADDED
@@ -0,0 +1,306 @@
1
+ # 🔧 Configuration de l'Admin pour Site-as-a-Package
2
+
3
+ ## 📋 Vue d'ensemble
4
+
5
+ Pour utiliser votre interface admin avec le système Site-as-a-Package, vous avez **deux options** :
6
+
7
+ ### **Option A : Admin dans le repo de données** (Recommandé)
8
+ - Admin hébergé dans `retro-portfolio-data`
9
+ - Modifie directement les fichiers data/
10
+ - Commit et push automatique vers GitHub
11
+ - Déclenche rebuild du site engine
12
+
13
+ ### **Option B : Admin local séparé**
14
+ - Admin sur votre machine uniquement
15
+ - Utilise l'API Python existante
16
+ - Vous push manuellement vers GitHub
17
+
18
+ ---
19
+
20
+ ## 🚀 Option A : Admin dans le Data Repo (Recommandé)
21
+
22
+ ### Architecture
23
+
24
+ ```
25
+ retro-portfolio-data/
26
+ ├── config/
27
+ ├── data/
28
+ ├── lang/
29
+ ├── admin/ ← Nouveau dossier
30
+ │ ├── index.html (admin.html)
31
+ │ ├── admin.css
32
+ │ ├── admin.js (logique admin)
33
+ │ └── api/
34
+ │ └── github-sync.php (ou .js pour serverless)
35
+ └── .github/workflows/
36
+ └── notify-engine.yml (notifie le engine après modification)
37
+ ```
38
+
39
+ ### Fonctionnement
40
+
41
+ 1. **Vous modifiez** via l'admin → `retro-portfolio-data.github.io/admin/`
42
+ 2. **Admin commit** les changements vers `data/` via GitHub API
43
+ 3. **Webhook** notifie `retro-portfolio-engine`
44
+ 4. **Engine rebuild** automatiquement avec nouvelles données
45
+
46
+ ### Avantages
47
+
48
+ ✅ Accessible de partout (web-based)
49
+ ✅ Pas besoin de Python local
50
+ ✅ Git history automatique
51
+ ✅ Rebuild automatique du site
52
+
53
+ ### Configuration requise
54
+
55
+ 1. **Personal Access Token (PAT)** avec scope `repo`
56
+ 2. **GitHub Pages** activé sur `retro-portfolio-data`
57
+
58
+ ---
59
+
60
+ ## 🛠️ Option B : Admin Local (Simple)
61
+
62
+ ### Architecture
63
+
64
+ ```
65
+ votre-machine/
66
+ ├── retro-portfolio-data/ (clone du repo)
67
+ │ ├── config/
68
+ │ ├── data/
69
+ │ └── lang/
70
+ └── admin-local/
71
+ ├── admin.html
72
+ ├── admin_api.py (votre API existante)
73
+ └── scripts/
74
+ ```
75
+
76
+ ### Fonctionnement
77
+
78
+ 1. **Clone** `retro-portfolio-data` localement
79
+ 2. **Lancez** `python3 admin_api.py`
80
+ 3. **Ouvrez** `admin.html` dans le navigateur
81
+ 4. **Modifiez** vos données
82
+ 5. **Push manuellement** :
83
+ ```bash
84
+ cd retro-portfolio-data
85
+ git add data/
86
+ git commit -m "Update content via admin"
87
+ git push
88
+ ```
89
+ 6. **Rebuild** déclenché automatiquement
90
+
91
+ ### Avantages
92
+
93
+ ✅ Utilise votre admin existant
94
+ ✅ Pas de configuration complexe
95
+ ✅ Contrôle total
96
+
97
+ ### Inconvénients
98
+
99
+ ❌ Nécessite Python local
100
+ ❌ Push manuel requis
101
+ ❌ Pas accessible à distance
102
+
103
+ ---
104
+
105
+ ## 🎨 Configuration de l'Option B (Quick Start)
106
+
107
+ ### 1. Préparer le repo de données
108
+
109
+ ```bash
110
+ # Créer et cloner le repo de données
111
+ git clone https://github.com/YOUR_USERNAME/retro-portfolio-data.git
112
+ cd retro-portfolio-data
113
+
114
+ # Copier vos données existantes
115
+ cp -r ../retro-portfolio/config ./
116
+ cp -r ../retro-portfolio/data ./
117
+ cp -r ../retro-portfolio/lang ./
118
+
119
+ git add .
120
+ git commit -m "Initial data import"
121
+ git push
122
+ ```
123
+
124
+ ### 2. Configurer l'admin local
125
+
126
+ ```bash
127
+ # Créer dossier admin
128
+ mkdir -p admin-local
129
+
130
+ # Copier admin files
131
+ cp ../retro-portfolio/admin.html admin-local/
132
+ cp ../retro-portfolio/admin.css admin-local/
133
+ cp ../retro-portfolio/admin_api.py admin-local/
134
+ cp -r ../retro-portfolio/scripts admin-local/
135
+
136
+ # Copier .env (credentials Cloudinary)
137
+ cp ../retro-portfolio/.env admin-local/
138
+ ```
139
+
140
+ ### 3. Modifier admin_api.py pour pointer vers le data repo
141
+
142
+ ```python
143
+ # admin-local/admin_api.py
144
+
145
+ # Modifier les chemins
146
+ DATA_DIR = '../retro-portfolio-data/data'
147
+ CONFIG_DIR = '../retro-portfolio-data/config'
148
+ LANG_DIR = '../retro-portfolio-data/lang'
149
+ ```
150
+
151
+ ### 4. Lancer l'admin
152
+
153
+ ```bash
154
+ cd admin-local
155
+ python3 admin_api.py
156
+
157
+ # Dans un autre terminal
158
+ cd retro-portfolio-data
159
+ python3 -m http.server 8001
160
+
161
+ # Ouvrir http://localhost:8001/admin-local/admin.html
162
+ ```
163
+
164
+ ### 5. Workflow de mise à jour
165
+
166
+ ```bash
167
+ # Après modification via admin
168
+ cd retro-portfolio-data
169
+
170
+ # Vérifier les changements
171
+ git status
172
+
173
+ # Commit
174
+ git add data/ lang/
175
+ git commit -m "Update content via admin"
176
+
177
+ # Push (déclenche rebuild automatique)
178
+ git push
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 🌐 Configuration de l'Option A (Avancé)
184
+
185
+ ### 1. Activer GitHub Pages sur le data repo
186
+
187
+ ```bash
188
+ # Dans retro-portfolio-data/
189
+ mkdir -p admin
190
+ cp ../retro-portfolio/admin.html admin/index.html
191
+ cp ../retro-portfolio/admin.css admin/
192
+
193
+ git add admin/
194
+ git commit -m "Add web-based admin"
195
+ git push
196
+ ```
197
+
198
+ Settings → Pages → Source: `main` branch → `/` (root)
199
+
200
+ ### 2. Créer l'API serverless
201
+
202
+ ```javascript
203
+ // admin/api/sync.js (pour Vercel/Netlify)
204
+ import { Octokit } from "@octokit/rest";
205
+
206
+ export default async function handler(req, res) {
207
+ if (req.method !== 'POST') {
208
+ return res.status(405).json({ error: 'Method not allowed' });
209
+ }
210
+
211
+ const { category, data, message } = req.body;
212
+ const PAT = process.env.GITHUB_PAT;
213
+
214
+ const octokit = new Octokit({ auth: PAT });
215
+
216
+ try {
217
+ // Get current file content
218
+ const { data: currentFile } = await octokit.repos.getContent({
219
+ owner: 'YOUR_USERNAME',
220
+ repo: 'retro-portfolio-data',
221
+ path: `data/${category}.json`,
222
+ });
223
+
224
+ // Update file
225
+ await octokit.repos.createOrUpdateFileContents({
226
+ owner: 'YOUR_USERNAME',
227
+ repo: 'retro-portfolio-data',
228
+ path: `data/${category}.json`,
229
+ message: message || 'Update via admin',
230
+ content: Buffer.from(JSON.stringify(data, null, 2)).toString('base64'),
231
+ sha: currentFile.sha,
232
+ });
233
+
234
+ res.status(200).json({ success: true });
235
+ } catch (error) {
236
+ res.status(500).json({ error: error.message });
237
+ }
238
+ }
239
+ ```
240
+
241
+ ### 3. Adapter admin.html
242
+
243
+ ```javascript
244
+ // Dans admin.html, remplacer les appels API
245
+
246
+ async function saveData(category, items) {
247
+ const response = await fetch('/api/sync', {
248
+ method: 'POST',
249
+ headers: { 'Content-Type': 'application/json' },
250
+ body: JSON.stringify({
251
+ category,
252
+ data: { items },
253
+ message: `Update ${category} via web admin`
254
+ })
255
+ });
256
+
257
+ if (!response.ok) throw new Error('Failed to save');
258
+
259
+ // Déclencher rebuild du engine
260
+ await triggerRebuild();
261
+ }
262
+
263
+ async function triggerRebuild() {
264
+ await fetch('https://api.github.com/repos/YOUR_USERNAME/retro-portfolio-engine/dispatches', {
265
+ method: 'POST',
266
+ headers: {
267
+ 'Authorization': `token ${GITHUB_PAT}`,
268
+ 'Accept': 'application/vnd.github+json'
269
+ },
270
+ body: JSON.stringify({ event_type: 'data-updated' })
271
+ });
272
+ }
273
+ ```
274
+
275
+ ---
276
+
277
+ ## 🔐 Sécurité
278
+
279
+ ### Pour l'Option A (Web Admin)
280
+
281
+ 1. **Protéger l'admin** avec authentification
282
+ 2. **Utiliser des secrets** pour le PAT (variables d'environnement)
283
+ 3. **Limiter les permissions** du PAT au strict nécessaire
284
+
285
+ ### Pour l'Option B (Local Admin)
286
+
287
+ 1. **Ne jamais commit** votre `.env` avec credentials
288
+ 2. **Ajouter** `.env` au `.gitignore`
289
+
290
+ ---
291
+
292
+ ## 📊 Recommandation
293
+
294
+ Pour votre cas, je recommande **Option B** car :
295
+
296
+ ✅ Plus simple à mettre en place
297
+ ✅ Réutilise votre admin existant
298
+ ✅ Pas de configuration serverless
299
+ ✅ Contrôle total local
300
+
301
+ Le workflow devient :
302
+ ```
303
+ Modifier via admin local → git push → Rebuild auto du site
304
+ ```
305
+
306
+ Voulez-vous que je vous prépare les scripts pour l'Option B ?
package/README.md ADDED
@@ -0,0 +1,300 @@
1
+ # 🎨 Site-as-a-Package Engine
2
+
3
+ Un moteur de site web autonome qui récupère son contenu depuis un repo distant et se construit automatiquement. Concept **Site-as-a-Package** : le code du moteur est séparé des données.
4
+
5
+ ## 🚀 Concept
6
+
7
+ Au lieu d'avoir un site avec du contenu figé, ce repo contient uniquement :
8
+
9
+ 1. **Le moteur de rendu** (HTML, CSS, JS)
10
+ 2. **Un script de build** qui récupère les données distantes
11
+ 3. **Une GitHub Action** qui automatise tout
12
+
13
+ Les **données, traductions et configurations** sont stockées dans un repo séparé. À chaque build, le moteur récupère la dernière version du contenu.
14
+
15
+ ## 📁 Architecture
16
+
17
+ ```
18
+ retro-portfolio-engine/ (Ce repo - Le moteur)
19
+ ├── index.html Squelette HTML
20
+ ├── style.css, fonts.css Styles
21
+ ├── js/
22
+ │ ├── manifest-loader.js Charge le manifest distribué
23
+ │ ├── i18n.js, render.js Modules du moteur
24
+ │ └── ...
25
+ ├── build.sh Script de build avec cache
26
+ ├── manifest.json Configuration du data repo
27
+ └── .github/workflows/
28
+ └── build-and-deploy.yml Automatisation GitHub
29
+
30
+ retro-portfolio-data/ (Repo séparé - Les données)
31
+ ├── manifest.json Point d'entrée
32
+ ├── config/
33
+ │ ├── app.json
34
+ │ ├── languages.json
35
+ │ └── categories.json
36
+ ├── data/
37
+ │ ├── painting.json
38
+ │ └── ...
39
+ └── lang/
40
+ ├── en.json
41
+ └── fr.json
42
+ ```
43
+
44
+ ## 🔧 Installation
45
+
46
+ ### 1. Cloner le repo moteur
47
+
48
+ ```bash
49
+ git clone https://github.com/YOUR_USERNAME/retro-portfolio-engine.git
50
+ cd retro-portfolio-engine
51
+ ```
52
+
53
+ ### 2. Configurer le manifest
54
+
55
+ Éditez `manifest.json` et changez les URLs :
56
+
57
+ ```json
58
+ {
59
+ "dataRepository": {
60
+ "owner": "YOUR_USERNAME",
61
+ "repo": "retro-portfolio-data",
62
+ "branch": "main",
63
+ "baseUrl": "https://raw.githubusercontent.com/YOUR_USERNAME/retro-portfolio-data/main/"
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 3. Build local (avec cache)
69
+
70
+ ```bash
71
+ # Build normal (utilise le cache si disponible)
72
+ ./build.sh
73
+
74
+ # Force le re-téléchargement complet
75
+ ./build.sh --force
76
+
77
+ # Tester localement
78
+ cd dist
79
+ python3 -m http.server 8000
80
+ # Ouvrir http://localhost:8000
81
+ ```
82
+
83
+ ### 4. Déploiement automatique sur GitHub Pages
84
+
85
+ 1. **Activer GitHub Pages** dans les paramètres du repo :
86
+ - Settings → Pages
87
+ - Source : **GitHub Actions**
88
+
89
+ 2. **Configurer le secret (optionnel)** :
90
+ - Settings → Secrets → Actions
91
+ - Ajouter `MANIFEST_URL` si votre manifest est ailleurs
92
+
93
+ 3. **Déclencher un build** :
94
+ - Push sur `main` → build automatique
95
+ - Actions → Build and Deploy Site → Run workflow
96
+
97
+ ## 🎯 Utilisation
98
+
99
+ ### Mode 1 : Build automatique (recommandé)
100
+
101
+ GitHub Action se déclenche automatiquement :
102
+
103
+ - ✅ À chaque push sur `main`
104
+ - ✅ Tous les jours à minuit (récupère les nouvelles données)
105
+ - ✅ Manuellement depuis l'onglet Actions
106
+
107
+ ### Mode 2 : Build manuel
108
+
109
+ ```bash
110
+ # Build avec cache (rapide)
111
+ ./build.sh
112
+
113
+ # Build complet (ignore le cache)
114
+ ./build.sh --force
115
+
116
+ # Build avec manifest custom
117
+ ./build.sh --manifest https://example.com/manifest.json
118
+
119
+ # Options avancées
120
+ ./build.sh --help
121
+ ```
122
+
123
+ ## 🔄 Mettre à jour le contenu
124
+
125
+ ### Option A : Modifier le repo de données
126
+
127
+ 1. Modifier les fichiers dans `retro-portfolio-data/`
128
+ 2. Commit & push
129
+ 3. (Optionnel) Déclencher un rebuild du moteur
130
+
131
+ ### Option B : Utiliser un webhook
132
+
133
+ Configurez le repo de données pour notifier le moteur :
134
+
135
+ ```yaml
136
+ # Dans retro-portfolio-data/.github/workflows/notify.yml
137
+ on:
138
+ push:
139
+ branches: [main]
140
+
141
+ jobs:
142
+ notify-engine:
143
+ runs-on: ubuntu-latest
144
+ steps:
145
+ - name: Trigger rebuild
146
+ run: |
147
+ curl -X POST \
148
+ -H "Authorization: token ${{ secrets.PAT_TOKEN }}" \
149
+ https://api.github.com/repos/YOUR_USERNAME/retro-portfolio-engine/dispatches \
150
+ -d '{"event_type": "data-updated"}'
151
+ ```
152
+
153
+ ## 🛡️ Sécurité et Bonnes Pratiques
154
+
155
+ ### ✅ CORS
156
+
157
+ `raw.githubusercontent.com` envoie automatiquement `Access-Control-Allow-Origin: *`, donc **pas de problème CORS** pour les requêtes GET.
158
+
159
+ ### ✅ Validation des données
160
+
161
+ Le `manifest-loader.js` valide la structure du manifest :
162
+
163
+ ```javascript
164
+ validateManifest(manifest) {
165
+ const required = ['version', 'dataRepository', 'structure'];
166
+ // Validation stricte
167
+ }
168
+ ```
169
+
170
+ ### ✅ Cache intelligent
171
+
172
+ Le script `build.sh` utilise un cache local (`.cache/`) :
173
+
174
+ - ⚡ Accélère les builds répétés
175
+ - 🔄 TTL de 1 heure par défaut
176
+ - 🔨 Option `--force` pour forcer le refresh
177
+
178
+ ### ✅ Sanitisation
179
+
180
+ Tous les contenus HTML sont sanitisés pour éviter les XSS :
181
+
182
+ ```javascript
183
+ sanitizeHTML(str) {
184
+ const temp = document.createElement('div');
185
+ temp.textContent = str;
186
+ return temp.innerHTML;
187
+ }
188
+ ```
189
+
190
+ ## 📊 Fonctionnalités
191
+
192
+ ### ✨ Avantages
193
+
194
+ - **🔄 Toujours à jour** : Récupère la dernière version du contenu à chaque build
195
+ - **📦 Léger** : Le repo moteur est quasi vide
196
+ - **🌍 Multi-langues** : Support natif de plusieurs langues
197
+ - **⚡ Performant** : Cache intelligent, build optimisé
198
+ - **🔒 Sécurisé** : Validation, sanitisation, pas d'eval()
199
+ - **🚀 Zero-config** : Push et c'est déployé
200
+
201
+ ### 🎨 Personnalisation
202
+
203
+ Vous pouvez modifier :
204
+
205
+ - `style.css` : Apparence visuelle
206
+ - `js/render.js` : Logique de rendu
207
+ - `manifest.json` : Source des données
208
+
209
+ ## 🐛 Dépannage
210
+
211
+ ### Le build échoue
212
+
213
+ ```bash
214
+ # Vérifier que jq est installé
215
+ which jq
216
+
217
+ # Tester manuellement le manifest
218
+ curl https://raw.githubusercontent.com/YOUR_USERNAME/retro-portfolio-data/main/manifest.json
219
+
220
+ # Build en mode verbose
221
+ bash -x build.sh
222
+ ```
223
+
224
+ ### Les données ne se mettent pas à jour
225
+
226
+ ```bash
227
+ # Forcer le re-téléchargement
228
+ ./build.sh --force
229
+
230
+ # Vider le cache
231
+ rm -rf .cache
232
+ ./build.sh
233
+ ```
234
+
235
+ ### GitHub Action échoue
236
+
237
+ 1. Vérifier que GitHub Pages est activé
238
+ 2. Vérifier les permissions dans Settings → Actions → Workflow permissions
239
+ 3. Consulter les logs dans Actions → Build and Deploy
240
+
241
+ ## 📚 Documentation Avancée
242
+
243
+ ### Structure du Manifest
244
+
245
+ ```json
246
+ {
247
+ "version": "1.0.0",
248
+ "dataRepository": {
249
+ "type": "github",
250
+ "baseUrl": "https://raw.githubusercontent.com/..."
251
+ },
252
+ "structure": {
253
+ "config": {
254
+ "app": "config/app.json",
255
+ "languages": "config/languages.json"
256
+ },
257
+ "data": {
258
+ "painting": "data/painting.json"
259
+ },
260
+ "lang": {
261
+ "en": "lang/en.json"
262
+ }
263
+ }
264
+ }
265
+ ```
266
+
267
+ ### API du ManifestLoader
268
+
269
+ ```javascript
270
+ // Initialiser
271
+ await ManifestLoader.init('./manifest.json');
272
+
273
+ // Récupérer des URLs
274
+ const appConfigUrl = ManifestLoader.getConfigUrl('app');
275
+ const paintingUrl = ManifestLoader.getDataUrl('painting');
276
+ const frTransUrl = ManifestLoader.getLangUrl('fr');
277
+
278
+ // Lister les langues disponibles
279
+ const langs = ManifestLoader.getAvailableLanguages();
280
+ ```
281
+
282
+ ## 🤝 Contribution
283
+
284
+ Les contributions sont les bienvenues ! Pour contribuer :
285
+
286
+ 1. Fork le projet
287
+ 2. Créer une branche (`git checkout -b feature/amazing`)
288
+ 3. Commit vos changements
289
+ 4. Push et créer une Pull Request
290
+
291
+ ## 📄 Licence
292
+
293
+ MIT License - Utilisation libre
294
+
295
+ ---
296
+
297
+ **Fait avec 💜 par Alex**
298
+
299
+ 🔗 [Demo](https://YOUR_USERNAME.github.io/retro-portfolio-engine/)
300
+ 🐛 [Issues](https://github.com/YOUR_USERNAME/retro-portfolio-engine/issues)