diff --git a/README.md b/README.md index d93aab9..cb836a8 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,17 @@ dd-timer/ ## Changelog +### v1.1.5 +- ✨ **Onglet Accouplement** — selection des 2 parents, deduction automatique du bebe, saisie du nombre de couples et bebes obtenus pour alimenter les statistiques globales +- ✨ **Sidebar navigation** — menu hamburger avec panneau lateral overlay (Dashboard, Enclos, Accouplement, Reappro, Inventaire, Workflows, Parametres) +- ✨ **Onglet Parametres** — son d'alarme, notifications PC et mobile regroupes dans une vue dediee accessible via la sidebar +- ✨ **Reappro ♂/♀** — chaque etape de l'arbre affiche les genres necessaires (♂/♀) avec toggle d'inversion par croisement +- 🎨 Statistiques globales integrees au Dashboard (bebes, couples, taux de reussite, races obtenues) +- 🎨 Onglets horizontaux simplifies — seuls Dashboard et Enclos restent, le reste est dans la sidebar +- 🔧 Retrait du systeme de bebes des enclos — les accouplements se font desormais via l'onglet dedie +- 🔧 Migration automatique des donnees bebes existantes vers les statistiques archivees +- 🗑 Suppression de l'onglet Statistiques (donnees conservees dans le Dashboard) + ### v1.1.4 - ✨ **Cible serenite par DD** — champ cible avec ETA en temps reel (calcul automatique baffeur/caresseur necessaire) - ✨ **Cible niveau par DD** — champ cible avec ETA en temps reel (modele XP lineaire base sur le tier de la mangeoire) diff --git a/docs/plans/2026-03-27-v1.2.0-design.md b/docs/plans/2026-03-27-v1.2.0-design.md new file mode 100644 index 0000000..8b5b7b1 --- /dev/null +++ b/docs/plans/2026-03-27-v1.2.0-design.md @@ -0,0 +1,47 @@ +# Design — v1.2.0 (Accouplement, Sidebar, Réappro ♂/♀) + +## 1. Sidebar navigation + +Bouton hamburger (☰) fixe en haut à gauche. Panneau overlay glissant depuis la gauche par-dessus le contenu (fond semi-transparent). Se ferme au clic sur un item ou sur le fond. + +Contenu : +- Dashboard +- Enclos 1 à 6 (dynamique, nom personnalisé) +- Accouplement (nouveau) +- Réappro +- Inventaire +- Workflows + +La barre d'onglets horizontale actuelle reste en place. + +## 2. Dashboard enrichi + +Ajouts : +- **Section "Statistiques globales"** : tout ce qui est dans l'onglet Stats actuel (KPIs, bébés par race, taux de réussite, stats par enclos). L'onglet Stats est supprimé de SPECIAL_TABS. +- **Section "Paramètres"** : card dédiée en bas avec sélecteur de son, toggle notifications Windows, bouton ntfy mobile. Retiré du header dashboard. + +## 3. Onglet Accouplement + +Workflow : +1. Sélection Parent 1 : grille de cards filtrables par gen (image + badge gen + nom) +2. Sélection Parent 2 : filtré aux races compatibles avec Parent 1 +3. Résultat : card du bébé déduit (image + gen + nom) +4. Saisie : nombre de couples + nombre de bébés obtenus +5. Bouton "Enregistrer" → alimente les stats globales + +Données : `S.accouplements` — tableau de `{ parentA, parentB, baby, gen, couples, babiesObtained, date }` + +Stats globales agrègent ces données comme elles agrègent actuellement `enc.babyHistory`. + +## 4. Retrait des bébés des enclos + +- Suppression bouton "Ajouter bébé", sous-onglets "Élevage / Historique bébés" +- Suppression de `enc.babies` et `enc.babyHistory` dans les nouveaux enclos +- Migration : les `enc.babyHistory` existants → `S.archivedStats` au premier lancement +- `isBabyUnlocked()` supprimé + +## 5. Réappro ♂/♀ + +Chaque étape de l'arbre indique ♂ et ♀ nécessaires. Convention : Parent A = ♂, Parent B = ♀ dans BREEDING_RECIPES. Toggle pour inverser par étape. + +Affichage : `×4 ♂` ou `×4 ♀` sur chaque mini-card parent. Résumé gen 1 : "4♂ Rousse + 4♀ Dorée". diff --git a/docs/plans/2026-03-27-v1.2.0-implementation.md b/docs/plans/2026-03-27-v1.2.0-implementation.md new file mode 100644 index 0000000..ef6ec41 --- /dev/null +++ b/docs/plans/2026-03-27-v1.2.0-implementation.md @@ -0,0 +1,640 @@ +# v1.2.0 Implementation Plan — Accouplement, Sidebar, Réappro ♂/♀ + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Ajouter un onglet Accouplement dédié, une sidebar de navigation overlay, intégrer les stats dans le dashboard, et ajouter les contraintes ♂/♀ dans la réappro. + +**Architecture:** Tout le code est dans `src/index.html` (monolithique). On modifie le CSS, le HTML, et le JS inline. Pas de framework. Rendu impératif via innerHTML. État central dans `S`, persisté via `save()`. + +**Tech Stack:** Vanilla JS, CSS custom properties, Electron IPC. + +**Fichier principal:** `/mnt/c/Users/micka/Desktop/dd-timer/src/index.html` + +--- + +## Task 1 : Retrait des bébés des enclos + migration + +**But :** Supprimer le système de bébés des enclos et migrer les données existantes vers `S.archivedStats`. + +**Files:** +- Modify: `src/index.html` + +**Step 1 : Migration des données au chargement** + +Dans la fonction `load()` ou juste après le chargement de `S`, ajouter une migration one-shot : + +```javascript +// Migration v1.2.0 : archiver les babyHistory des enclos +if(!S._migratedBabies120){ + S.enclos.forEach(enc=>{ + if(enc.babyHistory&&enc.babyHistory.length){ + if(!S.archivedStats)S.archivedStats={babies:[],totalMax:0}; + if(!S.archivedStats.babies)S.archivedStats.babies=[]; + enc.babyHistory.forEach(b=>{ + S.archivedStats.babies.push({...b,enclosName:enc.name||('Enclos '+(S.enclos.indexOf(enc)+1))}); + S.archivedStats.totalMax=(S.archivedStats.totalMax||0)+Math.min(5,Math.floor((b.ddCount||2)/2)); + }); + } + }); + S._migratedBabies120=true; + save(); +} +``` + +**Step 2 : Supprimer le code des bébés dans les enclos** + +Supprimer ou commenter les fonctions et le code suivant : +- `isBabyUnlocked()` (ligne ~2095) +- `updateBabyBtnState()` (ligne ~2101) +- `maxBabiesForEnclos()` (ligne ~2115) +- `currentBabiesCount()` (ligne ~2118) +- `openBabyModal()` (ligne ~2127) +- `closeBabyModal()` (ligne ~2134) +- `modalBack()`, `modalNext()`, `renderModalStep()` (lignes ~2138-2250) +- `selectModalGen()`, `selectModalRaceByIdx()`, `selectModalRace()`, `changeCount()` (lignes ~2250-2285) +- `renderBabies()` (ligne ~2015) +- `renderBabyHistory()` (ligne ~2049) +- `switchSubTab()` (ligne ~2001) +- `removeBaby()` (ligne ~2079) +- `clearSessionBabies()` (ligne ~2087) +- Le HTML du modal bébé (lignes ~352-369) + +**Step 3 : Nettoyer renderContent pour les enclos** + +Dans `renderContent()` (ligne ~1465), retirer les sous-onglets "elevage"/"history" et le bouton bébé du rendu des enclos. + +**Step 4 : Commit** + +``` +feat: retire bébés des enclos, migre données vers archivedStats +``` + +--- + +## Task 2 : Onglet Accouplement — données et constante + +**But :** Créer la structure de données et le lookup inversé pour les accouplements. + +**Files:** +- Modify: `src/index.html` + +**Step 1 : Ajouter 'accouplement' dans SPECIAL_TABS** + +Ligne ~698 : +```javascript +const SPECIAL_TABS=['dashboard','appro','inventaire','accouplement','workflows']; +// Note : 'stats' retiré (sera dans le dashboard) +``` + +**Step 2 : Créer le lookup inversé BREEDING_BY_PARENTS** + +Après `BREEDING_RECIPES` (ligne ~677) : +```javascript +// Lookup inversé : "ParentA|ParentB" → babyRace +const BREEDING_BY_PARENTS={}; +Object.entries(BREEDING_RECIPES).forEach(([baby,[a,b]])=>{ + BREEDING_BY_PARENTS[a+'|'+b]=baby; + if(a!==b) BREEDING_BY_PARENTS[b+'|'+a]=baby; +}); + +// Lookup : pour un parent donné, quels partenaires sont possibles ? +const COMPATIBLE_PARTNERS={}; +Object.entries(BREEDING_RECIPES).forEach(([baby,[a,b]])=>{ + if(!COMPATIBLE_PARTNERS[a])COMPATIBLE_PARTNERS[a]=[]; + COMPATIBLE_PARTNERS[a].push({partner:b,baby,gen:RACE_GEN[baby]}); + if(a!==b){ + if(!COMPATIBLE_PARTNERS[b])COMPATIBLE_PARTNERS[b]=[]; + COMPATIBLE_PARTNERS[b].push({partner:a,baby,gen:RACE_GEN[baby]}); + } +}); +``` + +**Step 3 : Initialiser S.accouplements** + +Dans le chargement de S (après `load()`) : +```javascript +if(!S.accouplements) S.accouplements=[]; +``` + +**Step 4 : Commit** + +``` +feat: ajoute structures données accouplement et lookup inversé +``` + +--- + +## Task 3 : Onglet Accouplement — rendu UI + +**But :** Interface complète de l'onglet accouplement. + +**Files:** +- Modify: `src/index.html` (CSS + JS) + +**Step 1 : CSS pour l'onglet accouplement** + +Ajouter dans la section ``, ligne ~313) : + +```css +/* Accouplement */ +.accoup-grid{display:flex;flex-wrap:wrap;gap:10px;justify-content:center} +.accoup-card{background:var(--bg2);border:2px solid var(--border);border-radius:var(--r);padding:10px;cursor:pointer;text-align:center;width:100px;transition:border-color .2s,transform .15s} +.accoup-card:hover{border-color:var(--text);transform:translateY(-2px)} +.accoup-card.selected{border-color:var(--ok);box-shadow:0 0 12px rgba(40,232,136,.25)} +.accoup-result{display:flex;align-items:center;gap:20px;justify-content:center;flex-wrap:wrap;padding:20px 0} +.accoup-arrow{font-size:2rem;color:var(--muted)} +.accoup-inputs{display:flex;gap:16px;align-items:center;justify-content:center;flex-wrap:wrap;padding:16px 0} +.accoup-input-group{display:flex;flex-direction:column;align-items:center;gap:6px} +.accoup-input-group label{font-size:.82rem;color:var(--muted)} +.accoup-input-group input{width:70px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:8px;font:700 1rem 'Nunito',sans-serif;text-align:center} +.accoup-gen-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;justify-content:center} +.accoup-gen-tab{padding:4px 12px;border-radius:8px;font-size:.78rem;font-weight:700;cursor:pointer;border:1px solid var(--border);background:var(--bg2);color:var(--muted);transition:all .2s} +.accoup-gen-tab.active{background:var(--ok);color:#fff;border-color:var(--ok)} +``` + +**Step 2 : État local de l'onglet** + +```javascript +const accoupState={parent1:null,parent2:null,filterGen:null,couples:'',babies:''}; +``` + +**Step 3 : Fonction renderAccouplement()** + +```javascript +function renderAccouplement(){ + const c=document.getElementById('enclos-content'); + c.removeAttribute('data-enc'); + + const allRaces=Object.keys(RACE_GEN).sort((a,b)=>(RACE_GEN[a]-RACE_GEN[b])||a.localeCompare(b)); + const gens=[...new Set(Object.values(RACE_GEN))].sort((a,b)=>a-b); + + // Si parent1 sélectionné, filtrer les partenaires compatibles + let availableRaces=allRaces; + if(accoupState.parent1){ + const partners=COMPATIBLE_PARTNERS[accoupState.parent1]||[]; + availableRaces=partners.map(p=>p.partner); + } + + // Filtrer par gen + const fg=accoupState.parent1?null:accoupState.filterGen; + const filtered=fg?availableRaces.filter(r=>RACE_GEN[r]===fg):availableRaces; + + // Déduire le bébé + let baby=null,babyGen=null; + if(accoupState.parent1&&accoupState.parent2){ + baby=BREEDING_BY_PARENTS[accoupState.parent1+'|'+accoupState.parent2]||null; + if(baby)babyGen=RACE_GEN[baby]; + } + + // Gen tabs (uniquement si pas de parent1 sélectionné) + let genTabs=''; + if(!accoupState.parent1){ + genTabs=`
+
Toutes
+ ${gens.map(g=>`
Gen ${g}
`).join('')} +
`; + } + + let html=`
Accouplement
`; + + // Étape 1 ou 2 : sélection parent + const stepLabel=accoupState.parent1?'Sélectionne le Parent 2':'Sélectionne le Parent 1'; + const selectedParent1=accoupState.parent1; + + if(selectedParent1){ + const gc1=GEN_COLORS[RACE_GEN[selectedParent1]]||'#888'; + html+=`
+
+ Parent 1 + +
+
+ ${getDDImage(selectedParent1)} +
+
${selectedParent1}
+ Gen ${RACE_GEN[selectedParent1]} +
+
+
`; + } + + // Résultat si les deux parents sont sélectionnés + if(baby){ + const gcb=GEN_COLORS[babyGen]||'#888'; + const gc2=GEN_COLORS[RACE_GEN[accoupState.parent2]]||'#888'; + html+=`
+ Parent 2 — ${accoupState.parent2} +
+ ${getDDImage(accoupState.parent2)} +
+
${accoupState.parent2}
+ Gen ${RACE_GEN[accoupState.parent2]} +
+ +
+
`; + + html+=`
+ Bébé à naître +
+ ${getDDImage(accoupState.parent1)} + + + ${getDDImage(accoupState.parent2)} + +
+ ${getDDImage(baby)} +
${baby}
+ Gen ${babyGen} +
+
+
+
+ + +
+
+ + +
+
+
+ +
+
`; + }else{ + // Grille de sélection + html+=`
+ ${stepLabel} + ${genTabs} +
+ ${filtered.map(race=>{ + const gc=GEN_COLORS[RACE_GEN[race]]||'#888'; + return`
+ ${getDDImage(race)} +
${race}
+ Gen ${RACE_GEN[race]} +
`; + }).join('')} +
+
`; + } + + c.innerHTML=html; +} +``` + +**Step 4 : Fonctions d'interaction** + +```javascript +function selectAccoupParent(race){ + if(!accoupState.parent1){ + accoupState.parent1=race; + accoupState.parent2=null; + }else{ + accoupState.parent2=race; + } + accoupState.couples=''; + accoupState.babies=''; + renderAccouplement(); +} + +function enregistrerAccouplement(){ + const couples=parseInt(accoupState.couples)||0; + const babies=parseInt(accoupState.babies)||0; + if(couples<=0){alert('Renseigne le nombre de couples.');return;} + const baby=BREEDING_BY_PARENTS[accoupState.parent1+'|'+accoupState.parent2]; + if(!baby)return; + + if(!S.accouplements)S.accouplements=[]; + S.accouplements.push({ + parentA:accoupState.parent1, + parentB:accoupState.parent2, + baby, + gen:RACE_GEN[baby], + couples, + babiesObtained:babies, + date:Date.now() + }); + save(); + + // Reset + accoupState.parent1=null; + accoupState.parent2=null; + accoupState.couples=''; + accoupState.babies=''; + renderAccouplement(); +} +``` + +**Step 5 : Router dans renderContent()** + +Ligne ~1465, ajouter le cas : +```javascript +if(S.activeId==='accouplement'){renderAccouplement();return;} +``` + +**Step 6 : Ajouter l'onglet dans renderTabs()** + +Dans `renderTabs()` (ligne ~1341), ajouter un onglet "Accouplement" dans les special tabs, avec icône 💑. + +**Step 7 : Commit** + +``` +feat: ajoute onglet Accouplement avec sélection parents et enregistrement +``` + +--- + +## Task 4 : Intégrer les stats dans le Dashboard + section Paramètres + +**But :** Déplacer les stats et les settings dans le dashboard. Supprimer l'onglet Stats. + +**Files:** +- Modify: `src/index.html` + +**Step 1 : Retirer 'stats' de SPECIAL_TABS** + +Déjà fait dans Task 2 Step 1. + +**Step 2 : Modifier renderDashboard()** + +Après le contenu actuel du dashboard (grille des enclos), ajouter : + +1. **Section Stats globales** : Reprendre le contenu de `renderStats()` (lignes ~2306-2427) mais en tant que section du dashboard plutôt qu'onglet séparé. Adapter pour inclure les données de `S.accouplements` en plus de `S.archivedStats`. + +2. **Section Paramètres** : Déplacer le sélecteur de son (lignes ~1421-1433), le toggle notifs (lignes ~1435-1449) et le bouton ntfy (lignes ~1451-1457) depuis le header de `renderTabs()` vers une card "Paramètres" en bas du dashboard. + +**Calcul des stats depuis S.accouplements :** +```javascript +// Agréger accouplements dans les stats +const accoupBabies={}; +let accoupTotal=0,accoupCouples=0; +(S.accouplements||[]).forEach(a=>{ + accoupBabies[a.baby]=(accoupBabies[a.baby]||0)+a.babiesObtained; + accoupTotal+=a.babiesObtained; + accoupCouples+=a.couples; +}); +``` + +**Step 3 : Retirer les settings du header dans renderTabs()** + +Supprimer les lignes ~1421-1457 (sound select, notif btn, ntfy btn) de `renderTabs()`. + +**Step 4 : Supprimer renderStats()** + +Supprimer `renderStats()` (lignes ~2306-2427) et `resetStats()` (lignes ~2290-2304) — le code est intégré dans le dashboard. + +**Step 5 : Commit** + +``` +feat: intègre stats et paramètres dans le dashboard, retire onglet Stats +``` + +--- + +## Task 5 : Sidebar navigation + +**But :** Ajouter un menu hamburger avec sidebar overlay. + +**Files:** +- Modify: `src/index.html` (CSS + HTML + JS) + +**Step 1 : CSS de la sidebar** + +```css +/* Sidebar */ +.sidebar-toggle{position:fixed;top:12px;left:12px;z-index:1100;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:1.3rem;padding:6px 10px;cursor:pointer;transition:background .2s;line-height:1} +.sidebar-toggle:hover{background:var(--bg4)} +.sidebar-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1200;opacity:0;pointer-events:none;transition:opacity .25s} +.sidebar-overlay.open{opacity:1;pointer-events:all} +.sidebar{position:fixed;top:0;left:-280px;width:270px;height:100%;background:var(--bg2);border-right:1px solid var(--border);z-index:1300;transition:left .25s;padding:60px 0 20px;overflow-y:auto;display:flex;flex-direction:column;gap:2px} +.sidebar.open{left:0} +.sidebar-item{display:flex;align-items:center;gap:10px;padding:10px 20px;color:var(--muted);font-size:.88rem;font-weight:600;cursor:pointer;transition:background .15s,color .15s;border-left:3px solid transparent} +.sidebar-item:hover{background:var(--bg3);color:var(--text)} +.sidebar-item.active{color:var(--text);border-left-color:var(--ok);background:var(--bg3)} +.sidebar-sep{height:1px;background:var(--border);margin:8px 16px} +.sidebar-label{padding:8px 20px;font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);opacity:.6} +``` + +**Step 2 : HTML — ajouter le bouton et le conteneur sidebar** + +Juste après `` (ou avant la section `
`) : +```html + + + +``` + +**Step 3 : Ajouter un padding-left au header pour ne pas chevaucher le bouton hamburger** + +Ajouter `padding-left:50px` au `header` ou au conteneur principal. + +**Step 4 : JS — fonctions sidebar** + +```javascript +function toggleSidebar(){ + const sb=document.getElementById('sidebar'); + const ov=document.getElementById('sidebar-overlay'); + const open=sb.classList.toggle('open'); + ov.classList.toggle('open',open); + if(open)renderSidebar(); +} +function closeSidebar(){ + document.getElementById('sidebar').classList.remove('open'); + document.getElementById('sidebar-overlay').classList.remove('open'); +} +function sidebarNav(id){ + selectEnclos(id); + closeSidebar(); +} +function renderSidebar(){ + const sb=document.getElementById('sidebar'); + let html=``; + html+=``; + html+=``; + S.enclos.forEach((enc,i)=>{ + const label=enc.name||('Enclos '+(i+1)); + const isActive=S.activeId===enc.id; + html+=``; + }); + html+=``; + html+=``; + html+=``; + html+=``; + html+=``; + html+=``; + sb.innerHTML=html; +} +``` + +**Step 5 : Commit** + +``` +feat: ajoute sidebar navigation overlay avec menu hamburger +``` + +--- + +## Task 6 : Réappro ♂/♀ + +**But :** Ajouter les contraintes de genre dans l'arbre de réapprovisionnement. + +**Files:** +- Modify: `src/index.html` + +**Step 1 : Modifier calcAppro() pour assigner ♂/♀** + +Dans `calcAppro()` (ligne ~2507), modifier chaque étape pour indiquer le genre : + +```javascript +// Convention : parentA = ♂, parentB = ♀ (sauf si inversé par l'utilisateur) +const inverted=approState.inverted||{}; +const[rawA,rawB]=BREEDING_RECIPES[race]; +const isInv=inverted[race]; +const a=isInv?rawB:rawA, b=isInv?rawA:rawB; +// a = ♂, b = ♀ + +// Pour les besoins : marquer le genre +if(!needs[a])needs[a]={total:0,m:0,f:0}; +if(!needs[b])needs[b]={total:0,m:0,f:0}; +needs[a].m=(needs[a].m||0)+couplesReal; +needs[a].total=(needs[a].total||0)+couplesReal; +needs[b].f=(needs[b].f||0)+couplesReal; +needs[b].total=(needs[b].total||0)+couplesReal; +``` + +Le `needs` actuel est un simple nombre (`needs[race]=qty`). Il faut le transformer en objet `{total, m, f}`. + +**Step 2 : Toggle d'inversion ♂/♀ par étape** + +Ajouter dans `approState` : +```javascript +approState.inverted={}; // race → boolean +``` + +Fonction : +```javascript +function toggleApproGender(race){ + if(!approState.inverted)approState.inverted={}; + approState.inverted[race]=!approState.inverted[race]; + calcAppro(); +} +``` + +Bouton dans le rendu de chaque étape (à côté du toggle reproducteur) : +```html + +``` + +**Step 3 : Affichage ♂/♀ sur les mini-cards** + +Modifier la fonction `miniCard()` dans `calcAppro()` pour accepter un paramètre genre : + +```javascript +function miniCard(name,qty,gen,gender){ + const gc=GEN_COLORS[gen||RACE_GEN[name]]||'#888'; + const genderBadge=gender==='m'?'' + :gender==='f'?'':''; + return`
+
${getDDImage(name)} + Gen ${gen||RACE_GEN[name]} +
+ ${name}${genderBadge} + ×${qty} +
`; +} +``` + +Appels : `miniCard(s.parentA, s.couples, RACE_GEN[s.parentA], 'm')` et `miniCard(s.parentB, s.couples, RACE_GEN[s.parentB], 'f')`. + +**Step 4 : Résumé Gen 1 avec genres** + +Modifier le rendu des matières premières Gen 1 pour afficher `×4 ♂` et `×4 ♀` séparément : + +```javascript +gen1Needs.forEach(([name,data])=>{ + if(data.m>0) html+=miniCard(name,data.m,1,'m'); + if(data.f>0) html+=miniCard(name,data.f,1,'f'); +}); +``` + +**Step 5 : Commit** + +``` +feat: ajoute contraintes ♂/♀ dans réappro avec toggle d'inversion +``` + +--- + +## Task 7 : Mise à jour du rendu des tabs + nettoyage final + +**But :** S'assurer que les tabs affichent correctement les nouveaux onglets et nettoyer le code mort. + +**Files:** +- Modify: `src/index.html` + +**Step 1 : Mettre à jour renderTabs()** + +Ajouter l'onglet Accouplement (💑) dans la barre horizontale, entre Dashboard et les enclos ou entre Réappro et Inventaire. S'assurer que l'onglet Stats n'apparaît plus. + +**Step 2 : Mettre à jour renderContent()** + +Vérifier le routing complet : +```javascript +function renderContent(){ + clearElCache(); + const c=document.getElementById('enclos-content'); + if(S.activeId==='dashboard'){renderDashboard();return;} + if(S.activeId==='accouplement'){renderAccouplement();return;} + if(S.activeId==='appro'){renderApprovisionnement();return;} + if(S.activeId==='inventaire'){renderInventaire();return;} + if(S.activeId==='workflows'){renderWorkflows();return;} + // Sinon c'est un enclos + const enc=activeEnclos(); + if(!enc)return; + renderEnclos(enc); +} +``` + +**Step 3 : Nettoyage** + +- Supprimer tout code mort lié aux bébés dans les enclos +- Supprimer le rendu de l'onglet Stats dans renderTabs() +- Vérifier que `save()` persiste `S.accouplements` +- Vérifier que la migration `_migratedBabies120` fonctionne correctement + +**Step 4 : Mettre à jour le README (version 1.2.0)** + +**Step 5 : Commit** + +``` +feat: nettoyage final, routing mis à jour, v1.2.0 prêt +``` + +--- + +## Ordre d'exécution + +| Task | Dépendance | Description | +|------|-----------|-------------| +| 1 | Aucune | Retrait bébés + migration | +| 2 | Aucune | Données accouplement + lookup | +| 3 | Task 2 | UI onglet accouplement | +| 4 | Task 1 | Stats dans dashboard + paramètres | +| 5 | Aucune | Sidebar navigation | +| 6 | Aucune | Réappro ♂/♀ | +| 7 | Tasks 1-6 | Nettoyage + routing final | + +Tasks 1, 2, 5, 6 sont indépendantes et peuvent être faites en parallèle. +Task 3 dépend de 2. Task 4 dépend de 1. Task 7 finalise le tout. diff --git a/package.json b/package.json index 0d71465..755503a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minuteur-dragodinde", - "version": "1.1.4", + "version": "1.1.5", "description": "Minuteur elevage Dragodinde Dofus 3", "main": "main.js", "author": "Mickael", diff --git a/src/index.html b/src/index.html index 4819cc5..6aea6b1 100644 --- a/src/index.html +++ b/src/index.html @@ -178,18 +178,8 @@ input::-webkit-input-placeholder{color:var(--muted)} .modal-footer{padding:16px 22px;border-top:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap} /* STEPS */ -.modal-steps{display:flex;align-items:center;gap:8px;margin-bottom:18px;flex-wrap:wrap} -.step-pill{padding:5px 14px;border-radius:20px;font-size:0.82rem;font-weight:800;background:var(--bg3);color:var(--muted);border:1px solid var(--border)} -.step-pill.active{background:var(--ser);color:#fff;border-color:var(--ser)} -.step-pill.done{background:var(--ok);color:#000;border-color:var(--ok)} -.step-sep{color:var(--muted);font-size:0.9rem} /* GEN BUTTONS */ -.gen-btns{display:flex;gap:10px;flex-wrap:wrap;justify-content:center} -.gen-btn{width:72px;height:72px;border-radius:12px;border:2px solid var(--border);background:var(--bg3);color:var(--text);cursor:pointer;font:800 1.1rem 'Nunito',sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;transition:.15s;gap:2px} -.gen-btn:hover{border-color:var(--ser);background:var(--bg4)} -.gen-btn.selected{border-color:var(--ser);background:rgba(192,96,255,.12)} -.gen-btn .gen-lbl{font-size:0.7rem;font-weight:700;color:var(--muted)} /* RACE CARDS (style jeu) */ .race-cards-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px} @@ -211,26 +201,7 @@ input::-webkit-input-placeholder{color:var(--muted)} .race-card-parents-txt{font-size:0.72rem;color:#666;text-align:center;line-height:1.3} /* COUNT STEP */ -.count-wrap{display:flex;flex-direction:column;align-items:center;gap:16px;padding:20px 0} -.count-selected-card{max-width:200px;margin:0 auto} -.count-row{display:flex;align-items:center;gap:14px} -.count-btn{width:44px;height:44px;border-radius:50%;border:2px solid var(--border);background:var(--bg3);color:var(--text);font-size:1.4rem;cursor:pointer;transition:.15s;display:flex;align-items:center;justify-content:center} -.count-btn:hover:not(:disabled){background:var(--bg4);border-color:var(--ser)} -.count-btn:disabled{opacity:.3;cursor:not-allowed} -.count-val{font-family:'Cinzel',serif;font-size:2.5rem;font-weight:700;color:var(--ser);min-width:60px;text-align:center} -.count-max{font-size:0.85rem;color:var(--muted)} -/* BABIES LIST in enclos */ -.babies-section{background:var(--bg2);border:1px solid var(--border);border-radius:var(--r);padding:16px} -.babies-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px} -.babies-title{font-family:'Cinzel',serif;font-size:0.75rem;letter-spacing:2.5px;text-transform:uppercase;color:var(--muted)} -.btn-add-baby{padding:8px 16px;border-radius:8px;border:none;font:800 0.9rem 'Nunito',sans-serif;cursor:pointer;background:linear-gradient(135deg,#c04080,var(--amour));color:#fff;display:flex;align-items:center;gap:6px;transition:.15s} -.btn-add-baby:hover{opacity:.85} -.babies-list{display:flex;flex-wrap:wrap;gap:8px} -.baby-chip{display:flex;align-items:center;gap:7px;padding:6px 12px;border-radius:20px;font-size:0.88rem;font-weight:700;border:1px solid rgba(255,255,255,.15)} -.baby-chip-del{background:none;border:none;cursor:pointer;color:rgba(255,255,255,.5);font-size:0.9rem;padding:0 2px;margin-left:2px} -.baby-chip-del:hover{color:#fff} -.babies-empty{color:var(--muted);font-style:italic;font-size:0.9rem;padding:8px 0} /* STATS TAB */ .stats-wrap{display:flex;flex-direction:column;gap:18px} @@ -310,11 +281,40 @@ input::-webkit-input-placeholder{color:var(--muted)} .level-row input:focus{outline:none;border-color:rgb(255,160,64);background:rgba(255,160,64,.14)} .level-row .level-arrow{color:rgba(255,160,64,.5);font-size:0.9rem;font-weight:800} .level-row .level-eta{font-size:0.78rem;font-weight:800;color:rgb(255,160,64);white-space:nowrap;margin-left:auto} +/* Sidebar */ +.sidebar-toggle{position:fixed;top:12px;left:12px;z-index:1100;background:var(--bg3);border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:1.3rem;padding:6px 10px;cursor:pointer;transition:background .2s;line-height:1} +.sidebar-toggle:hover{background:var(--bg4)} +.sidebar-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1200;opacity:0;pointer-events:none;transition:opacity .25s} +.sidebar-overlay.open{opacity:1;pointer-events:all} +.sidebar{position:fixed;top:0;left:-280px;width:270px;height:100%;background:var(--bg2);border-right:1px solid var(--border);z-index:1300;transition:left .25s;padding:60px 0 20px;overflow-y:auto;display:flex;flex-direction:column;gap:2px} +.sidebar.open{left:0} +.sidebar-item{display:flex;align-items:center;gap:10px;padding:10px 20px;color:var(--muted);font-size:.88rem;font-weight:600;cursor:pointer;transition:background .15s,color .15s;border-left:3px solid transparent} +.sidebar-item:hover{background:var(--bg3);color:var(--text)} +.sidebar-item.active{color:var(--text);border-left-color:var(--ok);background:var(--bg3)} +.sidebar-sep{height:1px;background:var(--border);margin:8px 16px} +.sidebar-label{padding:8px 20px;font-size:.7rem;font-weight:800;text-transform:uppercase;letter-spacing:1.5px;color:var(--muted);opacity:.6} +/* Accouplement */ +.accoup-grid{display:flex;flex-wrap:wrap;gap:10px;justify-content:center} +.accoup-card{background:var(--bg2);border:2px solid var(--border);border-radius:var(--r);padding:10px;cursor:pointer;text-align:center;width:100px;transition:border-color .2s,transform .15s} +.accoup-card:hover{border-color:var(--text);transform:translateY(-2px)} +.accoup-card.selected{border-color:var(--ok);box-shadow:0 0 12px rgba(40,232,136,.25)} +.accoup-result{display:flex;align-items:center;gap:20px;justify-content:center;flex-wrap:wrap;padding:20px 0} +.accoup-arrow{font-size:2rem;color:var(--muted)} +.accoup-inputs{display:flex;gap:16px;align-items:center;justify-content:center;flex-wrap:wrap;padding:16px 0} +.accoup-input-group{display:flex;flex-direction:column;align-items:center;gap:6px} +.accoup-input-group label{font-size:.82rem;color:var(--muted)} +.accoup-input-group input{width:70px;background:var(--bg2);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:8px;font:700 1rem 'Nunito',sans-serif;text-align:center} +.accoup-gen-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:12px;justify-content:center} +.accoup-gen-tab{padding:4px 12px;border-radius:8px;font-size:.78rem;font-weight:700;cursor:pointer;border:1px solid var(--border);background:var(--bg2);color:var(--muted);transition:all .2s} +.accoup-gen-tab.active{background:var(--ok);color:#fff;border-color:var(--ok)} + + +
-
+

⚔ MINUTEUR DRAGODINDE

Dofus 3 · Gestion multi-enclos en temps réel ·

@@ -349,22 +349,6 @@ input::-webkit-input-placeholder{color:var(--muted)}
-