docs(r0): kickoff SIOP V2 — vision, cadrage, charte graphique, maquettes HD

Refondation design-first du projet SIOP (v1 gelée) :
- Playbook 00-vision (charte projet, personas, benchmark Atlas)
- Playbook 01-cadrage (releases R0→R5 + DoD, user-story map, exigences, risques)
- Playbook 02-design : charte graphique, tokens.css bi-thème (palettes
  analytics validées CVD/contraste par script), maquettes HD navigables
  (7 écrans dont démo-login DEMO_MODE, bilan codé, portail QR, mobile)

Prochaine étape : validation des maquettes par le référent avant tout code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-15 21:06:33 +01:00
commit 04e61056f6
14 changed files with 1547 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
# Benchmark — Atlas CMMS et adaptation ascenseurs
> **Rôle de ce document (playbook)** : avant de concevoir, on étudie l'état de l'art. Ici,
> [Atlas CMMS](https://github.com/grashjs/cmms) (GMAO open source, Spring Boot + React) est
> notre référence fonctionnelle : on liste ce qu'on **reprend**, ce qu'on **adapte** au
> métier ascenseur, ce qu'on **écarte**, et nos **différenciateurs**.
## 1. Ce qu'Atlas fait bien (repris tel quel, réimplémenté dans notre stack)
| Module Atlas | Reprise SIOP | Release |
| --- | --- | --- |
| Work Orders (statuts, priorités, assignés, coûts) | Ordres de travail, machine à états stricte | R2 |
| Requests (portail + approbation → WO) | Demandes convergeant vers l'OT (lien 1-1) | R2 |
| Preventive Maintenance (triggers) | Génération périodique d'OT avec checklist | R2 |
| Assets + hiérarchie | Ascenseurs + **organes** (hiérarchie 2 niveaux) | R1 |
| Locations | Sites/emplacements + carte | R1 |
| Parts & Inventory / Purchase Orders | Stock dérivé des mouvements, BC, seuils | R3 |
| People & Teams, rôles/permissions | Matrice rôles × objets × droits, en base | R0 |
| Vendors & Customers | Tiers unifiés | R3 |
| Files | Bibliothèque de documents rattachables | R3 |
| Analytics | Tableau de bord KPI + agrégations SQL | R3 |
| Mobile app | App technicien Expo offline-first | R4 |
## 2. Ce qu'on adapte au métier ascenseur (le cœur de la valeur)
| Spécificité métier (source : carnet SPELEV) | Traduction produit |
| --- | --- |
| **Bilan d'intervention codé** (6 champs à valeurs codifiées : état portes, position cabine, anomalie, cause extérieure, action, élément) | Référentiels administrables + garde de clôture : pas d'OT terminé sans bilan valide |
| **Périodicités réglementaires** calées calendrier (mensuel → annuel, ancrage au mois — ex. parachute en juillet) | Gabarits de tâches à périodicité + génération mensuelle idempotente + « premier contrôle = tout » |
| **Grille du mois** cochée en cabine | Checklist embarquée sur l'OT préventif, cochable hors-ligne sur mobile |
| **PV contradictoire signé** (technicien + client) | Signatures tactiles + PDF fidèle au carnet, générable hors connexion (post-v1 : R5+) |
| **Urgence « personne bloquée »** | Priorité dédiée, circuit court (alerte immédiate, pas de validation préalable) |
| **Étiquette QR en cabine** | QR = URL canonique de la fiche → signalement prérempli (public) et fiche technicien (mobile) |
## 3. Ce qu'on écarte (v1)
- Multi-tenant SaaS, facturation, abonnements (Atlas est un produit commercial hébergé).
- Workflows configurables génériques — nos flux métier sont fixés par le métier.
- Intégrations tierces (SAP, IoT…) — hors périmètre v1.
## 4. Nos différenciateurs (au-delà d'Atlas)
1. **IA** (R5) : assistant RAG citant ses sources (notices, historiques), aide à la saisie
du bilan ; principe « l'IA propose, l'humain valide ».
2. **Design métier** : interface pensée pour les personas ascensoristes (gros statuts,
urgences visibles, terminologie du carnet) — pas une GMAO générique habillée.
3. **DX/démo** : sélecteur de compte démo 1 clic (`DEMO_MODE`) — essentiel pour la
démonstration commerciale ET le développement.
## 5. Choix de stack face à Atlas
Atlas = Java Spring Boot + React (MUI). Nous **ne clonons pas la stack**, seulement le
fonctionnel : notre stack TypeScript de bout en bout (NestJS/React/Expo) + FastAPI pour
l'IA est plus cohérente pour une équipe unique et l'enseignement (un seul langage principal).
Décision motivée en ADR-001.

View File

@@ -0,0 +1,67 @@
# Charte projet — SIOP V2
> **Rôle de ce document (playbook)** : la charte projet est le contrat fondateur. Une page
> suffit pour dire *pourquoi* le projet existe, *ce qu'est* le succès et *qui* décide.
> Tout ce qui suit dans le playbook en découle. À rédiger AVANT toute conception.
## 1. Vision
Remplacer le carnet d'intervention papier des ascensoristes par une **GMAO complète et
belle**, pensée pour le métier de la maintenance d'ascenseurs — du signalement d'une panne
par un gardien d'immeuble jusqu'au PV signé par le client, en passant par le préventif
réglementaire et l'assistance IA au technicien.
## 2. Problème
- Le suivi des interventions se fait sur **carnet papier** (perte, illisibilité, aucune analyse).
- Les **périodicités réglementaires** (contrôles mensuels → annuels) sont suivies à la main.
- Le bureau ne sait pas **où en sont les techniciens** ni quels équipements coûtent cher.
- Les GMAO génériques du marché ignorent les spécificités ascenseur (organes, carnet codé,
PV contradictoire, urgence « personne bloquée »).
## 3. Objectifs jumeaux
| # | Objectif | Mesure de succès |
| --- | --- | --- |
| A | **Produit** : GMAO ascenseurs complète (web + mobile + IA), clone adapté d'Atlas CMMS | Déployée en production (Dokploy), utilisée sur un parc réel de démonstration, releases R0→R5 recettées |
| B | **Playbook** : documentation pédagogique de toutes les phases du cycle de vie | Un étudiant peut refaire chaque phase avec le seul playbook ; le référent l'utilise comme base de sa future entreprise de digitalisation |
## 4. Parties prenantes
| Rôle | Qui | Responsabilité |
| --- | --- | --- |
| Commanditaire & référent métier | Pr. Daaif (ENSET) | Décisions produit, validation des recettes et des maquettes |
| Entreprise partenaire | SPELEV (maintenance d'ascenseurs) | Réalité métier : carnet, périodicités, organisation |
| Équipe de réalisation | Pr. Daaif + assistant IA (puis étudiants ENSET) | Conception, développement, tests, docs |
| Hébergeur | Serveur d'un partenaire (PaaS Dokploy) | Production |
| Bénéficiaires pédagogiques | Étudiants ENSET | Apprennent sur le playbook et contribuent |
## 5. Périmètre (v1 du produit = releases R0 → R5)
**Inclus** : référentiel (sites, ascenseurs, organes, QR), personnes & permissions, ordres
de travail avec bilan codé, portail de demandes, maintenance préventive à périodicité,
compteurs, stock & achats, tiers, coûts (pièces + main-d'œuvre), analytics, bibliothèque de
fichiers, app mobile technicien offline (scan QR, checklist, synchro), IA (assistant RAG,
bilan assisté). Détail par release : `../01-cadrage/releases.md`.
**Exclus (v1)** : facturation client, multi-tenant SaaS, agent vocal temps réel et
affectation géospatiale automatique (étudiés en fin de R5 si le budget temps le permet).
## 6. Contraintes
- **Design-first** : maquettes validées avant le code (leçon de la v1).
- **Langue** : produit et docs en français ; code en anglais.
- **Réglementaire** : loi marocaine 09-08 (données personnelles, géolocalisation, audio).
- **Hébergement** : PaaS auto-hébergé (Dokploy) sur le serveur du partenaire.
- **Équipe réduite** : 1 encadrant + IA, étudiants à intégrer → conventions strictes, CI bloquante.
## 7. Risques majeurs (détail : `../01-cadrage/risques.md`)
Dérive de périmètre (mitigation : releases fermées) ; dette design (mitigation : revue pixel
par release) ; dépendance au serveur partenaire (mitigation : tout est Docker, portable).
## 8. Gouvernance
Le référent tranche toute décision produit (sollicité explicitement à chaque bifurcation).
Les décisions techniques sont consignées en ADR (`../03-architecture/adr/`). Une release se
clôt par : recette formelle signée + déploiement + chapitre de playbook relu.

View File

@@ -0,0 +1,64 @@
# Personas — SIOP V2
> **Rôle de ce document (playbook)** : un persona n'est pas une fiche décorative — chaque
> écran conçu en phase design doit pouvoir répondre à « quel persona, quelle tâche, quel
> contexte ? ». Les rôles applicatifs (matrice de permissions) dérivent de ces personas.
## P1 — Salma, dispatchrice (bureau)
- **Contexte** : au bureau SPELEV, 2 écrans, toute la journée dans l'application.
- **Tâches** : recevoir les demandes, créer/assigner les OT, surveiller les urgences,
relancer les techniciens, préparer les bons de commande.
- **Douleurs** : jongler entre téléphone et papier ; ne pas savoir qui est où ; urgences
« personne bloquée » à traiter en secondes.
- **Attentes produit** : tableau de bord temps réel, création d'OT en < 30 s, assignation
en 1 clic, alertes visibles.
- **Rôle applicatif** : *Dispatcher* (tout voir, tout créer/éditer, pas d'administration).
## P2 — Ahmed, technicien itinérant (terrain)
- **Contexte** : en déplacement, souvent en sous-sol **sans réseau**, gants, pressé.
- **Tâches** : consulter ses OT du jour, scanner le QR en cabine, dérouler la checklist du
mois, saisir le bilan codé, faire signer le client, repartir.
- **Douleurs** : réseau absent, ressaisie au bureau le soir, carnet papier perdu.
- **Attentes produit** : mobile **offline-first**, gros boutons, checklist en 2 taps,
synchro automatique au retour du réseau.
- **Rôle applicatif** : *Technicien* (ses OT, saisie terrain ; « voir autre » limité).
## P3 — Karim, gardien d'immeuble (demandeur)
- **Contexte** : occasionnel, smartphone, aucune formation à l'outil.
- **Tâches** : signaler une panne (photo à l'appui), suivre l'avancement.
- **Douleurs** : ne sait pas si sa demande a été vue ; rappelle 3 fois.
- **Attentes produit** : portail ultra-simple (ou scan du QR en cabine formulaire
prérempli), suivi du statut sans jargon interne.
- **Rôle applicatif** : *Demandeur* (ses demandes uniquement, rien d'autre).
## P4 — Nadia, gestionnaire (back-office)
- **Contexte** : mi-temps sur l'outil ; responsable stock, achats, contrats.
- **Tâches** : inventaire, seuils et réappro, bons de commande, tiers, documents
(certificats, notices), coûts par équipement.
- **Attentes produit** : tables efficaces (tri/filtre/export), alertes de stock,
bibliothèque documentaire rangée.
- **Rôle applicatif** : *Gestionnaire* (stock, achats, tiers, fichiers ; lecture OT).
## P5 — M. Bennis, direction (lecture)
- **Contexte** : consulte 1 fois par semaine, souvent sur mobile.
- **Tâches** : indicateurs (pannes, coûts, taux de préventif, réactivité), décisions
contrat/renouvellement de parc.
- **Attentes produit** : tableau de bord lisible en 30 s, tendances, export.
- **Rôle applicatif** : *Vue seule* (analytics + lecture).
## P0 — L'administrateur (transverse)
Paramètre l'instance : utilisateurs, permissions, référentiels du bilan codé, gabarits de
périodicité. Rôle *Administrateur* (tout). C'est aussi **le compte de démonstration** par
défaut du sélecteur `DEMO_MODE`.
---
**Correspondance personas → rôles de la matrice** : Administrateur, Dispatcher, Technicien,
Technicien limité (variante « voir autre » restreinte de P2), Gestionnaire, Demandeur,
Vue seule 7 rôles seedés dès R0, tous accessibles en 1 clic via le sélecteur démo.

View File

@@ -0,0 +1,55 @@
# Exigences non fonctionnelles — SIOP V2
> **Rôle de ce document (playbook)** : les exigences fonctionnelles vivent dans la
> user-story map et les issues ; ici on fige ce qui est **transversal** — qualité, sécurité,
> conformité, exploitation — avec des critères **mesurables** (sinon invérifiables).
## Qualité & design
| Exigence | Critère mesurable |
| --- | --- |
| Fidélité au design | Revue pixel par release : écrans conformes aux maquettes validées (`../02-design/`) |
| Réactivité | Interfaces utilisables de 360 px (mobile) à 1920 px ; tables scrollables sans casser la page |
| Thème | Clair **et** sombre, pilotés par les tokens ; aucun style codé en dur hors tokens |
| Accessibilité | Navigation clavier sur les parcours critiques ; libellés/aria sur tous les contrôles ; contraste AA |
| Langue | 100 % français (produit), zéro franglais dans l'UI |
## Performance
| Exigence | Critère |
| --- | --- |
| Chargement des listes | < 1 s pour 10 000 OT (pagination serveur, index SQL) |
| Tableau de bord | < 2 s (agrégations SQL, jamais de calcul en mémoire sur tout l'historique) |
| Mobile offline | Ouverture d'un OT déjà synchronisé : instantanée (< 200 ms, lecture locale) |
## Sécurité
| Exigence | Critère |
| --- | --- |
| API fermée par défaut | Guard JWT global ; routes publiques explicitement `@Public()` ; test de couverture des routes |
| Permissions | Matrice rôles × objets × droits **en base** (jamais dans le JWT), testée rôle par rôle |
| Démo-login | `POST /auth/demo-login` **inexistant** (404) si `DEMO_MODE` n'est pas `true` ; jamais activé en production réelle ; test e2e dédié |
| Fichiers | Accès aux objets MinIO uniquement par URL signée à durée courte ; clés opaques |
| Secrets | Jamais en git ; `.env.example` exhaustifs ; secrets Dokploy |
## Conformité (loi 09-08)
Consentement explicite pour toute géolocalisation ; géolocalisation limitée aux heures de
service ; purge des enregistrements audio après transcription (R5) ; droit d'accès/rectification
des données personnelles (procédure au runbook).
## Fiabilité & exploitation
| Exigence | Critère |
| --- | --- |
| Sauvegardes | PostgreSQL + MinIO sauvegardés quotidiennement ; restauration testée 1×/release (runbook) |
| Migrations | `prisma migrate deploy` rejouable ; seed idempotent (rejouer ne casse rien) |
| Offline mobile | Aucune saisie terrain perdue : file locale + verrou optimiste + rejeu (testé) |
| Supervision | Endpoint `/health` (base, Redis, MinIO) ; vérifié au déploiement |
## Testabilité
Pyramide : unitaires (logique pure), intégration contre infra réelle (Docker), parcours
navigateur Playwright (1+ par release), mobile testé en Node (`node:sqlite`), contrat
OpenAPI bloquant en CI. Couverture API 70 % **bloquante**. Chaque bug corrigé = un test
qui l'aurait attrapé.

View File

@@ -0,0 +1,71 @@
# Plan de releases — SIOP V2
> **Rôle de ce document (playbook)** : découper le produit en releases **fermées** — chaque
> release a un périmètre figé, une définition de fini (DoD) et se termine par une recette
> formelle + un déploiement. On n'ouvre pas Rn+1 avant la clôture de Rn. C'est l'antidote
> à la dérive de périmètre constatée en v1.
## Définition de fini (DoD) commune à toutes les releases
1. Toutes les stories de la release recettées (critères d'acceptation × preuves).
2. CI verte : lint, typecheck, tests (couverture API ≥ 70 % bloquante), contrat OpenAPI à jour.
3. **Revue pixel** : les écrans livrés sont conformes aux maquettes validées.
4. Déployé en production (Dokploy) et vérifié en ligne.
5. Chapitre(s) de playbook de la release relus par le référent.
6. Journal + tag git `release/rX`.
## R0 — Fondations *(aucun écran métier)*
**But** : tout ce qui rend les releases suivantes rapides et propres.
- Playbook 00-vision, 01-cadrage, 02-design (charte + tokens + **maquettes HD validées**), 03-architecture (ADRs, C4, modèle de données).
- Monorepo pnpm/Turborepo, infra Docker locale (PostgreSQL + Redis + MinIO), CI (4 pipelines : node, contrat, python, e2e).
- `apps/api` : auth JWT, matrice de permissions (7 rôles × objets × droits, en base), seed réaliste (parc d'ascenseurs de démonstration).
- **Sélecteur de compte démo** (`DEMO_MODE=true`) : bascule de rôle en < 3 s, local et démo, jamais en prod réelle.
- `apps/web` : coquille applicative (login, layout, navigation, design system appliqué, page vitrine du design system).
- Déploiement Dokploy « hello SIOP » (l'app en ligne dès R0).
**Hors R0** : tout écran métier.
## R1 — Référentiel
Sites/emplacements (hiérarchie + carte), ascenseurs + organes (hiérarchie, statuts, QR
imprimable), catégories, personnes & équipes (invitation, activation, profils).
**Recette type** : créer un site un ascenseur ses organes imprimer l'étiquette QR
inviter un technicien.
## R2 — Exploitation
Ordres de travail (machine à états, priorités dont **« personne bloquée »**, assignation,
commentaires, pièces jointes), **bilan codé** (6 champs + référentiels administrables +
garde de clôture), portail de demandes (public via QR + interne) convergeant vers l'OT,
préventif (gabarits à périodicité, génération mensuelle idempotente, checklist, premier
contrôle), compteurs.
**Recette type** : demande gardien approbation OT intervention bilan codé
clôture suivi demandeur ; génération du préventif de juillet.
## R3 — Gestion
Stock (mouvements, seuils, alertes), consommation sur OT à prix figé, bons de commande,
tiers, main-d'œuvre valorisée (taux figé) et coût total OT, analytics (KPI + graphiques),
bibliothèque de fichiers (documents rattachables).
**Recette type** : consommer sous seuil BC réception réappro ; coût complet d'un OT ;
tableau de bord direction.
## R4 — Mobile technicien
App Expo offline-first : OT assignés, fiche, scan QR fiche ascenseur, checklist du mois
cochable hors-ligne, clôture terrain, synchro à verrou optimiste (patron validé en v1).
**Recette type** : mode avion en sous-sol cocher la grille retour réseau synchro.
## R5 — IA
Assistant RAG (notices + historiques ingérés, réponses **citant leurs sources**), aide à
la saisie du bilan (suggestion de codes à partir d'une description libre). Si budget temps :
transcription vocale du bilan. Principe : l'IA propose, l'humain valide.
**Recette type** : « quel couple de serrage pour les guides du Gen2 ? » réponse sourcée.
## Après v1 (backlog v2)
PV PDF signé hors-ligne, check-in/out géolocalisés, agent vocal, affectation géospatiale
automatique, multi-langue, exports comptables.

View File

@@ -0,0 +1,20 @@
# Registre des risques — SIOP V2
> **Rôle de ce document (playbook)** : nommer les risques AVANT qu'ils ne frappent, avec
> une mitigation concrète et un signal d'alerte. Relu à chaque clôture de release ;
> les risques réalisés sont documentés (post-mortem court) — c'est de la matière de cours.
| # | Risque | Prob. | Impact | Mitigation | Signal d'alerte |
| --- | --- | --- | --- | --- | --- |
| 1 | **Dérive de périmètre** (la cause n°1 de l'abandon de la v1) | Haute | Haut | Releases fermées (DoD stricte) ; toute idée hors release va au backlog v2, jamais dans la release courante | Une story « en plus » apparaît dans un jalon en cours |
| 2 | **Dette design** — l'esthétique se dégrade sous la pression du fonctionnel | Haute | Haut | Maquettes validées avant code ; revue pixel par release ; tokens obligatoires (pas de styles ad hoc) | Un écran livré « à peu près comme la maquette » |
| 3 | **Playbook en retard** sur le produit (docs bâclées en fin de release) | Moyenne | Haut (objectif B) | Le chapitre s'écrit PENDANT la release ; sa relecture fait partie de la DoD | Chapitre vide à mi-release |
| 4 | **Serveur partenaire indisponible** ou repris | Moyenne | Moyen | Tout est Docker/Dokploy → portable en heures ; sauvegardes exportées hors du serveur | Latence/pannes récurrentes |
| 5 | **Rupture d'outillage** (versions Expo/React/Prisma, pnpm) | Moyenne | Moyen | Versions épinglées ; leçons v1 documentées (jest-expo↔jest, peers Expo/pnpm, worklets) ; monter de version = tâche dédiée, jamais en passant | CI rouge après un simple `install` |
| 6 | **Équipe qui s'élargit** (étudiants) sans conventions intégrées | Moyenne | Moyen | CLAUDE.md + 04-developpement comme contrat d'équipe ; PR + CI obligatoires dès leur arrivée ; exercices d'onboarding (07-formation) | Styles/patterns divergents dans les PR |
| 7 | **IA (R5) survendue** — attentes RAG > réalité | Moyenne | Moyen | Jeu d'évaluation constitué AVANT le développement ; « l'IA propose, l'humain valide » ; périmètre R5 réduit et honnête | Réponses non sourcées jugées « suffisantes » |
| 8 | **Données de démo peu crédibles** → démos commerciales ternes | Basse | Moyen | Seed réaliste dès R0 (parc marocain nommé, historiques plausibles) maintenu à chaque release | Démo qui montre des « Test 123 » |
## Post-mortems
*(vide — se remplit quand un risque se réalise)*

View File

@@ -0,0 +1,53 @@
# User-story map — SIOP V2
> **Rôle de ce document (playbook)** : la carte relie les activités des personas (colonnes)
> aux releases (lignes). Chaque cellule = stories. Les critères d'acceptation détaillés
> sont écrits au cadrage de chaque release (format *Étant donné / Quand / Alors*) dans les
> issues GitHub du jalon correspondant.
## Activités (le « squelette » horizontal)
| | A. Gérer le parc | B. Exploiter (pannes) | C. Prévenir (réglementaire) | D. Gérer les ressources | E. Piloter | F. Terrain (mobile) | G. Être assisté (IA) |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Personas | Salma, Nadia | Salma, Ahmed, Karim | Salma, Ahmed | Nadia | M. Bennis | Ahmed | Ahmed, Salma |
## Carte (stories clés par release)
### R0 — Fondations
- En tant qu'**équipe**, je me connecte avec mon rôle et ne vois que ce que ma matrice autorise.
- En tant que **développeur/démonstrateur**, je bascule de compte en 1 clic (`DEMO_MODE`).
- En tant que **visiteur**, je vois une app en ligne au design abouti (coquille + design system).
### R1 — Référentiel
- **A** Salma crée un site (adresse, position carte) puis un ascenseur (marque, n° série, mise en service) et ses organes.
- **A** Salma imprime l'étiquette **QR** à coller en cabine.
- **A** L'admin invite un technicien (lien d'activation) et compose des équipes.
### R2 — Exploitation
- **B** Karim scanne le QR en cabine → formulaire de signalement prérempli avec photo ; il suit ensuite l'avancement sans jargon.
- **B** Salma approuve la demande → un OT est créé (lien 1-1) et assigné en 1 clic.
- **B** Un OT « **personne bloquée** » saute en tête partout, visuellement impossible à manquer.
- **B** Ahmed clôt son OT par le **bilan codé** (6 champs) ; la clôture est bloquée si incomplet.
- **C** Le 1er du mois, les OT préventifs se génèrent avec la **checklist des tâches dues** (parachute en juillet, premier contrôle = tout) ; regénérer ne double rien.
- **C** La clôture d'un OT préventif exige la checklist complète (Fait / N-A).
### R3 — Gestion
- **D** Nadia consomme une pièce sur un OT (prix figé) ; sous le seuil, une alerte part et un BC se prépare ; la réception réapprovisionne.
- **D** Nadia range notices et certificats dans la **bibliothèque**, rattachés à l'ascenseur.
- **D** La main-d'œuvre saisie (taux figé par personne) donne le **coût total** de l'OT.
- **E** M. Bennis lit en 30 s : OT par statut, pannes par organe, taux de préventif, coûts, top équipements.
### R4 — Mobile
- **F** Ahmed voit ses OT triés priorité/échéance, **même sans réseau**.
- **F** Il scanne le QR → fiche de l'ascenseur et ses interventions.
- **F** Il coche la grille du mois hors-ligne et clôt du terrain ; tout se synchronise au retour du réseau **sans perdre une saisie** (verrou optimiste).
### R5 — IA
- **G** Ahmed demande « procédure de réglage des portes Gen2 » → réponse **avec source** (notice ingérée).
- **G** Ahmed décrit la panne en langage libre → l'assistant **suggère les codes** du bilan ; il valide ou corrige.
## Priorisation
Le chemin critique produit est **B + C** (c'est le carnet papier remplacé) — d'où R2 au
centre du programme. A (R1) est le prérequis de tout ; D/E (R3) rendent l'outil gestionnaire ;
F (R4) supprime la ressaisie ; G (R5) différencie.

View File

@@ -0,0 +1,79 @@
# Charte graphique — SIOP V2
> **Rôle de ce document (playbook)** : fixer l'identité visuelle AVANT le code. Toute
> valeur visuelle vit dans [`tokens.css`](tokens.css) (source de vérité, importée telle
> quelle par l'application) ; ce document explique les **choix** et leurs raisons.
> Règle absolue : aucun style codé en dur hors tokens — c'est ce qui a rendu la v1 « flottante ».
## 1. Positionnement
SIOP est un **outil de travail professionnel** pour ascensoristes : sobre, dense sans être
étouffant, lisible en plein soleil sur un chantier comme au bureau. Le monde visuel est
celui du métier — acier, cabine, signalisation de sécurité — pas celui d'une app grand
public. Deux thèmes (clair/sombre) au même niveau de soin.
## 2. Couleur
| Token | Valeur (clair) | Rôle & justification |
| --- | --- | --- |
| `--encre` | `#1B2534` | Texte principal — un « noir acier » à biais bleu, jamais de noir pur |
| `--bleu-treuil` | `#1F4FB8` | **Primaire** (actions, liens, focus) — bleu de vêtement de travail, profond et désaturé, volontairement distinct du bleu néon par défaut des frameworks |
| `--safran` | `#DD8A0B` | **Accent de marque** — safran marocain / gilet haute visibilité. Parcimonieux : logo, rail actif de navigation, moments de marque. Jamais pour un statut |
| Fonds | `#F5F7FA` / surfaces `#FFFFFF` | Acier clair, biais froid assumé |
| Sombre | fond `#0F1622`, surface `#182234` | « Machinerie de nuit » — pas une simple inversion |
**Statuts (réservés, jamais réutilisés ailleurs)** : Ouvert `bleu`, En cours `indigo`,
En attente `ambre`, Terminé `vert`, Annulé `gris`. Priorités : **Personne bloquée**
(rouge vif + pastille animée — la seule animation d'alerte de l'app), Haute `rouge`,
Moyenne `ambre`, Basse `verte`. Toujours pastille/pictogramme + libellé, jamais la couleur seule.
**Palettes graphiques (analytics) — validées par script (CVD, contraste, bande de
luminosité)** :
- Clair : `#3B69D6 · #DD8A0B · #1F9E8E · #8A63D2` (ordre fixe, jamais recyclé ; étiquettes
directes obligatoires — le safran passe sous 3:1 sur blanc).
- Sombre : `#5580E4 · #C7820A · #26A896 · #9673DE` (repas dédiés, pas d'inversion automatique).
## 3. Typographie
- **UI & titres : Manrope** (géométrique-humaniste, OFL, auto-hébergée — jamais de CDN).
Titres en 600/700, `text-wrap: balance`. Les maquettes HTML approximent avec Avenir Next
(présent sur macOS) en attendant l'app.
- **Données : chiffres tabulaires** (`font-variant-numeric: tabular-nums`) partout où des
nombres s'alignent (tables, KPI, coûts).
- Échelle : 12 / 13 / 14 (base) / 16 / 20 / 24 / 32. Libellés en capitales : 11 px,
`letter-spacing: 0.06em`.
## 4. Forme & espace
- Grille d'espacement **4 px** ; densité « bureau » (tables 40 px/ligne) mais cibles
tactiles ≥ 44 px sur mobile.
- Rayons : 10 px (cartes), 8 px (contrôles), 999 px (pastilles).
- Élévation discrète : bordures d'abord (`--bordure`), ombre légère seulement au survol/menus.
- **Motif signature « rail »** : l'élément actif de la navigation porte un rail vertical
safran de 3 px (la cage d'ascenseur) — c'est LE geste identitaire, utilisé nulle part ailleurs.
## 5. Iconographie & logo
- **Lucide**, trait 1,75 px, 1620 px dans l'UI.
- Logotype : « SIOP » en Manrope 700 + marque **▲▼** (montée/descente cabine) safran sur
encre. Favicon = la marque seule.
## 6. Mouvement
Fonctionnel uniquement : transitions 120160 ms (`ease-out`) sur survol/ouverture ;
`prefers-reduced-motion` respecté ; **une seule** animation continue autorisée — la
pastille « personne bloquée ».
## 7. Ton rédactionnel
Français métier, direct, sans jargon système : « Clôturer l'intervention », pas
« Soumettre le formulaire ». Les erreurs disent quoi faire (« La checklist est incomplète :
traitez chaque tâche »), jamais « Erreur 400 ». Vocabulaire du carnet : OT, bilan, organe,
grille du mois.
## 8. Application
`tokens.css` est copié tel quel dans `apps/web` (et mappé vers la config Tailwind/shadcn).
La page `/design` de l'app (R0) expose tous les tokens et composants — c'est la référence
vivante des revues pixel.

View File

@@ -0,0 +1,821 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SIOP V2 — Maquettes haute-fidélité</title>
</head>
<body>
<!--
SIOP V2 — Maquettes HD (R0, phase design).
Source de vérité des valeurs : ../tokens.css (répliquées ici pour l'autonomie du fichier).
7 écrans navigables + bascule clair/sombre. À valider par le référent AVANT tout code.
-->
<style>
/* ═══ Tokens (répliques de ../tokens.css) ═══ */
:root{
--primaire:#1f4fb8;--primaire-actif:#1a439c;--primaire-doux:#eaf0fb;
--safran:#dd8a0b;--safran-doux:#fdf3e3;
--fond:#f5f7fa;--surface:#ffffff;--surface-2:#eef2f7;
--bordure:#dce3ec;--bordure-forte:#b9c4d4;
--encre:#1b2534;--encre-2:#55647a;--encre-3:#8494ab;
--st-ouvert:#3d6fe0;--st-ouvert-fond:#e9effc;--st-encours:#6d5bd8;--st-encours-fond:#efecfa;
--st-attente:#b96f07;--st-attente-fond:#fbf1df;--st-termine:#178a50;--st-termine-fond:#e6f5ec;
--st-annule:#68788f;--st-annule-fond:#edf0f4;
--prio-bloque:#d92626;--prio-bloque-fond:#fdeaea;--prio-haute:#d92626;
--prio-moyenne:#b96f07;--prio-basse:#178a50;--prio-aucune:#8494ab;
--succes:#178a50;--alerte:#b96f07;--danger:#d92626;--info:#3d6fe0;
--graf-1:#3b69d6;--graf-2:#dd8a0b;--graf-3:#1f9e8e;--graf-4:#8a63d2;--graf-grille:#e6ebf2;
--nav-fond:#16233b;--nav-encre:#c6d2e4;--nav-encre-vif:#ffffff;--nav-bordure:#24344f;
--police-ui:"Manrope","Avenir Next","Segoe UI Variable",system-ui,sans-serif;
--rayon:10px;--rayon-controle:8px;--ombre-menu:0 8px 24px rgb(16 30 54/.14);
--transition:140ms ease-out;
}
@media (prefers-color-scheme:dark){:root{
--primaire:#6e92e8;--primaire-actif:#8facf0;--primaire-doux:#1d2c4c;
--safran:#e89a1f;--safran-doux:#33270f;
--fond:#0f1622;--surface:#182234;--surface-2:#1e2a40;
--bordure:#2b3850;--bordure-forte:#3d4d6b;
--encre:#e8edf5;--encre-2:#a7b4c8;--encre-3:#6d7d96;
--st-ouvert:#7da2ee;--st-ouvert-fond:#1c2a47;--st-encours:#a394ec;--st-encours-fond:#262040;
--st-attente:#e0a33c;--st-attente-fond:#322510;--st-termine:#4bc084;--st-termine-fond:#12301f;
--st-annule:#93a3ba;--st-annule-fond:#222d3f;
--prio-bloque:#f26d6d;--prio-bloque-fond:#3a1414;--prio-haute:#f26d6d;
--prio-moyenne:#e0a33c;--prio-basse:#4bc084;--prio-aucune:#6d7d96;
--succes:#4bc084;--alerte:#e0a33c;--danger:#f26d6d;--info:#7da2ee;
--graf-1:#5580e4;--graf-2:#c7820a;--graf-3:#26a896;--graf-4:#9673de;--graf-grille:#24304a;
--nav-fond:#0c1524;--nav-encre:#a7b4c8;--nav-encre-vif:#f2f6fc;--nav-bordure:#1e2c44;
--ombre-menu:0 8px 24px rgb(0 0 0/.45);
}}
:root[data-theme="light"]{
--primaire:#1f4fb8;--primaire-actif:#1a439c;--primaire-doux:#eaf0fb;
--safran:#dd8a0b;--safran-doux:#fdf3e3;
--fond:#f5f7fa;--surface:#ffffff;--surface-2:#eef2f7;
--bordure:#dce3ec;--bordure-forte:#b9c4d4;
--encre:#1b2534;--encre-2:#55647a;--encre-3:#8494ab;
--st-ouvert:#3d6fe0;--st-ouvert-fond:#e9effc;--st-encours:#6d5bd8;--st-encours-fond:#efecfa;
--st-attente:#b96f07;--st-attente-fond:#fbf1df;--st-termine:#178a50;--st-termine-fond:#e6f5ec;
--st-annule:#68788f;--st-annule-fond:#edf0f4;
--prio-bloque:#d92626;--prio-bloque-fond:#fdeaea;--prio-haute:#d92626;
--prio-moyenne:#b96f07;--prio-basse:#178a50;--prio-aucune:#8494ab;
--succes:#178a50;--alerte:#b96f07;--danger:#d92626;--info:#3d6fe0;
--graf-1:#3b69d6;--graf-2:#dd8a0b;--graf-3:#1f9e8e;--graf-4:#8a63d2;--graf-grille:#e6ebf2;
--nav-fond:#16233b;--nav-encre:#c6d2e4;--nav-encre-vif:#ffffff;--nav-bordure:#24344f;
--ombre-menu:0 8px 24px rgb(16 30 54/.14);
}
:root[data-theme="dark"]{
--primaire:#6e92e8;--primaire-actif:#8facf0;--primaire-doux:#1d2c4c;
--safran:#e89a1f;--safran-doux:#33270f;
--fond:#0f1622;--surface:#182234;--surface-2:#1e2a40;
--bordure:#2b3850;--bordure-forte:#3d4d6b;
--encre:#e8edf5;--encre-2:#a7b4c8;--encre-3:#6d7d96;
--st-ouvert:#7da2ee;--st-ouvert-fond:#1c2a47;--st-encours:#a394ec;--st-encours-fond:#262040;
--st-attente:#e0a33c;--st-attente-fond:#322510;--st-termine:#4bc084;--st-termine-fond:#12301f;
--st-annule:#93a3ba;--st-annule-fond:#222d3f;
--prio-bloque:#f26d6d;--prio-bloque-fond:#3a1414;--prio-haute:#f26d6d;
--prio-moyenne:#e0a33c;--prio-basse:#4bc084;--prio-aucune:#6d7d96;
--succes:#4bc084;--alerte:#e0a33c;--danger:#f26d6d;--info:#7da2ee;
--graf-1:#5580e4;--graf-2:#c7820a;--graf-3:#26a896;--graf-4:#9673de;--graf-grille:#24304a;
--nav-fond:#0c1524;--nav-encre:#a7b4c8;--nav-encre-vif:#f2f6fc;--nav-bordure:#1e2c44;
--ombre-menu:0 8px 24px rgb(0 0 0/.45);
}
/* ═══ Base ═══ */
*{box-sizing:border-box;margin:0}
html,body{background:var(--fond)}
body{font-family:var(--police-ui);color:var(--encre);font-size:14px;line-height:1.5}
button{font:inherit;cursor:pointer;border:0;background:none;color:inherit}
button:focus-visible,a:focus-visible{outline:2px solid var(--primaire);outline-offset:2px;border-radius:4px}
table{border-collapse:collapse;font-variant-numeric:tabular-nums}
.num{font-variant-numeric:tabular-nums}
/* ═══ Barre de revue (outil maquette, pas l'app) ═══ */
.revue{position:sticky;top:0;z-index:50;display:flex;align-items:center;gap:12px;flex-wrap:wrap;
padding:10px 16px;background:var(--nav-fond);color:var(--nav-encre);font-size:13px}
.revue b{color:var(--nav-encre-vif);letter-spacing:.02em}
.revue .onglets{display:flex;gap:4px;flex-wrap:wrap}
.revue .onglets button{padding:6px 10px;border-radius:6px;color:var(--nav-encre)}
.revue .onglets button:hover{background:rgb(255 255 255/.08);color:var(--nav-encre-vif)}
.revue .onglets button[aria-selected="true"]{background:rgb(255 255 255/.12);color:var(--nav-encre-vif);
box-shadow:inset 3px 0 0 var(--safran)}
.revue .droite{margin-left:auto;display:flex;align-items:center;gap:8px}
.revue .theme{padding:6px 10px;border:1px solid var(--nav-bordure);border-radius:6px}
.note-ecran{max-width:1280px;margin:14px auto 0;padding:0 20px;color:var(--encre-2);font-size:13px}
.note-ecran b{color:var(--encre)}
/* ═══ Coquille applicative ═══ */
.ecran{display:none;max-width:1280px;margin:14px auto 40px;padding:0 20px}
.ecran.actif{display:block}
.app{display:grid;grid-template-columns:232px 1fr;min-height:720px;border:1px solid var(--bordure);
border-radius:14px;overflow:hidden;background:var(--fond)}
.app.pleine{grid-template-columns:1fr}
/* Sidebar (toujours sombre — identité) */
.nav{background:var(--nav-fond);color:var(--nav-encre);display:flex;flex-direction:column;padding:16px 0}
.logo{display:flex;align-items:center;gap:10px;padding:2px 18px 16px;color:var(--nav-encre-vif)}
.logo .marque{display:flex;flex-direction:column;line-height:.55;font-size:11px;color:var(--safran)}
.logo b{font-size:19px;letter-spacing:.04em}
.logo small{color:var(--nav-encre);font-weight:400;font-size:11px;display:block;margin-top:2px}
.nav .groupe{padding:12px 18px 4px;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:#6b7f9e}
.nav a{display:flex;align-items:center;gap:10px;padding:8px 18px;color:var(--nav-encre);
text-decoration:none;font-size:13.5px;border-left:3px solid transparent;transition:background var(--transition)}
.nav a:hover{background:rgb(255 255 255/.06);color:var(--nav-encre-vif)}
.nav a.actif{background:rgb(255 255 255/.09);color:var(--nav-encre-vif);border-left-color:var(--safran)}
.nav a .badge-nav{margin-left:auto;background:var(--prio-bloque);color:#fff;font-size:11px;
padding:1px 7px;border-radius:999px;font-weight:700}
.nav svg{width:17px;height:17px;stroke-width:1.75;flex:none}
/* Topbar */
.contenu{display:flex;flex-direction:column;min-width:0}
.topbar{display:flex;align-items:center;gap:12px;padding:12px 24px;background:var(--surface);
border-bottom:1px solid var(--bordure)}
.recherche{flex:1;max-width:420px;display:flex;align-items:center;gap:8px;background:var(--surface-2);
border:1px solid var(--bordure);border-radius:8px;padding:7px 12px;color:var(--encre-3);font-size:13px}
.recherche svg{width:15px;height:15px}
.chip-urgence{display:flex;align-items:center;gap:7px;background:var(--prio-bloque-fond);
color:var(--prio-bloque);border:1px solid color-mix(in srgb,var(--prio-bloque) 30%,transparent);
padding:6px 12px;border-radius:999px;font-weight:700;font-size:12.5px}
.point-pulse{width:8px;height:8px;border-radius:50%;background:var(--prio-bloque);animation:pulse 1.4s ease-out infinite}
@keyframes pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--prio-bloque) 45%,transparent)}
70%{box-shadow:0 0 0 8px transparent}100%{box-shadow:0 0 0 0 transparent}}
@media (prefers-reduced-motion:reduce){.point-pulse{animation:none}}
.compte{margin-left:auto;display:flex;align-items:center;gap:10px}
.chip-demo{font-size:11px;font-weight:700;letter-spacing:.05em;color:var(--safran);
background:var(--safran-doux);padding:3px 9px;border-radius:999px;border:1px dashed var(--safran)}
.avatar{width:30px;height:30px;border-radius:50%;background:var(--primaire);color:#fff;display:flex;
align-items:center;justify-content:center;font-size:12px;font-weight:700;flex:none}
.compte .qui{line-height:1.15;font-size:12.5px}
.compte .qui small{color:var(--encre-2)}
/* Corps de page */
.page{padding:20px 24px;display:flex;flex-direction:column;gap:16px}
.entete-page{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.entete-page h1{font-size:21px;font-weight:700;letter-spacing:-.01em;text-wrap:balance}
.entete-page .actions{margin-left:auto;display:flex;gap:8px}
.filajout{color:var(--encre-3);font-size:12.5px}
.filajout a{color:var(--encre-2);text-decoration:none}
/* Boutons */
.btn{display:inline-flex;align-items:center;gap:7px;padding:8px 14px;border-radius:8px;font-size:13.5px;
font-weight:600;border:1px solid var(--bordure-forte);background:var(--surface);color:var(--encre);
transition:filter var(--transition)}
.btn:hover{filter:brightness(.97)}
.btn.prim{background:var(--primaire);border-color:var(--primaire);color:#fff}
.btn.succes{background:var(--succes);border-color:var(--succes);color:#fff}
.btn.danger-l{color:var(--danger);border-color:color-mix(in srgb,var(--danger) 40%,transparent);background:transparent}
.btn.ghost{border-color:transparent;background:transparent;color:var(--encre-2)}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn svg{width:15px;height:15px;stroke-width:2}
/* Cartes */
.carte{background:var(--surface);border:1px solid var(--bordure);border-radius:var(--rayon);padding:16px 18px}
.carte h2{font-size:13px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--encre-2);margin-bottom:12px}
.grille-2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.grille-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
@media(max-width:900px){.grille-2,.grille-3{grid-template-columns:1fr}}
/* Tuiles KPI */
.kpis{display:grid;grid-template-columns:repeat(5,1fr);gap:12px}
@media(max-width:1100px){.kpis{grid-template-columns:repeat(2,1fr)}}
.kpi{background:var(--surface);border:1px solid var(--bordure);border-radius:var(--rayon);padding:14px 16px}
.kpi .lib{font-size:11.5px;letter-spacing:.05em;text-transform:uppercase;color:var(--encre-2);font-weight:600}
.kpi .val{font-size:27px;font-weight:700;letter-spacing:-.02em;margin-top:4px}
.kpi .delta{font-size:12px;font-weight:600;margin-top:2px}
.delta.bon{color:var(--succes)}.delta.mauvais{color:var(--danger)}.delta.neutre{color:var(--encre-3)}
.kpi.alarme{border-color:color-mix(in srgb,var(--danger) 45%,transparent)}
.kpi.alarme .val{color:var(--danger)}
.anneau{display:flex;align-items:center;gap:12px}
.anneau svg{flex:none}
/* Pastilles statut / priorité */
.st{display:inline-flex;align-items:center;gap:6px;padding:2px 10px;border-radius:999px;font-size:12px;font-weight:700}
.st::before{content:"";width:7px;height:7px;border-radius:50%;background:currentColor}
.st.ouvert{color:var(--st-ouvert);background:var(--st-ouvert-fond)}
.st.encours{color:var(--st-encours);background:var(--st-encours-fond)}
.st.attente{color:var(--st-attente);background:var(--st-attente-fond)}
.st.termine{color:var(--st-termine);background:var(--st-termine-fond)}
.st.annule{color:var(--st-annule);background:var(--st-annule-fond)}
.prio{display:inline-flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600}
.prio i{width:8px;height:8px;border-radius:50%;flex:none}
.prio.bloque{color:var(--prio-bloque)}.prio.bloque i{background:var(--prio-bloque);animation:pulse 1.4s ease-out infinite}
.prio.haute{color:var(--prio-haute)}.prio.haute i{background:var(--prio-haute)}
.prio.moyenne{color:var(--prio-moyenne)}.prio.moyenne i{background:var(--prio-moyenne)}
.prio.basse{color:var(--prio-basse)}.prio.basse i{background:var(--prio-basse)}
/* Bandeau urgence */
.bandeau-urgence{display:flex;align-items:center;gap:14px;background:var(--prio-bloque-fond);
border:1px solid color-mix(in srgb,var(--prio-bloque) 35%,transparent);border-left:4px solid var(--prio-bloque);
border-radius:var(--rayon);padding:12px 16px}
.bandeau-urgence b{color:var(--prio-bloque)}
.bandeau-urgence .depuis{color:var(--encre-2);font-size:12.5px}
/* Tables */
.table{width:100%;overflow-x:auto}
.table table{width:100%;font-size:13.5px}
.table th{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--encre-2);text-align:left;
padding:9px 12px;border-bottom:1px solid var(--bordure);background:var(--surface-2);white-space:nowrap}
.table td{padding:9px 12px;border-bottom:1px solid var(--bordure);vertical-align:middle}
.table tbody tr:hover{background:var(--primaire-doux)}
.table .ref{font-weight:700;color:var(--primaire)}
.table .sous{color:var(--encre-2);font-size:12px;display:block}
.ligne-bloque td:first-child{box-shadow:inset 3px 0 0 var(--prio-bloque)}
.avatars{display:flex}
.avatars .avatar{width:24px;height:24px;font-size:10px;border:2px solid var(--surface);margin-left:-6px}
.avatars .avatar:first-child{margin-left:0}
/* Filtres */
.filtres{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.select{display:inline-flex;align-items:center;gap:6px;border:1px solid var(--bordure-forte);
border-radius:8px;padding:7px 11px;font-size:13px;background:var(--surface);color:var(--encre-2)}
.select b{color:var(--encre);font-weight:600}
/* Graphiques */
.graf{display:flex;flex-direction:column;gap:10px}
.barres-h .ligne-b{display:grid;grid-template-columns:110px 1fr 40px;align-items:center;gap:10px;font-size:13px}
.barres-h .fond-b{background:var(--graf-grille);border-radius:4px;height:14px;overflow:hidden}
.barres-h .rempli{height:100%;border-radius:4px 0 0 4px}
.barres-h .valeur{text-align:right;font-weight:600}
.legende{display:flex;gap:14px;flex-wrap:wrap;font-size:12px;color:var(--encre-2)}
.legende span{display:inline-flex;align-items:center;gap:6px}
.legende i{width:9px;height:9px;border-radius:3px}
/* Connexion */
.conn{min-height:640px;display:flex;align-items:center;justify-content:center;background:
linear-gradient(160deg,var(--fond) 55%,color-mix(in srgb,var(--primaire) 7%,var(--fond)))}
.carte-conn{width:400px;max-width:94%;background:var(--surface);border:1px solid var(--bordure);
border-radius:14px;padding:32px;display:flex;flex-direction:column;gap:14px}
.carte-conn .logo{padding:0 0 4px;color:var(--encre)}
.carte-conn .logo small{color:var(--encre-2)}
.champ label{display:block;font-size:12.5px;font-weight:600;color:var(--encre-2);margin-bottom:5px}
.champ input{width:100%;padding:9px 12px;border:1px solid var(--bordure-forte);border-radius:8px;
font:inherit;background:var(--surface);color:var(--encre)}
.champ input::placeholder{color:var(--encre-3)}
.sep{display:flex;align-items:center;gap:10px;color:var(--encre-3);font-size:11.5px;
letter-spacing:.06em;text-transform:uppercase;margin-top:6px}
.sep::before,.sep::after{content:"";flex:1;height:1px;background:var(--bordure)}
.demo-liste{display:flex;flex-direction:column;gap:6px}
.demo-liste button{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--bordure);
border-radius:8px;background:var(--surface);text-align:left;transition:border-color var(--transition)}
.demo-liste button:hover{border-color:var(--primaire);background:var(--primaire-doux)}
.demo-liste .role{margin-left:auto;font-size:11.5px;color:var(--encre-2)}
/* Fiche : listes de définitions */
.infos{display:grid;grid-template-columns:130px 1fr;row-gap:9px;font-size:13.5px}
.infos dt{color:var(--encre-2)}
.infos dd{font-weight:500}
.infos a{color:var(--primaire);text-decoration:none;font-weight:600}
.chrono{list-style:none;display:flex;flex-direction:column;gap:0;font-size:13px}
.chrono li{position:relative;padding:0 0 16px 22px}
.chrono li::before{content:"";position:absolute;left:5px;top:5px;width:8px;height:8px;border-radius:50%;
background:var(--bordure-forte)}
.chrono li.fait::before{background:var(--primaire)}
.chrono li::after{content:"";position:absolute;left:8.5px;top:15px;bottom:-2px;width:1px;background:var(--bordure)}
.chrono li:last-child::after{display:none}
.chrono .quand{color:var(--encre-3);font-size:11.5px}
.check{display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid var(--bordure);font-size:13.5px}
.check:last-of-type{border-bottom:0}
.coche{width:19px;height:19px;border-radius:5px;border:1.5px solid var(--bordure-forte);flex:none;
display:flex;align-items:center;justify-content:center;color:#fff}
.coche.ok{background:var(--succes);border-color:var(--succes)}
.coche.na{background:var(--encre-3);border-color:var(--encre-3)}
.check .qui-fait{margin-left:auto;color:var(--encre-3);font-size:12px}
.total{display:flex;justify-content:space-between;font-weight:700;padding-top:10px;font-size:14px}
/* Bilan codé */
.bilan{display:grid;grid-template-columns:1fr 1fr;gap:10px 14px}
@media(max-width:900px){.bilan{grid-template-columns:1fr}}
.bilan .champ-b{font-size:12.5px}
.bilan .champ-b label{color:var(--encre-2);font-weight:600;display:block;margin-bottom:4px}
.bilan .champ-b label em{color:var(--danger);font-style:normal}
.faux-select{display:flex;justify-content:space-between;align-items:center;border:1px solid var(--bordure-forte);
border-radius:8px;padding:8px 11px;background:var(--surface);color:var(--encre)}
.faux-select.vide{color:var(--encre-3)}
/* Organes / documents */
.item-liste{display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid var(--bordure);font-size:13.5px}
.item-liste:last-child{border-bottom:0}
.item-liste .ico{width:34px;height:34px;border-radius:8px;background:var(--surface-2);display:flex;
align-items:center;justify-content:center;color:var(--encre-2);flex:none}
.item-liste .ico svg{width:16px;height:16px;stroke-width:1.75}
.item-liste .fin{margin-left:auto;display:flex;align-items:center;gap:10px;color:var(--encre-2);font-size:12.5px}
.qr{display:flex;gap:16px;align-items:center}
.qr svg{border:1px solid var(--bordure);border-radius:8px;background:#fff;padding:8px;flex:none}
/* Portail demandeur */
.portail{max-width:430px;margin:28px auto;display:flex;flex-direction:column;gap:14px}
.gros-toggle{display:flex;align-items:center;gap:12px;background:var(--prio-bloque-fond);
border:1px solid color-mix(in srgb,var(--prio-bloque) 30%,transparent);border-radius:10px;padding:12px 14px}
.gros-toggle b{color:var(--prio-bloque)}
.interrupteur{margin-left:auto;width:44px;height:24px;border-radius:999px;background:var(--bordure-forte);position:relative;flex:none}
.interrupteur::after{content:"";position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;background:#fff}
.zone-photo{border:1.5px dashed var(--bordure-forte);border-radius:10px;padding:18px;text-align:center;color:var(--encre-2);font-size:13px}
.suivi-simple{display:flex;align-items:center;gap:0;font-size:11.5px;color:var(--encre-3);margin-top:6px}
.suivi-simple .etape{display:flex;align-items:center;gap:6px}
.suivi-simple .etape.fait{color:var(--succes);font-weight:700}
.suivi-simple .lien-e{width:26px;height:1px;background:var(--bordure-forte);margin:0 6px}
/* Mobile */
.tels{display:flex;gap:24px;justify-content:center;flex-wrap:wrap}
.tel{width:300px;border:1px solid var(--bordure-forte);border-radius:28px;overflow:hidden;background:var(--fond);
box-shadow:var(--ombre-menu)}
.tel .encoche{height:26px;background:var(--nav-fond);display:flex;justify-content:center;align-items:center}
.tel .encoche i{width:70px;height:7px;border-radius:999px;background:rgb(255 255 255/.18)}
.tel .ecran-tel{padding:14px;display:flex;flex-direction:column;gap:10px;min-height:520px}
.tel h3{font-size:16px;font-weight:700;display:flex;align-items:center;gap:8px}
.tel h3 .sync{margin-left:auto;font-size:10.5px;font-weight:700;color:var(--st-termine);
background:var(--st-termine-fond);padding:2px 8px;border-radius:999px}
.tel h3 .sync.off{color:var(--st-attente);background:var(--st-attente-fond)}
.carte-ot-tel{background:var(--surface);border:1px solid var(--bordure);border-radius:12px;padding:12px}
.carte-ot-tel .type-t{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--encre-2);
font-weight:700;display:flex;justify-content:space-between;align-items:center}
.carte-ot-tel b{display:block;margin:3px 0 2px;font-size:14.5px}
.carte-ot-tel .ou{color:var(--encre-2);font-size:12.5px}
.btn-tel{display:flex;justify-content:center;align-items:center;gap:8px;padding:12px;border-radius:10px;
font-weight:700;font-size:14.5px;background:var(--primaire);color:#fff;width:100%}
.btn-tel.vert{background:var(--succes)}
.btn-tel.gris{background:var(--bordure-forte);color:var(--surface)}
.paire-boutons{display:flex;gap:8px}
.btn-choix{flex:1;text-align:center;padding:9px;border:1.5px solid var(--bordure-forte);border-radius:9px;font-weight:600;font-size:13px}
.btn-choix.actif{border-color:var(--primaire);background:var(--primaire-doux);color:var(--primaire)}
.viseur{flex:1;min-height:330px;border-radius:12px;background:
radial-gradient(circle at 50% 45%, #2c3a52 0%, #131c2c 70%);position:relative;display:flex;
align-items:center;justify-content:center}
.viseur .cadre-qr{width:170px;height:170px;border-radius:14px;border:2.5px solid var(--safran);
box-shadow:0 0 0 2000px rgb(9 14 22/.45)}
.viseur p{position:absolute;bottom:14px;color:#dfe7f2;font-size:12.5px}
/* Design system : nuanciers */
.nuancier{display:flex;gap:8px;flex-wrap:wrap}
.puce-c{width:84px;font-size:10.5px;color:var(--encre-2)}
.puce-c i{display:block;height:40px;border-radius:8px;border:1px solid var(--bordure);margin-bottom:4px}
</style>
<!-- ═══════════ BARRE DE REVUE ═══════════ -->
<header class="revue">
<b>SIOP V2 · Maquettes HD</b>
<nav class="onglets" role="tablist" aria-label="Écrans">
<button role="tab" aria-selected="true" data-cible="connexion">1 · Connexion</button>
<button role="tab" aria-selected="false" data-cible="dashboard">2 · Tableau de bord</button>
<button role="tab" aria-selected="false" data-cible="liste-ot">3 · Ordres de travail</button>
<button role="tab" aria-selected="false" data-cible="fiche-ot">4 · Fiche OT</button>
<button role="tab" aria-selected="false" data-cible="ascenseur">5 · Fiche ascenseur</button>
<button role="tab" aria-selected="false" data-cible="portail">6 · Portail demandeur</button>
<button role="tab" aria-selected="false" data-cible="mobile">7 · Mobile technicien</button>
</nav>
<div class="droite"><button class="theme" id="btn-theme">Thème : auto</button></div>
</header>
<!-- ═══════════ 1 · CONNEXION ═══════════ -->
<p class="note-ecran" data-note="connexion"><b>Intention :</b> première impression sobre ; le <b>mode démonstration</b> (demande DX du référent) permet d'entrer dans n'importe quel rôle en 1 clic — visible uniquement quand <code>DEMO_MODE=true</code>.</p>
<section class="ecran actif" id="connexion">
<div class="app pleine"><div class="conn">
<div class="carte-conn">
<div class="logo">
<span class="marque"><br></span>
<div><b>SIOP</b><small>Maintenance d'ascenseurs</small></div>
</div>
<div class="champ"><label for="m-email">Email</label><input id="m-email" placeholder="prenom.nom@spelev.ma"></div>
<div class="champ"><label for="m-mdp">Mot de passe</label><input id="m-mdp" type="password" placeholder="••••••••"></div>
<button class="btn prim" style="justify-content:center">Se connecter</button>
<div class="sep">Mode démonstration</div>
<div class="demo-liste">
<button><span class="avatar">SI</span> Salma Idrissi <span class="role">Dispatchrice</span></button>
<button><span class="avatar" style="background:var(--graf-3)">AB</span> Ahmed Benali <span class="role">Technicien</span></button>
<button><span class="avatar" style="background:var(--graf-4)">NB</span> Nadia Berrada <span class="role">Gestionnaire</span></button>
<button><span class="avatar" style="background:var(--st-annule)">KD</span> Karim Doukkali <span class="role">Demandeur</span></button>
<button><span class="avatar" style="background:var(--safran)">RB</span> R. Bennis <span class="role">Vue seule</span></button>
</div>
<div style="text-align:center"><span class="chip-demo">DEMO_MODE=true — absent en production</span></div>
</div>
</div></div>
</section>
<!-- ═══════════ 2 · TABLEAU DE BORD ═══════════ -->
<p class="note-ecran" data-note="dashboard" hidden><b>Intention :</b> Salma lit la situation en 10 s. L'urgence « personne bloquée » est <b>impossible à manquer</b> (bandeau + pastille animée — seule animation continue de l'app). KPI en chiffres tabulaires, graphiques aux couleurs validées (CVD).</p>
<section class="ecran" id="dashboard">
<div class="app">
<aside class="nav">
<div class="logo"><span class="marque"><br></span><div><b>SIOP</b></div></div>
<div class="groupe">Exploitation</div>
<a href="#" class="actif"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>Tableau de bord</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14.7 6.3a4.5 4.5 0 0 0-6 6L3 18v3h3l5.7-5.7a4.5 4.5 0 0 0 6-6l-2.6 2.6-2.4-2.4z"/></svg>Ordres de travail<span class="badge-nav">1</span></a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 10h8M8 14h5M21 12a9 9 0 1 1-4-7.5"/></svg>Demandes</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>Préventif</a>
<div class="groupe">Parc</div>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="6" y="2" width="12" height="20" rx="2"/><path d="M10 7l2-2 2 2M10 17l2 2 2-2"/></svg>Ascenseurs</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0z"/><circle cx="12" cy="10" r="3"/></svg>Sites</a>
<div class="groupe">Ressources</div>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 8l-9-5-9 5v8l9 5 9-5V8zM3.3 8.3L12 13l8.7-4.7M12 13v9"/></svg>Stock &amp; achats</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 21a7 7 0 1 1 6-11l6 6v3h-3l-1-1-2 1-1-2-2 1z" transform="rotate(180 12 12)"/><circle cx="9" cy="9" r="1.5"/></svg>Tiers</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg>Fichiers</a>
<div class="groupe">Pilotage</div>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 3v18h18"/><rect x="7" y="12" width="3" height="6"/><rect x="12" y="8" width="3" height="10"/><rect x="17" y="5" width="3" height="13"/></svg>Statistiques</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="8" r="3.5"/><path d="M2.5 20a6.5 6.5 0 0 1 13 0M17 8a3 3 0 1 1 0 .01M16.5 13.5a6 6 0 0 1 5 6"/></svg>Personnes</a>
</aside>
<div class="contenu">
<div class="topbar">
<div class="recherche"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>Rechercher un OT, un ascenseur, un site… ⌘K</div>
<span class="chip-urgence"><span class="point-pulse"></span>1 personne bloquée</span>
<div class="compte"><span class="chip-demo">DÉMO</span><span class="avatar">SI</span>
<div class="qui"><b>Salma Idrissi</b><br><small>Dispatchrice ▾</small></div></div>
</div>
<div class="page">
<div class="bandeau-urgence">
<span class="point-pulse"></span>
<div><b>Personne bloquée — Tour Atlas, ascenseur B2</b> (Casablanca)<br>
<span class="depuis">Signalé il y a 4 min par le gardien · aucune intervention en cours</span></div>
<div style="margin-left:auto"><button class="btn prim">Créer l'OT d'urgence</button></div>
</div>
<div class="kpis">
<div class="kpi"><div class="lib">OT ouverts</div><div class="val num">12</div><div class="delta neutre">dont 5 en cours</div></div>
<div class="kpi alarme"><div class="lib">En retard</div><div class="val num">3</div><div class="delta mauvais">+2 cette semaine</div></div>
<div class="kpi"><div class="lib">Préventif · juillet</div>
<div class="anneau">
<svg width="46" height="46" viewBox="0 0 46 46" role="img" aria-label="78 pour cent">
<circle cx="23" cy="23" r="19" fill="none" stroke="var(--graf-grille)" stroke-width="6"/>
<circle cx="23" cy="23" r="19" fill="none" stroke="var(--primaire)" stroke-width="6"
stroke-dasharray="93 120" stroke-linecap="round" transform="rotate(-90 23 23)"/>
</svg>
<div><div class="val num" style="font-size:22px">78 %</div><div class="delta bon">32 / 41 grilles</div></div>
</div></div>
<div class="kpi"><div class="lib">Demandes à traiter</div><div class="val num">4</div><div class="delta neutre">la + ancienne : 2 h</div></div>
<div class="kpi"><div class="lib">Coût · juillet</div><div class="val num">18 450 <span style="font-size:14px;color:var(--encre-2)">MAD</span></div><div class="delta bon">12 % vs juin</div></div>
</div>
<div class="grille-2">
<div class="carte graf">
<h2>OT par statut</h2>
<div class="barres-h">
<div class="ligne-b"><span>Ouvert</span><div class="fond-b"><div class="rempli" style="width:58%;background:var(--st-ouvert)"></div></div><span class="valeur num">7</span></div>
<div class="ligne-b"><span>En cours</span><div class="fond-b"><div class="rempli" style="width:42%;background:var(--st-encours)"></div></div><span class="valeur num">5</span></div>
<div class="ligne-b"><span>En attente</span><div class="fond-b"><div class="rempli" style="width:17%;background:var(--st-attente)"></div></div><span class="valeur num">2</span></div>
<div class="ligne-b"><span>Terminé (30 j)</span><div class="fond-b"><div class="rempli" style="width:100%;background:var(--st-termine)"></div></div><span class="valeur num">34</span></div>
</div>
</div>
<div class="carte graf">
<h2>Pannes par organe · 12 mois</h2>
<div class="barres-h">
<div class="ligne-b"><span>Portes</span><div class="fond-b"><div class="rempli" style="width:100%;background:var(--graf-1)"></div></div><span class="valeur num">34</span></div>
<div class="ligne-b"><span>Armoire cde</span><div class="fond-b"><div class="rempli" style="width:53%;background:var(--graf-1)"></div></div><span class="valeur num">18</span></div>
<div class="ligne-b"><span>Boutons</span><div class="fond-b"><div class="rempli" style="width:35%;background:var(--graf-1)"></div></div><span class="valeur num">12</span></div>
<div class="ligne-b"><span>Treuil</span><div class="fond-b"><div class="rempli" style="width:21%;background:var(--graf-1)"></div></div><span class="valeur num">7</span></div>
<div class="ligne-b"><span>Parachute</span><div class="fond-b"><div class="rempli" style="width:15%;background:var(--graf-1)"></div></div><span class="valeur num">5</span></div>
</div>
</div>
</div>
<div class="carte">
<h2>Interventions récentes</h2>
<div class="table"><table>
<thead><tr><th></th><th>Objet</th><th>Équipement</th><th>Priorité</th><th>Statut</th><th>Assignés</th></tr></thead>
<tbody>
<tr><td class="ref num">OT-2026-0341</td><td>Bruit anormal en gaine</td><td>Asc. A1<span class="sous">Résidence Al Manar</span></td><td><span class="prio haute"><i></i>Haute</span></td><td><span class="st encours">En cours</span></td><td><div class="avatars"><span class="avatar">AB</span></div></td></tr>
<tr><td class="ref num">OT-2026-0339</td><td>Grille du mois — juillet</td><td>Asc. C1<span class="sous">Anfa Place</span></td><td><span class="prio basse"><i></i>Basse</span></td><td><span class="st ouvert">Ouvert</span></td><td><div class="avatars"><span class="avatar" style="background:var(--graf-3)">FA</span></div></td></tr>
<tr><td class="ref num">OT-2026-0336</td><td>Remplacement contact porte</td><td>Asc. B2<span class="sous">Tour Atlas</span></td><td><span class="prio moyenne"><i></i>Moyenne</span></td><td><span class="st attente">En attente</span></td><td><div class="avatars"><span class="avatar">AB</span><span class="avatar" style="background:var(--graf-3)">FA</span></div></td></tr>
<tr><td class="ref num">OT-2026-0332</td><td>Réglage nivellement cabine</td><td>Asc. A1<span class="sous">Résidence Al Manar</span></td><td><span class="prio basse"><i></i>Basse</span></td><td><span class="st termine">Terminé</span></td><td><div class="avatars"><span class="avatar" style="background:var(--graf-3)">FA</span></div></td></tr>
</tbody>
</table></div>
</div>
</div>
</div>
</div>
</section>
<!-- ═══════════ 3 · LISTE OT ═══════════ -->
<p class="note-ecran" data-note="liste-ot" hidden><b>Intention :</b> table dense « bureau » (40 px/ligne), filtres en une rangée, l'OT « personne bloquée » porte une <b>strie rouge</b> + pastille animée. Références cliquables, sites en sous-texte.</p>
<section class="ecran" id="liste-ot">
<div class="app"><aside class="nav">
<div class="logo"><span class="marque"><br></span><div><b>SIOP</b></div></div>
<div class="groupe">Exploitation</div>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>Tableau de bord</a>
<a href="#" class="actif"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14.7 6.3a4.5 4.5 0 0 0-6 6L3 18v3h3l5.7-5.7a4.5 4.5 0 0 0 6-6l-2.6 2.6-2.4-2.4z"/></svg>Ordres de travail<span class="badge-nav">1</span></a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 10h8M8 14h5M21 12a9 9 0 1 1-4-7.5"/></svg>Demandes</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>Préventif</a>
<div class="groupe">Parc</div>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="6" y="2" width="12" height="20" rx="2"/><path d="M10 7l2-2 2 2M10 17l2 2 2-2"/></svg>Ascenseurs</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0z"/><circle cx="12" cy="10" r="3"/></svg>Sites</a>
</aside>
<div class="contenu">
<div class="topbar">
<div class="recherche"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>Rechercher…</div>
<span class="chip-urgence"><span class="point-pulse"></span>1 personne bloquée</span>
<div class="compte"><span class="chip-demo">DÉMO</span><span class="avatar">SI</span>
<div class="qui"><b>Salma Idrissi</b><br><small>Dispatchrice ▾</small></div></div>
</div>
<div class="page">
<div class="entete-page">
<h1>Ordres de travail</h1>
<div class="actions"><button class="btn prim"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></svg>Nouvel OT</button></div>
</div>
<div class="filtres">
<span class="select">Statut : <b>Actifs</b></span>
<span class="select">Type : <b>Tous</b></span>
<span class="select">Priorité : <b>Toutes</b></span>
<span class="select">Assigné : <b>Tous</b></span>
<span style="color:var(--encre-3);font-size:12.5px;margin-left:auto" class="num">14 résultats</span>
</div>
<div class="carte" style="padding:0"><div class="table"><table>
<thead><tr><th></th><th>Objet</th><th>Équipement · Site</th><th>Type</th><th>Priorité</th><th>Statut</th><th>Assignés</th><th>Échéance</th></tr></thead>
<tbody>
<tr class="ligne-bloque"><td class="ref num">OT-2026-0342</td><td><b>Personne bloquée en cabine</b></td><td>Asc. B2<span class="sous">Tour Atlas, Casablanca</span></td><td>Dépannage</td><td><span class="prio bloque"><i></i>Personne bloquée</span></td><td><span class="st ouvert">Ouvert</span></td><td></td><td><b>immédiat</b></td></tr>
<tr><td class="ref num">OT-2026-0341</td><td>Bruit anormal en gaine</td><td>Asc. A1<span class="sous">Résidence Al Manar, Mohammedia</span></td><td>Dépannage</td><td><span class="prio haute"><i></i>Haute</span></td><td><span class="st encours">En cours</span></td><td><div class="avatars"><span class="avatar">AB</span></div></td><td class="num">15 juil.</td></tr>
<tr><td class="ref num">OT-2026-0339</td><td>Grille du mois — juillet</td><td>Asc. C1<span class="sous">Anfa Place, Casablanca</span></td><td>Maintenance</td><td><span class="prio basse"><i></i>Basse</span></td><td><span class="st ouvert">Ouvert</span></td><td><div class="avatars"><span class="avatar" style="background:var(--graf-3)">FA</span></div></td><td class="num">31 juil.</td></tr>
<tr><td class="ref num">OT-2026-0338</td><td>Grille du mois — juillet</td><td>Asc. A1<span class="sous">Résidence Al Manar, Mohammedia</span></td><td>Maintenance</td><td><span class="prio basse"><i></i>Basse</span></td><td><span class="st encours">En cours</span></td><td><div class="avatars"><span class="avatar">AB</span></div></td><td class="num">31 juil.</td></tr>
<tr><td class="ref num">OT-2026-0336</td><td>Remplacement contact de porte</td><td>Asc. B2<span class="sous">Tour Atlas, Casablanca</span></td><td>Dépannage</td><td><span class="prio moyenne"><i></i>Moyenne</span></td><td><span class="st attente">En attente</span></td><td><div class="avatars"><span class="avatar">AB</span><span class="avatar" style="background:var(--graf-3)">FA</span></div></td><td class="num">18 juil.</td></tr>
<tr><td class="ref num">OT-2026-0335</td><td>Ampoule cabine grillée</td><td>Asc. D3<span class="sous">Clinique Andalous, Mohammedia</span></td><td>Travaux</td><td><span class="prio basse"><i></i>Basse</span></td><td><span class="st ouvert">Ouvert</span></td><td></td><td class="num">22 juil.</td></tr>
<tr><td class="ref num">OT-2026-0332</td><td>Réglage nivellement cabine</td><td>Asc. A1<span class="sous">Résidence Al Manar, Mohammedia</span></td><td>Dépannage</td><td><span class="prio basse"><i></i>Basse</span></td><td><span class="st termine">Terminé</span></td><td><div class="avatars"><span class="avatar" style="background:var(--graf-3)">FA</span></div></td><td class="num">12 juil.</td></tr>
</tbody>
</table></div></div>
</div>
</div>
</div>
</section>
<!-- ═══════════ 4 · FICHE OT ═══════════ -->
<p class="note-ecran" data-note="fiche-ot" hidden><b>Intention :</b> tout le carnet SPELEV sur un écran — activité chronologique, <b>bilan codé 6 champs</b> (requis marqués, clôture bloquée sinon), coûts pièces + main-d'œuvre, assignation en 1 clic.</p>
<section class="ecran" id="fiche-ot">
<div class="app"><aside class="nav">
<div class="logo"><span class="marque"><br></span><div><b>SIOP</b></div></div>
<div class="groupe">Exploitation</div>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>Tableau de bord</a>
<a href="#" class="actif"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14.7 6.3a4.5 4.5 0 0 0-6 6L3 18v3h3l5.7-5.7a4.5 4.5 0 0 0 6-6l-2.6 2.6-2.4-2.4z"/></svg>Ordres de travail</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M8 10h8M8 14h5M21 12a9 9 0 1 1-4-7.5"/></svg>Demandes</a>
</aside>
<div class="contenu">
<div class="topbar">
<div class="recherche"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>Rechercher…</div>
<div class="compte"><span class="chip-demo">DÉMO</span><span class="avatar">SI</span>
<div class="qui"><b>Salma Idrissi</b><br><small>Dispatchrice ▾</small></div></div>
</div>
<div class="page">
<div class="filajout"><a href="#">Ordres de travail</a> / OT-2026-0341</div>
<div class="entete-page">
<h1>Bruit anormal en gaine</h1>
<span class="st encours">En cours</span>
<div class="actions">
<button class="btn ghost">En attente</button>
<button class="btn danger-l">Annuler</button>
<button class="btn succes">Clôturer l'intervention</button>
</div>
</div>
<div class="grille-2">
<div class="carte">
<h2>Intervention</h2>
<dl class="infos">
<dt></dt><dd class="num">OT-2026-0341 · Dépannage</dd>
<dt>Équipement</dt><dd><a href="#">Ascenseur A1 — Otis Gen2</a></dd>
<dt>Site</dt><dd>Résidence Al Manar, Mohammedia</dd>
<dt>Priorité</dt><dd><span class="prio haute"><i></i>Haute</span></dd>
<dt>Échéance</dt><dd class="num">15 juillet 2026</dd>
<dt>Assignés</dt><dd style="display:flex;align-items:center;gap:8px">
<span class="avatars"><span class="avatar">AB</span></span> Ahmed Benali
<button class="btn ghost" style="padding:3px 8px;font-size:12px">+ Assigner</button></dd>
<dt>Demande liée</dt><dd><a href="#" class="num">DEM-2026-0107</a> — Karim Doukkali (gardien)</dd>
</dl>
</div>
<div class="carte">
<h2>Activité</h2>
<ul class="chrono">
<li class="fait"><b>Ahmed Benali</b> a démarré l'intervention<br><span class="quand">aujourd'hui · 09 h 41</span></li>
<li class="fait">Commentaire d'Ahmed : « Frottement guide côté gaine, contrôle des coulisseaux en cours. »<br><span class="quand">aujourd'hui · 10 h 05</span></li>
<li class="fait"><b>Salma Idrissi</b> a assigné Ahmed Benali<br><span class="quand">hier · 16 h 22</span></li>
<li>OT créé depuis la demande <span class="num">DEM-2026-0107</span><br><span class="quand">hier · 16 h 20</span></li>
</ul>
</div>
</div>
<div class="grille-2">
<div class="carte">
<h2>Bilan d'intervention <span style="color:var(--encre-3);text-transform:none;letter-spacing:0">— requis pour clôturer</span></h2>
<div class="bilan">
<div class="champ-b"><label>État des portes <em>*</em></label><div class="faux-select">Fonctionnement normal <span></span></div></div>
<div class="champ-b"><label>Position cabine</label><div class="faux-select">Entre deux niveaux <span></span></div></div>
<div class="champ-b"><label>Anomalie constatée</label><div class="faux-select">Frottement mécanique <span></span></div></div>
<div class="champ-b"><label>Cause extérieure</label><div class="faux-select vide">Sélectionner… <span></span></div></div>
<div class="champ-b"><label>Action réalisée <em>*</em></label><div class="faux-select">Réglage <span></span></div></div>
<div class="champ-b"><label>Élément concerné <em>*</em></label><div class="faux-select vide">Sélectionner… <span></span></div></div>
</div>
<p style="margin-top:12px;font-size:12.5px;color:var(--alerte)">⚠ « Élément concerné » manquant — la clôture restera bloquée.</p>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="carte">
<h2>Pièces &amp; main-d'œuvre</h2>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 8l-9-5-9 5v8l9 5 9-5V8z"/></svg></span>Coulisseau de guide × 2<span class="fin num">240 MAD</span></div>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 8l-9-5-9 5v8l9 5 9-5V8z"/></svg></span>Graisse guide (cartouche)<span class="fin num">85 MAD</span></div>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 3"/></svg></span>Ahmed Benali · 1 h 30 × 120 MAD/h<span class="fin num">180 MAD</span></div>
<div class="total"><span>Coût total</span><span class="num">505 MAD</span></div>
</div>
<div class="carte">
<h2>Documents</h2>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>photo-guide-frottement.jpg<span class="fin">2,1 Mo · <a href="#" style="color:var(--primaire)"></a></span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ═══════════ 5 · FICHE ASCENSEUR ═══════════ -->
<p class="note-ecran" data-note="ascenseur" hidden><b>Intention :</b> l'identité de l'appareil (l'« ID Appareil » du carnet), ses <b>organes</b>, son historique, ses documents et son <b>étiquette QR</b> imprimable — tout au même endroit.</p>
<section class="ecran" id="ascenseur">
<div class="app"><aside class="nav">
<div class="logo"><span class="marque"><br></span><div><b>SIOP</b></div></div>
<div class="groupe">Parc</div>
<a href="#" class="actif"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="6" y="2" width="12" height="20" rx="2"/><path d="M10 7l2-2 2 2M10 17l2 2 2-2"/></svg>Ascenseurs</a>
<a href="#"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M20 10c0 6-8 12-8 12S4 16 4 10a8 8 0 1 1 16 0z"/><circle cx="12" cy="10" r="3"/></svg>Sites</a>
</aside>
<div class="contenu">
<div class="topbar">
<div class="recherche"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="m21 21-4-4"/></svg>Rechercher…</div>
<div class="compte"><span class="chip-demo">DÉMO</span><span class="avatar">SI</span>
<div class="qui"><b>Salma Idrissi</b><br><small>Dispatchrice ▾</small></div></div>
</div>
<div class="page">
<div class="filajout"><a href="#">Ascenseurs</a> / Ascenseur A1</div>
<div class="entete-page">
<h1>Ascenseur A1 — Otis Gen2 Premier</h1>
<span class="st termine">En service</span>
<div class="actions">
<button class="btn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="8" height="8" rx="1"/><rect x="13" y="3" width="8" height="8" rx="1"/><rect x="3" y="13" width="8" height="8" rx="1"/><path d="M13 13h3v3h-3zM19 13h2v2h-2zM13 19h2v2h-2zM18 18h3v3h-3z"/></svg>Étiquette QR</button>
<button class="btn prim"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></svg>Nouvel OT</button>
</div>
</div>
<div class="grille-3">
<div class="carte" style="grid-column:span 2">
<h2>Identité</h2>
<dl class="infos">
<dt>N° de série</dt><dd class="num">OT-2020-4521</dd>
<dt>Marque · modèle</dt><dd>Otis — Gen2 Premier</dd>
<dt>Mise en service</dt><dd class="num">15 mars 2020</dd>
<dt>Site</dt><dd><a href="#">Résidence Al Manar</a> — Bd Hassan II, Mohammedia</dd>
<dt>Contrat préventif</dt><dd><span class="st termine">Actif</span> · premier contrôle effectué</dd>
<dt>Charge · niveaux</dt><dd class="num">630 kg · 8 niveaux</dd>
</dl>
</div>
<div class="carte">
<h2>Étiquette cabine</h2>
<div class="qr">
<svg width="86" height="86" viewBox="0 0 86 86" role="img" aria-label="QR code de l'ascenseur A1">
<rect width="86" height="86" fill="#fff"/>
<g fill="#1b2534">
<rect x="8" y="8" width="20" height="20"/><rect x="58" y="8" width="20" height="20"/><rect x="8" y="58" width="20" height="20"/>
<rect x="12" y="12" width="12" height="12" fill="#fff"/><rect x="62" y="12" width="12" height="12" fill="#fff"/><rect x="12" y="62" width="12" height="12" fill="#fff"/>
<rect x="15" y="15" width="6" height="6"/><rect x="65" y="15" width="6" height="6"/><rect x="15" y="65" width="6" height="6"/>
<rect x="34" y="10" width="4" height="4"/><rect x="42" y="10" width="8" height="4"/><rect x="34" y="18" width="4" height="8"/><rect x="46" y="18" width="4" height="4"/>
<rect x="10" y="34" width="4" height="4"/><rect x="18" y="34" width="8" height="4"/><rect x="30" y="34" width="4" height="8"/><rect x="42" y="34" width="4" height="4"/>
<rect x="50" y="34" width="4" height="8"/><rect x="58" y="38" width="8" height="4"/><rect x="70" y="34" width="4" height="4"/>
<rect x="34" y="46" width="8" height="4"/><rect x="46" y="46" width="4" height="4"/><rect x="58" y="50" width="4" height="4"/><rect x="66" y="46" width="8" height="4"/>
<rect x="34" y="58" width="4" height="8"/><rect x="42" y="62" width="8" height="4"/><rect x="58" y="58" width="8" height="8"/><rect x="70" y="70" width="6" height="6"/>
<rect x="42" y="70" width="4" height="6"/><rect x="50" y="70" width="4" height="4"/>
</g>
</svg>
<div style="font-size:12.5px;color:var(--encre-2)">Collée en cabine.<br>Scan gardien → signalement prérempli.<br>Scan technicien → cette fiche.</div>
</div>
</div>
</div>
<div class="grille-2">
<div class="carte">
<h2>Organes</h2>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="4" y="3" width="16" height="18" rx="1"/><path d="M12 3v18"/></svg></span>Portes cabine — Fermator 40/10<span class="fin"><span class="st attente">Surveillance</span></span></div>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/><path d="M12 4v2M12 18v2M4 12h2M18 12h2"/></svg></span>Treuil — Gen2 gearless<span class="fin"><span class="st termine">Bon état</span></span></div>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 3v12M6 9l6 6 6-6"/><path d="M5 21h14"/></svg></span>Parachute — essai juillet ✓<span class="fin"><span class="st termine">Conforme</span></span></div>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="5" y="4" width="14" height="16" rx="1"/><path d="M9 8h6M9 12h6M9 16h3"/></svg></span>Armoire de commande — MCS 220<span class="fin"><span class="st termine">Bon état</span></span></div>
</div>
<div style="display:flex;flex-direction:column;gap:16px">
<div class="carte">
<h2>Historique récent</h2>
<ul class="chrono">
<li class="fait"><span class="num">OT-2026-0341</span> · Bruit en gaine — <span class="st encours">En cours</span><br><span class="quand">ouvert hier</span></li>
<li class="fait"><span class="num">OT-2026-0338</span> · Grille du mois juillet — <span class="st encours">En cours</span><br><span class="quand">généré le 1ᵉʳ juillet</span></li>
<li class="fait"><span class="num">OT-2026-0332</span> · Nivellement cabine — <span class="st termine">Terminé</span><br><span class="quand">12 juillet · bilan : Réglage / Guides</span></li>
</ul>
</div>
<div class="carte">
<h2>Documents</h2>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>Notice Otis Gen2 Premier.pdf<span class="fin">1,2 Mo · <a href="#" style="color:var(--primaire)"></a></span></div>
<div class="item-liste"><span class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></span>Certificat parachute 2026.pdf<span class="fin">340 Ko · <a href="#" style="color:var(--primaire)"></a></span></div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ═══════════ 6 · PORTAIL DEMANDEUR ═══════════ -->
<p class="note-ecran" data-note="portail" hidden><b>Intention :</b> Karim (gardien, aucune formation) arrive ici en scannant le QR : équipement <b>prérempli</b>, un seul choix critique (« personne bloquée ? »), suivi sans jargon (Reçu → En cours → Résolu).</p>
<section class="ecran" id="portail">
<div class="app pleine"><div style="background:var(--fond);min-height:640px">
<div class="portail">
<div class="logo" style="color:var(--encre);padding:0">
<span class="marque"><br></span><div><b style="font-size:19px">SIOP</b><small style="color:var(--encre-2)">Signalement — Résidence Al Manar</small></div>
</div>
<div class="carte" style="display:flex;flex-direction:column;gap:12px">
<div style="display:flex;align-items:center;gap:10px;background:var(--primaire-doux);border-radius:8px;padding:10px 12px;font-size:13px">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--primaire)" stroke-width="1.75"><rect x="6" y="2" width="12" height="20" rx="2"/><path d="M10 7l2-2 2 2M10 17l2 2 2-2"/></svg>
<b>Ascenseur A1 — hall principal</b> <span style="color:var(--encre-2)">(détecté par le QR)</span>
</div>
<div class="gros-toggle"><b>Une personne est-elle bloquée dans la cabine ?</b><span class="interrupteur" role="switch" aria-checked="false" aria-label="Personne bloquée"></span></div>
<div class="champ"><label for="m-desc">Décrivez le problème</label>
<input id="m-desc" placeholder="Ex. : la porte ne se ferme plus au 3ᵉ étage"></div>
<div class="zone-photo">📷 Ajouter une photo (facultatif)</div>
<button class="btn prim" style="justify-content:center;padding:11px">Envoyer le signalement</button>
</div>
<div class="carte">
<h2>Mes signalements</h2>
<div class="item-liste" style="align-items:flex-start"><div>
<b>Porte bloquée au 3ᵉ étage</b><span class="sous num">DEM-2026-0107 · hier</span>
<div class="suivi-simple">
<span class="etape fait">✓ Reçu</span><span class="lien-e"></span>
<span class="etape fait">Intervention en cours</span><span class="lien-e"></span>
<span class="etape">Résolu</span>
</div></div>
</div>
<div class="item-liste" style="align-items:flex-start"><div>
<b>Voyant cabine éteint</b><span class="sous num">DEM-2026-0093 · 2 juil.</span>
<div class="suivi-simple">
<span class="etape fait">✓ Reçu</span><span class="lien-e"></span>
<span class="etape fait">✓ Intervention</span><span class="lien-e"></span>
<span class="etape fait">✓ Résolu</span>
</div></div>
</div>
</div>
</div>
</div></div>
</section>
<!-- ═══════════ 7 · MOBILE ═══════════ -->
<p class="note-ecran" data-note="mobile" hidden><b>Intention :</b> Ahmed au sous-sol, gants, sans réseau — gros boutons (≥ 44 px), état de synchro toujours visible, grille du mois en 2 taps, clôture bloquée tant que la grille n'est pas complète.</p>
<section class="ecran" id="mobile">
<div class="app pleine" style="border:0;background:transparent"><div class="tels">
<div class="tel"><div class="encoche"><i></i></div>
<div class="ecran-tel">
<h3>Mes interventions <span class="sync">⟳ à jour</span></h3>
<div class="carte-ot-tel" style="border-left:3px solid var(--prio-haute)">
<div class="type-t">Dépannage <span class="prio haute"><i></i>Haute</span></div>
<b>Bruit anormal en gaine</b>
<div class="ou">Asc. A1 · Résidence Al Manar</div>
</div>
<div class="carte-ot-tel">
<div class="type-t">Maintenance <span class="prio basse"><i></i>Basse</span></div>
<b>Grille du mois — juillet</b>
<div class="ou">Asc. A1 · Résidence Al Manar</div>
</div>
<div class="carte-ot-tel">
<div class="type-t">Dépannage <span class="prio moyenne"><i></i>Moyenne</span></div>
<b>Contact de porte</b>
<div class="ou">Asc. B2 · Tour Atlas</div>
</div>
<div style="margin-top:auto"><button class="btn-tel">▣ Scanner un ascenseur</button></div>
</div>
</div>
<div class="tel"><div class="encoche"><i></i></div>
<div class="ecran-tel">
<h3>Grille du mois <span class="sync off">⟳ hors-ligne · 2 en attente</span></h3>
<div style="font-size:12.5px;color:var(--encre-2)">OT-2026-0338 · Asc. A1 · <b class="num" style="color:var(--encre)">2/4 traitées</b></div>
<div class="carte" style="padding:4px 14px">
<div class="check"><span class="coche ok"></span>Jeu et fixation des portes</div>
<div class="check"><span class="coche na"></span>Éclairage de secours <span class="qui-fait">N-A</span></div>
<div class="check"><span class="coche"></span>Niveau d'huile du treuil</div>
<div class="check"><span class="coche"></span>Essai parachute (juillet)</div>
</div>
<div class="paire-boutons"><span class="btn-choix actif">Fait</span><span class="btn-choix">Non applicable</span></div>
<div style="margin-top:auto;display:flex;flex-direction:column;gap:8px">
<button class="btn-tel gris" disabled>Clôturer (grille incomplète)</button>
<div style="text-align:center;font-size:11.5px;color:var(--encre-3)">Vos coches partiront à la prochaine synchro</div>
</div>
</div>
</div>
<div class="tel"><div class="encoche"><i></i></div>
<div class="ecran-tel" style="padding:10px">
<h3 style="padding:4px 4px 0">Scanner</h3>
<div class="viseur"><div class="cadre-qr"></div><p>Visez le QR collé en cabine</p></div>
<button class="btn-tel vert">Ascenseur A1 reconnu → ouvrir la fiche</button>
</div>
</div>
</div></div>
</section>
<script>
(function(){
var onglets=document.querySelectorAll('.revue .onglets button');
var ecrans=document.querySelectorAll('.ecran');
var notes=document.querySelectorAll('.note-ecran');
onglets.forEach(function(b){b.addEventListener('click',function(){
onglets.forEach(function(x){x.setAttribute('aria-selected','false')});
b.setAttribute('aria-selected','true');
var cible=b.dataset.cible;
ecrans.forEach(function(e){e.classList.toggle('actif',e.id===cible)});
notes.forEach(function(n){n.hidden=(n.dataset.note!==cible)});
window.scrollTo({top:0});
})});
var ordre=['auto','light','dark'];var i=0;
var bt=document.getElementById('btn-theme');
bt.addEventListener('click',function(){
i=(i+1)%3;var m=ordre[i];
if(m==='auto'){document.documentElement.removeAttribute('data-theme')}
else{document.documentElement.setAttribute('data-theme',m)}
bt.textContent='Thème : '+(m==='auto'?'auto':(m==='light'?'clair':'sombre'));
});
})();
</script>
</body>
</html>

156
docs/02-design/tokens.css Normal file
View File

@@ -0,0 +1,156 @@
/*
* SIOP V2 — Design tokens (source de vérité visuelle)
* Importé tel quel par apps/web ; les maquettes HD l'utilisent aussi.
* Règle : AUCUNE valeur visuelle codée en dur hors de ce fichier.
* Thèmes : clair par défaut, sombre via @media + surcharge [data-theme].
*/
:root {
/* ——— Marque ——— */
--primaire: #1f4fb8; /* bleu-treuil : actions, liens, focus */
--primaire-actif: #1a439c;
--primaire-doux: #eaf0fb; /* fonds de sélection/hover primaires */
--safran: #dd8a0b; /* accent de marque — parcimonieux */
--safran-doux: #fdf3e3;
/* ——— Neutres (acier, biais froid) ——— */
--fond: #f5f7fa;
--surface: #ffffff;
--surface-2: #eef2f7; /* en-têtes de table, zones repliées */
--bordure: #dce3ec;
--bordure-forte: #b9c4d4;
--encre: #1b2534; /* texte principal */
--encre-2: #55647a; /* texte secondaire */
--encre-3: #8494ab; /* placeholder, désactivé */
/* ——— Statuts OT (réservés) ——— */
--st-ouvert: #3d6fe0; --st-ouvert-fond: #e9effc;
--st-encours: #6d5bd8; --st-encours-fond: #efecfa;
--st-attente: #b96f07; --st-attente-fond: #fbf1df;
--st-termine: #178a50; --st-termine-fond: #e6f5ec;
--st-annule: #68788f; --st-annule-fond: #edf0f4;
/* ——— Priorités (réservées) ——— */
--prio-bloque: #d92626; --prio-bloque-fond: #fdeaea; /* personne bloquée */
--prio-haute: #d92626;
--prio-moyenne: #b96f07;
--prio-basse: #178a50;
--prio-aucune: #8494ab;
/* ——— Sémantique générale ——— */
--succes: #178a50;
--alerte: #b96f07;
--danger: #d92626;
--info: #3d6fe0;
/* ——— Palette graphique (analytics, ordre FIXE, validée CVD) ——— */
--graf-1: #3b69d6;
--graf-2: #dd8a0b;
--graf-3: #1f9e8e;
--graf-4: #8a63d2;
--graf-grille: #e6ebf2;
/* ——— Typographie ——— */
--police-ui: "Manrope", "Avenir Next", "Segoe UI Variable", system-ui, sans-serif;
--police-mono: "SF Mono", ui-monospace, "Cascadia Mono", monospace;
--t-11: 11px; --t-12: 12px; --t-13: 13px; --t-14: 14px;
--t-16: 16px; --t-20: 20px; --t-24: 24px; --t-32: 32px;
/* ——— Forme & espace (grille 4 px) ——— */
--e-1: 4px; --e-2: 8px; --e-3: 12px; --e-4: 16px; --e-5: 20px; --e-6: 24px; --e-8: 32px;
--rayon: 10px; /* cartes */
--rayon-controle: 8px; /* inputs, boutons */
--rayon-pastille: 999px;
--ombre-menu: 0 8px 24px rgb(16 30 54 / 0.14);
--rail: 3px; /* motif signature : rail actif safran */
/* ——— Mouvement ——— */
--transition: 140ms ease-out;
}
/* ——— Thème sombre (« machinerie de nuit », pas une inversion) ——— */
@media (prefers-color-scheme: dark) {
:root {
--primaire: #6e92e8;
--primaire-actif: #8facf0;
--primaire-doux: #1d2c4c;
--safran: #e89a1f;
--safran-doux: #33270f;
--fond: #0f1622;
--surface: #182234;
--surface-2: #1e2a40;
--bordure: #2b3850;
--bordure-forte: #3d4d6b;
--encre: #e8edf5;
--encre-2: #a7b4c8;
--encre-3: #6d7d96;
--st-ouvert: #7da2ee; --st-ouvert-fond: #1c2a47;
--st-encours: #a394ec; --st-encours-fond: #262040;
--st-attente: #e0a33c; --st-attente-fond: #322510;
--st-termine: #4bc084; --st-termine-fond: #12301f;
--st-annule: #93a3ba; --st-annule-fond: #222d3f;
--prio-bloque: #f26d6d; --prio-bloque-fond: #3a1414;
--prio-haute: #f26d6d;
--prio-moyenne: #e0a33c;
--prio-basse: #4bc084;
--prio-aucune: #6d7d96;
--succes: #4bc084;
--alerte: #e0a33c;
--danger: #f26d6d;
--info: #7da2ee;
--graf-1: #5580e4;
--graf-2: #c7820a;
--graf-3: #26a896;
--graf-4: #9673de;
--graf-grille: #24304a;
--ombre-menu: 0 8px 24px rgb(0 0 0 / 0.45);
}
}
/* Le sélecteur de thème de l'app doit GAGNER dans les deux sens. */
:root[data-theme="light"] {
--primaire: #1f4fb8; --primaire-actif: #1a439c; --primaire-doux: #eaf0fb;
--safran: #dd8a0b; --safran-doux: #fdf3e3;
--fond: #f5f7fa; --surface: #ffffff; --surface-2: #eef2f7;
--bordure: #dce3ec; --bordure-forte: #b9c4d4;
--encre: #1b2534; --encre-2: #55647a; --encre-3: #8494ab;
--st-ouvert: #3d6fe0; --st-ouvert-fond: #e9effc;
--st-encours: #6d5bd8; --st-encours-fond: #efecfa;
--st-attente: #b96f07; --st-attente-fond: #fbf1df;
--st-termine: #178a50; --st-termine-fond: #e6f5ec;
--st-annule: #68788f; --st-annule-fond: #edf0f4;
--prio-bloque: #d92626; --prio-bloque-fond: #fdeaea;
--prio-haute: #d92626; --prio-moyenne: #b96f07; --prio-basse: #178a50; --prio-aucune: #8494ab;
--succes: #178a50; --alerte: #b96f07; --danger: #d92626; --info: #3d6fe0;
--graf-1: #3b69d6; --graf-2: #dd8a0b; --graf-3: #1f9e8e; --graf-4: #8a63d2;
--graf-grille: #e6ebf2;
--ombre-menu: 0 8px 24px rgb(16 30 54 / 0.14);
}
:root[data-theme="dark"] {
--primaire: #6e92e8; --primaire-actif: #8facf0; --primaire-doux: #1d2c4c;
--safran: #e89a1f; --safran-doux: #33270f;
--fond: #0f1622; --surface: #182234; --surface-2: #1e2a40;
--bordure: #2b3850; --bordure-forte: #3d4d6b;
--encre: #e8edf5; --encre-2: #a7b4c8; --encre-3: #6d7d96;
--st-ouvert: #7da2ee; --st-ouvert-fond: #1c2a47;
--st-encours: #a394ec; --st-encours-fond: #262040;
--st-attente: #e0a33c; --st-attente-fond: #322510;
--st-termine: #4bc084; --st-termine-fond: #12301f;
--st-annule: #93a3ba; --st-annule-fond: #222d3f;
--prio-bloque: #f26d6d; --prio-bloque-fond: #3a1414;
--prio-haute: #f26d6d; --prio-moyenne: #e0a33c; --prio-basse: #4bc084; --prio-aucune: #6d7d96;
--succes: #4bc084; --alerte: #e0a33c; --danger: #f26d6d; --info: #7da2ee;
--graf-1: #5580e4; --graf-2: #c7820a; --graf-3: #26a896; --graf-4: #9673de;
--graf-grille: #24304a;
--ombre-menu: 0 8px 24px rgb(0 0 0 / 0.45);
}
@media (prefers-reduced-motion: reduce) {
:root { --transition: 0ms; }
}

22
docs/journal/journal.md Normal file
View File

@@ -0,0 +1,22 @@
# Journal de bord — SIOP V2
Trace chronologique des sessions (la plus récente en premier). Le **playbook** (`docs/0X-*`) est le livre ; ici, c'est le quotidien.
---
## 2026-07-15 — Pr. Daaif (+ Claude) — R0 : kickoff, vision, cadrage, design
**Actions**
- **Refondation décidée** : nouveau dépôt `siop-spelev/siop2`, 100 % nouveau code ; la v1 (`siop`) est gelée comme référence. Jalons R0→R5 créés.
- Playbook initialisé : **00-vision** (charte projet, personas, benchmark Atlas), **01-cadrage** (plan de releases avec DoD, user-story map, exigences non fonctionnelles mesurables, registre des risques).
- **02-design** : charte graphique (bleu-treuil / safran / acier, Manrope, motif « rail »), `tokens.css` (source de vérité, bi-thème), palettes analytics **validées par script** (CVD + contraste, modes clair et sombre), **maquettes HD** navigables (7 écrans : connexion + démo-login, tableau de bord, liste OT, fiche OT avec bilan codé, fiche ascenseur + QR, portail demandeur, mobile technicien) — publiées pour revue.
**Décisions (référent, via questions)**
- Diagnostic v1 : esthétique absente, docs éparpillées, périmètre dérivant, bascule de comptes pénible → V2 **design-first**, playbook en livre, releases fermées, **sélecteur de compte démo** (`DEMO_MODE`) dès R0.
- 100 % nouveau code ; périmètre v1 = web + mobile + IA par paliers ; déploiement Dokploy ; nom conservé : **SIOP**.
**Prochaine étape** : validation de la charte + maquettes par le référent (**bloquant** — aucun code applicatif avant), puis 03-architecture + squelette monorepo.
---