Files
ASPIC/ecran_fond.html
T

660 lines
34 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>
<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;
}
/* Étiquette "Contacts" sur la tranche du 1er classeur (2 septembre 2026,
essai). Positionnée en JS plutôt qu'en CSS pur : comme l'image est en
"contain", sa taille et sa position réelles à l'écran changent avec la
proportion de la fenêtre — même calcul que "background-size:contain"
refait à la main pour caler ce texte sur l'étiquette blanche, quelle
que soit la fenêtre. Coordonnées mesurées sur ecran_fond.jpg (image de
référence 1280×650) : rectangle d'étiquette à peu près x:[54,91],
y:[376,481] sur cette image. rotate(-90deg) : lecture bas → haut,
comme demandé (on tourne la tête pour lire), plutôt que haut → bas. */
#etiquette-contacts{
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 cliquable calée sur la tranche visible (face plate, sans le
dessus incliné) du 1er 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. */
#classeur-contacts{
position:fixed;
display:block;
box-shadow:inset 0 0 0 0 rgba(255,255,255,0);
transition:box-shadow .12s ease;
}
#classeur-contacts:hover{
box-shadow:inset 0 0 0 2px rgba(255,255,255,.7);
}
/* Chevalet en bas à droite (2 septembre 2026, essai — simplifié le même
jour à la demande de Jean-Marc) : plus que le prénom de l'utilisateur
connecté (USR_Prenom), la société retirée ("l'utilisateur la connaît
déjà"). Une seule ligne, donc plus de place pour une police nettement
plus grande — c'était le point d'origine du problème de lisibilité.
Donnée réelle de la session (preferences.php, voir script), jamais de
texte figé. Ellipsis de sécurité si un prénom venait à être long. */
#chevalet{
position:fixed;
display:flex;align-items:center;justify-content:center;
text-align:center;
color:#333;
font-family:"Segoe UI","Arial",sans-serif;
font-weight:700;
pointer-events:none;
user-select:none;
overflow:hidden;
}
#chevalet-nom{
white-space:nowrap;
max-width:100%;
overflow:hidden;
text-overflow:ellipsis;
}
/* 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. Toggle en pur JS pour ce
test : ne déclenche pas encore la reconnaissance vocale de micro.js. */
#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;}
#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, sur le bureau. Même source que #statut-version dans
liste_contacts.html (version.php, voir commentaire de chargerVersion()
ci-dessous) : jamais de numéro figé en dur, pour rester juste même si
la version évolue sans repasser par cet écran. Repositionné le
2 septembre 2026 : plus à droite (bord droit de l'écran plutôt que
calé sur le chevalet) et plus bas, juste au-dessus de la bande claire
qui marque la tranche avant du bureau. Couleur assombrie et rendue
opaque (au lieu d'une transparence à 62%) à la demande de Jean-Marc —
reste plus sobre que les objets fonctionnels, mais plus affirmée
qu'avant. */
/* 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;
}
#version-desk{
position:fixed;
color:#3a2712;
font-family:"Segoe UI","Arial",sans-serif;
font-weight:700;
letter-spacing:0.02em;
text-align:right;
white-space:nowrap;
pointer-events:none;
user-select:none;
}
/* 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, juste au-dessus du chevalet
— 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>
<a id="classeur-contacts" href="liste_contacts.html" title="Ouvrir le classeur des contacts"></a>
<div id="etiquette-contacts">Contacts</div>
<div id="chevalet">
<div id="chevalet-nom"></div>
</div>
<button id="micro-desk" type="button"></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="version-desk"></div>
<script>
// Référence de l'image : 1024×457 (voir ecran_fond.jpg — 2ᵉ recadrage du
// 2 septembre 2026). 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()).
const IMG_W = 1024, IMG_H = 457;
// Centre de l'étiquette du 1er classeur, remesuré une 3ᵉ fois le
// 2 septembre 2026 : la mesure précédente (69) incluait par erreur
// l'étiquette du classeur bleu voisin dans le calcul du centre ligne par
// ligne (les deux étiquettes, blanches toutes les deux, se touchent
// presque et le seuil de luminosité ne les distinguait pas) — d'où un
// texte qui débordait vers la droite, dans le classeur bleu, au lieu de
// rester dans le classeur bleu-vert. Remesuré en isolant la seule
// première plage claire (x<80) sur plusieurs lignes : rectangle
// d'étiquette réel x:[44,71], y:[240,324], centre (57.5, 282).
const ETIQ_CENTRE_X_FRAC = 57.5 / IMG_W;
const ETIQ_CENTRE_Y_FRAC = 282 / IMG_H;
const ETIQ_POLICE_FRAC = 19 / IMG_W;
// Rectangle de la tranche visible du 1er classeur (face plate, sans le
// dessus incliné), mesuré sur ecran_fond.jpg : x:[37,78], y:[224,397].
const CLASSEUR_X1 = 37, CLASSEUR_X2 = 78;
const CLASSEUR_Y1 = 224, CLASSEUR_Y2 = 397;
// Rectangle de la partie blanche du chevalet, mesuré sur ecran_fond.jpg :
// x:[860,982], y:[359,394] — léger retrait (marge intérieure) pour ne pas
// coller le texte au cadre en bois.
const CHEVALET_X1 = 866, CHEVALET_X2 = 976;
const CHEVALET_Y1 = 362, CHEVALET_Y2 = 391;
// Une seule ligne désormais (prénom seul) : le chevalet est bas et
// large, la police peut être nettement plus grande qu'avant.
const CHEVALET_POLICE_FRAC = 18 / IMG_W;
// Zone cliquable/survolable du micro : tout le silhouette du micro, du
// pied à la tête (bras de la nacelle inclus), mesurée sur ecran_fond.jpg :
// x:[465,543], y:[236,406].
const MICRO_ZONE_X1 = 465, MICRO_ZONE_X2 = 543;
const MICRO_ZONE_Y1 = 236, MICRO_ZONE_Y2 = 406;
// Centre du rond noir en haut du micro (grille), pour l'icône seule —
// remonté le 2 septembre 2026 (icône perçue trop basse avec 268) : la
// pointe du dôme est à y≈242, sa partie courbe se termine vers y≈260
// avant de devenir un cylindre à largeur quasi constante (mesh) jusqu'à
// y≈308. Centre replacé dans le haut du dôme, pas dans le cylindre :
// (512, 255), diamètre réduit à ~22px pour ne pas déborder au-dessus de
// la pointe (y=242).
const ICONE_CENTRE_X = 512, ICONE_CENTRE_Y = 255;
const ICONE_TAILLE_FRAC = 22 / IMG_W;
// Coin haut-droit du texte de version (2 septembre 2026, repositionné) :
// plus près du bord droit de l'écran (1024 de large) qu'aligné sur le
// chevalet, et juste au-dessus de la bande claire de la tranche avant du
// bureau — mesurée sur ecran_fond.jpg vers y:[442,450] ; le texte est
// calé pour se terminer avant, vers y=436.
const VERSION_X = 1012, VERSION_Y = 424;
const VERSION_POLICE_FRAC = 11 / IMG_W;
// Pastille "nombre de contacts", sous le trou cerclé de métal du classeur
// Contacts. Trou mesuré sur ecran_fond.jpg : centre (58, 354), anneau
// métallique jusqu'à y≈365. Pastille centrée sur le même axe X que le
// trou (cohérence visuelle), posée juste en dessous, avant la fin du
// classeur (bas de la tranche à y=397, voir CLASSEUR_Y2). Diamètre réduit
// de 22 à 17 le 2 septembre 2026 (retour de Jean-Marc : "trop présente") —
// centre recalé à (58, 381) pour rester au milieu de l'espace disponible
// entre l'anneau et le bas du classeur avec la nouvelle taille.
const PASTILLE_CONTACTS_CENTRE_X = 58, PASTILLE_CONTACTS_CENTRE_Y = 381;
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),
// du haut de l'image jusqu'à juste au-dessus du chevalet (CHEVALET_Y1 =
// 362, marge de 10px). Grille 4 colonnes × 5 lignes = 20 cases, 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 = 352;
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;
}
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;
}
const etiquette = document.getElementById('etiquette-contacts');
etiquette.style.left = (offX + ETIQ_CENTRE_X_FRAC * dispW) + 'px';
etiquette.style.top = (offY + ETIQ_CENTRE_Y_FRAC * dispH) + 'px';
etiquette.style.fontSize = (ETIQ_POLICE_FRAC * dispW) + 'px';
const classeur = document.getElementById('classeur-contacts');
classeur.style.left = (offX + (CLASSEUR_X1 / IMG_W) * dispW) + 'px';
classeur.style.top = (offY + (CLASSEUR_Y1 / IMG_H) * dispH) + 'px';
classeur.style.width = (((CLASSEUR_X2 - CLASSEUR_X1) / IMG_W) * dispW) + 'px';
classeur.style.height = (((CLASSEUR_Y2 - CLASSEUR_Y1) / IMG_H) * dispH) + 'px';
const chevalet = document.getElementById('chevalet');
chevalet.style.left = (offX + (CHEVALET_X1 / IMG_W) * dispW) + 'px';
chevalet.style.top = (offY + (CHEVALET_Y1 / IMG_H) * dispH) + 'px';
chevalet.style.width = (((CHEVALET_X2 - CHEVALET_X1) / IMG_W) * dispW) + 'px';
chevalet.style.height = (((CHEVALET_Y2 - CHEVALET_Y1) / IMG_H) * dispH) + 'px';
chevalet.style.fontSize = (CHEVALET_POLICE_FRAC * 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';
const versionDesk = document.getElementById('version-desk');
versionDesk.style.left = (offX + (VERSION_X / IMG_W) * dispW - 200) + 'px';
versionDesk.style.width = '200px';
versionDesk.style.top = (offY + (VERSION_Y / IMG_H) * dispH) + 'px';
versionDesk.style.fontSize = (VERSION_POLICE_FRAC * dispW) + 'px';
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)';
});
}
window.addEventListener('resize', positionner);
positionner();
// Bascule ouvert/fermé du micro du bureau — pur affichage pour ce test
// (icône + infobulle), ne démarre pas encore la reconnaissance vocale
// réelle de micro.js. Fermé par défaut à l'arrivée sur l'écran.
let microOuvert = false;
function appliquerEtatMicroDesk() {
const bouton = document.getElementById('micro-desk');
const glyphRepos = document.getElementById('glyphe-repos');
const glyphEcoute = document.getElementById('glyphe-ecoute');
// setAttribute/removeAttribute plutôt que la propriété .hidden : sur un
// <svg> inline, la réflexion de .hidden s'est révélée peu fiable au test
// (attribut inchangé malgré l'affectation) — passage par l'attribut DOM
// directement pour un résultat garanti.
if (microOuvert) {
glyphRepos.setAttribute('hidden', '');
glyphEcoute.removeAttribute('hidden');
bouton.title = 'Le micro est ouvert, cliquez pour le fermer';
} else {
glyphRepos.removeAttribute('hidden');
glyphEcoute.setAttribute('hidden', '');
bouton.title = 'Le micro est fermé, cliquez pour l\'ouvrir';
}
}
document.getElementById('micro-desk').addEventListener('click', () => {
microOuvert = !microOuvert;
appliquerEtatMicroDesk();
});
appliquerEtatMicroDesk();
// Identité affichée sur le chevalet : preferences.php (voir journal
// section 22), qui renvoie USR_Prenom (ajouté le 2 septembre 2026 dans
// ASP_Users à la demande de Jean-Marc). Société retirée de l'affichage
// le même jour ("l'utilisateur la connaît déjà") — plus besoin d'aller
// chercher USR_Societe ici. Repli silencieux (mêmes principes que
// chargerPreferences()) si la session n'est pas active.
async function chargerIdentiteChevalet() {
const elNom = document.getElementById('chevalet-nom');
try {
const reponsePrefs = await fetch('preferences.php');
if (!reponsePrefs.ok) throw new Error('HTTP ' + reponsePrefs.status);
const prefs = await reponsePrefs.json();
elNom.textContent = prefs.USR_Prenom || prefs.USR_Code || '—';
} catch (erreur) {
console.error('Erreur de chargement de preferences.php (chevalet) :', erreur);
elNom.textContent = 'Prénom';
}
}
chargerIdentiteChevalet();
// Numéro de version affiché sur le bureau : 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 chargerVersionDesk() {
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('version-desk').textContent = data.version;
} catch (erreur) {
console.error('Erreur de chargement de version.php (bureau) :', erreur);
}
}
chargerVersionDesk();
// 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 chargerIdentiteChevalet), 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 conservé comme échappatoire silencieuse
// (même logique que le reste de l'appli), pour ne pas rester bloqué sur
// cet écran tant qu'il ne porte aucun objet fonctionnel.
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
window.location.href = 'liste_todolist.html';
}
});
</script>
</body>
</html>