From fefe280b9f4093d91fa297dd555b1ad4de9ee5f6 Mon Sep 17 00:00:00 2001 From: Marc Lasserre Date: Wed, 27 May 2026 20:24:42 +0200 Subject: [PATCH] feat: implement native drag & drop planning and responsive performance spinners v3.1.0 --- .stylelintrc.json | 9 +++ CHANGELOG.md | 10 ++++ app.js | 88 +++++++++++++++++------------ eslint.config.js | 21 +++++++ index.html | 138 +++++++++++++++++++++++++++++++++++++--------- style.css | 57 +++++++++++++++++++ 6 files changed, 262 insertions(+), 61 deletions(-) create mode 100644 .stylelintrc.json create mode 100644 eslint.config.js diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 0000000..5ae3699 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,9 @@ +{ + "rules": { + "block-no-empty": true, + "color-no-invalid-hex": true, + "comment-no-empty": true, + "declaration-block-no-duplicate-properties": true, + "declaration-block-no-shorthand-property-overrides": true + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a27c2..439fc1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ et ce projet respecte le [Versionnement Sémantique](https://semver.org/lang/fr/ --- +## [3.1.0] - 2026-05-27 +### Ajouté +- **Moteur Drag & Drop Natif :** Implémentation d'un système de planification agile (Kanban) dans `app.js` permettant de glisser-déposer les séances d'entraînement à la souris sans aucune dépendance logicielle externe. +- **Cockpit Physiologique & Matériel :** Intégration de 4 indicateurs de performance (Fraîcheur VRC, Charge Hebdo, Usure Chaîne Vélo, Paires Carbone) basés sur des tracés SVG natifs et légers. +- **Gouvernance & Linters :** Déploiement des fichiers de configuration modernes `eslint.config.js` (Format Flat Config de niveau industriel) et `.stylelintrc.json` à la racine du projet. + +### Modifié +- **Design System Responsif :** Optimisation de la structure Bootstrap 5 pour forcer l'alignement des "spinners" sur une seule ligne (`row`) sur écran desktop et une répartition fluide sur mobile. +- **Pont de Développement :** Restructuration du conteneur de validation openSUSE Leap 15.6 via un montage de volume Podman sécurisé (`:Z`) permettant l'audit du code en temps réel depuis l'hôte Fedora. + ## [3.0.0] - 2026-05-23 ### Ajouté diff --git a/app.js b/app.js index 1393500..fb38959 100644 --- a/app.js +++ b/app.js @@ -1,44 +1,60 @@ -/** - * RYM Horizon - Core Navigation Script - * @description Gestionnaire d'onglets découplé conforme aux standards W3C - */ +document.addEventListener("DOMContentLoaded", () => { + // --- GESTION DES ONGLETS EXISTANTE --- + const navLinks = document.querySelectorAll(".nav-link"); + const tabContents = document.querySelectorAll(".tab-content"); -document.addEventListener('DOMContentLoaded', () => { - - // Sélecteurs d'éléments de navigation - const navLinks = document.querySelectorAll('.sidebar .nav-link'); - const tabContents = document.querySelectorAll('.tab-content'); - - /** - * Bascule l'affichage vers l'onglet ciblé - * @param {string} targetTabId - L'identifiant de la section à afficher - */ - const switchTab = (targetTabId) => { - // 1. Masquer tous les contenus actifs - tabContents.forEach(content => content.classList.remove('active')); - - // 2. Afficher le contenu demandé - const activeTarget = document.getElementById(targetTabId); - if (activeTarget) { - activeTarget.classList.add('active'); - } - }; - - // Attribution dynamique des écouteurs d'événements navLinks.forEach(link => { - link.addEventListener('click', (event) => { - event.preventDefault(); + link.addEventListener("click", (e) => { + e.preventDefault(); + navLinks.forEach(l => l.classList.remove("active")); + tabContents.forEach(tc => tc.classList.remove("active")); - // Récupération de l'ID cible via le dataset - const targetTab = link.getAttribute('data-tab'); + link.classList.add("active"); + const tabId = link.getAttribute("data-tab"); + document.getElementById(tabId).classList.add("active"); + }); + }); - if (targetTab) { - // Gestion de l'état graphique de la navigation - navLinks.forEach(item => item.classList.remove('active')); - link.classList.add('active'); + // --- OPTION 1 : DRAG & DROP ENGINE (AAA SOUVERAIN) --- + const draggables = document.querySelectorAll(".rym-draggable-workout"); + const dropZones = document.querySelectorAll(".drop-zone"); - // Exécution de la bascule logique - switchTab(targetTab); + draggables.forEach(draggable => { + draggable.addEventListener("dragstart", (e) => { + e.dataTransfer.setData("text/plain", draggable.id); + // Petit délai pour l'effet visuel de déplacement + setTimeout(() => { + draggable.style.display = "none"; + }, 0); + }); + + draggable.addEventListener("dragend", () => { + draggable.style.display = "block"; + }); + }); + + dropZones.forEach(zone => { + zone.addEventListener("dragover", (e) => { + e.preventDefault(); // Indispensable pour autoriser le drop + zone.classList.add("drag-over"); + }); + + zone.addEventListener("dragleave", () => { + zone.classList.remove("drag-over"); + }); + + zone.addEventListener("drop", (e) => { + e.preventDefault(); + zone.classList.remove("drag-over"); + + const id = e.dataTransfer.getData("text/plain"); + const draggableElement = document.getElementById(id); + + if (draggableElement) { + zone.appendChild(draggableElement); + // Ici se branchera l'appel API asynchrone vers ton Gitea/Serveur + const targetDay = zone.getAttribute("data-day"); + console.log(`Séance déplacée avec succès sur le jour : ${targetDay}`); } }); }); diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..0f023b5 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,21 @@ +export default [ + { + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + window: "readonly", + document: "readonly", + console: "readonly", + setTimeout: "readonly" + } + }, + rules: { + "no-unused-vars": "warn", + "no-undef": "error", + "no-console": "off", + "eqeqeq": "error", + "curly": "error" + } + } +]; diff --git a/index.html b/index.html index 8c9bdc6..b26d929 100644 --- a/index.html +++ b/index.html @@ -69,36 +69,124 @@ Saison : Triathlon +
+
Indicateurs de Performance & Souveraineté
+
+ +
+
+ + + + +
+ 82% +
+
+
Fraîcheur VRC
+
+ +
+
+ + + + +
+ 65% +
+
+
Charge Hebdo
+
+ +
+
+ + + + +
+ 45% +
+
+
Chaîne Vélo
+
+ +
+
+ + + + +
+ 90% +
+
+
Paires Carbone
+
+ +
+
+
-
Vue Hebdomadaire (Standard)
-
- - - - - - - - - -
MomentLunMarMerJeuVenSamDim
Matin--LoisirRepos
A-M----
Soir------
+
Vue Hebdomadaire Dynamique
+
+ +
+
+
Lundi
+
+ Natation : 2500m +
+
+
+ +
+
+
Mardi
+
+ VMA Balayage +
+
+
+ +
+
+
Mercredi
+
+ Vélo Tempête +
+
+
+ +
+
+
Jeudi
+
+
+ +
+
+
Vendredi
+
+
+ +
+
+
Samedi
+
+
+ +
+
+
Dimanche
+
+
+
-
-
Agenda Précision Premium (24h/24)
- -
-
-
06:00
Sommeil / Réveil
-
07:00
Natation : 2500m Seuil
-
08:00
Petit-Déjeuner & Hydratation
-
...
...
-
23:00
Repos / Nuit
-
-
-
diff --git a/style.css b/style.css index 563d02a..ad9f7d7 100644 --- a/style.css +++ b/style.css @@ -91,3 +91,60 @@ body { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } } + +/* --- CONFIGURATION DES SPINNERS (OPTION 2) --- */ +.rym-spinner-wrapper { + position: relative; + width: 90px; + height: 90px; + margin: 0 auto; +} + +.rym-spinner-svg { + width: 100%; + height: 100%; + transform: rotate(-90deg); +} + +.circle-bg { + fill: none; + stroke: #e6e6e6; + stroke-width: 2.8; +} + +.circle-stroke { + fill: none; + stroke-width: 2.8; + stroke-linecap: round; + transition: stroke-dasharray 0.5s ease; +} + +.rym-spinner-text { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + text-align: center; +} + +/* --- DRAG AND DROP KANBAN (OPTION 1) --- */ +.drop-zone { + min-height: 140px; + transition: background-color 0.2s ease, border-color 0.2s ease; +} + +.drop-zone.drag-over { + background-color: #e2e8f0 !important; + border: 2px dashed #0dcaf0 !important; +} + +.rym-draggable-workout { + cursor: grab; + transition: transform 0.1s ease, opacity 0.1s ease; + user-select: none; +} + +.rym-draggable-workout:active { + cursor: grabbing; + opacity: 0.5; +}