Compare commits

..

No commits in common. "main" and "v0.1.0-alpha" have entirely different histories.

11 changed files with 140 additions and 906 deletions

View File

@ -1,47 +0,0 @@
name: DevSecOps Pipeline - Universal Quality Gate
on:
push:
branches: [ "main", "develop", "dev/*" ]
pull_request:
branches: [ "main" ]
jobs:
health-check:
name: System & Environment Check
runs-on: ubuntu-latest
steps:
- name: System Check
run: |
echo "🚀 Pipeline CI/CD opérationnel !"
uname -a
gitleaks-scan:
name: Secret Detection (Gitleaks)
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download and Run Gitleaks
run: |
wget -q https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz
tar -xzf gitleaks_8.18.2_linux_x64.tar.gz gitleaks
./gitleaks detect --source="." --verbose --redact
trivy-iac-scan:
name: IaC Security & Vulnerability Scan (Trivy)
runs-on: ubuntu-latest
needs: gitleaks-scan
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Run Trivy Scan
continue-on-error: true
run: |
wget -q https://github.com/aquasec/trivy/releases/download/v0.49.1/trivy_0.49.1_Linux-64bit.tar.gz
tar -xzf trivy_0.49.1_Linux-64bit.tar.gz trivy
./trivy config .

53
.gitignore vendored
View File

@ -1,53 +0,0 @@
# ====================================================================
# RYM Horizon - Git Ignore Rules
# ====================================================================
# --- Système d'Exploitation (Hôtes & Laboratoires) ---
# Fedora / Linux généraux
.directory
.Trash-*
*~
# Fichiers systèmes Windows / macOS au cas où un collaborateur en utilise
Thumbs.db
Desktop.ini
.DS_Store
.AppleDouble
.LSOverride
# --- Éditeurs de Code & IDE ---
# VS Codium / VS Code
.vscode/
*.code-workspace
.history/
# --- Infrastructure locale (Podman / Docker) ---
.docker/
.podman/
*.tar
*.tar.gz
# Ne pas ignorer les fichiers de conf Nginx si nécessaire,
# mais ignorer les logs locaux si générés dans le projet
*.log
# --- Fichiers d'environnement & Sécurité ---
# Contient les clés d'API, secrets RYM Bank, accès serveurs
.env
.env.local
.env.*.local
*.pem
*.key
*.secret
# --- Dépendances futures ---
# Pour anticiper l'arrivée de Node.js, npm, ou de préprocesseurs CSS
node_modules/
.npm/
dist/
build/
.sass-cache/
.cache/
# --- Nginx erreurs fichiers ---
# A remplir au fur et à mesure
50x.html

View File

@ -1,9 +0,0 @@
{
"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
}
}

View File

@ -1,54 +0,0 @@
RYM Horizon — Spécifications Architecturales
Version : 3.0.0 (Souveraine & Épurée)
Date : Samedi 23 Mai 2026
Statut : Validé en Laboratoire Local
1. Philosophie & Alignement Géopolitique
RYM Horizon est le cockpit d'organisation temporelle et de gestion des actifs de la performance pour l'athlète d'endurance. Conformément à la charte "Tout sauf US", l'intégralité de l'environnement de développement et de déploiement est étanche aux lois extra-territoriales américaines (Cloud Act, EAR).
Hébergement cible : Serveurs souverains européens (Debian VPS).
Laboratoire local : Conteneurisé sous OS Européen (Allemagne/Suède).
Flux Financiers : Intégration native exclusive des rails européens (Wero / Euro Numérique).
2. Infrastructure du Laboratoire Local (Podman & openSUSE)
Pour valider le fonctionnement de l'application sans dépendance externe, le projet est exécuté dans un bac à sable isolé.
Composants techniques :
Orchestrateur : Podman (Rootless, natif Fedora, sans démon privilégié).
Système d'Exploitation : openSUSE Leap 15.6 (Distribution allemande/suédoise, ultra-stable).
Serveur Web : Nginx (Configuration optimisée).
Architecture Réseau & Ports :
Pour des raisons de sécurité (mode Rootless de Podman), le conteneur n'a pas les droits pour ouvrir le port standard 80. La configuration a été basculée sur le port 8080. Un tunnel de port lie la machine hôte au conteneur.
Accès local de développement : http://localhost:8080
3. Spécifications Fonctionnelles de l'Interface (v3.0.0)
L'interface abandonne le look "calendrier de bureau" au profit d'un cockpit de vie multi-échelle, inspiré de la clarté de Runna, de la précision de Garmin et du minimalisme de Stripe/Vercel.
Les 4 Échelles Temporelles :
Vue Jour : Découpage de la journée en cours (Matin / Après-midi / Soir) pour coller au bi-quotidien des athlètes.
Vue Semaine : Timeline horizontale fluide avec capacité de réorganisation des séances (Drag & Drop).
Vue Mois : Aperçu stratégique de la charge globale et des blocs d'assimilation.
Vue Macro / Olympiades : Planification à long terme des objectifs majeurs (Ex: Ironman, Pics de forme) et des budgets matériels associés.
Cockpit Physiologique & Matériel (Les Spinners) :
Indicateurs circulaires fins calculant en temps réel :
Fraîcheur / VRC : Niveau de tolérance à la charge d'entraînement.
Santé Matériel : Indice d'usure des actifs critiques (chaînes, chaussures carbone) corrélé directement à la RYM Bank pour le provisionnement budgétaire automatique.

