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:
174
.ideai/tickets/91/assets/build_mockup.py
Normal file
174
.ideai/tickets/91/assets/build_mockup.py
Normal 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)
|
||||
BIN
.ideai/tickets/91/assets/watch_screen_mockup.png
Normal file
BIN
.ideai/tickets/91/assets/watch_screen_mockup.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
64
.ideai/tickets/91/assets/watch_screen_mockup.svg
Normal file
64
.ideai/tickets/91/assets/watch_screen_mockup.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.1 MiB |
@ -1,6 +1,6 @@
|
||||
---
|
||||
issueRef: "#91"
|
||||
version: 3
|
||||
version: 4
|
||||
updatedBy: {"kind":"user"}
|
||||
updatedAt: 1784656427938
|
||||
updatedAt: 1785230763779
|
||||
---
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: "990a5e5a-8490-43ab-b7f6-7cae61be25fd"
|
||||
number: 91
|
||||
title: "Interface montre synchronisée"
|
||||
status: "open"
|
||||
status: "closed"
|
||||
priority: "low"
|
||||
sprint: null
|
||||
links: []
|
||||
@ -10,7 +10,7 @@ agentRefs: []
|
||||
createdBy: {"kind":"user"}
|
||||
updatedBy: {"kind":"user"}
|
||||
createdAt: 1784656109713
|
||||
updatedAt: 1784656427938
|
||||
version: 3
|
||||
updatedAt: 1785230763779
|
||||
version: 4
|
||||
---
|
||||
J'aimerais avoir une version pour ma montre android qui soit synchronisée avec l'application téléphone. Elle me permettrait entre autre de lancer/arrêter les chronos (donc dans le cas d'un exercice sous chrono, de lancer le chrono ou de le mettre en pause) ou dans le cas d'une étape de lancer le chrono de l'étape, en gardant la décision prise comme quoi si la première étape est un chrono et que l'exercice est sous chrono, le lancement de chrono est commun a l'exercice et à l'étape. Ca permettrait en plus de ça de pouvoir passer une étape, une séquence, une série ou terminer une série. Bref, pouvoir gérer sa séance depuis sa montre. l'UX/UI est bien sur a voir avec l'agent UX pour savoir comment organiser ça. Heavy propose une fonctionnalité similaire si il y a besoin d'une inspiration
|
||||
Reference in New Issue
Block a user