14 Commits

Author SHA1 Message Date
syoul 9f3752b621 chore: merge dev → main v1.4.0
ci/woodpecker/push/woodpecker Pipeline was successful
- feat: bouton ℹ flottant isolé sous ☰ (mobile) / top-left (desktop)
- fix: supprimer label 'Vitesse' + bouton ✕ AnimationPlayer
- fix: couleur émetteurs rouge #e53935 (meilleur contraste vs or)
- fix: InfoPanel — dégradés or→rouge / or→vert documentés
- docs: features-roadmap.md (14 features planifiées)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:31:49 +01:00
syoul 6fc1705f6d chore: bump version to 1.4.0
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:31:36 +01:00
syoul 15807c7bcb fix: InfoPanel — couleur émetteurs rouge (dégradé or→rouge) + description dégradés
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:27:16 +01:00
syoul bac113e51b fix: couleur émetteurs #e53935 (rouge) au lieu de #ff6d00 (orange)
ci/woodpecker/push/woodpecker Pipeline was successful
Meilleur contraste avec la couleur neutre or #d4a843.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:22:45 +01:00
syoul 6d01c8d29e fix: supprimer bouton ✕ de l'AnimationPlayer (fermeture via ▶ Animer)
ci/woodpecker/push/woodpecker Pipeline was successful
Économise une ligne sur mobile. onClose retiré de l'interface et du JSX.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:19:52 +01:00
syoul 46b11710cc fix: supprimer label 'Vitesse' dans AnimationPlayer (gain de place mobile)
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:15:41 +01:00
syoul 78ede01d11 feat: déplacer bouton ℹ hors du PeriodSelector → bouton flottant isolé
ci/woodpecker/push/woodpecker Pipeline was successful
- Sous ☰ sur mobile (top-16 left-4), top-4 left-4 sur desktop
- PeriodSelector : suppression prop onInfo + bouton ℹ intégré

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:12:24 +01:00
syoul 70de7e4c06 chore: merge dev → main v1.3.2
ci/woodpecker/push/woodpecker Pipeline was successful
- fix: bouton Clusters bottom-44 mobile / bottom-24 desktop
- fix: bouton Clusters bottom-36/32 (iterations précédentes)
- fix: plan historique-genesis.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:08:09 +01:00
syoul 65f26e2b58 chore: bump version to 1.3.2
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:07:52 +01:00
syoul 104949427c fix: bouton Clusters bottom-44 mobile / bottom-24 desktop
ci/woodpecker/push/woodpecker Pipeline was successful
Sur mobile réel, la police forcée à 16px fait wrapper les contrôles
AnimationPlayer en 2 lignes (~165px). bottom-44 (176px) sur mobile,
bottom-24 (96px) sur sm+ où les contrôles ne wrappent pas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:05:01 +01:00
syoul 9ec95dfc91 fix: bouton Clusters bottom-36 — dépasse le bord supérieur de l'AnimationPlayer
ci/woodpecker/push/woodpecker Pipeline was successful
Player: bottom-4 + ~114px hauteur → bord sup à ~130px.
bottom-32 (128px) chevauchait de 2px. bottom-36 (144px) donne 14px de marge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 12:52:51 +01:00
syoul ab1bad2209 chore: merge dev → main v1.3.1
ci/woodpecker/push/woodpecker Pipeline was successful
- fix: retirer mention Mock de l'InfoPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 12:39:32 +01:00
syoul 3dbd8704ff chore: bump version to 1.3.1
ci/woodpecker/push/woodpecker Pipeline was successful
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 12:39:22 +01:00
syoul b0104207c4 fix: retirer la mention Mock de l'InfoPanel (détail interne)
ci/woodpecker/push/woodpecker Pipeline failed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 12:39:13 +01:00
6 changed files with 16 additions and 33 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "g1flux", "name": "g1flux",
"private": true, "private": true,
"version": "1.3.0", "version": "1.4.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+9 -2
View File
@@ -131,6 +131,15 @@ export default function App() {
</button> </button>
)} )}
{/* Bouton info — sous ☰ sur mobile, top-left sur desktop */}
<button
onClick={() => setInfoOpen(true)}
className={`absolute ${isMobile ? 'top-16' : 'top-4'} left-4 z-[1001] w-10 h-10 bg-[#0a0b0f]/90 backdrop-blur-sm border border-[#2e2f3a] rounded-xl flex items-center justify-center text-[#6b7280] hover:text-[#d4a843] transition-colors text-base`}
aria-label="Aide"
>
</button>
{/* Period selector — floating over map */} {/* Period selector — floating over map */}
<div className={`absolute top-4 z-[1000] ${isMobile ? 'left-16 right-4' : 'left-1/2 -translate-x-1/2'}`}> <div className={`absolute top-4 z-[1000] ${isMobile ? 'left-16 right-4' : 'left-1/2 -translate-x-1/2'}`}>
<PeriodSelector <PeriodSelector
@@ -143,7 +152,6 @@ export default function App() {
geoPercent={visibleStats && visibleStats.transactionCount > 0 geoPercent={visibleStats && visibleStats.transactionCount > 0
? Math.round((visibleStats.geoCount / visibleStats.transactionCount) * 100) ? Math.round((visibleStats.geoCount / visibleStats.transactionCount) * 100)
: null} : null}
onInfo={() => setInfoOpen(true)}
/> />
</div> </div>
@@ -184,7 +192,6 @@ export default function App() {
onPlay={animation.play} onPlay={animation.play}
onPause={animation.pause} onPause={animation.pause}
onSpeedChange={animation.setSpeed} onSpeedChange={animation.setSpeed}
onClose={animation.deactivate}
/> />
)} )}
+1 -12
View File
@@ -9,7 +9,6 @@ interface AnimationPlayerProps {
onPlay: () => void; onPlay: () => void;
onPause: () => void; onPause: () => void;
onSpeedChange: (s: 1 | 2 | 4) => void; onSpeedChange: (s: 1 | 2 | 4) => void;
onClose: () => void;
} }
export function AnimationPlayer({ export function AnimationPlayer({
@@ -21,7 +20,6 @@ export function AnimationPlayer({
onPlay, onPlay,
onPause, onPause,
onSpeedChange, onSpeedChange,
onClose,
}: AnimationPlayerProps) { }: AnimationPlayerProps) {
const frame = frames[currentIndex]; const frame = frames[currentIndex];
@@ -78,8 +76,7 @@ export function AnimationPlayer({
{/* Speed selector */} {/* Speed selector */}
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<span className="text-[#4b5563] text-xs mr-1">Vitesse</span> {([1, 2, 4] as const).map((s) => (
{([1, 2, 4] as const).map((s) => (
<button <button
key={s} key={s}
onClick={() => onSpeedChange(s)} onClick={() => onSpeedChange(s)}
@@ -94,14 +91,6 @@ export function AnimationPlayer({
))} ))}
</div> </div>
{/* Close */}
<button
onClick={onClose}
className="text-[#4b5563] hover:text-white transition-colors px-2 py-1 text-sm ml-2"
title="Quitter l'animation"
>
</button>
</div> </div>
</div> </div>
</div> </div>
+2 -2
View File
@@ -17,7 +17,7 @@ function lerpColor(hex1: string, hex2: string, t: number): string {
} }
const COLOR_NEUTRAL = '#d4a843'; // or Ğ1 const COLOR_NEUTRAL = '#d4a843'; // or Ğ1
const COLOR_NEG = '#ff6d00'; // orange vif const COLOR_NEG = '#e53935'; // rouge vif
const COLOR_POS = '#00c853'; // vert vif const COLOR_POS = '#00c853'; // vert vif
const NEUTRAL_THRESHOLD = 0.05; // ±5 % → couleur neutre const NEUTRAL_THRESHOLD = 0.05; // ±5 % → couleur neutre
const CLUSTER_RADIUS = 38; // pixels — distance max pour regrouper deux villes const CLUSTER_RADIUS = 38; // pixels — distance max pour regrouper deux villes
@@ -350,7 +350,7 @@ export function FlowMap({ arcs, focusCity, onCityClick }: FlowMapProps) {
{/* Bouton cluster / villes */} {/* Bouton cluster / villes */}
<button <button
onClick={() => setClustered(c => !c)} onClick={() => setClustered(c => !c)}
className={`absolute bottom-32 left-4 z-[1002] px-3 py-1.5 rounded-lg text-xs font-medium border transition-all duration-200 ${ className={`absolute bottom-44 sm:bottom-24 left-4 z-[1002] px-3 py-1.5 rounded-lg text-xs font-medium border transition-all duration-200 ${
clustered clustered
? 'bg-[#d4a843] text-[#0a0b0f] border-[#d4a843] shadow-[0_0_10px_rgba(212,168,67,0.35)]' ? 'bg-[#d4a843] text-[#0a0b0f] border-[#d4a843] shadow-[0_0_10px_rgba(212,168,67,0.35)]'
: 'bg-[#0a0b0f]/90 text-[#6b7280] border-[#2e2f3a] hover:text-[#d4a843] hover:border-[#d4a843]' : 'bg-[#0a0b0f]/90 text-[#6b7280] border-[#2e2f3a] hover:text-[#d4a843] hover:border-[#d4a843]'
+2 -5
View File
@@ -54,8 +54,8 @@ export function InfoPanel({ onClose }: InfoPanelProps) {
</Feature> </Feature>
<Feature icon="●" name="Couleur des nœuds"> <Feature icon="●" name="Couleur des nœuds">
<span className="text-green-400">Vert</span> = receveur net (reçoit plus que ce qu'il émet) ·{' '} <span className="text-green-400">Vert</span> = receveur net (reçoit plus que ce qu'il émet) ·{' '}
<span className="text-[#d4a843]">Or</span> = équilibré ·{' '} <span className="text-[#d4a843]">Or</span> = équilibré (dégradé or vert selon l'excédent reçu) ·{' '}
<span className="text-orange-400">Orange</span> = émetteur net. <span className="text-[#e53935]">Rouge</span> = émetteur net (dégradé or rouge selon l'excédent émis).
</Feature> </Feature>
<Feature icon="↗" name="Clic sur un nœud"> <Feature icon="↗" name="Clic sur un nœud">
Affiche la liste des villes du cluster avec leur balance individuelle, Affiche la liste des villes du cluster avec leur balance individuelle,
@@ -100,9 +100,6 @@ export function InfoPanel({ onClose }: InfoPanelProps) {
<Feature icon="●" name="Live Ğ1v2"> <Feature icon="●" name="Live Ğ1v2">
Données temps réel de la blockchain Ğ1v2, actualisées toutes les 30 secondes. Données temps réel de la blockchain Ğ1v2, actualisées toutes les 30 secondes.
</Feature> </Feature>
<Feature icon="○" name="Mock">
Données simulées, utilisées si l'API live est indisponible.
</Feature>
</Section> </Section>
</div> </div>
+1 -11
View File
@@ -8,7 +8,6 @@ interface PeriodSelectorProps {
viewMode: 'heatmap' | 'flow'; viewMode: 'heatmap' | 'flow';
onViewModeChange: (mode: 'heatmap' | 'flow') => void; onViewModeChange: (mode: 'heatmap' | 'flow') => void;
geoPercent?: number | null; geoPercent?: number | null;
onInfo: () => void;
} }
const PERIODS = [ const PERIODS = [
@@ -19,7 +18,7 @@ const PERIODS = [
const PRESET_DAYS = new Set([1, 7, 30]); const PRESET_DAYS = new Set([1, 7, 30]);
export function PeriodSelector({ value, onChange, animationActive, onAnimate, viewMode, onViewModeChange, geoPercent, onInfo }: PeriodSelectorProps) { export function PeriodSelector({ value, onChange, animationActive, onAnimate, viewMode, onViewModeChange, geoPercent }: PeriodSelectorProps) {
const [customOpen, setCustomOpen] = useState(false); const [customOpen, setCustomOpen] = useState(false);
const [inputVal, setInputVal] = useState(''); const [inputVal, setInputVal] = useState('');
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
@@ -132,15 +131,6 @@ export function PeriodSelector({ value, onChange, animationActive, onAnimate, vi
</span> </span>
)} )}
<div className="w-px mx-1 bg-[#2e2f3a] self-stretch" />
<button
onClick={onInfo}
className="px-2 py-1.5 rounded-md text-sm text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23] transition-all duration-200 cursor-pointer leading-none"
aria-label="Aide"
>
</button>
</div> </div>
); );
} }