chore(wip): consolidation intermédiaire multi-tickets (sprints Statistiques, UI, Bug resolution, Serveur-client)

Regroupe l'état de travail en cours réalisé dans un même worktree sur
plusieurs tickets/sprints (#85, #136, #145, #155-160, #162-164),
mélangeant des tickets QA et inProgress. Ne constitue pas une feature
terminée : commit de sauvegarde avant triage/split par ticket en
branches feature/* dédiées. Exclut les dossiers d'environnement de
build locaux et le heap dump parasite (.gitignore mis à jour).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 16:48:54 +02:00
parent 58272e354a
commit 917777e18b
279 changed files with 13546 additions and 674 deletions

View File

@ -0,0 +1,174 @@
import base64
import pathlib
import textwrap
root = pathlib.Path("/home/anthony/Documents/Projects/GameTime")
anton = base64.b64encode((root / "assets/fonts/Anton-Regular.ttf").read_bytes()).decode()
archivo = base64.b64encode((root / "assets/fonts/Archivo-Variable.ttf").read_bytes()).decode()
# Palette Court Blazer (theme sombre)
GOLD = "#C9A24A"
CRIMSON = "#D72638"
BG = "#080A12"
SURFACE = "#141824"
TEXT = "#F5F1E8"
TEXT_SECONDARY = "#A7ADBA"
BORDER = "#242A3A"
TILE_W = 260
TILE_H = 330
FACE_D = 216
GAP = 24
N = 5
WIDTH = N * TILE_W + (N + 1) * GAP
HEIGHT = TILE_H + 190
def watch_face(cx, cy, r, label_top, big_value, big_color, status_line, button_label, button_style, disconnected=False):
parts = []
# bezel
parts.append(f'<circle cx="{cx}" cy="{cy}" r="{r+6}" fill="{BORDER}"/>')
parts.append(f'<circle cx="{cx}" cy="{cy}" r="{r}" fill="{SURFACE}"/>')
# crimson top accent arc (clin d\'oeil liseré 2px des cartes cles, adapte en arc sur le bezel interne)
if not disconnected:
parts.append(f'<path d="M {cx - r*0.55} {cy - r*0.82} A {r} {r} 0 0 1 {cx + r*0.55} {cy - r*0.82}" '
f'fill="none" stroke="{CRIMSON}" stroke-width="3" stroke-linecap="round"/>')
else:
parts.append(f'<path d="M {cx - r*0.55} {cy - r*0.82} A {r} {r} 0 0 1 {cx + r*0.55} {cy - r*0.82}" '
f'fill="none" stroke="{BORDER}" stroke-width="3" stroke-linecap="round"/>')
top_y = cy - r * 0.44
value_y = cy - r * 0.02
status_y = cy + r * 0.30
btn_cy = cy + r * 0.62
btn_w = r * 1.5
btn_h = 40
label_color = TEXT_SECONDARY if not disconnected else "#5A6072"
parts.append(
f'<text x="{cx}" y="{top_y}" text-anchor="middle" font-family="Archivo" font-weight="600" '
f'font-size="13" letter-spacing="1" fill="{label_color}">{label_top}</text>'
)
parts.append(
f'<text x="{cx}" y="{value_y}" text-anchor="middle" font-family="Anton" font-size="56" '
f'fill="{big_color}">{big_value}</text>'
)
parts.append(
f'<text x="{cx}" y="{status_y}" text-anchor="middle" font-family="Archivo" font-weight="400" '
f'font-size="13.5" fill="{TEXT_SECONDARY if not disconnected else "#5A6072"}">{status_line}</text>'
)
# button pill
bx = cx - btn_w / 2
by = btn_cy - btn_h / 2
if button_style == "gold_fill":
fill, stroke, textcol = GOLD, GOLD, "#1A1206"
elif button_style == "crimson_outline":
fill, stroke, textcol = "none", CRIMSON, CRIMSON
elif button_style == "crimson_fill":
fill, stroke, textcol = CRIMSON, CRIMSON, TEXT
else: # muted outline (disconnected)
fill, stroke, textcol = "none", "#3A4152", TEXT_SECONDARY
parts.append(
f'<rect x="{bx:.1f}" y="{by:.1f}" width="{btn_w:.1f}" height="{btn_h}" rx="{btn_h/2:.1f}" '
f'fill="{fill}" stroke="{stroke}" stroke-width="1.6"/>'
)
parts.append(
f'<text x="{cx}" y="{btn_cy + 5}" text-anchor="middle" font-family="Archivo" font-weight="700" '
f'font-size="15" fill="{textcol}">{button_label}</text>'
)
return "\n".join(parts)
states = [
dict(
caption="1 — Pret a demarrer",
sub="Etape / exercice sous chrono, pas encore lance",
label_top="DRIBBLE COMBO",
big_value="00:00",
big_color=TEXT_SECONDARY,
status_line="Serie 2 / 4",
button_label="Demarrer",
button_style="gold_fill",
),
dict(
caption="2 — Chrono en cours",
sub="Le chrono actif tourne (exercice ou etape)",
label_top="DRIBBLE COMBO",
big_value="00:23",
big_color=GOLD,
status_line="Serie 2 / 4 - en cours",
button_label="Arreter",
button_style="crimson_outline",
),
dict(
caption="3 — Chrono en pause",
sub="Chrono arrete avec valeur, reprise possible",
label_top="DRIBBLE COMBO",
big_value="00:23",
big_color=GOLD,
status_line="Serie 2 / 4 - en pause",
button_label="Reprendre",
button_style="gold_fill",
),
dict(
caption="4 — Pas de chrono actif",
sub="Serie reps/score libre : l'action utile est de conclure",
label_top="TIRS EN SUSPENSION",
big_value="2 / 4",
big_color=GOLD,
status_line="Serie en cours",
button_label="Terminer la serie",
button_style="crimson_fill",
),
dict(
caption="5 — Montre non synchronisee",
sub="Perte de liaison : dernier etat connu, sans alarmer",
label_top="DERNIERE DONNEE RECUE",
big_value="00:23",
big_color="#5A6072",
status_line="Non synchronisee",
button_label="Reessayer",
button_style="muted",
disconnected=True,
),
]
svg_parts = [
f'<svg xmlns="http://www.w3.org/2000/svg" width="{WIDTH}" height="{HEIGHT}" viewBox="0 0 {WIDTH} {HEIGHT}">',
'<defs><style>',
f'@font-face {{ font-family: "Anton"; src: url(data:font/ttf;base64,{anton}) format("truetype"); }}',
f'@font-face {{ font-family: "Archivo"; src: url(data:font/ttf;base64,{archivo}) format("truetype"); }}',
'</style></defs>',
f'<rect x="0" y="0" width="{WIDTH}" height="{HEIGHT}" fill="{BG}"/>',
f'<text x="{WIDTH/2}" y="46" text-anchor="middle" font-family="Anton" font-size="30" fill="{TEXT}">GAMETIME — MONTRE SYNCHRONISEE</text>',
f'<text x="{WIDTH/2}" y="72" text-anchor="middle" font-family="Archivo" font-weight="400" font-size="15" fill="{TEXT_SECONDARY}">Ecran unique, bouton contextuel unique — style Court Blazer</text>',
]
for i, st in enumerate(states):
cx = GAP + TILE_W * i + TILE_W / 2
cy = 96 + FACE_D / 2 + 10
r = FACE_D / 2
svg_parts.append(watch_face(cx, cy, r, st["label_top"], st["big_value"], st["big_color"],
st["status_line"], st["button_label"], st["button_style"],
disconnected=st.get("disconnected", False)))
cap_y = cy + r + 34
sub_y = cap_y + 22
svg_parts.append(
f'<text x="{cx}" y="{cap_y}" text-anchor="middle" font-family="Archivo" font-weight="700" '
f'font-size="14" fill="{TEXT}">{st["caption"]}</text>'
)
lines = textwrap.wrap(st["sub"], width=30)
for li, line in enumerate(lines):
svg_parts.append(
f'<text x="{cx}" y="{sub_y + li * 15}" text-anchor="middle" font-family="Archivo" font-weight="400" '
f'font-size="11.5" fill="{TEXT_SECONDARY}">{line}</text>'
)
svg_parts.append('</svg>')
out = root / ".ideai/tickets/91/assets/watch_screen_mockup.svg"
out.write_text("\n".join(svg_parts), encoding="utf-8")
print("written", out, out.stat().st_size)

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.1 MiB