Files
ASPIC/ecran_fond.html
T

906 lines
50 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASPIC — Écran de fond (essai)</title>
<!-- Icône d'onglet (3 septembre 2026, voir journal) : logo Mévélia Solutions,
fourni par Jean-Marc, reprise à l'identique sur tous les écrans. -->
<link rel="icon" href="mevelia-icone.png">
<style>
/* ecran_fond.html — 2 septembre 2026
Premier essai de l'écran de fond "bureau à l'ancienne" (voir journal de
développement, section 28, et réflexions stratégiques, section 11).
Volontairement minimal pour ce test : affichage plein écran de l'image
de référence, sans aucun objet cliquable/fonctionnel — juste de quoi
voir le rendu réel après identification, avant d'aller plus loin.
Image mise à jour le 2 septembre 2026 (soir) — nouvelle version sans
tableau en liège, recadrée par Jean-Marc lui-même après un premier essai
où le "cover" rognait le bas de l'image sur son écran (proportions
d'écran différentes du 16:9 de l'image). Passage de background-size
"cover" à "contain" à cette occasion : l'image entière reste désormais
toujours visible, quelle que soit la proportion de l'écran, quitte à
laisser une bande de la couleur du mur de part et d'autre plutôt que de
rogner — plus robuste qu'un recadrage qui ne marche que pour une
proportion d'écran précise.
Image recadrée une 2ᵉ fois (toujours le 2 septembre 2026) : Jean-Marc a
utilisé une lecture de fenêtre temporaire (affichée le temps du recadrage
puis retirée) pour recadrer directement à ses proportions d'écran
réelles — les bandes "contain" restent le filet de sécurité pour tout
autre écran, mais ne devraient plus apparaître chez lui. Nouvelle image
1024×457 : toutes les coordonnées ci-dessous ont été remesurées sur
cette version (celles mesurées sur la précédente, 1280×650, ne
s'appliquent plus). */
@font-face{
font-family:"Metro DF";
src:url('MetroDF.woff2') format('woff2'),url('MetroDF.ttf') format('truetype');
font-weight:normal;font-style:normal;font-display:swap;
}
*{box-sizing:border-box;}
html,body{
margin:0;padding:0;border:0;width:100%;height:100%;overflow:hidden;
font-family:"Segoe UI","Arial",sans-serif;
}
body{
/* Couleur échantillonnée sur le mur de la photo, pour que les bandes
laissées par "contain" (si la proportion de l'écran diffère de celle
de l'image) se fondent avec la scène plutôt que de créer un bord net. */
background:#ad580d url('ecran_fond.jpg') center center / contain no-repeat;
position:relative;
}
/* Étiquettes des 9 classeurs (7 septembre 2026 — évolution de l'essai à
un seul classeur "Contacts" du 2 septembre). Nouvelle photo fournie par
Jean-Marc, mêmes dimensions de référence (1024×457) et même bureau/mur/
micro (vérifié par comparaison pixel à pixel avec l'ancienne image :
seule la zone des classeurs, x≈[40,400], diffère réellement — le reste
n'est que du bruit de compression) — les constantes MICRO_ZONE, ICONE et
ZONE_POSTITS restent donc inchangées plus bas, seules les constantes de
classeurs et la pastille "Contacts" ont été remesurées sur la nouvelle
photo.
Toujours positionnées en JS (voir creerClasseurs()/positionner()) pour
le même motif que le 2 septembre : l'image étant en "contain", sa
taille/position réelles à l'écran changent avec la proportion de la
fenêtre. rotate(-90deg) : lecture bas → haut (on tourne la tête pour
lire), inchangé. Règles génériques (classe, plus un seul id) : il y a
désormais 9 étiquettes/classeurs au lieu d'un seul — voir le tableau
CLASSEURS dans le script. */
.etiquette-classeur{
position:fixed;
transform:translate(-50%,-50%) rotate(-90deg);
transform-origin:center;
white-space:nowrap;
color:#2a2a2a;
font-family:"Segoe UI","Arial",sans-serif;
font-weight:600;
letter-spacing:0.03em;
pointer-events:none;
user-select:none;
}
/* Zone calée sur la tranche visible (face plate, sans le dessus incliné)
de chaque classeur — même principe de positionnement en JS que
l'étiquette ci-dessus. Pas d'habillage visuel par défaut (le classeur
photographié suffit), juste un léger éclaircissement au survol pour
signaler que c'est cliquable, en plus du curseur et de l'infobulle —
UNIQUEMENT sur le classeur réellement branché (voir .classeur-actif
ci-dessous) : les 8 autres n'ont pas encore d'écran derrière (voir
tableau CLASSEURS), pas de survol trompeur pour eux tant que ce n'est
pas le cas.*/
.classeur-zone{
position:fixed;
display:block;
}
.classeur-zone.classeur-actif{
cursor:pointer;
box-shadow:inset 0 0 0 0 rgba(255,255,255,0);
transition:box-shadow .12s ease;
}
.classeur-zone.classeur-actif:hover{
box-shadow:inset 0 0 0 2px rgba(255,255,255,.7);
}
/* Chevalet supprimé le 3 septembre 2026 (voir journal) : Jean-Marc l'a
rangé — "libère de la place sur le bureau" — dans la nouvelle photo qui
ne le montre plus. Le prénom de l'utilisateur reste affiché ailleurs,
dans le groupe société/prénom/marque repris de liste_contacts.html
(voir #statut-version-bloc plus bas), désormais fixé en bas à droite
de l'écran plutôt que calé sur un objet du bureau. */
/* Micro cliquable (2 septembre 2026, essai — retouché le même jour après
retour de Jean-Marc). Deux éléments distincts, superposés mais avec des
rôles différents :
- #micro-desk : la zone cliquable/survolable RÉELLE, calée sur tout le
silhouette du micro (pied, tige, corps, tête) — pas seulement la
petite tête. Corrige le souci remonté : l'infobulle ne se déclenchait
qu'en survolant précisément la petite grille du haut, zone trop
étroite pour être fiable à la souris. Invisible (pas de fond), juste
le curseur + l'infobulle (title) + le clic.
- #micro-icone : uniquement l'icône elle-même, petite, calée sur le
rond noir de la tête (voir positionner()). pointer-events:none pour
laisser passer le clic/survol vers #micro-desk en dessous. Le léger
liseré au survol (auparavant sur le bouton) est maintenant affiché
ici via le sélecteur de fratrie ~ quand #micro-desk est survolé, pour
rester visuellement au niveau de la tête plutôt qu'engober tout le
micro d'un cadre. Icônes redessinées en SVG à partir de
mic_repos.png / mic_ecoute.png (déjà utilisées pour le bouton micro
du bandeau, voir micro.js) — recolorées, sans le disque rouge
d'origine, pour se fondre dans la grille plutôt que ressortir comme
un autocollant. Fermé = glyphe micro en gris clair ; ouvert = barres
façon vu-mètre en --rouge-micro, animées (voir @keyframes) — "les
barres qui bougent" demandé par Jean-Marc.
Branché sur la reconnaissance vocale réelle le 3 septembre 2026 :
un script pilote désormais #micro-desk et bascule la classe "ecoute"
dessus (voir plus bas #micro-desk.ecoute ~ #micro-icone pour la
bascule visuelle des deux glyphes, via un combinateur de fratrie
puisque #micro-icone n'est pas un descendant de #micro-desk — voir
plus haut pourquoi ces deux éléments sont séparés). Le toggle JS
local qui existait ici pour le tout premier essai d'affichage (pur
visuel, sans reconnaissance vocale) est retiré.
Mise à jour du même jour (suite de session) : ce micro est
temporairement détourné de son usage final. Plutôt que micro.js (le
vrai pilotage, agents nommés Viktor/Matilda/Léon/Nikita, mode
notation — resté inchangé et toujours utilisé tel quel sur
liste_contacts.html), c'est d'abord micro_test.js qui a été chargé
ici, le temps de la phase de constitution d'une banque de dictées
réelles (table ASP_DicteesTest, thème "Gestion des Contacts").
4 septembre 2026 (soir) : cette phase de collecte a rempli son rôle
(décision de Jean-Marc, "l'ancien panneau ne nous sert plus à rien")
— micro_test.js est retiré d'ici (le fichier reste en place, juste
plus chargé) au profit de contact_ia_panel.js, qui branche sur ce
même bouton la première interprétation réelle d'une dictée de
contact par LLM (voir contact_ia_interprete.php). Toujours un outil
de mise au point, pas un module utilisateur final : rien n'est créé
en base contact/profession réels, seulement enregistré pour analyse
(voir contact_ia_valide.php). Pour reconnecter le vrai pilotage sur
ce micro une fois cette phase terminée : remettre
<script src="micro.js"> à la place de
<script src="contact_ia_panel.js"> en bas de cette page, rien
d'autre à défaire. */
#micro-desk{
position:fixed;
border:none;background:transparent;padding:0;margin:0;
cursor:pointer;
}
#micro-icone{
position:fixed;
display:flex;align-items:center;justify-content:center;
border-radius:50%;
pointer-events:none;
box-shadow:0 0 0 0 rgba(255,255,255,0);
transition:box-shadow .12s ease;
}
#micro-desk:hover ~ #micro-icone{
box-shadow:0 0 0 3px rgba(255,255,255,.3);
}
#micro-icone svg{
width:70%;height:70%;display:block;
}
#micro-icone svg[hidden]{display:none;}
/* Bascule repos/écoute pilotée par la classe "ecoute" que le script chargé
en bas de page ajoute sur #micro-desk (contact_ia_panel.js actuellement —
voir commentaire plus haut sur le détournement temporaire de ce micro ; même
mécanisme que #bouton-micro.ecoute dans liste_contacts.html avec
micro.js) — combinateur de fratrie ~ nécessaire ici puisque
#micro-icone n'est pas un descendant du bouton. Spécificité (3 id + 1
classe) volontairement plus élevée que la règle [hidden] ci-dessus pour
la surclasser sans !important, l'attribut hidden restant présent dans
le HTML de #glyphe-ecoute (jamais modifié en JS ici désormais). */
#micro-desk.ecoute ~ #micro-icone #glyphe-repos{display:none;}
#micro-desk.ecoute ~ #micro-icone #glyphe-ecoute{display:block;}
#glyphe-repos{
fill:#d9d9d3;
opacity:.92;
}
#glyphe-ecoute{
fill:#e14a5c; /* proche de --rouge-micro (#D9364B) de liste_contacts.html */
}
#glyphe-ecoute .barre{
transform-box:fill-box;
transform-origin:50% 100%;
animation:pulse-barre 1s ease-in-out infinite;
}
#glyphe-ecoute .barre:nth-child(1){ animation-delay:-.6s; }
#glyphe-ecoute .barre:nth-child(2){ animation-delay:-.3s; }
#glyphe-ecoute .barre:nth-child(3){ animation-delay:0s; }
#glyphe-ecoute .barre:nth-child(4){ animation-delay:-.45s; }
@keyframes pulse-barre{
0%,100%{ transform:scaleY(.55); }
50%{ transform:scaleY(1); }
}
/* Numéro de version : voir #statut-version-bloc plus haut — depuis le
3 septembre 2026, fixé en bas à droite de l'écran (même place et même
habillage que dans liste_contacts.html) plutôt que calé sur le bureau. */
/* Pastille "nombre de contacts" sur le classeur Contacts (2 septembre
2026, essai). Idée de Jean-Marc : au fur et à mesure que chaque classeur
sera branché à sa table, afficher une petite pastille collée dessous le
trou cerclé de métal, avec un chiffre — pour donner une indication
immédiate du contenu sans avoir à ouvrir le classeur. Couleur de fond
mesurée sur ecran_fond.jpg (teinte réelle du classeur "Contacts",
rgb≈3,79,99, plus sombre en photo qu'à l'écran) puis assombrie d'un
facteur ~0.55 pour que le chiffre blanc ressorte sans jurer avec le
classeur — pas une couleur arbitraire. Liseré blanc autour de la
pastille (2 fichiers/gris clair, cohérent avec le liseré déjà utilisé
sur les étiquettes de tous les classeurs sur la photo) + légère ombre
portée pour la détacher visuellement, plutôt qu'un anneau de couleur :
Jean-Marc n'avait pas de préférence tranchée sur la couleur de l'anneau,
le blanc a été choisi pour rester cohérent avec l'habillage existant
(à changer facilement si Jean-Marc préfère une autre couleur après test
réel). pointer-events:none : la pastille est purement informative, le
clic doit continuer à passer à travers vers #classeur-contacts en
dessous (même logique que #micro-icone au-dessus de #micro-desk). Le
pattern (variables IMG, positionnement dans positionner(), chargement
via un endpoint réel) est pensé pour être répliqué sur les prochains
classeurs au fur et à mesure de leur branchement — voir commentaire de
PASTILLE_CONTACTS_* plus bas dans le script. Retouchée le même jour
après premier test réel de Jean-Marc ("trop présente, liseré trop
pétant") : diamètre réduit (22px de référence → 17px) et liseré blanc
assoupli (opacité .85→.4, épaisseur 2px→1.5px) pour rester une
indication discrète plutôt qu'un élément qui attire l'œil en premier. */
#pastille-contacts{
position:fixed;
border-radius:50%;
background:#022b36;
box-shadow:0 0 0 1.5px rgba(255,255,255,.4), 0 1px 2px rgba(0,0,0,.45);
display:flex;align-items:center;justify-content:center;
pointer-events:none;
user-select:none;
}
#pastille-contacts span{
color:#fff;
font-family:"Segoe UI","Arial",sans-serif;
font-weight:700;
line-height:1;
}
/* Bloc société + prénom + marque (3 septembre 2026, voir journal). Posé
d'abord calé sur le bureau comme le reste de cet écran, puis Jean-Marc a
précisé vouloir "les infos en bas à droite à la même place que dans la
liste des contacts" — repris ici en position FIXE par rapport à l'écran
(et non plus en fraction de l'image), avec exactement les mêmes id, la
même mise en forme et les mêmes tailles qu'en bas de liste_contacts.html
(pixels fixes, pas de recalcul dans positionner()) : société, point
séparateur, prénom, point séparateur, puis #marque-aspic (icône Mévélia
+ logo ASPIC miniature + version). */
#statut-version-bloc{
position:fixed;
right:16px;
bottom:10px;
display:flex;align-items:center;gap:8px;
}
.point-separateur{
font-size:14px;line-height:1;color:#2a2a2a;
}
#societe-utilisateur, #prenom-utilisateur{
font-size:11px;font-weight:700;color:#2a2a2a;
font-family:"Segoe UI","Arial",sans-serif;
white-space:nowrap;
}
#marque-aspic{
display:flex;align-items:center;gap:4px;
}
#icone-mevelia{
width:16px;height:16px;
border-radius:50%;
display:block;
}
#logo-aspic-mini{
display:flex;align-items:center;
background:#1B1B1B;
border-radius:4px;
padding:2px 6px;
font-family:"Metro DF","Segoe UI Black","Segoe UI Semibold","Segoe UI",Arial,sans-serif;
font-weight:900;font-size:9px;letter-spacing:1px;
}
#logo-aspic-mini .A{color:#FF5A1F;}
#logo-aspic-mini .rest{color:#FFFFFF;}
#statut-version{
color:#4a4a4a;
font-family:"Segoe UI","Arial",sans-serif;
font-weight:700;
font-size:11px;
}
/* Post-its de la ToDoList personnelle (2 septembre 2026, premier
affichage réel — voir journal section 28). Posés dans la zone décrite
par ZONE_POSTITS_* plus bas dans le script : une bande occupant 30% de
la largeur de l'écran depuis le bord droit, jusqu'à la ligne du bureau
(chevalet supprimé le 3 septembre 2026, voir journal — la zone descend
désormais jusqu'à la nouvelle limite mesurée sur la photo, avec la même
marge de sécurité qu'avant) — décision de Jean-Marc ("le vilain petit canard sera l'humain", donc
plus de place que chaque assistant, dont la ToDoList n'est pas encore
gérée — voir Réflexions stratégiques section 11 et le fil du 2-3
septembre). Grille à 4 colonnes × 5 lignes = 20 cases, volontairement
alignée sur CAPACITE_TABLEAU_PAR_UTILISATEUR (todolist.php) pour cette
première version — les deux devront évoluer ensemble si la capacité
change. Chaque post-it reçoit une légère rotation/décalage à
l'intérieur de sa propre case (jamais à cheval sur la case voisine,
donc jamais de chevauchement ce soir — Jean-Marc a autorisé le
chevauchement partiel mais a refusé l'empilement en tas ; rester dans
sa case est la façon la plus simple de garantir qu'on ne recrée jamais
ce tas). Décalage/rotation calculés de façon déterministe à partir de
TDL_Incr (voir hashDeterministe() dans le script) : la disposition ne
"saute" pas à chaque rechargement de l'écran.
Couleur jaune classique pour l'utilisateur (--postit-jaune) — les
couleurs par assistant restent à faire le jour où leur ToDoList sera
gérée (AGN_Couleur, voir Réflexions stratégiques section 11).
Punaise ronde en haut (photo confirmée comme du vrai liège par
Jean-Marc le 2 septembre) plutôt qu'un simple post-it posé — cohérent
avec la matière réelle du fond d'écran. Pastille d'urgence toujours
dans le coin haut-droit, jamais recouverte par du jitter (voir calcul
de bornes dans positionnerPostits()) : c'est le repère visuel ET la
garantie d'une zone de clic non ambiguë même si deux post-its finissent
visuellement proches. Pas de texte sur le post-it (choix explicite de
Jean-Marc) : le titre de la tâche n'apparaît qu'au survol (title natif),
le clic ne fait rien pour l'instant (voir journal — navigation vers
liste_todolist.html volontairement pas encore branchée). */
.postit{
position:fixed;
box-shadow:2px 4px 8px rgba(0,0,0,.35);
background:
linear-gradient(135deg, transparent 50%, rgba(0,0,0,.12) 50%, rgba(0,0,0,.12) 62%, transparent 62%) bottom right / 22% 22% no-repeat,
var(--couleur-postit, #fbe14a);
cursor:default;
}
.postit::before{
content:'';
position:absolute;
top:-9px; left:50%;
transform:translateX(-50%);
width:13%; height:13%;
min-width:8px; min-height:8px;
border-radius:50%;
background:radial-gradient(circle at 35% 30%, #fff8, transparent 40%), #8a3b12;
box-shadow:0 2px 3px rgba(0,0,0,.5);
}
.postit .pastille-urgence-postit{
position:absolute;
top:-15%; right:-15%;
width:32%; height:32%;
border-radius:50%;
box-shadow:0 0 0 2px rgba(255,255,255,.9), 0 1px 2px rgba(0,0,0,.4);
}
.postit .pastille-urgence-postit.rouge{ background:#c0392b; }
.postit .pastille-urgence-postit.orange{ background:#ff5a1f; }
</style>
</head>
<body>
<!-- Les 9 zones cliquables + étiquettes de classeurs (7 septembre 2026) sont
créées par creerClasseurs() (voir script) à partir du tableau CLASSEURS,
sur le même principe que les post-its plus bas : éviter de dupliquer 9
fois à la main un bloc HTML quasi identique. -->
<button id="micro-desk" type="button" title="Le micro est inactif"></button>
<div id="micro-icone">
<svg id="glyphe-repos" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z"/>
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
</svg>
<svg id="glyphe-ecoute" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" hidden>
<rect class="barre" x="2" y="14" width="3.5" height="7" rx="1"/>
<rect class="barre" x="8" y="9" width="3.5" height="12" rx="1"/>
<rect class="barre" x="14" y="4" width="3.5" height="17" rx="1"/>
<rect class="barre" x="20" y="11" width="3.5" height="10" rx="1"/>
</svg>
</div>
<div id="pastille-contacts"><span id="pastille-contacts-nombre">…</span></div>
<div id="statut-version-bloc">
<span id="societe-utilisateur" hidden></span>
<span class="point-separateur" id="point-societe" hidden aria-hidden="true">&#8226;</span>
<span id="prenom-utilisateur"></span>
<span class="point-separateur" aria-hidden="true">&#8226;</span>
<div id="marque-aspic" title="ASPIC
Créé par Mevelia Solutions">
<img id="icone-mevelia" src="mevelia-icone.png" alt="Mévélia Solutions">
<span id="logo-aspic-mini"><span class="A">A</span><span class="rest">SPIC</span></span>
<span id="statut-version"></span>
</div>
</div>
<script>
// Référence de l'image : 1024×457, inchangée en dimensions (voir
// ecran_fond.jpg — 3ᵉ version le 3 septembre 2026, Jean-Marc a raccourci
// le bureau de 13px en bas de la photo et ajouté 13px de liège en haut,
// chevalet retiré). Toutes les coordonnées ci-dessous sont exprimées en
// pixels de CETTE image de référence, puis converties en fraction pour
// rester calées quelle que soit la taille réelle d'affichage (voir calcul
// dans positionner()). Constantes remesurées directement sur la nouvelle
// photo (comparaison pixel à pixel avec l'ancienne, tranche du classeur et
// silhouette du micro) : décalage vertical uniforme de +13 confirmé pour
// tout ce qui était calé sur le bureau ou le mur, aucun décalage
// horizontal — cohérent avec la description de Jean-Marc.
// 4ᵉ version le 7 septembre 2026 : Jean-Marc a retravaillé l'image pour
// passer d'un unique classeur "Contacts" à une rangée de 9 classeurs
// (mêmes dimensions 1024×457, même bureau/mur/micro/carnet/téléphone,
// vérifié par différence pixel à pixel entre les deux photos — seule la
// zone des classeurs, x≈[40,400], a réellement changé). Constantes de
// classeurs et pastille "Contacts" remesurées ci-dessous sur cette
// nouvelle version ; MICRO_ZONE_*/ICONE_*/ZONE_POSTITS_* inchangées.
const IMG_W = 1024, IMG_H = 457;
// -------- Classeurs (7 septembre 2026 — 9 classeurs remplacent l'unique
// classeur "Contacts" du 2 septembre) --------
// Nouvelle photo fournie par Jean-Marc, mêmes dimensions de référence
// 1024×457. Toutes les coordonnées ci-dessous ont été mesurées directement
// sur cette nouvelle photo par analyse pixel par pixel (relevé des
// transitions de couleur colonne par colonne / ligne par ligne), pas à
// l'œil — même exigence de précision que les mesures manuelles de
// Jean-Marc sur les versions précédentes.
//
// CLASSEURS_Y1/Y2 : rectangle vertical de la tranche visible, commun aux
// 9 classeurs (mesuré quasi identique sur chacun, 230-233 en haut et
// 409-411 en bas — la photo montre les 9 classeurs à la même hauteur).
const CLASSEURS_Y1 = 231, CLASSEURS_Y2 = 410;
// Rectangle vertical utile de la partie blanche de l'étiquette (mesuré
// constant à 255-339 sur les 9 classeurs) : c'est cette hauteur, une fois
// convertie à l'écran, qui borne la largeur du texte tourné à -90°
// (voir calculerPoliceEtiquette() plus bas).
const ETIQ_LABEL_Y1 = 255, ETIQ_LABEL_Y2 = 339;
const ETIQ_CENTRE_Y_FRAC = ((ETIQ_LABEL_Y1 + ETIQ_LABEL_Y2) / 2) / IMG_H;
// Largeur utile de la partie blanche de l'étiquette (mesurée 22-25px selon
// le classeur, valeur basse retenue par sécurité — cette largeur borne
// l'ÉPAISSEUR du texte tourné, donc indirectement la taille de police
// maximale des libellés courts, voir calculerPoliceEtiquette() plus bas).
const ETIQ_LABEL_LARGEUR = 22;
// Marges de sécurité (10-6%) sur les deux contraintes ci-dessus. Plus
// resserrées que la précédente version (12%) : la taille de police n'est
// plus estimée à partir d'un ratio de largeur de caractère deviné, mais
// mesurée pour de vrai par le navigateur lui-même (voir
// calculerPoliceEtiquette()) — la marge ne sert donc plus qu'à absorber
// l'écart entre la police réellement utilisée chez Jean-Marc (Segoe UI,
// Windows) et celle du moteur qui affiche la page au moment du calcul,
// jamais à compenser une approximation de largeur de texte.
const ETIQ_MARGE_HAUTEUR = 0.94;
const ETIQ_MARGE_LARGEUR = 0.90;
// Facteur hauteur de ligne approximatif (line-height non fixé en CSS ici,
// donc dépendant du navigateur — 1.2 est une valeur usuelle prudente) :
// c'est cette épaisseur de ligne, pas la taille de police elle-même, qui
// doit tenir dans ETIQ_LABEL_LARGEUR une fois le texte tourné à -90°.
const ETIQ_LIGNE_HAUTEUR = 1.2;
// Seuil plancher, en dessous duquel le texte deviendrait illisible quel
// que soit le libellé (aucun classeur actuel ne devrait l'atteindre).
const ETIQ_POLICE_MIN_FRAC = 9 / IMG_W;
// Plafond commun aux libellés courts : épaisseur de ligne maximale qui
// tient dans la largeur physique de l'étiquette (2ᵉ contrainte de
// calculerPoliceEtiquette() ci-dessous, extraite ici en constante
// partagée) — c'est la taille qu'atteignent "Contacts", "Catalogue", "En
// cours" ou "Factures" une fois que la contrainte de hauteur cesse d'être
// la plus stricte des deux. Sert aussi de police forcée pour "Documents"
// (policeForceeFrac dans CLASSEURS, voir plus bas) le 7 septembre 2026
// (soir).
const ETIQ_POLICE_CAP_FRAC = (ETIQ_LABEL_LARGEUR * ETIQ_MARGE_LARGEUR) / ETIQ_LIGNE_HAUTEUR / IMG_W;
// Tableau des 9 classeurs, dans l'ordre demandé par Jean-Marc le
// 7 septembre 2026 (photo retravaillée : le 1er classeur a glissé vers la
// gauche et devient "Consignes", "Contacts" passe en 2e position).
// x1/x2 : bornes horizontales de la tranche visible (classeur entier, y
// compris le bord en plastique coloré autour de l'étiquette) — définissent
// la zone cliquable/survolable. labelCx : centre horizontal mesuré du
// RECTANGLE BLANC de l'étiquette lui-même (pas du classeur entier) —
// retouché le 7 septembre 2026 après retour de Jean-Marc ("Consignes" trop
// à droite, "Contacts"/"Catalogue" à décaler vers la gauche) : l'étiquette
// n'est pas toujours parfaitement centrée sur la tranche du classeur,
// mieux vaut mesurer sa propre position que supposer qu'elle coïncide avec
// le centre de la tranche. Re-décalé une seconde fois le 7 septembre 2026
// (soir) pour "Consignes" seul ("presque parfait", encore un peu trop à
// droite) : -2px supplémentaires. href : écran réel derrière le classeur,
// ou null quand cet écran n'existe pas encore — seul un classeur avec href
// reçoit la classe "classeur-actif" (survol + clic, voir creerClasseurs()) ;
// les 8 autres affichent leur étiquette mais ne sont pas encore cliquables,
// faute d'écran à ouvrir (voir le journal pour la liste des écrans
// restant à construire). policeForceeFrac : taille de police imposée (en
// fraction de IMG_W), au lieu de la double contrainte calculée par
// calculerPoliceEtiquette() — voir commentaire sur "Documents" ci-dessous
// pour le seul cas qui l'utilise pour l'instant.
const CLASSEURS = [
{id:'consignes', label:'Consignes', x1:44, x2:79.5, labelCx:57.2, href:null},
{id:'contacts', label:'Contacts', x1:79.5, x2:118, labelCx:96.6, href:'liste_contacts.html'},
{id:'catalogue', label:'Catalogue', x1:118, x2:155, labelCx:135.0, href:null},
// "Documentations" remplacé par "Documents" le 7 septembre 2026 (soir),
// à la demande de Jean-Marc (le libellé complet restait peu lisible même
// après le passage à measureText() : un mot de 14 lettres doit beaucoup
// rétrécir pour tenir dans la hauteur de l'étiquette). Police forcée
// (policeForceeFrac, voir ETIQ_POLICE_CAP_FRAC plus haut) plutôt que
// laissée au calcul automatique : "Documents" (9 lettres, comme
// "Consignes") tiendrait déjà largement dans la hauteur disponible même
// à la taille plafond commune aux libellés courts — autant l'aligner
// explicitement sur ce plafond pour garantir la même taille bien lisible
// que "Contacts"/"Catalogue"/"En cours"/"Factures", plutôt que de
// dépendre d'un calcul qui pourrait légèrement varier d'un libellé à
// l'autre. id conservé en interne à 'documentations' (cohérence avec
// l'historique de la photo/des mesures), seul le libellé affiché change.
{id:'documentations', label:'Documents', x1:155, x2:192.5, labelCx:173.9, href:null},
{id:'prospection', label:'Prospection', x1:192.5, x2:234, labelCx:212.6, href:null},
// "Communication" remplacé par "Réseaux" le 7 septembre 2026 (soir), à la
// demande de Jean-Marc : ce classeur regroupe la génération de posts
// Instagram et la veille concurrentielle sur les réseaux sociaux —
// "Réseaux" colle mieux à cet usage que le générique "Communication",
// en plus d'être bien plus court (donc bien plus lisible une fois
// tourné à -90°, voir calculerPoliceEtiquette()). id conservé en interne
// à 'communication' (cohérence avec l'historique de la photo/des
// mesures), seul le libellé affiché change.
{id:'communication', label:'Réseaux', x1:234, x2:274.5, labelCx:253.0, href:null},
{id:'en-cours', label:'En cours', x1:274.5, x2:312.5, labelCx:293.0, href:null},
{id:'factures', label:'Factures', x1:312.5, x2:352.5, labelCx:332.5, href:null},
{id:'journalier', label:'Journalier', x1:352.5, x2:395.5, labelCx:372.6, href:null},
];
// Zone cliquable/survolable du micro, centre du rond noir de la grille
// (icône) et zone des post-its personnels : AUCUN changement le 7
// septembre 2026 — vérifié par comparaison pixel à pixel avec l'ancienne
// photo, identiques au-delà de x≈400 (voir commentaire CSS plus haut).
const MICRO_ZONE_X1 = 465, MICRO_ZONE_X2 = 543;
const MICRO_ZONE_Y1 = 249, MICRO_ZONE_Y2 = 419;
const ICONE_CENTRE_X = 512, ICONE_CENTRE_Y = 268;
const ICONE_TAILLE_FRAC = 22 / IMG_W;
// Pastille "nombre de contacts" — sous le trou de préhension du classeur
// Contacts, à mi-chemin entre ce trou et le bas du classeur (retour de
// Jean-Marc le 7 septembre 2026 : la 1ʳᵉ mesure avait par erreur calé la
// pastille SUR le trou lui-même, pas en dessous). Trou de préhension
// repéré par sa forme ovale sombre, mesurée sur la nouvelle photo :
// x≈[89,107] (centre 98), y≈[360,374] (bas du trou 374). Milieu entre ce
// bas de trou (374) et le bas du classeur (CLASSEURS_Y2=410) : (374+410)/2
// = 392.
const PASTILLE_CONTACTS_CENTRE_X = 98, PASTILLE_CONTACTS_CENTRE_Y = 392;
const PASTILLE_CONTACTS_DIAM_FRAC = 17 / IMG_W;
// Zone des post-its personnels (2 septembre 2026, premier affichage réel
// — voir commentaire CSS .postit ci-dessus pour le détail des décisions).
// 30% de la largeur de l'écran depuis le bord droit (1024 × 0.30 ≈ 307).
// Bas de la zone (Y2) recalculé le 3 septembre 2026 : n'était plus borné
// par le chevalet (supprimé) mais par la nouvelle ligne de bureau
// (CLASSEURS_Y2 = 410, ex-CLASSEUR_Y2 avant le passage à 9 classeurs le
// 7 septembre — valeur inchangée), avec la même marge de sécurité qu'avant
// (~15px) — 352 devient 395, quelques pixels de plus qu'un simple +13
// puisque l'espace se libère réellement, comme relevé par Jean-Marc. Grille
// 4 colonnes × 5 lignes = 20 cases, volontairement alignée sur
// CAPACITE_TABLEAU_PAR_UTILISATEUR (todolist.php) — à faire évoluer
// ensemble le jour où cette capacité changera.
const ZONE_POSTITS_X1 = 717, ZONE_POSTITS_X2 = 1024;
const ZONE_POSTITS_Y1 = 6, ZONE_POSTITS_Y2 = 395;
const GRILLE_POSTITS_COLONNES = 4, GRILLE_POSTITS_LIGNES = 5;
// Taille d'un post-it : ~60% de la plus petite dimension de case, pour
// laisser de la marge au jitter sans jamais déborder sur la case voisine
// (voir positionnerPostits()).
const POSTIT_RATIO_CASE = 0.6;
/**
* Petit générateur pseudo-aléatoire déterministe (0..1), à partir d'un
* entier (TDL_Incr) — pour que le jitter (rotation/décalage) d'un post-it
* donné reste toujours le même d'un rechargement à l'autre de l'écran,
* plutôt que de "sauter" à chaque fois (mulberry32, implémentation
* minimale, largement suffisante ici — pas besoin de qualité crypto).
*/
function pseudoAleatoire(graine) {
let t = (graine + 0x6D2B79F5) | 0;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
}
// Crée les 9 zones cliquables + étiquettes de classeurs, une seule fois
// (voir tableau CLASSEURS plus haut) — même principe que les post-its,
// appelée une fois au chargement, positionnée ensuite (comme tout le
// reste) dans positionner(). Un classeur avec href (voir CLASSEURS) est
// un vrai lien <a> ; les 8 autres, sans écran derrière pour l'instant,
// sont de simples <div> — pas de lien mort ni de survol trompeur (voir
// commentaire CSS .classeur-zone plus haut).
function creerClasseurs() {
CLASSEURS.forEach(function (c) {
const zone = document.createElement(c.href ? 'a' : 'div');
zone.className = 'classeur-zone' + (c.href ? ' classeur-actif' : '');
zone.id = 'classeur-' + c.id;
zone.dataset.classeurId = c.id;
if (c.href) {
zone.href = c.href;
zone.title = 'Ouvrir le classeur ' + c.label.toLowerCase();
}
document.body.appendChild(zone);
const etiquette = document.createElement('div');
etiquette.className = 'etiquette-classeur';
etiquette.id = 'etiquette-' + c.id;
etiquette.textContent = c.label;
document.body.appendChild(etiquette);
});
}
// Mesure de la largeur RÉELLE d'un texte (7 septembre 2026, remplace une
// première version qui estimait cette largeur à partir d'un ratio de
// largeur de caractère deviné — trop approximatif : "Prospection" et
// "Journalier" étaient ressortis trop petits à l'usage réel chez
// Jean-Marc). Utilise un <canvas> hors-écran avec exactement la même
// police que .etiquette-classeur (voir CSS plus haut) : measureText()
// interroge directement le moteur de rendu de police du navigateur EN
// COURS D'UTILISATION — celui de Jean-Marc (Segoe UI, Windows) une fois la
// page redéployée chez lui — plutôt qu'une estimation calculée ici. Un
// seul canvas et un petit cache par (libellé, taille) : positionner() est
// rappelée à chaque redimensionnement de fenêtre, pas la peine de remesurer
// à chaque fois un texte qui n'a pas changé.
const canvasMesure = document.createElement('canvas');
const ctxMesure = canvasMesure.getContext('2d');
const largeursTexteMesurees = {};
function mesurerLargeurTexte(libelle, taillePx) {
const cle = libelle + '@' + taillePx;
if (largeursTexteMesurees[cle] !== undefined) return largeursTexteMesurees[cle];
ctxMesure.font = '600 ' + taillePx + 'px "Segoe UI","Arial",sans-serif';
// letter-spacing:0.03em (voir .etiquette-classeur) ajouté à la main :
// measureText() ne le prend pas en compte nativement de façon fiable
// sur tous les navigateurs, un calcul explicite est plus sûr qu'un
// support incertain de ctx.letterSpacing.
const largeurBase = ctxMesure.measureText(libelle).width;
const largeur = largeurBase + (libelle.length - 1) * 0.03 * taillePx;
largeursTexteMesurees[cle] = largeur;
return largeur;
}
// Taille de police (en fraction de IMG_W) pour l'étiquette d'un classeur —
// double contrainte, la plus stricte des deux l'emporte :
// 1) La largeur RÉELLE du texte (mesurée ci-dessus à une taille de
// référence, puis mise à l'échelle) doit tenir dans la hauteur
// disponible de l'étiquette (ETIQ_LABEL_Y2-Y1) : c'est cette hauteur
// qui devient la largeur du texte une fois tourné à -90°. C'est cette
// contrainte qui borne les libellés longs ("Documentations",
// "Communication").
// 2) L'épaisseur d'une ligne de texte (≈ taille × ETIQ_LIGNE_HAUTEUR) doit
// tenir dans la largeur physique de l'étiquette (ETIQ_LABEL_LARGEUR) :
// sans cette 2ᵉ contrainte, un libellé court comme "Factures" pourrait
// grandir au point de déborder du rectangle blanc sur les côtés.
// Bornée par ETIQ_POLICE_MIN_FRAC en dessous (seuil de lisibilité) — pas de
// plafond arbitraire au-dessus, les deux contraintes ci-dessus suffisent.
function calculerPoliceEtiquette(libelle) {
const TAILLE_REF = 100; // px CSS, taille arbitraire pour la mesure
const largeurRef = mesurerLargeurTexte(libelle, TAILLE_REF);
const hauteurDispoImg = (ETIQ_LABEL_Y2 - ETIQ_LABEL_Y1) * ETIQ_MARGE_HAUTEUR;
const parContrainteHauteur = (hauteurDispoImg / largeurRef) * TAILLE_REF / IMG_W;
return Math.max(ETIQ_POLICE_MIN_FRAC, Math.min(parContrainteHauteur, ETIQ_POLICE_CAP_FRAC));
}
function positionner() {
const vw = window.innerWidth, vh = window.innerHeight;
const ratioImg = IMG_W / IMG_H;
const ratioVp = vw / vh;
let dispW, dispH, offX, offY;
if (ratioVp > ratioImg) {
dispH = vh; dispW = dispH * ratioImg;
offX = (vw - dispW) / 2; offY = 0;
} else {
dispW = vw; dispH = dispW / ratioImg;
offX = 0; offY = (vh - dispH) / 2;
}
// Les 9 classeurs (zone cliquable + étiquette tournée, voir
// creerClasseurs() et calculerPoliceEtiquette() plus haut).
CLASSEURS.forEach(function (c) {
const zone = document.getElementById('classeur-' + c.id);
zone.style.left = (offX + (c.x1 / IMG_W) * dispW) + 'px';
zone.style.top = (offY + (CLASSEURS_Y1 / IMG_H) * dispH) + 'px';
zone.style.width = (((c.x2 - c.x1) / IMG_W) * dispW) + 'px';
zone.style.height = (((CLASSEURS_Y2 - CLASSEURS_Y1) / IMG_H) * dispH) + 'px';
const etiquette = document.getElementById('etiquette-' + c.id);
etiquette.style.left = (offX + (c.labelCx / IMG_W) * dispW) + 'px';
etiquette.style.top = (offY + ETIQ_CENTRE_Y_FRAC * dispH) + 'px';
const taillePoliceFrac = (c.policeForceeFrac !== undefined) ? c.policeForceeFrac : calculerPoliceEtiquette(c.label);
etiquette.style.fontSize = (taillePoliceFrac * dispW) + 'px';
});
const microDesk = document.getElementById('micro-desk');
microDesk.style.left = (offX + (MICRO_ZONE_X1 / IMG_W) * dispW) + 'px';
microDesk.style.top = (offY + (MICRO_ZONE_Y1 / IMG_H) * dispH) + 'px';
microDesk.style.width = (((MICRO_ZONE_X2 - MICRO_ZONE_X1) / IMG_W) * dispW) + 'px';
microDesk.style.height = (((MICRO_ZONE_Y2 - MICRO_ZONE_Y1) / IMG_H) * dispH) + 'px';
const microIcone = document.getElementById('micro-icone');
const iconeTaille = ICONE_TAILLE_FRAC * dispW;
microIcone.style.left = (offX + (ICONE_CENTRE_X / IMG_W) * dispW - iconeTaille / 2) + 'px';
microIcone.style.top = (offY + (ICONE_CENTRE_Y / IMG_H) * dispH - iconeTaille / 2) + 'px';
microIcone.style.width = iconeTaille + 'px';
microIcone.style.height = iconeTaille + 'px';
// #statut-version-bloc (société/prénom/marque) ne fait plus partie de ce
// calcul depuis le 3 septembre 2026 : position fixe en bas à droite de
// l'écran, en pixels fixes CSS — voir le commentaire au-dessus de sa
// règle #statut-version-bloc et le journal.
const pastilleContacts = document.getElementById('pastille-contacts');
const pastilleDiam = PASTILLE_CONTACTS_DIAM_FRAC * dispW;
pastilleContacts.style.left = (offX + (PASTILLE_CONTACTS_CENTRE_X / IMG_W) * dispW - pastilleDiam / 2) + 'px';
pastilleContacts.style.top = (offY + (PASTILLE_CONTACTS_CENTRE_Y / IMG_H) * dispH - pastilleDiam / 2) + 'px';
pastilleContacts.style.width = pastilleDiam + 'px';
pastilleContacts.style.height = pastilleDiam + 'px';
// Taille de police proportionnelle au diamètre, réduite si le nombre a
// 3 chiffres ou plus pour continuer à tenir dans le rond sans déborder.
const nbChiffres = document.getElementById('pastille-contacts-nombre').textContent.length;
const facteurPolice = nbChiffres >= 4 ? 0.34 : (nbChiffres === 3 ? 0.42 : 0.52);
document.getElementById('pastille-contacts-nombre').style.fontSize = (pastilleDiam * facteurPolice) + 'px';
// Post-its personnels : chaque .postit porte sa case (data-colonne/
// data-ligne, attribuées au chargement — voir creerPostits()) et une
// graine (data-graine = TDL_Incr) pour un jitter stable d'un
// rechargement à l'autre. Le jitter reste borné à l'intérieur de la
// case (marge × 0.8, jamais 1) pour ne jamais déborder sur la case
// voisine — c'est ce qui garantit l'absence d'empilement.
const zoneW = ((ZONE_POSTITS_X2 - ZONE_POSTITS_X1) / IMG_W) * dispW;
const zoneH = ((ZONE_POSTITS_Y2 - ZONE_POSTITS_Y1) / IMG_H) * dispH;
const caseW = zoneW / GRILLE_POSTITS_COLONNES;
const caseH = zoneH / GRILLE_POSTITS_LIGNES;
const postitTaille = Math.min(caseW, caseH) * POSTIT_RATIO_CASE;
const zoneLeft = offX + (ZONE_POSTITS_X1 / IMG_W) * dispW;
const zoneTop = offY + (ZONE_POSTITS_Y1 / IMG_H) * dispH;
const margeX = (caseW - postitTaille) / 2 * 0.8;
const margeY = (caseH - postitTaille) / 2 * 0.8;
document.querySelectorAll('.postit').forEach(function (postit) {
const col = Number(postit.dataset.colonne);
const ligne = Number(postit.dataset.ligne);
const graine = Number(postit.dataset.graine);
const caseLeft = zoneLeft + col * caseW;
const caseTop = zoneTop + ligne * caseH;
const decalageX = (pseudoAleatoire(graine) * 2 - 1) * margeX;
const decalageY = (pseudoAleatoire(graine + 1) * 2 - 1) * margeY;
const rotation = (pseudoAleatoire(graine + 2) * 2 - 1) * 7; // ±7°
postit.style.width = postitTaille + 'px';
postit.style.height = postitTaille + 'px';
postit.style.left = (caseLeft + (caseW - postitTaille) / 2 + decalageX) + 'px';
postit.style.top = (caseTop + (caseH - postitTaille) / 2 + decalageY) + 'px';
postit.style.transform = 'rotate(' + rotation.toFixed(1) + 'deg)';
});
}
creerClasseurs();
window.addEventListener('resize', positionner);
positionner();
// Bascule ouvert/fermé du micro du bureau : gérée par le script chargé en
// fin de page (contact_ia_panel.js actuellement, voir commentaire plus haut),
// qui pilote le clic, la reconnaissance vocale et la classe "ecoute" sur
// #micro-desk — voir la règle CSS correspondante plus
// haut pour la bascule visuelle des deux glyphes. L'ancien toggle purement
// visuel du premier essai (2-3 septembre 2026) est retiré.
// Société et prénom de l'utilisateur connecté (3 septembre 2026, voir
// journal) : preferences.php, même endpoint et même logique que
// chargerPreferences() dans liste_contacts.html — repris ici à l'identique
// pour le groupe #statut-version-bloc (voir plus haut, id communs avec
// liste_contacts.html). Le chevalet qui affichait seul le prénom
// (USR_Prenom, ajouté le 2 septembre 2026 dans ASP_Users) a été retiré du
// bureau le 3 septembre — cette fonction ne porte donc plus que ce groupe.
// Repli silencieux si la session n'est pas active.
async function chargerIdentiteUtilisateur() {
const elSociete = document.getElementById('societe-utilisateur');
const elPointSociete = document.getElementById('point-societe');
const elPrenom = document.getElementById('prenom-utilisateur');
try {
const reponsePrefs = await fetch('preferences.php');
if (!reponsePrefs.ok) throw new Error('HTTP ' + reponsePrefs.status);
const prefs = await reponsePrefs.json();
if (prefs.USR_Societe) {
elSociete.textContent = prefs.USR_Societe;
elSociete.hidden = false;
elPointSociete.hidden = false;
} else {
elSociete.hidden = true;
elPointSociete.hidden = true;
}
if (prefs.USR_Prenom) elPrenom.textContent = prefs.USR_Prenom;
} catch (erreur) {
console.error('Erreur de chargement de preferences.php (identité) :', erreur);
}
}
chargerIdentiteUtilisateur();
// Numéro de version : même endpoint que #statut-version dans
// liste_contacts.html (chargerVersion(), voir journal). Repli silencieux
// si non joignable (fichier hors serveur, ou version.php absent de ce
// dossier) : l'écran reste utilisable sans.
async function chargerVersionBureau() {
try {
const reponse = await fetch('version.php');
if (!reponse.ok) throw new Error('HTTP ' + reponse.status);
const data = await reponse.json();
if (data.version) {
document.getElementById('statut-version').textContent = data.version;
// Infobulle du groupe icône Mévélia + logo ASPIC + version (3
// septembre 2026, voir journal), identique à liste_contacts.html.
const elMarque = document.getElementById('marque-aspic');
if (elMarque) elMarque.title = 'ASPIC ' + data.version + '\nCréé par Mevelia Solutions';
}
} catch (erreur) {
console.error('Erreur de chargement de version.php (bureau) :', erreur);
}
}
chargerVersionBureau();
// Pastille "nombre de contacts" du classeur Contacts : total réel de la
// table (contacts.php?compte=1, voir journal section 28 et commentaire
// ajouté dans contacts.php), jamais un chiffre figé. Repli visuel "—" en
// cas d'échec (même logique que chargerIdentiteUtilisateur), pour ne jamais
// laisser une pastille vide sans explication. Le pattern (constantes
// PASTILLE_*, positionnement dans positionner(), chargement ici) est
// pensé pour être dupliqué sur les prochains classeurs à mesure qu'ils
// seront branchés à leur table.
async function chargerCompteContacts() {
const elNombre = document.getElementById('pastille-contacts-nombre');
try {
const reponse = await fetch('contacts.php?compte=1');
if (!reponse.ok) throw new Error('HTTP ' + reponse.status);
const data = await reponse.json();
elNombre.textContent = (typeof data.total === 'number') ? String(data.total) : '—';
} catch (erreur) {
console.error('Erreur de chargement de contacts.php?compte=1 (pastille classeur) :', erreur);
elNombre.textContent = '—';
}
positionner(); // recalcule la taille de police selon le nombre réel de chiffres
}
chargerCompteContacts();
// Post-its personnels (2 septembre 2026, premier affichage réel — voir
// commentaire CSS .postit et constantes ZONE_POSTITS_*/GRILLE_POSTITS_*
// plus haut). Source : todolist.php (déjà utilisé par liste_todolist.html,
// voir journal section 26), filtré ici côté client sur l'utilisateur
// connecté (TDL_ToDoUser) et sur les tâches non réalisées (TDL_RealDate
// vide) — l'endpoint reste volontairement non filtré par utilisateur
// (voir son commentaire d'en-tête), donc le tri "c'est MES post-its" se
// fait ici. L'API renvoie déjà les tâches triées par urgence (rouge avant
// orange avant vert, voir calculerGroupe()) : cet ordre est conservé pour
// remplir la grille case par case, donc les plus urgentes arrivent
// toujours en premier (en haut à gauche de la zone). Une tâche déjà
// réalisée ne crée jamais de post-it : elle libère sa place sur le
// tableau dès qu'elle est marquée faite (voir aussi CAPACITE_TABLEAU_
// PAR_UTILISATEUR dans todolist.php — même logique des deux côtés).
// Coupé à la capacité de la grille par sécurité (ne devrait normalement
// jamais arriver, la capacité étant déjà appliquée à la création côté
// todolist.php).
async function chargerMesPostits() {
try {
const [reponsePrefs, reponseTaches] = await Promise.all([
fetch('preferences.php'),
fetch('todolist.php'),
]);
if (!reponsePrefs.ok) throw new Error('HTTP ' + reponsePrefs.status + ' (preferences.php)');
if (!reponseTaches.ok) throw new Error('HTTP ' + reponseTaches.status + ' (todolist.php)');
const prefs = await reponsePrefs.json();
const dataTaches = await reponseTaches.json();
const capacite = GRILLE_POSTITS_COLONNES * GRILLE_POSTITS_LIGNES;
const mesTaches = (dataTaches.taches || [])
.filter(t => t.TDL_ToDoUser === prefs.USR_Code && !t.TDL_RealDate)
.slice(0, capacite);
mesTaches.forEach((tache, index) => {
const postit = document.createElement('div');
postit.className = 'postit';
postit.style.setProperty('--couleur-postit', '#fbe14a'); // jaune classique, toujours pour l'humain
postit.title = tache.TDL_ToDoTxt || '';
postit.dataset.colonne = index % GRILLE_POSTITS_COLONNES;
postit.dataset.ligne = Math.floor(index / GRILLE_POSTITS_COLONNES);
postit.dataset.graine = tache.TDL_Incr;
const pastille = document.createElement('div');
// TDL_Groupe 0 = rouge, 1 = orange (le vert n'atteint jamais cet
// écran, voir filtre ci-dessus sur TDL_RealDate).
pastille.className = 'pastille-urgence-postit ' + (tache.TDL_Groupe === 0 ? 'rouge' : 'orange');
postit.appendChild(pastille);
document.body.appendChild(postit);
});
positionner(); // place les post-its qui viennent d'être créés
} catch (erreur) {
console.error('Erreur de chargement des post-its personnels (todolist.php/preferences.php) :', erreur);
}
}
chargerMesPostits();
// Lien visible "→ Liste des tâches" retiré le 2 septembre 2026 à la
// demande de Jean-Marc. Échap avait été conservé ce soir-là comme
// échappatoire silencieuse vers liste_todolist.html, pour ne pas rester
// bloqué sur cet écran tant qu'il ne portait aucun objet fonctionnel.
// Retiré le 3 septembre 2026 (bug signalé par Jean-Marc) : cet écran est
// depuis la veille le véritable écran d'accueil post-identification (voir
// journal, section 28) — liste_contacts.html y ramène désormais par Échap
// (voir journal du 3 septembre), donc un Échap qui, depuis l'écran
// d'accueil lui-même, repartait ailleurs créait une boucle incohérente.
// Aucune redirection ne remplace celle-ci : Échap ne fait plus rien ici,
// faute d'un "retour" qui ait un sens depuis l'écran d'accueil. À revoir
// le jour où la ToDoList aura son propre objet cliquable sur le bureau.
</script>
<script src="contact_ia_panel.js"></script>
</body>
</html>