﻿# Audit de djuju02.fr â€” Ã©tat des lieux et corrections

Analyse des fichiers fournis, hÃ©bergement OVH mutualisÃ© **PERSO** (cluster100, eu-west-gra, PHP 8.2).

---

## ðŸ”´ Critique â€” Ã  traiter maintenant

### C1. Le `client_secret` Twitch est compromis

`_secrets/twitch.php` contenait en clair :

```
client_id     = [client_id, non secret, retiré par précaution]
client_secret = [SECRET RÉVOQUÉ — ne jamais écrire une clé dans la doc]
```

Ce fichier a Ã©tÃ© transmis dans une conversation. **ConsidÃ©rez le secret comme public.** Avec ce couple, un tiers peut obtenir des jetons d'application au nom de votre app Twitch, usurper votre identitÃ© applicative et consommer votre quota d'API.

**Action, dans cet ordre :**

1. https://dev.twitch.tv/console/apps â†’ votre application â†’ **New Secret**. L'ancien est rÃ©voquÃ© immÃ©diatement.
2. Reportez la nouvelle valeur dans `_secrets/twitch.php` sur le serveur (jamais ailleurs).
3. `chmod 600 ~/_secrets/twitch.php`
4. VÃ©rifiez la liste des OAuth Redirect URLs de l'app : une seule entrÃ©e, exactement `https://djuju02.fr/twitch/api/oauth_callback.php`.
5. Si le fichier a transitÃ© par un dÃ©pÃ´t Git, changer le secret ne suffit pas â€” il reste dans l'historique. Purgez-le (`git filter-repo`) ou repartez d'un dÃ©pÃ´t neuf.

Le `client_id` n'est pas un secret (il est visible dans l'URL d'autorisation), inutile de le changer.

**Ce qui Ã©tait dÃ©jÃ  bien :** le chemin `/home/djujufm/_secrets/twitch.php` est hors de la racine web. C'est le bon rÃ©flexe, on le conserve.

### C2. Aucun `state` dans le flux OAuth

Le fichier de config dÃ©clare un `redirect_uri` mais rien dans le code fourni ne gÃ©rait le paramÃ¨tre `state`. Sans lui, un attaquant peut construire un lien de callback qui lie **son** compte Twitch Ã  **votre** session (CSRF de connexion).

**CorrigÃ©** : `oauth_start.php` gÃ©nÃ¨re un `state` de 32 octets, le stocke en session avec une expiration de 10 min ; `oauth_callback.php` le vÃ©rifie avec `hash_equals()` et le consomme immÃ©diatement.

### C3. Les jetons n'avaient pas d'emplacement de stockage dÃ©fini

**CorrigÃ©** : `_storage/tokens/twitch.json`, hors racine web, chiffrÃ© en AES-256-GCM, Ã©crit en 0600 de faÃ§on atomique. Le rafraÃ®chissement est automatique avec 60 s de marge.

---

## ðŸŸ  Ã‰levÃ©

### E1. CSP en balise `<meta>`, avec `'unsafe-inline'` partout

```html
<meta http-equiv="Content-Security-Policy" content="... script-src 'self' 'unsafe-inline' ...">
```

Trois problÃ¨mes :

- `'unsafe-inline'` sur `script-src` annule l'essentiel de la protection XSS : c'est exactement ce qu'une injection cherche Ã  exÃ©cuter.
- Une balise `<meta>` ne protÃ¨ge que ce qui vient **aprÃ¨s** elle dans le document, et ne couvre ni les erreurs 404/500, ni les rÃ©ponses non-HTML.
- La politique Ã©tait diffÃ©rente d'une page Ã  l'autre (`overlays.html` autorisait `images.unsplash.com`, pas les autres).

**CorrigÃ©** : CSP unique envoyÃ©e en en-tÃªte HTTP depuis `www/.htaccess`, sans `'unsafe-inline'` pour les scripts. Tout le JS est externalisÃ©. Exception documentÃ©e et isolÃ©e pour `/jeux/` (`www/jeux/.htaccess`), le temps d'externaliser le JS des jeux.

