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 +
| Moment | Lun | Mar | Mer | Jeu | Ven | Sam | Dim |
|---|---|---|---|---|---|---|---|
| Matin | - | - | Loisir | Repos | |||
| A-M | - | - | - | - | |||
| Soir | - | - | - | - | - | - |