Corrige les pièces qui débordaient du bac à droite/en bas
Build and deploy / deploy (push) Successful in 36s
Build and deploy / deploy (push) Successful in 36s
La bordure était posée directement sur le conteneur aux coordonnées physiques (300x420) : à cause du box-sizing, la bordure mangeait sur l'espace intérieur réel (292x416) sans que matter-js le sache, donc les pièces pouvaient visuellement dépasser le cadre. La bordure vit maintenant sur un conteneur externe non dimensionné, qui s'ajuste automatiquement autour du conteneur physique sans réduire sa zone utile. Ajoute aussi l'échelle de repère (ordre de fusion, taille proportionnelle au rayon) et le second aperçu de la pièce suivante, travaillés en même temps. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ V1 à V11 sont livrées :
|
||||
- **V8** : les Citoyens peuvent désormais proposer une question pour Le Char — une par personne (upsert), modifiable autant de fois que voulu depuis une nouvelle section sur `/profile`, jusqu'à ce que la date du Tribunal (`settings.tribunal_date`) soit atteinte (même mécanisme de gel que les scores d'Icare, no-op silencieux plutôt qu'une erreur). Les Archontes modèrent ces suggestions depuis une nouvelle section « Suggestions des Citoyens » sur `/char/questions` : chaque proposition affiche l'auteur, avec un bouton « Ajouter à la banque » (la copie dans `chariot_questions`, à la fin de l'ordre existant) et un bouton de rejet (suppression, confirmation à deux temps comme le reste de la page). Par ailleurs, `/admin` affiche maintenant l'adresse email de chaque membre (RPC `admin_list_members()`, seul moyen d'exposer `auth.users.email` aux juges sans passer par la `service_role key` côté client).
|
||||
- **V9** : **Le Mur de la Honte** (`/mur`) — chaque Citoyen peut publier une note anonyme par jour (dix places maximum sur le mur, remis à zéro chaque jour — pas de suppression, juste un filtrage par date). L'anonymat n'est qu'à moitié réel : les Archontes voient une section « Historique complet » (filtrable par jour) avec l'auteur de chaque note jamais publiée, cohérent avec le thème du site (les Archontes ont un ascendant sur les Citoyens). Pas de gel lié à la date du Tribunal : contrairement au Char, c'est une mécanique de toute la semaine, pas de la seule soirée de clôture.
|
||||
- **V10** : **L'Urne de l'Agora** (`/urne`) — vote quotidien inspiré du vote à l'urne de l'Athènes antique : chaque Citoyen dépose un jeton par jour sur une personne de son choix (jamais lui-même), confirmation à deux temps avant l'envoi (vote définitif pour la journée, aucune RPC de modification). Contrairement au Mur de la Honte, les jetons s'accumulent sur toute la semaine dans un classement public et cumulatif — **identique pour tout le monde, Archontes compris** : contraste volontaire avec le Mur, ici les Archontes n'ont aucune vision privilégiée sur qui a voté pour qui, seul le total par personne est public. Les Archontes ne votent pas et ne peuvent pas recevoir de jetons.
|
||||
- **V11** : deuxième mini-jeu compétitif, **La Corne d'Abondance** (`/corne`) — jeu de fusion façon « Watermelon Game » : des avatars de Citoyens tombent dans un bac (physique gérée par `matter-js`, seule dépendance de ce type dans le projet — l'empilement stable de cercles est notoirement difficile à obtenir sans bugs de superposition/tremblement en physique artisanale), deux avatars de même taille qui se touchent fusionnent en un plus gros, jusqu'au débordement soutenu (5 secondes de délai de grâce, pour ne pas sanctionner un simple rebond) qui met fin à la partie — les pièces qui dépassent la ligne de danger clignotent (`.animate-danger-blink`) pour signaler que le compte à rebours est en cours. **Un palier de taille par Citoyen** (pas les Archontes) plutôt qu'un nombre de paliers arbitraire (`buildTierRadii`, `src/lib/melon/constants.ts`) : `citizens[tier]` (indexation directe, sans tirage) donne l'association palier → Citoyen, **fixe pour tout le monde** (pas juste pour une partie) puisque `corne/page.tsx` trie déjà les Citoyens par pseudo, un ordre déterministe. `TIER_OVERRIDES` (`src/lib/melon/constants.ts`, id de profil → index de palier) permet de fixer certains Citoyens à une taille précise à la main ; `applyTierOverrides()` les place d'abord, puis remplit le reste dans l'ordre alphabétique. Même mécanique de gloires que Le Vol d'Icare : record personnel all-time, scores figés à la date du Tribunal, gloires du top 3 attribuées automatiquement (`pg_cron`, sans intervention d'un Archonte) — n'importe qui (Archontes compris) peut jouer, seul le pool d'avatars affichés est limité aux Citoyens. Rendu en DOM (pas canvas) pour réutiliser tel quel le composant `Avatar` existant (`src/components/avatar.tsx`, étendu pour accepter une taille en pixels arbitraire en plus des tailles nommées) — la position **et la rotation** de chaque pièce sont appliquées en impératif (`style.transform`, `translate(...) rotate(body.angle)`) à 60 fps depuis la boucle physique matter-js, jamais via `setState`. Le halo de l'animation d'apparition doit porter `rounded-full` lui-même, sinon son ombre portée dessine un carré visible autour du rond ; l'apparition d'une pièce utilise un simple fondu (`.animate-fade-in`) plutôt que l'animation avec mise à l'échelle utilisée ailleurs dans l'app (`.animate-reveal`), pour que sa taille exacte soit lisible dès la première frame. Trait de visée vertical + preview flottante au-dessus du bac pour montrer où la prochaine pièce va tomber, plus un second aperçu, plus petit et fixe (pas de position à viser), pour voir venir deux coups à l'avance.
|
||||
- **V11** : deuxième mini-jeu compétitif, **La Corne d'Abondance** (`/corne`) — jeu de fusion façon « Watermelon Game » : des avatars de Citoyens tombent dans un bac (physique gérée par `matter-js`, seule dépendance de ce type dans le projet — l'empilement stable de cercles est notoirement difficile à obtenir sans bugs de superposition/tremblement en physique artisanale), deux avatars de même taille qui se touchent fusionnent en un plus gros, jusqu'au débordement soutenu (5 secondes de délai de grâce, pour ne pas sanctionner un simple rebond) qui met fin à la partie — les pièces qui dépassent la ligne de danger clignotent (`.animate-danger-blink`) pour signaler que le compte à rebours est en cours. **Un palier de taille par Citoyen** (pas les Archontes) plutôt qu'un nombre de paliers arbitraire (`buildTierRadii`, `src/lib/melon/constants.ts`) : `citizens[tier]` (indexation directe, sans tirage) donne l'association palier → Citoyen, **fixe pour tout le monde** (pas juste pour une partie) puisque `corne/page.tsx` trie déjà les Citoyens par pseudo, un ordre déterministe. `TIER_OVERRIDES` (`src/lib/melon/constants.ts`, id de profil → index de palier) permet de fixer certains Citoyens à une taille précise à la main ; `applyTierOverrides()` les place d'abord, puis remplit le reste dans l'ordre alphabétique. Même mécanique de gloires que Le Vol d'Icare : record personnel all-time, scores figés à la date du Tribunal, gloires du top 3 attribuées automatiquement (`pg_cron`, sans intervention d'un Archonte) — n'importe qui (Archontes compris) peut jouer, seul le pool d'avatars affichés est limité aux Citoyens. Rendu en DOM (pas canvas) pour réutiliser tel quel le composant `Avatar` existant (`src/components/avatar.tsx`, étendu pour accepter une taille en pixels arbitraire en plus des tailles nommées) — la position **et la rotation** de chaque pièce sont appliquées en impératif (`style.transform`, `translate(...) rotate(body.angle)`) à 60 fps depuis la boucle physique matter-js, jamais via `setState`. Le halo de l'animation d'apparition doit porter `rounded-full` lui-même, sinon son ombre portée dessine un carré visible autour du rond ; l'apparition d'une pièce utilise un simple fondu (`.animate-fade-in`) plutôt que l'animation avec mise à l'échelle utilisée ailleurs dans l'app (`.animate-reveal`), pour que sa taille exacte soit lisible dès la première frame. Trait de visée vertical + preview flottante au-dessus du bac pour montrer où la prochaine pièce va tomber, plus un second aperçu, plus petit et fixe (pas de position à viser), pour voir venir deux coups à l'avance. La bordure du bac vit sur un conteneur externe non dimensionné (pas sur le conteneur aux coordonnées physiques 300×420) : la mettre directement dessus réduisait sa zone intérieure réelle (box-sizing) sans que matter-js le sache, laissant les pièces s'empiler visuellement en dehors du cadre à droite/en bas. Sous le bac, une échelle de repère (taille d'affichage réduite mais proportionnelle au vrai rayon du palier, une seule ligne défilante, chaque rond centré sur son propre milieu plutôt qu'aligné en bas — un alignement bas avec des tailles très variables décalait progressivement la flèche par rapport aux plus gros ronds) montre l'ordre complet de fusion du plus petit au plus grand Citoyen.
|
||||
|
||||
Hors périmètre pour l'instant (voir roadmap en fin de doc) : éditions/saisons, mode grand écran.
|
||||
|
||||
|
||||
@@ -390,14 +390,22 @@ export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFin
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={containerRef}
|
||||
onPointerDown={updateDropXFromEvent}
|
||||
onPointerMove={updateDropXFromEvent}
|
||||
onPointerUp={handlePointerUp}
|
||||
className="relative touch-none overflow-visible rounded-b-2xl border-x-4 border-b-4 border-gold/50 bg-ink-2/40 select-none"
|
||||
style={{ width: CONTAINER_WIDTH, height: CONTAINER_HEIGHT }}
|
||||
>
|
||||
{/* La bordure vit sur ce conteneur externe (sans largeur/hauteur
|
||||
fixées : il s'ajuste automatiquement autour de l'enfant). La
|
||||
bordure participant au box-sizing, la mettre directement sur le
|
||||
conteneur aux coordonnées physiques (300×420) réduisait sa zone
|
||||
intérieure réelle à 292×416 sans que la physique le sache — les
|
||||
pièces pouvaient donc s'empiler jusqu'à x=300 alors que le cadre
|
||||
visible s'arrêtait à 292, d'où le débordement à droite/en bas. */}
|
||||
<div className="rounded-b-2xl border-x-4 border-b-4 border-gold/50">
|
||||
<div
|
||||
ref={containerRef}
|
||||
onPointerDown={updateDropXFromEvent}
|
||||
onPointerMove={updateDropXFromEvent}
|
||||
onPointerUp={handlePointerUp}
|
||||
className="relative touch-none overflow-visible rounded-b-2xl bg-ink-2/40 select-none"
|
||||
style={{ width: CONTAINER_WIDTH, height: CONTAINER_HEIGHT }}
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-x-0 border-t border-dashed border-oxblood/50"
|
||||
style={{ top: DANGER_LINE_Y }}
|
||||
@@ -483,11 +491,38 @@ export function MelonGame({ citizens, onFinish }: { citizens: MemberRow[]; onFin
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-xs text-marble/50">
|
||||
Touche/clique pour viser et lâche — deux avatars de même taille fusionnent.
|
||||
</p>
|
||||
|
||||
{/* Échelle de repère : ordre de fusion complet, du plus petit au plus
|
||||
grand — taille d'affichage proportionnelle au vrai rayon du
|
||||
palier (mais réduite et plafonnée) pour que la progression se
|
||||
voie d'un coup d'œil, sans dépendre du nombre de Citoyens. Une
|
||||
seule ligne défilante (pas de flex-wrap). items-center (pas
|
||||
items-end) : aligner sur le bas avec des tailles très variables
|
||||
donnait un décalage qui grandissait avec la taille (la flèche,
|
||||
à décalage fixe, n'était plus au bon endroit pour les gros
|
||||
ronds) — centrer chaque rond sur son propre milieu reste
|
||||
symétrique quelle que soit sa taille. */}
|
||||
<div
|
||||
className="marble-surface flex w-full items-center gap-x-1 gap-y-2 overflow-x-auto rounded-lg border border-gold/25 p-2"
|
||||
style={{ maxWidth: CONTAINER_WIDTH }}
|
||||
>
|
||||
{citizens.map((citizen, tier) => (
|
||||
<div key={citizen.id} className="flex shrink-0 items-center gap-1">
|
||||
<Avatar
|
||||
pseudo={citizen.pseudo}
|
||||
avatarUrl={citizen.avatar_url}
|
||||
size={Math.round(16 + (tierRadii[tier] / tierRadii[maxTier]) * 24)}
|
||||
/>
|
||||
{tier < citizens.length - 1 && <span className="text-xs text-text-mut">→</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user