### E2. Aucun en-tÃªte de sÃ©curitÃ©, aucun forÃ§age HTTPS

Il n'y avait pas de `.htaccess`. Manquaient : redirection HTTPS, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`, `frame-ancestors`, `Options -Indexes`.

ConsÃ©quence concrÃ¨te de `Options -Indexes` manquant : `https://djuju02.fr/assets/img/` listait tout le dossier.

**CorrigÃ©** : voir `www/.htaccess`, sections 1, 2 et 4. HSTS est fourni **commentÃ©** â€” Ã  n'activer qu'aprÃ¨s avoir confirmÃ© que tous vos sous-domaines sont en HTTPS, sinon vous vous bloquez l'accÃ¨s pendant un an.

### E3. Formulaire de contact absent, page Contact pointant dans le vide

Le menu proposait Â« Contact Â» â†’ `portfolio/index.html#contact`, mais aucun traitement n'existait. Un formulaire PHP naÃ¯f est la premiÃ¨re porte d'entrÃ©e pour du spam relayÃ© (injection d'en-tÃªtes SMTP via `\r\n` dans le champ e-mail).

**CorrigÃ©** : `www/contact-envoi.php` â€” POST uniquement, jeton CSRF vÃ©rifiÃ© en temps constant, honeypot, limite d'un envoi par minute, validation stricte, et suppression des `\r\n` avant de construire `Reply-To`.

---

## ðŸŸ¡ Moyen

### M1. La navbar Ã©tait dupliquÃ©e dans chaque page â€” et se contredisait

Chaque page redÃ©finissait la navbar en `<style>` **puis** chargeait `shared-nav.css`. Les deux ne parlaient pas le mÃªme langage :

| Comportement | CSS inline de `index.html` | `shared-nav.css` | `shared-nav.js` |
|---|---|---|---|
| Sous-menu ouvert (mobile) | `.nav-item.active` | `.nav-item.submenu-open` | pose `submenu-open` |
| Menu mobile ouvert | `height: 0` â†’ `calc(100vh - 60px)` | `opacity` / `visibility` / `transform` | pose `active` |
| Survol du sous-menu | `.nav-item:hover` (toutes les entrÃ©es) | `.nav-item.has-submenu:hover` | â€” |

Le JS posait `submenu-open`, mais le CSS d'`index.html` n'Ã©coutait que `.active` : sur la page d'accueil en mobile, la flÃ¨che du sous-menu ne tournait pas et la hauteur du menu Ã©tait pilotÃ©e par deux mÃ©canismes concurrents. `.nav-item:hover .arrow-icon` faisait par ailleurs pivoter une flÃ¨che sur des entrÃ©es qui n'en ont pas.

**CorrigÃ©** : la navbar n'existe plus qu'Ã  deux endroits â€” `_app/partials/nav.php` pour le HTML, `assets/css/shared-nav.css` pour le style. ZÃ©ro `<style>` dans les pages.

### M2. Flash blanc au chargement en thÃ¨me sombre

`shared-nav.js` appliquait `.dark-mode` sur `DOMContentLoaded`, donc aprÃ¨s le premier rendu : la page apparaissait blanche puis basculait en noir.

**CorrigÃ©** : `assets/js/theme-init.js`, minuscule et **bloquant** dans le `<head>`, pose la classe sur `<html>` avant le premier pixel. Il respecte aussi `prefers-color-scheme` quand l'utilisateur n'a jamais choisi.

### M3. Images tierces en hotlink

`overlays.html` chargeait ses visuels depuis `images.unsplash.com`. Cela impose d'ouvrir la CSP Ã  un domaine externe, expose l'IP de vos visiteurs Ã  un tiers, et casse la page le jour oÃ¹ l'URL change.

**CorrigÃ©** : les visuels sont attendus dans `www/assets/img/overlays/`. La CSP `img-src 'self' data:` redevient stricte.

### M4. AccessibilitÃ©

- Les dÃ©clencheurs de sous-menu Ã©taient des `<a href="#">` : au clavier, ils naviguaient au lieu d'ouvrir. â†’ remplacÃ©s par des `<button type="button">` avec `aria-controls` / `aria-expanded`.
- Aucun lien d'Ã©vitement. â†’ `.skip-link` ajoutÃ©.
- Aucun `aria-current` sur la page active. â†’ ajoutÃ©.
- Le bouton de thÃ¨me n'indiquait pas son Ã©tat. â†’ `aria-pressed` synchronisÃ©.
- Aucune prise en compte de `prefers-reduced-motion`. â†’ ajoutÃ©e dans `base.css`.

### M5. SEO et partage

Aucune `meta description`, aucun Open Graph, aucun `canonical`, pas de `robots.txt`, pas de `sitemap.xml`, pas de page 404. Un lien du site collÃ© dans Discord ou un panneau Twitch n'affichait aucun aperÃ§u.

**CorrigÃ©** : tout est gÃ©nÃ©rÃ© depuis `_app/partials/head.php` Ã  partir du tableau `$page` de chaque page. `robots.txt`, `sitemap.xml`, `404.html`, `403.html`, `500.html` ajoutÃ©s.

### M6. Aucun cache, aucune compression

**CorrigÃ©** : `mod_deflate` + `mod_expires` dans `.htaccess`. Les assets sont servis avec `?v=<mtime>` via la fonction `asset()`, donc cachables un an sans risque de servir une version pÃ©rimÃ©e.

---

## ðŸ”µ Faible / hygiÃ¨ne

- **`html { overflow-y: scroll }`** Ã©tait rÃ©pÃ©tÃ© dans chaque page â†’ centralisÃ© dans `base.css`.
- **`.tag.animated` / `.tag.static`** : `animated` et `static` sont des noms trop gÃ©nÃ©riques, susceptibles d'entrer en collision avec une future classe utilitaire â†’ renommÃ©s `.tag-animated` / `.tag-static`.
- **`display: none` sur `.sun-icon`** Ã©tait dÃ©clarÃ© Ã  trois endroits diffÃ©rents â†’ une seule dÃ©claration.
- **`_bash_profile`** : `umask 077` ajoutÃ© (sur un mutualisÃ©, les fichiers crÃ©Ã©s en SSH ne doivent pas Ãªtre lisibles par les voisins), plus `HISTIGNORE` pour ne pas laisser un secret traÃ®ner dans l'historique shell.
- **Pas de `.ovhconfig`** : la version de PHP dÃ©pendait du rÃ©glage global du panneau. Fichier ajoutÃ©, Ã©pinglÃ© sur 8.2 avec `environment=production`.
- **Pas de `.gitignore`** : c'est trÃ¨s probablement ainsi que `twitch.php` a fini par circuler. AjoutÃ©, avec `_secrets/*` exclu et seul `*.example` conservÃ©.

---

## Checklist post-dÃ©ploiement

- [ ] Nouveau `client_secret` Twitch gÃ©nÃ©rÃ© et l'ancien rÃ©voquÃ©
- [ ] `chmod 600 ~/_secrets/twitch.php` vÃ©rifiÃ©
- [ ] `token_key` gÃ©nÃ©rÃ© (`php -r "echo bin2hex(random_bytes(32));"`)
- [ ] `https://djuju02.fr/_secrets/twitch.php` renvoie 404 (et non le fichier)
- [ ] `https://djuju02.fr/assets/img/` renvoie 403 (et non un listing)
- [ ] `http://djuju02.fr` redirige bien en 301 vers `https://`
- [ ] Les anciennes URLs `.html` redirigent en 301
- [ ] Aucune erreur CSP dans la console du navigateur
- [ ] Test sur https://securityheaders.com (objectif : A ou A+)
- [ ] Menu mobile testÃ© sur un vrai tÃ©lÃ©phone
- [ ] HSTS dÃ©commentÃ© **seulement aprÃ¨s** validation complÃ¨te du HTTPS

---

# ComplÃ©ments â€” aprÃ¨s rÃ©ception des fichiers manquants et de la capture FTP

## ðŸ”´ Nouveau point critique : permissions des dossiers sensibles

La capture FileZilla montre :

```
_secrets   drwxr-xr-x   djujufm users
private    drwxr-xr-x   djujufm users
www        drwxr-xr-x   djujufm users
```

`drwxr-xr-x` = **lisible et traversable par tout le monde**. Sur un hÃ©bergement
mutualisÃ©, Â« tout le monde Â» inclut les autres clients hÃ©bergÃ©s sur le mÃªme
cluster (ici cluster100). Un dossier nommÃ© `_secrets` en 755 est une invitation.

```bash
chmod 700 ~/_secrets ~/private
chmod 600 ~/_secrets/*
```

VÃ©rifiez aussi le propriÃ©taire du groupe : `users` est un groupe partagÃ©.

## ðŸŸ  Le `.htaccess` Ã  la racine du home

Il y a un `.htaccess` de 1401 octets dans `/home/djujufm/`, datÃ© du 08/09/2025.
Deux cas de figure, Ã  trancher avant toute manipulation :

- **Si la racine du site en multisite est `www`** : Apache ne lit jamais ce
  fichier (il ne remonte pas au-dessus du DocumentRoot). Il est inerte, mais
  il rÃ©vÃ¨le qu'il a pu servir Ã  autre chose auparavant.
- **Si la racine du site est `/`** : alors `_secrets/` et `private/` sont
  servis en HTTP, et seul ce `.htaccess` vous protÃ¨ge. C'est une configuration
  Ã  corriger immÃ©diatement.

Pour trancher :

```bash
curl -sI https://djuju02.fr/_secrets/twitch.php | head -1
```

`404` â†’ la racine est bien `www`, tout va bien.
`403` â†’ la racine est `/` et vous Ãªtes protÃ©gÃ© uniquement par le `.htaccess`.
`200` â†’ **votre `client_secret` est en libre accÃ¨s sur Internet.**

Dans les deux derniers cas : espace client OVH â†’ HÃ©bergements â†’ Multisite â†’
modifier le dossier racine du domaine pour `www`.

## ðŸŸ  Le JavaScript du portfolio ne s'exÃ©cutait pas

Le `<script>` de `portfolio/index.html` contenait des accolades orphelines :

```js
document.addEventListener('DOMContentLoaded', () => {
    const body = document.body;
    }        // â† de trop
    });      // â† de trop
```

Un fichier JS est analysÃ© en entier avant d'Ãªtre exÃ©cutÃ© : cette erreur de
syntaxe empÃªchait **tout le bloc** de se charger. `openModal()` et
`closeModal()` n'Ã©taient donc jamais dÃ©finies â€” cliquer sur un projet ne
faisait rien du tout. L'erreur vient probablement de la suppression du code
de thÃ¨me sans nettoyage des accolades.

CorrigÃ© dans `assets/js/portfolio.js`, avec au passage la suppression des
attributs `onclick=` (interdits par la CSP) au profit d'une dÃ©lÃ©gation
d'Ã©vÃ©nements, l'ouverture au clavier, la fermeture par Ã‰chap et le retour du
focus sur la carte d'origine.

## ðŸŸ  Music Roulette : ~3 Mo de JavaScript Ã  chaque premiÃ¨re visite

Le jeu est une application React **compilÃ©e dans le navigateur** par Babel
standalone, mise en forme par Tailwind CDN. ConsÃ©quences :

| Aspect | Situation |
|---|---|
| Poids | Babel standalone â‰ˆ 3 Mo, tÃ©lÃ©chargÃ©s avant le premier affichage |
| DÃ©lai | La compilation JSX ajoute plusieurs centaines de ms, surtout sur mobile |
| CSP | Babel et Tailwind exigent `'unsafe-eval'` |
| Versions | `react@18` et `cdn.tailwindcss.com` sans numÃ©ro : le contenu peut changer sans prÃ©avis |
| Tiers | unpkg.com et cdn.tailwindcss.com voient l'IP de chaque joueur |

TraitÃ© ainsi :

- versions **Ã©pinglÃ©es** (`react@18.3.1`, `@babel/standalone@7.24.7`,
  `tailwindcss/3.4.1`) â€” le jeu redevient reproductible ;
- JSX et CSS **externalisÃ©s** dans `/assets/`, donc mis en cache et plus
  besoin de `script-src 'unsafe-inline'` ;
- CSP permissive **confinÃ©e Ã  `/jeux/`** via `www/jeux/.htaccess`, avec la
  liste prÃ©cise des domaines (`itunes.apple.com` pour la recherche,
  `*.mzstatic.com` pour les pochettes et les extraits audio) ;
- la marche Ã  suivre pour prÃ©compiler et supprimer l'exception est Ã©crite en
  commentaire dans ce `.htaccess`.

Le reste du site conserve une CSP stricte, sans `'unsafe-eval'` ni
`'unsafe-inline'`.

## ðŸŸ¡ `.ovhconfig` : je m'Ã©tais trompÃ© d'emplacement

Je l'avais placÃ© dans `www/`. Votre serveur montre qu'il est Ã  la racine du
home â€” et c'est bien lÃ  qu'OVH le lit. CorrigÃ© : `/home/djujufm/.ovhconfig`.

## ðŸŸ¡ Le portfolio exposait une adresse e-mail en clair

`<a href="mailto:contact@djuju02.fr">` fonctionne, mais une adresse en clair
dans le HTML est aspirÃ©e par les robots Ã  spam en quelques jours. RemplacÃ©e
par le formulaire sÃ©curisÃ© (CSRF, honeypot, anti-injection d'en-tÃªtes,
limitation Ã  un envoi par minute).

## ðŸŸ¡ SÃ©lecteur CSS trop large dans le portfolio

`section { padding: 80px 5%; max-width: 1200px }` â€” un sÃ©lecteur d'Ã©lÃ©ment nu.
Tant que la page Ã©tait isolÃ©e, aucun souci ; une fois les feuilles mutualisÃ©es,
il s'appliquait Ã  toutes les `<section>` du site, y compris celles des autres
pages. RenommÃ© `.pf-section`.

---

# Correctif d'audit â€” aprÃ¨s lecture du `.htaccess` rÃ©el

Deux affirmations de la premiÃ¨re version Ã©taient fausses. Rectification.

## Â« Il n'y avait pas de `.htaccess` Â» â€” faux

Il y en avait un, Ã  `/home/djujufm/.htaccess`, datÃ© du 08/09/2025. Je jugeais
sur les seuls fichiers transmis, oÃ¹ il ne figurait pas.

Il est bien appliquÃ© : OVH dÃ©clare son bloc `<Directory>` sur `/home/djujufm`,
donc Apache lit les `.htaccess` Ã  partir de ce niveau puis descend vers `www/`.
C'est ce qui explique la prÃ©sence des en-tÃªtes `X-Content-Type-Options`,
`X-Frame-Options`, `Referrer-Policy` et `Permissions-Policy` alors que `www/`
ne contenait aucun fichier de configuration.

Il couvrait dÃ©jÃ  : HTTPS forcÃ©, canonicalisation sans `www`, quatre en-tÃªtes de
sÃ©curitÃ©, cache long sur les assets, `Options -Indexes`. Du travail correct.
Le niveau d'exposition Ã©tait donc plus faible que ce que j'annonÃ§ais.

Restaient absents : la Content-Security-Policy, le blocage des fichiers
sensibles, les pages d'erreur, la compression, et l'interdiction d'exÃ©cuter du
PHP dans les dossiers d'assets.

## ðŸŸ  Bug latent : boucle de redirection HTTPS

```apache
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
```

Le `[OR]` dÃ©clenche la redirection dÃ¨s qu'**un** des deux signaux n'indique pas
HTTPS. Sur un mutualisÃ© OVH, le TLS est terminÃ© par un proxy en amont :

- `%{HTTPS}` peut valoir `off` pour un visiteur pourtant en HTTPS ;
- `X-Forwarded-Proto` peut ne pas Ãªtre transmis, auquel cas `!https` est vrai.

Dans les deux cas, la condition reste vraie **aprÃ¨s** la redirection : le
navigateur boucle sur des 301 jusqu'Ã  abandonner, et le site devient
inaccessible.

Ã‡a ne se dÃ©clenche pas aujourd'hui parce que les deux signaux sont cohÃ©rents
sur votre configuration actuelle. C'est une dÃ©pendance Ã  un dÃ©tail
d'infrastructure que vous ne maÃ®trisez pas.

CorrigÃ© dans `www/.htaccess` : deux `RewriteCond` sans `[OR]` sont combinÃ©es en
ET par Apache, donc la redirection ne se produit que si aucun des deux signaux
n'indique HTTPS.

## Emplacement retenu

La configuration passe dans `www/.htaccess` et celui de la racine est vidÃ© de
ses rÃ¨gles (contenu d'origine conservÃ© en commentaire).

Deux raisons. D'abord, vous avez droit Ã  cinq sites : une rÃ¨gle placÃ©e au
niveau du home s'appliquerait Ã  tous, y compris Ã  un futur sous-domaine qui
n'en veut pas. Ensuite, Apache exÃ©cute les deux fichiers l'un aprÃ¨s l'autre â€”
deux redirections HTTPS et deux blocs de cache superposÃ©s produisent des
comportements pÃ©nibles Ã  diagnostiquer.

## `X-Frame-Options`

Vous aviez `SAMEORIGIN`, j'ai retenu `DENY` avec `frame-ancestors 'none'`.
Sans effet sur OBS, dont les sources navigateur ignorent cet en-tÃªte. Mais si
vous intÃ©grez une de vos pages dans une iframe sur votre propre site, repassez
Ã  `SAMEORIGIN` et `frame-ancestors 'self'` â€” les deux lignes sont commentÃ©es Ã 
cet endroit du fichier.

## Permissions â€” corrigÃ© le 05/08

`_secrets` et `private` Ã©taient en `drwxr-xr-x`, lisibles par les autres
comptes du cluster100. PassÃ©s en `drwx------`, et `_secrets/twitch.php` en
`600`. VÃ©rifiÃ©.

`_secrets` renvoie bien un `404` en HTTP : il est hors de la racine web, la
racine multisite est correctement rÃ©glÃ©e sur `www`.

---

# Ajout du rÃ©pertoire /overlays/

## Ce qui est bon dans ce dossier

Audit du code fourni : rien Ã  redire sur le fond.

- `textContent` partout, jamais `innerHTML` pour insÃ©rer une valeur â€” les
  paramÃ¨tres d'URL ne peuvent donc pas devenir du HTML exÃ©cutable ;
- aucun `<style>`, aucun `<script>` en ligne, aucun attribut `onclick` ;
- aucune ressource externe : ni Google Fonts, ni CDN. Rien ne fuit vers un
  tiers pendant que vos viewers regardent le stream ;
- CSP en `default-src 'none'` avec `'self'` sur les seules directives utiles.
  C'est plus strict que la politique du site principal, et nettement plus que
  l'exception `/jeux/`.

## ðŸ”´ Ce qui manquait : le dossier `twitch/`

Le zip ne contenait plus `www/twitch/`. Or ce rÃ©pertoire porte tout le flux
OAuth : `oauth_start.php`, `oauth_callback.php` et la page d'Ã©tat.

Le dÃ©ployer tel quel aurait cassÃ© l'autorisation Twitch. Le `redirect_uri`
dÃ©clarÃ© dans votre console dÃ©veloppeur pointe sur
`https://djuju02.fr/twitch/api/oauth_callback.php` â€” une URL en 404 fait
Ã©chouer l'Ã©change de jetons, et Twitch refuse toute modification du
`redirect_uri` sans revalidation de l'application.

`twitch/` est conservÃ©. Les deux rÃ©pertoires cohabitent sans se gÃªner : ils
n'ont ni URL, ni fichier, ni politique de sÃ©curitÃ© en commun.

## ðŸŸ¡ `?api=` acceptait n'importe quelle URL

```js
var url = params.get("api");
fetch(url, { cache: "no-store" })
```

L'URL vient de la barre d'adresse. Un lien piÃ©gÃ© du type
`/overlays/labels.html?api=https://exemple-malveillant/x.json` faisait Ã©mettre
une requÃªte vers un domaine tiers.

La portÃ©e rÃ©elle Ã©tait faible : `connect-src 'self'` bloque dÃ©jÃ  l'appel, et
les valeurs rÃ©cupÃ©rÃ©es passent par `textContent`. Mais faire reposer la
protection sur un seul mÃ©canisme est fragile â€” une CSP mal recopiÃ©e dans un
futur `.htaccess` suffirait Ã  rouvrir la voie.

AjoutÃ© dans `overlays.js` : seuls les chemins relatifs internes (`/â€¦`) sont
acceptÃ©s, tout le reste est ignorÃ© silencieusement.

## ðŸŸ¡ Trois incohÃ©rences d'intÃ©gration

**`robots.txt`** avait perdu `Disallow: /twitch/api/` et n'excluait pas
`/overlays/`. RÃ©tabli et complÃ©tÃ©.

**`.htaccess` racine** appliquait ses rÃ¨gles d'URL propre Ã  `/overlays/`.
Un lien collÃ© dans Streamlabs se serait mis Ã  rediriger, et une source
navigateur qui reÃ§oit une 301 peut Ã©chouer selon la version d'OBS. Ajout de
`RewriteRule ^overlays/ - [L]` : le rÃ©pertoire est dÃ©sormais servi tel quel.

**`assets/fonts/`** Ã©tait absent du zip alors que `core.css` l'attend. CrÃ©Ã©,
avec une note expliquant oÃ¹ rÃ©cupÃ©rer Orbitron et Rajdhani â€” et pourquoi ne
surtout pas les remplacer par un lien Google Fonts, qui exposerait l'IP de vos
viewers Ã  chaque affichage.

## ðŸŸ¡ La vitrine mentait

`creations/overlays.php` prÃ©sentait deux packs fictifs, Â« Neon Cyber Â» et
Â« Minimal Clean Â», avec des images de remplacement â€” alors que dix overlays
rÃ©els existent dÃ©sormais.

Page refaite : elle liste les dix, avec leurs dimensions exactes pour
Streamlabs, un lien de prÃ©visualisation, et un mode d'emploi. Les fausses
vignettes disparaissent, ce qui rÃ¨gle au passage deux placeholders.

Les cartes n'ont plus de vignette. Quand vous ferez des captures, dÃ©posez-les
dans `assets/img/overlays/` et ajoutez une clÃ© `image` au tableau `$overlays`.

## Note sur les deux Â« overlays Â»

Il existe maintenant deux URL proches, et c'est volontaire :

| URL | RÃ´le |
|---|---|
| `/creations/overlays` | Vitrine publique, indexÃ©e, dans le menu |
| `/overlays/` | Les overlays rÃ©els, `noindex`, Ã  coller dans OBS |

La galerie `/overlays/` reste accessible Ã  qui connaÃ®t l'adresse. Ce n'est pas
un secret â€” ce sont des habillages destinÃ©s Ã  Ãªtre affichÃ©s en direct â€” mais
si vous prÃ©fÃ©rez la fermer, une authentification HTTP basique suffit :
`htpasswd` dans le `.htaccess` du rÃ©pertoire. Ã€ ne pas mettre sur les fichiers
d'overlay eux-mÃªmes : OBS ne saurait pas s'authentifier.