View File

@ -1,69 +0,0 @@
# Journal des Modifications — RYM Horizon
Toutes les modifications notables de ce projet seront documentées dans ce fichier.
Le format est basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/)
et ce projet respecte le [Versionnement Sémantique](https://semver.org/lang/fr/).
---
## [3.1.3] - 2026-05-28
### Ajouté
- **Cockpit Holistique à 6 Piliers :** Extension du tableau de bord de 4 à 6 indicateurs circulaires SVG autonomes représentant l'intégralité des dimensions de l'athlète (Entraînement, Récupération, Nutrition, Hydratation, Soins, Matériel).
- **Usine à Badges Dynamique :** Implémentation d'un générateur d'activités et de tâches de maintenance à la volée via un formulaire JavaScript natif interconnecté au moteur de planification.
- **Sécurisation de la Supply Chain (SRI) :** Intégration des verrous d'intégrité cryptographique `integrity` (SHA-384) sur les liaisons CDN externes pour se conformer aux exigences de sécurité strictes de SonarQube.
### Modifié
- **Recentrage Produit (MVP) :** Nettoyage architectural complet de l'interface pour éliminer les briques tierces obsolètes (Espace Cohorte, Administrateur, MCO Infra) et recentrer l'application sur le profil unique de l'athlète d'endurance.
- **Moteur de Rendu Temporel :** Refonte de la logique événementielle dans `app.js` pour détruire et réassigner dynamiquement les écouteurs de glisser-déposer (*Drag & Drop*) sur les nouveaux composants injectés en cours de session.
- **Feuille de Style Souveraine :** Élimination des conflits d'opacité liés aux classes de transition natives de Bootstrap au profit d'un contrôle d'affichage exclusif et performant en CSS pur.
## [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.
- **Moteur Kanban Métier :** Implémentation du système de Drag & Drop natif dans l'onglet RYM Coach permettant de réorganiser le planning hebdomadaire.
- **Calculateur de Charge Dynamique :** Automatisation du recalcul du spinner "Charge Hebdo" en JavaScript à chaque déplacement d'entraînement.
### Modifié
- **Architecture de Navigation :** Consolidation du routage événementiel pour assurer la permutation étanche entre les onglets MCO Dashboard, RYM Bank, RYM Coach et Suivi Cohorte.
- **Design System :** Correction structurelle des imbrications de conteneurs Bootstrap 5 pour garantir un affichage fluide de la zone Expert.
- **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é
- **Architecture W3C :** Découplage complet et strict du code source en trois entités distinctes (`index.html`, `style.css`, `app.js`).
- **Logique JS Moderne :** Remplacement des attributs obsolètes `onclick` par un gestionnaire d'événements JavaScript non-intrusif basé sur les `data-attributes`.
- **Documentation :** Création du fichier `ARCHITECTURE.md` figeant la vision technique et la philosophie souveraine du projet.
- **Sécurité & Git :** Déploiement d'un fichier `.gitignore` standardisé pour protéger les secrets de la RYM Bank (fichiers `.env`, clés privées).
- **Cockpit à 4 Échelles :** Intégration de la vision multi-échelle (Jour J détaillé / Semaine fluide / Aperçu Mensuel / Macro-Olympiades).
- **Indicateurs Fins :** Implémentation des "spinners" circulaires en CSS pur pour le suivi de la fraîcheur physiologique (VRC) et de la santé du matériel.
### Modifié
- **Infrastructure Lab :** Bascule du serveur de test local vers un conteneur souverain européen **openSUSE Leap 15.6** opéré via **Podman** (Rootless).
- **Réseau :** Configuration et routage du serveur Nginx local sur le port alternatif `8080` pour contourner les restrictions de privilèges.
### Sécurisé
- Exclusion stricte des fichiers de configuration locaux et des dossiers caches d'éditeurs (`.vscode/`).
---
## [2.4.0] - 2026-05-10
### Ajouté
- Première ébauche de la vue calendrier hebdomadaire en tableau standard.
- Intégration des boutons d'exports de rapports pour l'Espace Expert (Cohort-Dashboard).
- Intégration visuelle de l'état de synchronisation avec l'API Strava.
- Structure HTML de base intégrant Bootstrap 5 (version monolithique avec style et script intégrés).
---
## [1.0.0] - 2026-04-01
### Ajouté
- Initialisation du projet RYM Horizon.
- Maquette de principe de l'interface et choix de la charte graphique de base.

68
LICENSE
View File

@ -1,68 +0,0 @@
====================================================================
RYM HORIZON — CONTRAT DE LICENCE COMMERCIALE ET DE CONFIDENTIALITÉ
====================================================================
Version : 1.0.0
Date de révision : 23 Mai 2026
Titulaire des droits : RYM Bank / Écosystème RYM
Juridiction exclusive : République Française / Union Européenne
--------------------------------------------------------------------
1. PROPRIÉTÉ ET DROITS D'AUTEUR
--------------------------------------------------------------------
Le présent logiciel, incluant sans limitation le code source (HTML,
CSS, JavaScript), l'architecture technique, les éléments graphiques,
l'arborescence et la documentation associée (ci-après "le Logiciel"),
est la propriété exclusive et entière de son auteur originel et de
la structure RYM Bank.
Le Logiciel est protégé par les lois françaises et internationales
relatives au droit d'auteur et à la propriété intellectuelle.
Tous droits non expressément concédés par le présent contrat sont
strictement réservés par le titulaire des droits.
--------------------------------------------------------------------
2. SOUVERAINETÉ ET ÉTANCHÉITÉ COMPORTEMENTALE
--------------------------------------------------------------------
Conformément à la charte fondamentale de l'écosystème RYM, ce logiciel
est conçu pour garantir une étanchéité absolue vis-à-vis des lois
extra-territoriales étrangères (notamment le US Cloud Act / USA PATRIOT Act).
Toute intégration future, modification ou exploitation du code source
doit impérativement respecter cette clause d'étanchéité et de
souveraineté numérique européenne.
--------------------------------------------------------------------
3. RESTRICTIONS D'UTILISATION ET DE DISTRIBUTION
--------------------------------------------------------------------
Sauf autorisation écrite, préalable et expresse du titulaire des
droits, il est strictement interdit de :
- Copier, modifier, adapter, traduire ou créer des œuvres dérivées
du Logiciel à des fins commerciales externes ou personnelles.
- Distribuer, louer, sous-licencier, vendre ou transférer le Logiciel
ou son code source à des tiers.
- Divulguer ou publier le code source sur des forges logicielles
publiques non contrôlées ou non hébergées en Europe de manière souveraine.
--------------------------------------------------------------------
4. CLAUSE PARTICULIÈRE : RECRUTEMENT ET COLLABORATION (LEAD TECH)
--------------------------------------------------------------------
Le présent code source est structuré selon les meilleures pratiques du
W3C à des fins d'audit technique et d'intégration de collaborateurs
clés (notamment un Lead Technical Officer).
L'accès à ce code par un tiers dans le cadre d'un recrutement, d'un
partenariat ou d'un co-développement implique de plein droit une
obligation stricte de secret professionnel, de réserve et de non-divulgation.
--------------------------------------------------------------------
5. EXCLUSION DE GARANTIE
--------------------------------------------------------------------
Le Logiciel est fourni "en l'état", sans garantie d'aucune sorte,
expresse ou implicite, y compris mais sans s'y limiter, les garanties
de commercialisation ou d'adéquation à un usage particulier.
--------------------------------------------------------------------
Fait à usage de droit. Pour toute demande de licence ou contribution :
Contact de l'infrastructure : www.gorym.fr
====================================================================

View File

@ -1,18 +0,0 @@
# RYM Horizon
> Le Cockpit de Performance Souverain pour l'Athlète d'Endurance.
## Présentation
RYM Horizon fusionne l'organisation temporelle, le suivi de la fraîcheur physiologique et la gestion budgétaire de la performance (MCO des actifs), de manière totalement étanche aux technologies extra-territoriales américaines.
## Installation Rapide (Laboratoire Local)
Le projet est configuré pour tourner de manière isolée sur un serveur web openSUSE via Podman.
1. Lancer le conteneur :
podman run -d -p 8080:8080 --name rym-suse-stable registry.opensuse.org/opensuse/leap:latest tail -f /dev/null
2. Accéder à l'application : `http://localhost:8080`
## Documentation
- Spécifications techniques : `ARCHITECTURE.md`
- Historique des versions : `CHANGELOG.md`

148
app.js
View File

@ -1,148 +0,0 @@
/**
* ====================================================================
* RYM HORIZON ENGINE APPLICATION (v3.1.3)
* ====================================================================
* Philosophie : Code découplé, souveraineté et performance pure.
* Architecture : Gestionnaire de vues + Moteur Drag & Drop + Usine à badges.
* Conformité : Sans dépendances tierces intrusives.
*/
document.addEventListener('DOMContentLoaded', () => {
// --- INTÉGRATION DE LA CHRONOLOGIE (SESSION LOG) ---
console.log("RYM Horizon Engine v3.1.3 : Initialisation du système...");
// ==========================================
// MODULE 1 : MANAGER DE NAVIGATION SOUVERAIN
// ==========================================
const tabButtons = document.querySelectorAll('.nav-view-switcher .nav-link');
const tabPanes = document.querySelectorAll('.tab-content .tab-pane');
tabButtons.forEach(button => {
button.addEventListener('click', (e) => {
const targetTabId = button.getAttribute('data-tab');
// 1. Mise à jour de l'état actif sur les boutons du menu
tabButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// 2. Commutation stricte des conteneurs de vues (Géré via CSS display !important)
tabPanes.forEach(pane => {
if (pane.id === targetTabId) {
pane.classList.add('active');
} else {
pane.classList.remove('active');
}
});
console.log(`[Navigation] Passage sur la vue : ${targetTabId}`);
});
});
// ==========================================
// MODULE 2 : USINE DE CRÉATION DE BADGES (À LA VOLÉE)
// ==========================================
const generatorForm = document.getElementById('rym-coach-generator');
const productionZone = document.getElementById('badge-production-zone');
// Dictionnaire de configuration des composants (Couleurs CSS natifs et Icônes)
const typeDictionary = {
run: { cssClass: 'run', icon: 'bi-lightning-fill' },
bike: { cssClass: 'bike', icon: 'bi-bicycle' },
swim: { cssClass: 'swim', icon: 'bi-water' },
matos: { cssClass: 'run', icon: 'bi-gear-wide-connected', customStyle: 'background: #fef2f2; color: #991b1b; border: 1px dashed #f87171;' },
soins: { cssClass: 'swim', icon: 'bi-heart-pulse-fill', customStyle: 'background: #faf5ff; color: #6b21a8; border: 1px dashed #c084fc;' }
};
generatorForm.addEventListener('submit', (e) => {
e.preventDefault(); // Bloque le rechargement de la page pour préserver le cache local
const type = document.getElementById('gen-type').value;
const title = document.getElementById('gen-title').value;
const config = typeDictionary[type];
const uniqueId = `w-generated-${Date.now()}`; // Identifiant unique basé sur le timestamp
// Nettoyage de la zone de production
productionZone.innerHTML = '';
// Création du nœud DOM du badge
const badge = document.createElement('div');
badge.id = uniqueId;
badge.className = `workout ${config.cssClass} rym-draggable-workout`;
badge.setAttribute('draggable', 'true');
if (config.customStyle) {
badge.setAttribute('style', config.customStyle);
}
// Injection du contenu structuré
badge.innerHTML = `<i class="bi ${config.icon} me-1"></i> ${title}`;
// Activation immédiate de la mécanique Drag sur le nouveau composant
attachDragEventsToElement(badge);
// Rendu final dans l'interface
productionZone.appendChild(badge);
generatorForm.reset();
console.log(`[Engine] Nouveau badge instancié avec succès ID: ${uniqueId}`);
});
// ==========================================
// MODULE 3 : MOTEUR DRAG & DROP MULTI-CIBLES
// ==========================================
let draggedElement = null;
/**
* Attache les écouteurs d'événements Drag à un élément unique
* @param {HTMLElement} element
*/
function attachDragEventsToElement(element) {
element.addEventListener('dragstart', (e) => {
draggedElement = element;
e.dataTransfer.setData('text/plain', element.id);
element.style.opacity = '0.4';
console.log(`[Drag] Début du déplacement de l'élément : ${element.id}`);
});
element.addEventListener('dragend', () => {
element.style.opacity = '1';
draggedElement = null;
});
}
// Initialisation du moteur Drag sur les cartes pré-existantes au boot
document.querySelectorAll('.rym-draggable-workout').forEach(attachDragEventsToElement);
// Configuration des zones de réception (Les jours de la semaine)
const dropZones = document.querySelectorAll('.drop-zone');
dropZones.forEach(zone => {
zone.addEventListener('dragover', (e) => {
e.preventDefault(); // Indispensable pour autoriser le drop dans le navigateur
zone.classList.add('drag-over');
});
zone.addEventListener('dragleave', () => {
zone.classList.remove('drag-over');
});
zone.addEventListener('drop', (e) => {
e.preventDefault();
zone.classList.remove('drag-over');
if (draggedElement) {
// Déplacement physique du nœud dans le conteneur du jour ciblé
zone.appendChild(draggedElement);
// Si le badge provient de la zone de production, on remet le texte d'attente
if (productionZone.children.length === 0) {
productionZone.innerHTML = 'Aucun badge généré';
}
console.log(`[Drop] Élément réassigné au jour : ${zone.getAttribute('data-day')}`);
}
});
});
});

View File

@ -1,21 +0,0 @@
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"
}
}
];

