Files
ASPIC/ecran_fond.html
T

693 lines
36 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;
}
/* É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 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>
<a id="classeur-contacts" href="liste_contacts.html" title="Ouvrir le classeur des contacts"></a>
<div id="etiquette-contacts">Contacts</div>
<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.
const IMG_W = 1024, IMG_H = 457;
// Centre de l'étiquette du 1er classeur (57.5, 282 -> 57.5, 295 : +13).
const ETIQ_CENTRE_X_FRAC = 57.5 / IMG_W;
const ETIQ_CENTRE_Y_FRAC = 295 / IMG_H;
const ETIQ_POLICE_FRAC = 19 / IMG_W;
// Rectangle de la tranche visible du 1er classeur (x inchangé, y:[224,397]
// -> y:[237,410]).
const CLASSEUR_X1 = 37, CLASSEUR_X2 = 78;
const CLASSEUR_Y1 = 237, CLASSEUR_Y2 = 410;
// Chevalet supprimé le 3 septembre 2026 (voir journal) : plus d'objet à
// cet endroit sur la nouvelle photo, Jean-Marc l'a rangé.
// Zone cliquable/survolable du micro : silhouette complète, x inchangé,
// y:[236,406] -> y:[249,419].
const MICRO_ZONE_X1 = 465, MICRO_ZONE_X2 = 543;
const MICRO_ZONE_Y1 = 249, MICRO_ZONE_Y2 = 419;
// Centre du rond noir en haut du micro (grille), pour l'icône seule :
// (512, 255) -> (512, 268).
const ICONE_CENTRE_X = 512, ICONE_CENTRE_Y = 268;
const ICONE_TAILLE_FRAC = 22 / IMG_W;
// Pastille "nombre de contacts", sous le trou cerclé de métal du classeur
// Contacts : centre (58, 381) -> (58, 394).
const PASTILLE_CONTACTS_CENTRE_X = 58, PASTILLE_CONTACTS_CENTRE_Y = 394;
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
// (CLASSEUR_Y2 = 410), 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;
}
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 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)';
});
}
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>