feat: refonte layout viewport + passe motion + finitions SEO/sécurité

- Layout app-style 100dvh : zéro scroll desktop (accueil, jeu, bilan, explorer),
  zéro superposition mobile (sauts sortis de la carte, badges consolidés en une puce,
  barre d'actions opaque z-indexée, indices en panneau interne, sheet explorer dégagé)
- CSS jeu mutualisé (jeu.css) + partager() factorisé (lib/partage.ts)
- Motion gwoka : reveals syncopés, révélation designée (ligne de distance animée,
  score en count-up, marqueur qui tombe), transitions d'état, bilan en cascade
- SEO : Seo.svelte (canonical, OG, Twitter), robots.txt, sitemap.xml
- Sécurité : hooks.server.ts (CSP, nosniff, DENY, Referrer-Policy, Permissions-Policy)
- Dette : 4 clés i18n mortes, zip debug ignoré
- Vérifié puppeteer : mobile 390x844 (0 overlap, 0 scroll), desktop 1440x900
  (0 scroll même +2 indices), révélation complète, zéro erreur console/CSP
This commit is contained in:
sucupira
2026-07-21 16:32:47 -04:00
parent 67142428ac
commit 9b867243fd
25 changed files with 1368 additions and 753 deletions
+31 -18
View File
@@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import GameMap from '$lib/components/GameMap.svelte';
import WikiFiche from '$lib/components/WikiFiche.svelte';
import Seo from '$lib/components/Seo.svelte';
import { t } from '$lib/i18n/store.svelte';
import { REGIONS } from '$lib/map/regions';
import type { ExplorerLieu, Region } from '$lib/types';
@@ -44,9 +45,7 @@
});
</script>
<svelte:head>
<title>{t().app.titre}{t().explorer.titre}</title>
</svelte:head>
<Seo title={t().explorer.titre} description={t().modes.explorerDesc} path="/explorer" />
<div class="explorer">
<div class="filtres" role="group" aria-label={t().explorer.filtrer}>
@@ -121,6 +120,7 @@
flex-direction: column;
position: relative;
min-height: 0;
overflow: hidden; /* la partie repliée du sheet ne doit pas agrandir la page */
}
.filtres {
@@ -152,7 +152,8 @@
.carte-explorer {
flex: 1;
min-height: 0;
padding: 0.6rem;
padding: 0.6rem 0.6rem 3.6rem; /* réserve la bande de la poignée du sheet :
la fiche repliée ne recouvre jamais la carte, les sauts ni l'attribution */
}
.erreur {
@@ -160,7 +161,8 @@
color: var(--oki-encre-doux);
}
/* Bottom sheet mobile */
/* Bottom sheet mobile : overlay dont les événements sont scopés à lui-même.
Glisse --dur-mesure --ease-ka (cadence gwoka), coupée en reduced-motion. */
.fiche {
position: absolute;
left: 0;
@@ -173,7 +175,7 @@
box-shadow: 0 -4px 18px rgba(11, 61, 46, 0.2);
padding: 1rem;
transform: translateY(calc(100% - 3.2rem));
transition: transform 0.25s ease;
transition: transform var(--dur-mesure) var(--ease-ka);
}
.fiche.ouvert {
@@ -237,21 +239,32 @@
font-size: 0.92rem;
}
/* Panneau latéral desktop */
/* Desktop : panneau latéral droit EN FLUX (grille) — plus aucune bande
verticale par-dessus la carte, toute la carte reste cliquable. */
@media (min-width: 768px) {
.fiche {
left: auto;
top: 0;
right: 0;
bottom: 0;
width: 380px;
max-height: none;
border-radius: var(--oki-radius) 0 0 var(--oki-radius);
transform: translateX(calc(100% - 3.2rem));
.explorer {
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
grid-template-rows: auto minmax(0, 1fr);
}
.fiche.ouvert {
transform: translateX(0);
.filtres {
grid-column: 1 / -1;
}
.carte-explorer {
padding: 0.8rem;
min-height: 0;
}
.fiche {
position: static;
transform: none;
max-height: none;
min-height: 0;
border-radius: 0;
border-left: 1px solid var(--oki-sable-fonce);
box-shadow: none;
}
}