View File

@ -3,261 +3,152 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RYM Horizon — Cockpit Intégré v3.1.3</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN"
crossorigin="anonymous"
>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css"
rel="stylesheet"
integrity="sha384-4LISF5TTv3qMT719A6O7faWdgANoF94m85M84IfTWM5cKWAYjHL0ive8SkWAAAAA"
crossorigin="anonymous"
>
<link rel="stylesheet" href="style.css">
<title>RYM Horizon | Écosystème Souverain</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root { --rym-main: #004a99; --rym-dark: #121416; --rym-accent: #00d1b2; --rym-premium: #ffd700; }
body { background-color: #f0f2f5; font-family: 'Inter', sans-serif; font-size: 0.9rem; }
.sidebar { min-height: 100vh; background: var(--rym-dark); color: white; }
.nav-link { color: #8a8d91; border-radius: 10px; padding: 10px 15px; margin-bottom: 4px; cursor: pointer; }
.nav-link.active { background: var(--rym-main); color: white; }
.sidebar-heading { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; color: #495057; margin-top: 1.5rem; padding-bottom: 5px; border-bottom: 1px solid #343a40; }
.card-rym { border: none; border-radius: 18px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); background: white; }
.tab-content { display: none; }
.tab-content.active { display: block; animation: fadeIn 0.3s; }
.premium-zone { border: 2px solid var(--rym-premium); position: relative; }
.hour-row { border-bottom: 1px solid #eee; min-height: 40px; display: flex; align-items: center; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
</style>
</head>
<body>
<header class="bg-white border-bottom py-3 sticky-top">
<div class="container-fluid px-4 d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center">
<span class="fw-bold fs-4 me-4">Rym<span class="text-primary">Horizon</span></span>
<div class="nav nav-view-switcher" role="tablist">
<button class="nav-link" data-tab="view-day">Jour</button>
<button class="nav-link active" data-tab="view-week">Semaine</button>
<button class="nav-link" data-tab="view-month">Mois</button>
<button class="nav-link" data-tab="view-macro">Macro</button>
<div class="container-fluid">
<div class="row">
<nav class="col-md-2 d-none d-md-block sidebar p-4 sticky-top">
<h4 class="fw-bold text-center mb-5">RYM <span class="text-info">HORIZON</span></h4>
<div class="sidebar-heading">Admin</div>
<ul class="nav flex-column"><li class="nav-item"><a class="nav-link active" onclick="showTab(event, 'dashboard')"><i class="bi bi-shield-check me-2"></i> MCO Dashboard</a></li></ul>
<div class="sidebar-heading">Finance</div>
<ul class="nav flex-column"><li class="nav-item"><a class="nav-link" onclick="showTab(event, 'bank')"><i class="bi bi-bank me-2"></i> RYM Bank</a></li></ul>
<div class="sidebar-heading">Utilisateur</div>
<ul class="nav flex-column"><li class="nav-item"><a class="nav-link" onclick="showTab(event, 'activity')"><i class="bi bi-heart-pulse me-2"></i> RYM Coach</a></li></ul>
<div class="sidebar-heading">Expert</div>
<ul class="nav flex-column"><li class="nav-item"><a class="nav-link" onclick="showTab(event, 'clients')"><i class="bi bi-people me-2"></i> Suivi Cohorte</a></li></ul>
</nav>
<main class="col-md-10 ms-sm-auto px-md-5 py-4">
<div id="dashboard" class="tab-content active">
<h2 class="mb-4">MCO Dashboard</h2>
<div class="card card-rym p-3 w-25"><h6 class="text-muted">Status Infra</h6><p class="text-success mb-0">● Online - France (OVH)</p></div>
</div>
</div>
<div class="d-flex align-items-center">
<div class="text-end me-3">
<small class="text-muted d-block" style="font-size: 0.7rem;">RYM BANK</small>
<span class="fw-bold">19 420,00 €</span>
<div id="bank" class="tab-content">
<h2 class="mb-4">RYM Bank</h2>
<div class="card card-rym p-4 bg-primary text-white w-50 shadow-lg">
<h6>Solde Courant</h6>
<h2 class="fw-bold">12 450,80 €</h2>
</div>
</div>
<i class="bi bi-wallet2 fs-4 text-primary"></i>
</div>
<div id="activity" class="tab-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>RYM Coach <small class="text-muted fs-6">| Mon Planning</small></h2>
<span class="badge bg-primary">Saison : Triathlon</span>
</div>
<div class="card card-rym p-4 shadow-sm mb-5">
<h5 class="mb-3 text-primary"><i class="bi bi-calendar-week me-2"></i>Vue Hebdomadaire (Standard)</h5>
<div class="table-responsive">
<table class="table table-bordered text-center align-middle">
<thead class="bg-dark text-white">
<tr><th>Moment</th><th>Lun</th><th>Mar</th><th>Mer</th><th>Jeu</th><th>Ven</th><th>Sam</th><th>Dim</th></tr>
</thead>
<tbody>
<tr><td class="bg-light fw-bold">Matin</td><td><i class="bi bi-water"></i></td><td>-</td><td><i class="bi bi-bicycle"></i></td><td><i class="bi bi-water"></i></td><td>-</td><td class="bg-info-subtle">Loisir</td><td class="bg-danger-subtle">Repos</td></tr>
<tr><td class="bg-light fw-bold">A-M</td><td>-</td><td><i class="bi bi-lightning"></i></td><td>-</td><td>-</td><td><i class="bi bi-lightning"></i></td><td><i class="bi bi-bicycle"></i></td><td>-</td></tr>
<tr><td class="bg-light fw-bold">Soir</td><td>-</td><td>-</td><td><i class="bi bi-person-walking"></i></td><td>-</td><td>-</td><td>-</td><td>-</td></tr>
</tbody>
</table>
</div>
</div>
<div class="card card-rym p-4 premium-zone shadow-lg">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="fw-bold text-dark"><i class="bi bi-clock me-2"></i>Agenda Précision Premium (24h/24)</h5>
<button class="btn btn-warning btn-sm fw-bold">Passer au Premium</button>
</div>
<div class="bg-light rounded p-3" style="max-height: 250px; overflow-y: auto;">
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">06:00</div><div class="col-11 ps-3 text-muted">Sommeil / Réveil</div></div>
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">07:00</div><div class="col-11 ps-3 bg-primary-subtle text-primary">Natation : 2500m Seuil</div></div>
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">08:00</div><div class="col-11 ps-3">Petit-Déjeuner & Hydratation</div></div>
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2 text-primary">...</div><div class="col-11 ps-3">...</div></div>
<div class="hour-row"><div class="col-1 fw-bold border-end pe-2">23:00</div><div class="col-11 ps-3 text-muted">Repos / Nuit</div></div>
</div>
</div>
</div>
<div id="clients" class="tab-content">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Espace Expert <small class="text-muted fs-6">| Dashboard Cohorte</small></h2>
<div class="btn-group shadow-sm">
<button class="btn btn-white border btn-sm"><i class="bi bi-file-earmark-pdf"></i> PDF</button>
<button class="btn btn-white border btn-sm"><i class="bi bi-file-earmark-excel"></i> XLS</button>
</div>
</div>
<div class="row g-4">
<div class="col-md-7">
<div class="card card-rym p-0 overflow-hidden">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light"><tr><th class="p-3">Athlète</th><th>Santé</th><th>Sync API</th><th>Action</th></tr></thead>
<tbody>
<tr>
<td class="p-3"><strong>Jean Dupont</strong><br><small>Ironman Nice 2026</small></td>
<td><span class="badge bg-success">Optimal</span></td>
<td><i class="bi bi-strava text-orange"></i> <i class="bi bi-check-circle-fill text-primary"></i></td>
<td><button class="btn btn-sm btn-outline-dark">Ouvrir Dossier</button></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="col-md-5">
<div class="card card-rym p-4 border-start border-primary border-5">
<h5 class="mb-3">Bilan Anthropométrique (Cible)</h5>
<div class="row g-2 small">
<div class="col-6">Poids: <strong>74.5 kg</strong></div><div class="col-6 text-end">Âge: <strong>32</strong></div>
<hr>
<div class="col-6">Cou: 39cm</div><div class="col-6 text-end">Poitrine: 104cm</div>
<div class="col-6">Hanches: 92cm</div><div class="col-6 text-end">Taille: 80cm</div>
<div class="col-6 text-primary">Cuisse (D/G): 58/58</div><div class="col-6 text-primary text-end">Mollet (D/G): 39/39</div>
</div>
<div class="mt-4 p-2 bg-light border rounded text-center x-small">
<i class="bi bi-shield-lock me-1"></i> Données Chiffrées (Souveraineté RYM)
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</header>
</div>
<main class="container-fluid py-4 px-4">
<section class="row g-4 mb-5">
<div class="col-12">
<div class="bg-white rounded-4 p-3 border d-flex justify-content-around flex-wrap gap-4">
<div class="d-flex align-items-center gap-3">
<div class="spinner-container">
<svg width="60" height="60"><circle cx="30" cy="30" r="26" fill="none" stroke="#e2e8f0" stroke-width="4"/><circle cx="30" cy="30" r="26" fill="none" stroke="#0d9488" stroke-width="4" stroke-dasharray="163" stroke-dashoffset="80"/></svg>
<div class="spinner-value" style="color:#0d9488" id="val-charge">12h</div>
</div>
<div><small class="text-muted d-block">Entraînement</small><span class="fw-bold">Charge Hebdo</span></div>
</div>
<div class="d-flex align-items-center gap-3">
<div class="spinner-container">
<svg width="60" height="60"><circle cx="30" cy="30" r="26" fill="none" stroke="#e2e8f0" stroke-width="4"/><circle cx="30" cy="30" r="26" fill="none" stroke="#16a34a" stroke-width="4" stroke-dasharray="163" stroke-dashoffset="30"/></svg>
<div class="spinner-value text-success" id="val-recup">82%</div>
</div>
<div><small class="text-muted d-block">Récupération</small><span class="fw-bold">VRC / Sommeil</span></div>
</div>
<div class="d-flex align-items-center gap-3">
<div class="spinner-container">
<svg width="60" height="60"><circle cx="30" cy="30" r="26" fill="none" stroke="#e2e8f0" stroke-width="4"/><circle cx="30" cy="30" r="26" fill="none" stroke="#2563eb" stroke-width="4" stroke-dasharray="163" stroke-dashoffset="40"/></svg>
<div class="spinner-value text-primary" id="val-nutrition">75%</div>
</div>
<div><small class="text-muted d-block">Nutrition</small><span class="fw-bold">Macro Cibles</span></div>
</div>
<div class="d-flex align-items-center gap-3">
<div class="spinner-container">
<svg width="60" height="60"><circle cx="30" cy="30" r="26" fill="none" stroke="#e2e8f0" stroke-width="4"/><circle cx="30" cy="30" r="26" fill="none" stroke="#0284c7" stroke-width="4" stroke-dasharray="163" stroke-dashoffset="50"/></svg>
<div class="spinner-value" style="color:#0284c7" id="val-hydra">2.5L</div>
</div>
<div><small class="text-muted d-block">Hydratation</small><span class="fw-bold">Eau / Électro</span></div>
</div>
<div class="d-flex align-items-center gap-3">
<div class="spinner-container">
<svg width="60" height="60"><circle cx="30" cy="30" r="26" fill="none" stroke="#e2e8f0" stroke-width="4"/><circle cx="30" cy="30" r="26" fill="none" stroke="#f59e0b" stroke-width="4" stroke-dasharray="163" stroke-dashoffset="120"/></svg>
<div class="spinner-value text-warning" id="val-soins">Kiné</div>
</div>
<div><small class="text-muted d-block">Soins / Santé</small><span class="fw-bold">Fatigue Musc.</span></div>
</div>
<div class="d-flex align-items-center gap-3">
<div class="spinner-container">
<svg width="60" height="60"><circle cx="30" cy="30" r="26" fill="none" stroke="#e2e8f0" stroke-width="4"/><circle cx="30" cy="30" r="26" fill="none" stroke="#ea580c" stroke-width="4" stroke-dasharray="163" stroke-dashoffset="130"/></svg>
<div class="spinner-value text-danger" id="val-matos">22%</div>
</div>
<div><small class="text-muted d-block">Matériel</small><span class="fw-bold text-danger">MCO Critique</span></div>
</div>
</div>
</div>
</section>
<div class="tab-content">
<div class="tab-pane" id="view-day">
<div class="row">
<div class="col-lg-6 mx-auto">
<h2 class="fw-bold h4 mb-4">Aujourd'hui, Samedi 23 mai</h2>
<div class="day-part-card">
<span class="text-muted small fw-bold">MATIN</span>
<div class="workout swim"><i class="bi bi-water me-2"></i><strong>Natation</strong> - 1h15 Endurance (2500m)</div>
</div>
<div class="day-part-card day-part-active">
<span class="text-primary small fw-bold">APRÈS-MIDI</span>
<p class="text-muted small mt-2">Repos actif / Préparation vélo</p>
</div>
<div class="day-part-card">
<span class="text-muted small fw-bold">SOIR</span>
<div class="workout run"><i class="bi bi-speedometer2 me-2"></i><strong>Course</strong> - 45' Footing souple</div>
</div>
</div>
</div>
</div>
<div class="tab-pane active" id="view-week">
<div class="row g-4">
<div class="col-xl-3">
<div class="bg-white border rounded-4 p-4 sticky-md-top" style="top: 90px; z-index: 10;">
<h5 class="fw-bold mb-3"><i class="bi bi-plus-circle-fill text-primary me-2"></i>RYM Coach Engine</h5>
<p class="text-muted small">Créez une activité ou une tâche de maintenance et planifiez-la par Drag & Drop.</p>
<hr class="text-muted opacity-25">
<form id="rym-coach-generator" autocomplete="off">
<div class="mb-3">
<label class="form-label small fw-bold text-muted">TYPE DE COMPOSANT</label>
<select class="form-select form-select-sm" id="gen-type">
<option value="run">🏃 Course à pied</option>
<option value="bike">🚴 Vélo / Home-Trainer</option>
<option value="swim">🏊 Natation</option>
<option value="matos">🔧 Maintenance Matériel</option>
<option value="soins">🧘 Soins / Récupération</option>
</select>
</div>
<div class="mb-3">
<label class="form-label small fw-bold text-muted">TITRE / DESCRIPTION</label>
<input type="text" class="form-control form-control-sm" id="gen-title" placeholder="ex: 6x800m VMA ou Purge freins" required>
</div>
<button type="submit" class="btn btn-primary btn-sm w-100 fw-bold">Générer le Badge</button>
</form>
<div class="mt-4">
<label class="form-label small fw-bold text-muted d-block mb-2">BADGE PRÊT À GLISSER :</label>
<div id="badge-production-zone" class="p-3 border border-dashed rounded-3 bg-light text-center small text-muted">
Aucun badge généré
</div>
</div>
</div>
</div>
<div class="col-xl-9">
<div class="d-flex justify-content-between align-items-end mb-3">
<h2 class="fw-bold h4 mb-0">Timeline Hebdomadaire</h2>
<span class="text-muted small">Semaine 21 — Bloc Intensité</span>
</div>
<div class="timeline-scroll">
<div class="day-card drop-zone" data-day="Lun">
<span class="d-block fw-bold">Lundi 18</span>
<small class="text-muted">Repos</small>
<div class="mt-4 text-center py-3 border rounded-3 border-dashed">😴 Assimilation</div>
</div>
<div class="day-card drop-zone" data-day="Mar">
<span class="d-block fw-bold">Mardi 19</span>
<small class="text-muted">Haute Intensité</small>
<div class="workout run rym-draggable-workout" draggable="true" id="w-vma">
<i class="bi bi-lightning-fill me-1"></i> 6x800m VMA
</div>
</div>
<div class="day-card today drop-zone" data-day="Mer">
<span class="d-block fw-bold text-primary">Mercredi 20 (Aujourd'hui)</span>
<small class="text-primary fw-bold">Bi-quotidien</small>
<div class="workout swim rym-draggable-workout" draggable="true" id="w-swim">
<i class="bi bi-water me-1"></i> Swim 2500m
</div>
<div class="workout bike rym-draggable-workout" draggable="true" id="w-bike">
<i class="bi bi-bicycle"></i> H.Trainer 1h
</div>
</div>
<div class="day-card drop-zone" data-day="Jeu">
<span class="d-block fw-bold">Jeudi 21</span>
<small class="text-muted">Récupération</small>
<div class="workout run rym-draggable-workout" draggable="true" id="w-footing">
<i class="bi bi-speedometer2 me-1"></i> Footing 40'
</div>
</div>
<div class="day-card drop-zone" data-day="Ven">
<span class="d-block fw-bold">Vendredi 22</span>
<small class="text-muted">Repos</small>
<div class="mt-4 text-center py-3 border rounded-3 border-dashed">🧘 Flexibilité</div>
</div>
<div class="day-card drop-zone" data-day="Sam">
<span class="d-block fw-bold">Samedi 23</span>
<small class="text-muted">Sortie Longue</small>
</div>
<div class="day-card drop-zone" data-day="Dim">
<span class="d-block fw-bold">Dimanche 24</span>
<small class="text-muted">Enchaînement</small>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="view-month">
<div class="row g-3">
<div class="col-md-3">
<div class="bg-white border rounded-4 p-3">
<h6 class="fw-bold">Semaine 1</h6>
<div class="progress mb-2" style="height: 6px;"><div class="progress-bar bg-primary" style="width: 70%"></div></div>
<small class="text-muted">Focus : Volume Foncier</small>
</div>
</div>
<div class="col-md-3">
<div class="bg-white border rounded-4 p-3">
<h6 class="fw-bold">Semaine 2</h6>
<div class="progress mb-2" style="height: 6px;"><div class="progress-bar bg-primary" style="width: 85%"></div></div>
<small class="text-muted">Focus : Charge Progressive</small>
</div>
</div>
<div class="col-md-3">
<div class="bg-white border rounded-4 p-3">
<h6 class="fw-bold">Semaine 3</h6>
<div class="progress mb-2" style="height: 6px;"><div class="progress-bar bg-primary" style="width: 40%"></div></div>
<small class="text-muted">Focus : Assimilation / Repos</small>
</div>
</div>
<div class="col-md-3">
<div class="bg-white border rounded-4 p-3">
<h6 class="fw-bold">Semaine 4</h6>
<div class="progress mb-2" style="height: 6px;"><div class="progress-bar bg-primary" style="width: 90%"></div></div>
<small class="text-muted">Focus : Intensité Cible</small>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="view-macro">
<div class="macro-card">
<div class="row">
<div class="col-md-8">
<h2 class="fw-bold mb-3">Saison 2026 : Road to Ironman Vitoria</h2>
<p class="opacity-75">Cycle de 24 semaines — Actuellement en Phase de Spécificité 1.</p>
<div class="d-flex gap-2 mt-4">
<span class="badge bg-primary">Prep Foncière</span>
<span class="badge bg-primary">Prep Spécifique</span>
<span class="badge bg-outline-light border text-white opacity-50">Affûtage</span>
</div>
</div>
<div class="col-md-4 text-end">
<div class="h1 fw-bold">J - 142</div>
<small class="opacity-75">Avant l'objectif A</small>
</div>
</div>
</div>
</div>
</div>
</main>
<script src="app.js" defer></script>
<script>
function showTab(event, tabId) {
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
document.querySelectorAll('.nav-link').forEach(link => link.classList.remove('active'));
document.getElementById(tabId).classList.add('active');
event.currentTarget.classList.add('active');
}
</script>
</body>
</html>

170
style.css
View File

@ -1,170 +0,0 @@
:root {
--rym-bg: #f8fafc;
--rym-surface: #ffffff;
--rym-accent: #2563eb;
--rym-text: #0f172a;
--rym-muted: #64748b;
--rym-stroke: #e2e8f0;
}
body {
background-color: var(--rym-bg);
color: var(--rym-text);
font-family: 'Inter', -apple-system, sans-serif;
letter-spacing: -0.01em;
}
/* Nav & Tabs Switcheur du haut */
.nav-view-switcher {
background: #e2e8f0;
padding: 4px;
border-radius: 12px;
display: inline-flex;
}
.nav-view-switcher .nav-link {
border-radius: 10px;
color: var(--rym-muted);
font-weight: 600;
font-size: 0.85rem;
padding: 8px 16px;
border: none;
background: none;
transition: all 0.2s ease;
}
.nav-view-switcher .nav-link.active {
background: var(--rym-surface);
color: var(--rym-text);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* Cockpit Spinners Originaux */
.spinner-container {
width: 60px;
height: 60px;
position: relative;
}
.spinner-container svg {
transform: rotate(-90deg);
}
.spinner-container circle {
transition: stroke-dashoffset 0.3s ease;
}
.spinner-value {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.75rem;
font-weight: 700;
}
/* Timeline Hebdo Défilante */
.timeline-scroll {
display: flex;
gap: 1.25rem;
overflow-x: auto;
padding: 10px 0 20px;
scroll-snap-type: x mandatory;
}
.day-card {
min-width: 280px;
background: var(--rym-surface);
border: 1px solid var(--rym-stroke);
border-radius: 20px;
padding: 1.5rem;
scroll-snap-align: start;
transition: background-color 0.2s ease, border-color 0.2s ease;
}
.day-card.today {
border-color: var(--rym-accent);
border-width: 2px;
}
/* Drag & Drop Zones visual feedback */
.drop-zone.drag-over {
background-color: #f1f5f9 !important;
border: 2px dashed var(--rym-accent) !important;
}
.rym-draggable-workout {
cursor: grab;
user-select: none;
}
.rym-draggable-workout:active {
cursor: grabbing;
opacity: 0.4;
}
/* Vue Journalière (Matin/Midi/Soir) */
.day-part-card {
border-left: 3px solid var(--rym-stroke);
padding-left: 15px;
margin-bottom: 20px;
position: relative;
}
.day-part-card::before {
content: '';
position: absolute;
left: -6px;
top: 0;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--rym-stroke);
}
.day-part-active {
border-left-color: var(--rym-accent);
}
.day-part-active::before {
background: var(--rym-accent);
}
/* Badges de Séances d'endurance */
.workout {
border-radius: 12px;
padding: 10px;
font-size: 0.9rem;
margin-top: 10px;
font-weight: 500;
}
.run { background: #fff7ed; color: #9a3412; }
.bike { background: #f0f9ff; color: #075985; }
.swim { background: #f0fdfa; color: #115e59; }
/* Carte Macro Annu */
.macro-card {
background: linear-gradient(135deg, #1e293b, #0f172a);
color: white;
border-radius: 20px;
padding: 2rem;
}
/* --- MOTEUR DE NAVIGATION SOUVERAIN (FIX DE L'AFFICHAGE) --- */
.tab-pane {
display: none !important;
opacity: 0;
}
/* Force l'affichage et l'opacité à 1 quand l'onglet est actif */
.tab-pane.active {
display: block !important;
opacity: 1 !important;
animation: rymFadeIn 0.25s ease-out forwards;
}
@keyframes rymFadeIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}