mirror of
https://github.com/JMP-MS/ASPIC.git
synced 2026-10-27 14:57:09 +00:00
1491 lines
80 KiB
HTML
1491 lines
80 KiB
HTML
<!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).
|
||
|
||
8 septembre 2026 — passage à plusieurs fonds interchangeables (voir
|
||
réflexions stratégiques). Jean-Marc a fourni un jeu d'images séparant
|
||
enfin ce qui était jusqu'ici une seule photo plate (ecran_fond.jpg,
|
||
bureau + classeurs + micro + carnet + téléphone fondus ensemble) en
|
||
deux calques réutilisables :
|
||
- avant_plan_bureau.png : classeurs, micro, carnet, téléphone et
|
||
plateau du bureau, en PNG transparent (fond réellement transparent,
|
||
vérifié) — IDENTIQUE quel que soit l'écran affiché.
|
||
- fond_bureau_1.jpg / fond_bureau_2.jpg / fond_bureau_3.jpg : les 3
|
||
décors interchangeables (liège actuel, tasseaux gris pour l'écran
|
||
assistants, tableau blanc pour les futurs tableaux de bord),
|
||
affichés SEULS derrière l'avant-plan, sans classeurs dessus.
|
||
Toutes les images font 1024×457 comme l'ancienne photo — même logique
|
||
de "contain" ci-dessous, appliquée à l'avant-plan ET au conteneur des
|
||
fonds ensemble (voir #fonds-container/#avant-plan plus bas), pour que
|
||
les deux calques restent alignés au pixel près quelle que soit la
|
||
proportion d'écran.
|
||
L'avant-plan étant un nouveau visuel (classeurs redessinés, pas la même
|
||
photo), toutes les coordonnées de classeurs/micro/pastille ci-dessous
|
||
ont dû être entièrement remesurées dessus (mesure par analyse pixel,
|
||
même exigence que d'habitude) — un premier passage, à affiner comme
|
||
toujours une fois vu en conditions réelles chez Jean-Marc.
|
||
Navigation : 2 flèches posées sur le bureau à droite (voir
|
||
#fleche-precedente/#fleche-suivante), 3 fonds codés en dur (décision de
|
||
Jean-Marc), pas de mémorisation du fond choisi d'une session à l'autre
|
||
(toujours le fond 1 par défaut), sens du glissement lié à la flèche
|
||
pressée (le fond n+1 entre par la droite en poussant l'actuel vers la
|
||
gauche, et inversement). */
|
||
@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 neutre pour les bandes laissées par "contain" quand la
|
||
proportion de l'écran diffère de celle des images (1024×457) — plus
|
||
de couleur unique "sampled sur le mur" possible depuis le 8 septembre
|
||
2026, chaque fond ayant sa propre teinte de mur. */
|
||
background:#2b2b2b;
|
||
position:relative;
|
||
}
|
||
/* Conteneur des fonds interchangeables (8 septembre 2026) : positionné et
|
||
dimensionné exactement comme l'était l'image unique auparavant (même
|
||
calcul "contain" dans positionner(), voir plus bas), mais désormais un
|
||
élément réel plutôt qu'un simple background CSS du body — nécessaire
|
||
pour pouvoir faire glisser un fond hors champ pendant qu'un autre entre
|
||
(voir changerFond()). overflow:hidden empêche le fond en cours de
|
||
transition de déborder pendant son glissement. */
|
||
#fonds-container{
|
||
position:fixed;
|
||
overflow:hidden;
|
||
}
|
||
/* Un fond = une image plein cadre du conteneur (même ratio 1024×457 que
|
||
l'avant-plan, donc pas de letterbox interne : 100%/100% suffit, jamais
|
||
de déformation). position:absolute pour que l'ancien et le nouveau
|
||
fond puissent coexister le temps du glissement (voir changerFond()). */
|
||
.fond-slide{
|
||
position:absolute;
|
||
inset:0;
|
||
background-size:100% 100%;
|
||
background-position:center;
|
||
background-repeat:no-repeat;
|
||
}
|
||
/* Avant-plan (classeurs + micro + carnet + téléphone + plateau du bureau) :
|
||
identique quel que soit le fond affiché derrière, voir consigne de
|
||
Jean-Marc du 8 septembre 2026. Toujours positionné/dimensionné
|
||
exactement comme #fonds-container (même calcul dans positionner()),
|
||
jamais animé — pointer-events:none pour laisser passer le clic vers les
|
||
zones cliquables réelles (classeur-zone, micro-desk, etc.), positionnées
|
||
par-dessus au même titre qu'avant. */
|
||
#avant-plan{
|
||
position:fixed;
|
||
width:100%;height:100%;
|
||
pointer-events:none;
|
||
display:block;
|
||
}
|
||
/* Flèches de navigation entre fonds (8 septembre 2026) : posées sur le
|
||
bureau à droite, taille volontairement modeste ("pas trop grosses",
|
||
Jean-Marc) — ARROW_TAILLE_FRAC plus bas dans le script, à ajuster une
|
||
fois vu en conditions réelles ("ça va dépendre du rendu", Jean-Marc).
|
||
pointer-events déjà "auto" par défaut (bouton), pas besoin de le forcer. */
|
||
.fleche-nav{
|
||
position:fixed;
|
||
border:none;background:transparent;padding:0;margin:0;
|
||
cursor:pointer;
|
||
display:block;
|
||
}
|
||
.fleche-nav img{
|
||
width:100%;height:100%;display:block;
|
||
}
|
||
.fleche-nav:hover{
|
||
filter:brightness(1.15);
|
||
}
|
||
/* É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;
|
||
}
|
||
/* Bouton "BO" (11 septembre 2026) : n'apparaît que pour un utilisateur
|
||
avec MIR_BO='O' (voir chargerIdentiteUtilisateur(), preferences.php) —
|
||
seul accès au BackOffice depuis la CRM, jamais d'URL à taper à la main.
|
||
hidden par défaut, retiré par JS uniquement si l'accès est accordé. */
|
||
#bouton-bo{
|
||
font:inherit;font-family:"Segoe UI","Arial",sans-serif;font-weight:700;
|
||
font-size:10px;background:#1B1B1B;color:#f2d94e;border:1px solid #444;
|
||
border-radius:4px;padding:3px 8px;cursor:pointer;
|
||
}
|
||
#bouton-bo[hidden]{display: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, 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).
|
||
Attachés au fond liège depuis le 8 septembre 2026 (retour de Jean-Marc :
|
||
"ils seront attachés au panneau de liège, ils partiront avec lui") : le
|
||
conteneur #postits-layer est déplacé (au sens JS, pas dupliqué) à
|
||
l'intérieur de la slide du fond liège (index 0) à chaque fois qu'elle
|
||
existe (voir creerFondSlide()/changerFond() dans le script) — les
|
||
post-its glissent donc avec ce fond précis pendant la transition, et
|
||
restent invisibles (hors DOM visible, simplement parqués dans une slide
|
||
retirée) tant qu'un autre fond est affiché. position:absolute (plus
|
||
fixed) car désormais positionnés par rapport à ce conteneur local, pas
|
||
par rapport à la fenêtre — voir positionner() (zoneLeft/zoneTop ne
|
||
rajoutent plus offX/offY). */
|
||
#postits-layer{
|
||
position:absolute;
|
||
inset:0;
|
||
}
|
||
.postit{
|
||
position:absolute;
|
||
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; }
|
||
|
||
/* Portraits des assistants, écran 2 (9 septembre 2026 — voir journal).
|
||
Même principe d'attachement que #postits-layer pour le fond liège :
|
||
#assistants-layer est déplacé (au sens JS) à l'intérieur de la slide du
|
||
fond "tasseaux gris" (index 1) à chaque fois qu'elle existe, voir
|
||
creerFondSlide()/changerFond() plus bas — invisible tant qu'un autre
|
||
fond est affiché, sans dupliquer sa logique de position. position:absolute
|
||
(pas fixed) : positionné par rapport à ce conteneur local (lui-même
|
||
inset:0 dans la slide, elle-même calée sur le rectangle "contain"), pas
|
||
par rapport à la fenêtre — voir positionner().
|
||
Nombre de colonnes non codé en dur (décision de Jean-Marc, 9 septembre
|
||
2026) : une carte par agent réellement renvoyé par agents.php, largeur
|
||
de colonne = largeur totale / nombre d'agents — s'adapte donc tout seul
|
||
si un agent est ajouté ou retiré, sans repasser par ce fichier. */
|
||
#assistants-layer{
|
||
position:absolute;
|
||
inset:0;
|
||
}
|
||
.assistant-carte{
|
||
position:absolute;
|
||
display:flex;
|
||
flex-direction:column;
|
||
align-items:center;
|
||
pointer-events:none; /* purement visuel pour l'instant, comme les post-its */
|
||
}
|
||
.assistant-cadre{
|
||
background:#fff;
|
||
box-shadow:2px 4px 8px rgba(0,0,0,.35);
|
||
box-sizing:border-box;
|
||
display:block;
|
||
}
|
||
.assistant-photo{
|
||
display:block;
|
||
width:100%;
|
||
height:100%;
|
||
object-fit:cover;
|
||
}
|
||
/* Repli visuel pour un agent sans AGN_Photo renseignée (ex. le temps que
|
||
Jean-Marc dépose la photo) : même esprit que le "?" utilisé dans les
|
||
essais visuels, jamais un cadre vide sans explication. */
|
||
.assistant-photo-repli{
|
||
display:flex;align-items:center;justify-content:center;
|
||
width:100%;height:100%;
|
||
background:#e0e0dc;
|
||
color:#9a9a95;
|
||
font-weight:700;
|
||
font-family:"Segoe UI","Arial",sans-serif;
|
||
}
|
||
.assistant-nom{
|
||
margin-top:6px;
|
||
font-weight:700;
|
||
color:#2a2a2a;
|
||
font-family:"Segoe UI","Arial",sans-serif;
|
||
white-space:nowrap;
|
||
line-height:1.15;
|
||
}
|
||
.assistant-fonction{
|
||
color:#3a3a3a;
|
||
font-weight:700; /* forcé en gras le 9 septembre 2026 (retour Jean-Marc) : la ligne passait inaperçue sur le fond tasseaux */
|
||
font-family:"Segoe UI","Arial",sans-serif;
|
||
white-space:nowrap;
|
||
line-height:1.15;
|
||
}
|
||
/* Panneau "Bureau des assistants" (9 septembre 2026) : même esprit que les
|
||
cadres photo (liseré blanc, ombre portée) mais fond gris plutôt que
|
||
blanc — posé une seule fois (pas par agent) dans l'espace qui reste
|
||
libre sous les 2 dernières colonnes (aujourd'hui Léon et Sophia, voir
|
||
positionner()), tant qu'il n'y a rien d'autre à y afficher. */
|
||
.bureau-panneau{
|
||
position:absolute;
|
||
box-sizing:border-box;
|
||
background:#cfcfc9;
|
||
border-style:solid;
|
||
border-color:#fff;
|
||
box-shadow:2px 4px 8px rgba(0,0,0,.35);
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:center;
|
||
text-align:center;
|
||
color:#2a2a2a;
|
||
font-weight:700;
|
||
font-family:"Segoe UI","Arial",sans-serif;
|
||
pointer-events:none;
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Fonds interchangeables + avant-plan fixe (8 septembre 2026, voir
|
||
commentaire d'en-tête). #fonds-container reste vide ici : rempli par
|
||
initialiserFonds() au chargement (1er fond) puis par changerFond() à
|
||
chaque clic sur une flèche. -->
|
||
<div id="fonds-container"></div>
|
||
<img id="avant-plan" src="avant_plan_bureau.png" alt="">
|
||
|
||
<!-- 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">•</span>
|
||
<span id="prenom-utilisateur"></span>
|
||
<span class="point-separateur" aria-hidden="true">•</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>
|
||
<button id="bouton-bo" type="button" hidden title="Ouvrir le BackOffice">BO</button>
|
||
</div>
|
||
|
||
<!-- Flèches de navigation entre fonds (8 septembre 2026) : posées sur le
|
||
bureau à droite (consigne de Jean-Marc), après tous les autres éléments
|
||
fixes pour rester cliquables au-dessus de tout (ordre du document =
|
||
ordre d'empilement, aucun z-index explicite ailleurs sur cette page). -->
|
||
<button id="fleche-precedente" class="fleche-nav" type="button" title="Écran précédent">
|
||
<img src="fleche_precedente.png" alt="Écran précédent">
|
||
</button>
|
||
<button id="fleche-suivante" class="fleche-nav" type="button" title="Écran suivant">
|
||
<img src="fleche_suivante.png" alt="Écran suivant">
|
||
</button>
|
||
|
||
|
||
<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).
|
||
// Remesuré le 8 septembre 2026 sur le nouvel avant-plan (avant_plan_bureau.png,
|
||
// classeurs redessinés — voir commentaire d'en-tête) : le bas passe de 410
|
||
// à 371 (ligne du bureau plus haute sur ce nouveau visuel, vérifiée sur
|
||
// toute la largeur de l'image — 100% des pixels opaques au-delà, contre
|
||
// 0% avant), et le haut de 231 à 236 (bas du petit "toit" incliné du
|
||
// classeur, mesuré par analyse pixel comme d'habitude).
|
||
// Remesuré une 2ᵉ fois le 8 septembre 2026 (soir) : Jean-Marc a recadré
|
||
// l'avant-plan (traces blanches sur le fond retirées, micro nettoyé), ce
|
||
// qui a décalé toute la composition d'environ 20px vers le bas — mêmes
|
||
// abscisses (vérifié par comparaison des silhouettes), toutes les
|
||
// ordonnées remesurées directement sur le nouveau fichier.
|
||
const CLASSEURS_Y1 = 257, CLASSEURS_Y2 = 393;
|
||
// Rectangle vertical utile de la partie blanche de l'étiquette (mesuré
|
||
// constant à 249-333 sur les 9 classeurs du nouvel avant-plan, 8 septembre
|
||
// 2026) : c'est cette hauteur, une fois convertie à l'écran, qui borne la
|
||
// largeur du texte tourné à -90° (voir calculerPoliceEtiquette() plus bas).
|
||
// Remesuré le 8 septembre 2026 (soir), même recadrage que ci-dessus.
|
||
const ETIQ_LABEL_Y1 = 269, ETIQ_LABEL_Y2 = 353;
|
||
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).
|
||
// Remesuré le 8 septembre 2026 sur le nouvel avant-plan (22-26px selon le
|
||
// classeur) : la même valeur basse (22) reste le bon choix par sécurité.
|
||
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.
|
||
// Remesuré intégralement le 8 septembre 2026 sur le nouvel avant-plan
|
||
// (avant_plan_bureau.png — 9 classeurs redessinés par Jean-Marc, mêmes
|
||
// libellés/ordre/href que la version précédente, seules les coordonnées
|
||
// changent). x1/x2 = limite de séparation entre classeurs (point le plus
|
||
// sombre mesuré dans l'intervalle entre deux étiquettes, pas juste le
|
||
// milieu géométrique — même principe que le 7 septembre). labelCx = centre
|
||
// du rectangle blanc de l'étiquette, mesuré indépendamment (les 9
|
||
// rectangles ne sont pas exactement centrés dans leur classeur).
|
||
// Nouvelle répartition (9 septembre 2026, réflexion stratégique de
|
||
// Jean-Marc — regroupement plus cohérent des rubriques, ex. Prospection
|
||
// fusionnée dans Contacts, En-cours+Journalier scindés différemment en
|
||
// Missions/Propositions/Livraisons/Journalier). Reprend exactement les 9
|
||
// positions physiques déjà mesurées sur l'avant-plan (x1/x2/labelCx
|
||
// inchangés, dans l'ordre des classeurs sur la photo) : seuls les
|
||
// libellés/ids/href changent, aucune nouvelle mesure de pixels nécessaire
|
||
// tant que l'avant-plan lui-même ne change pas.
|
||
const CLASSEURS = [
|
||
{id:'catalogue', label:'Catalogue', x1:44, x2:83, labelCx:63.5, href:null},
|
||
{id:'documents', label:'Documents', x1:83, x2:120, labelCx:101.0, href:null},
|
||
{id:'contacts', label:'Contacts', x1:120, x2:158, labelCx:138.5, href:'liste_contacts.html'},
|
||
{id:'missions', label:'Missions', x1:158, x2:197, labelCx:177.0, href:null},
|
||
{id:'propositions', label:'Propositions', x1:197, x2:239, labelCx:216.0, href:null},
|
||
{id:'livraisons', label:'Livraisons', x1:239, x2:280, labelCx:259.0, href:null},
|
||
{id:'factures', label:'Factures', x1:280, x2:321, labelCx:300.5, href:null},
|
||
{id:'journalier', label:'Journalier', x1:321, x2:363, labelCx:341.5, href:null},
|
||
{id:'gestion', label:'Gestion', x1:363, x2:415, labelCx:383.5, href:null},
|
||
];
|
||
|
||
// Zone cliquable/survolable du micro et centre de l'icône : remesurés le
|
||
// 8 septembre 2026 sur le nouvel avant-plan — micro d'un tout autre style
|
||
// (corps arrondi de studio, plus de petite grille ronde isolée en haut
|
||
// comme sur l'ancienne photo) : zone calée sur la silhouette entière
|
||
// (mesure directe des pixels non transparents), icône centrée sur le
|
||
// reflet du corps, repère visuel le plus naturel sur ce nouveau micro.
|
||
// Y1/Y2/icône remesurés le 8 septembre 2026 (soir), même recadrage
|
||
// (traces blanches/halo retirés autour du micro par Jean-Marc) — X
|
||
// inchangés (silhouette identique, seule la position verticale a bougé).
|
||
const MICRO_ZONE_X1 = 478, MICRO_ZONE_X2 = 549;
|
||
const MICRO_ZONE_Y1 = 271, MICRO_ZONE_Y2 = 393;
|
||
// X ramené vers la gauche le 8 septembre 2026 (retour de Jean-Marc après
|
||
// essai réel : le décalage à droite du 8 septembre précédent allait trop
|
||
// loin) — Y inchangé (remonté juste avant, jugé bon depuis).
|
||
const ICONE_CENTRE_X = 515, ICONE_CENTRE_Y = 288;
|
||
const ICONE_TAILLE_FRAC = 22 / IMG_W;
|
||
// Pastille "nombre de contacts" — remesurée le 8 septembre 2026 sur le
|
||
// classeur Contacts du nouvel avant-plan (2ᵉ classeur, labelCx=101).
|
||
// Trou de préhension nettement plus bas et plus proche du bureau que sur
|
||
// l'ancienne photo (quasiment au ras de CLASSEURS_Y2=371, sans place pour
|
||
// la caler "en dessous" comme avant) : posée à la place juste au-dessus du
|
||
// trou, sous l'étiquette (ETIQ_LABEL_Y2=333) — premier essai, à revoir
|
||
// avec Jean-Marc après un rendu réel si trop proche du trou ou du texte.
|
||
// Y remesuré le 8 septembre 2026 (soir), même recadrage.
|
||
// Redescendue un peu le 8 septembre 2026 (soir), retour de Jean-Marc
|
||
// après 6ᵉ test réel (trop remontée la fois précédente) : 401 → 404.
|
||
// X déplacé le 9 septembre 2026 : "Contacts" passe du 2ᵉ au 3ᵉ classeur
|
||
// dans la nouvelle répartition (voir tableau CLASSEURS) — X reprend le
|
||
// labelCx du nouvel emplacement (138.5) pour que la pastille reste sous
|
||
// le bon classeur ; Y inchangé (même hauteur de trou sur les 9 classeurs).
|
||
const PASTILLE_CONTACTS_CENTRE_X = 138.5, PASTILLE_CONTACTS_CENTRE_Y = 404;
|
||
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.
|
||
// Y2 remesuré le 8 septembre 2026 (nouvel avant-plan, CLASSEURS_Y2=371 —
|
||
// voir plus haut) : même marge de sécurité qu'avant l'utilisant.
|
||
// Y2 remesuré le 8 septembre 2026 (soir), même recadrage (CLASSEURS_Y2
|
||
// passé à 393) — même marge de sécurité qu'avant l'utilisant.
|
||
const ZONE_POSTITS_X1 = 717, ZONE_POSTITS_X2 = 1024;
|
||
const ZONE_POSTITS_Y1 = 6, ZONE_POSTITS_Y2 = 378;
|
||
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;
|
||
|
||
// -------- Flèches de navigation entre fonds (8 septembre 2026) --------
|
||
// Posées sur le bureau à droite (consigne de Jean-Marc), dans la zone
|
||
// vide du plateau au-delà du carnet/téléphone (x>668). Taille volontairement
|
||
// modeste ("pas trop grosses") — premier réglage, Jean-Marc a prévenu que
|
||
// la taille définitive "va dépendre du rendu" réel chez lui.
|
||
// Retouché le 8 septembre 2026 (soir), retour de Jean-Marc après premier
|
||
// test réel : remontées (trop près du bas), rapprochées l'une de l'autre,
|
||
// et collées plus à droite.
|
||
// Réduite d'environ 30% le 8 septembre 2026 (soir), retour de Jean-Marc
|
||
// après second test réel (34 → 24).
|
||
const FLECHE_TAILLE_FRAC = 24 / IMG_W;
|
||
// Flèche précédente réécartée un peu le 8 septembre 2026 (soir), retour
|
||
// de Jean-Marc après 4ᵉ test réel ("trop proches l'une de l'autre").
|
||
const FLECHE_PRECEDENTE_CENTRE_X = 962, FLECHE_CENTRE_Y = 410;
|
||
const FLECHE_SUIVANTE_CENTRE_X = 998;
|
||
|
||
// -------- Portraits des assistants, écran 2 (9 septembre 2026) --------
|
||
// Réglages issus des essais visuels validés par Jean-Marc (voir journal) :
|
||
// marge réduite au-dessus de la tête, visage recentré, cadrage descendant
|
||
// sur le buste, taille réduite de 15% par rapport au tout premier essai.
|
||
// ASSISTANT_PHOTO_RATIO doit rester cohérent avec le recadrage utilisé pour
|
||
// produire les fichiers assistant_<prenom>.jpg (outillage côté Claude) — à
|
||
// revoir ensemble si Jean-Marc fournit un jour des photos au format déjà
|
||
// différent.
|
||
const ASSISTANT_ZONE_TOP = 14; // marge haute, px de référence (IMG_H)
|
||
const ASSISTANT_PHOTO_H_MAX = 162; // hauteur max de la photo (hors liseré), px de référence
|
||
const ASSISTANT_PHOTO_RATIO = 0.657; // largeur / hauteur de la photo
|
||
const ASSISTANT_MAT = 5; // épaisseur du liseré blanc, px de référence
|
||
const ASSISTANT_COL_MARGE = 0.85; // fraction de la largeur de colonne réellement utilisable (reste = gouttière)
|
||
const ASSISTANT_NOM_POLICE_FRAC = 17 / IMG_W;
|
||
const ASSISTANT_FONCTION_POLICE_FRAC = 12 / IMG_W;
|
||
|
||
// Panneau "Bureau des assistants" (9 septembre 2026, retour Jean-Marc) :
|
||
// posé dans l'espace qui reste libre sous les 2 dernières colonnes de
|
||
// portraits (aujourd'hui Léon et Sophia), entre le bas du texte des
|
||
// cartes et le haut du plateau du bureau. BUREAU_PANNEAU_BAS reprend la
|
||
// même limite que CLASSEURS_Y2/MICRO_ZONE_Y2 plus haut (haut du plateau,
|
||
// commun à toute la largeur de l'image) avec la même petite marge de
|
||
// sécurité que ZONE_POSTITS_Y2.
|
||
const BUREAU_PANNEAU_BAS = 390; // px de référence (IMG_H)
|
||
const BUREAU_PANNEAU_GAP = 8; // espace entre le texte des cartes et le panneau, px de référence
|
||
const BUREAU_PANNEAU_POLICE_FRAC = 16 / IMG_W;
|
||
// Le panneau est un encadré ajusté au texte (mesuré, voir positionner()),
|
||
// pas une zone qui occupe tout l'espace disponible — cet espace ne sert
|
||
// qu'à centrer l'encadré (retour Jean-Marc, 9 septembre 2026).
|
||
const BUREAU_PANNEAU_PAD_H = 16; // marge intérieure horizontale, px de référence
|
||
const BUREAU_PANNEAU_PAD_V = 10; // marge intérieure verticale, px de référence
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
|
||
// Fonds interchangeables + avant-plan (8 septembre 2026) : occupent
|
||
// exactement le même rectangle "contain" que l'ancienne image unique,
|
||
// pour rester alignés au pixel près avec les classeurs/micro/pastille/
|
||
// post-its positionnés plus bas dans cette même fonction.
|
||
const fondsContainer = document.getElementById('fonds-container');
|
||
fondsContainer.style.left = offX + 'px';
|
||
fondsContainer.style.top = offY + 'px';
|
||
fondsContainer.style.width = dispW + 'px';
|
||
fondsContainer.style.height = dispH + 'px';
|
||
|
||
const avantPlan = document.getElementById('avant-plan');
|
||
avantPlan.style.left = offX + 'px';
|
||
avantPlan.style.top = offY + 'px';
|
||
avantPlan.style.width = dispW + 'px';
|
||
avantPlan.style.height = dispH + 'px';
|
||
|
||
// Flèches de navigation entre fonds (voir FLECHE_* plus haut).
|
||
const flecheTaille = FLECHE_TAILLE_FRAC * dispW;
|
||
const flechePrecedente = document.getElementById('fleche-precedente');
|
||
flechePrecedente.style.left = (offX + (FLECHE_PRECEDENTE_CENTRE_X / IMG_W) * dispW - flecheTaille / 2) + 'px';
|
||
flechePrecedente.style.top = (offY + (FLECHE_CENTRE_Y / IMG_H) * dispH - flecheTaille / 2) + 'px';
|
||
flechePrecedente.style.width = flecheTaille + 'px';
|
||
flechePrecedente.style.height = flecheTaille + 'px';
|
||
const flecheSuivante = document.getElementById('fleche-suivante');
|
||
flecheSuivante.style.left = (offX + (FLECHE_SUIVANTE_CENTRE_X / IMG_W) * dispW - flecheTaille / 2) + 'px';
|
||
flecheSuivante.style.top = (offY + (FLECHE_CENTRE_Y / IMG_H) * dispH - flecheTaille / 2) + 'px';
|
||
flecheSuivante.style.width = flecheTaille + 'px';
|
||
flecheSuivante.style.height = flecheTaille + 'px';
|
||
|
||
// 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.
|
||
// zoneLeft/zoneTop : plus de offX/offY depuis le 8 septembre 2026 — les
|
||
// post-its sont désormais positionnés à l'intérieur de #postits-layer,
|
||
// lui-même dans la slide du fond liège (inset:0 sur les deux), donc déjà
|
||
// calé sur le rectangle "contain" par la structure DOM elle-même (voir
|
||
// commentaire CSS #postits-layer plus haut).
|
||
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 = (ZONE_POSTITS_X1 / IMG_W) * dispW;
|
||
const zoneTop = (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)';
|
||
});
|
||
|
||
// Portraits des assistants (écran 2) : nombre de colonnes = nombre de
|
||
// cartes réellement créées par creerCartesAssistants() (voir plus bas),
|
||
// pas de valeur codée en dur — la largeur de colonne se recalcule donc
|
||
// toute seule si un agent est ajouté ou retiré côté ASP_Agents. scale
|
||
// convertit un px de référence (IMG_W/IMG_H) en px écran ; identique
|
||
// dans les deux sens puisque le rectangle "contain" respecte toujours le
|
||
// ratio IMG_W/IMG_H (voir calcul de dispW/dispH plus haut).
|
||
const cartesAssistants = coucheAssistants.querySelectorAll('.assistant-carte');
|
||
if (cartesAssistants.length > 0) {
|
||
const scale = dispW / IMG_W;
|
||
const colW = dispW / cartesAssistants.length;
|
||
const matPx = ASSISTANT_MAT * scale;
|
||
let photoH = ASSISTANT_PHOTO_H_MAX * scale;
|
||
// Contrainte de largeur : si la colonne est trop étroite pour la
|
||
// hauteur visée (beaucoup d'agents), on réduit la photo en proportion
|
||
// plutôt que de déborder sur la colonne voisine.
|
||
const largeurMaxCarte = colW * ASSISTANT_COL_MARGE;
|
||
if (photoH * ASSISTANT_PHOTO_RATIO + 2 * matPx > largeurMaxCarte) {
|
||
photoH = (largeurMaxCarte - 2 * matPx) / ASSISTANT_PHOTO_RATIO;
|
||
}
|
||
const photoW = photoH * ASSISTANT_PHOTO_RATIO;
|
||
const carteW = photoW + 2 * matPx;
|
||
const carteH = photoH + 2 * matPx;
|
||
const topPx = ASSISTANT_ZONE_TOP * scale;
|
||
const nomPx = ASSISTANT_NOM_POLICE_FRAC * dispW;
|
||
const fonctionPx = ASSISTANT_FONCTION_POLICE_FRAC * dispW;
|
||
|
||
cartesAssistants.forEach(function (carte, index) {
|
||
const cx = colW * index + colW / 2;
|
||
carte.style.left = (cx - carteW / 2) + 'px';
|
||
carte.style.top = topPx + 'px';
|
||
carte.style.width = carteW + 'px';
|
||
const cadre = carte.querySelector('.assistant-cadre');
|
||
cadre.style.width = carteW + 'px';
|
||
cadre.style.height = carteH + 'px';
|
||
cadre.style.padding = matPx + 'px';
|
||
const nomEl = carte.querySelector('.assistant-nom');
|
||
if (nomEl) nomEl.style.fontSize = nomPx + 'px';
|
||
const fonctionEl = carte.querySelector('.assistant-fonction');
|
||
if (fonctionEl) fonctionEl.style.fontSize = fonctionPx + 'px';
|
||
const repli = carte.querySelector('.assistant-photo-repli');
|
||
if (repli) repli.style.fontSize = (photoH * 0.3) + 'px';
|
||
});
|
||
|
||
// Panneau "Bureau des assistants" : la zone sous les 2 dernières
|
||
// colonnes (aujourd'hui Léon et Sophia) ne sert qu'à CENTRER
|
||
// l'encadré — l'encadré lui-même est ajusté au texte (mesuré via
|
||
// ctxMesure, voir plus haut), pas étiré sur toute la zone (retour
|
||
// Jean-Marc, 9 septembre 2026). Bas du texte des cartes calculé
|
||
// analytiquement (pas mesuré en DOM) : coucheAssistants peut être
|
||
// parquée hors document (fond non affiché), où
|
||
// offsetHeight/getBoundingClientRect renverraient 0.
|
||
if (cartesAssistants.length >= 2) {
|
||
const nomLigne = nomPx * 1.15; // line-height:1.15, voir .assistant-nom
|
||
const nomMargeHaut = 6 * scale; // margin-top:6px, voir .assistant-nom
|
||
const fonctionLigne = fonctionPx * 1.15; // line-height:1.15, voir .assistant-fonction
|
||
const basCarte = topPx + carteH + nomMargeHaut + nomLigne + fonctionLigne;
|
||
const zoneTop = basCarte + BUREAU_PANNEAU_GAP * scale;
|
||
const zoneBas = BUREAU_PANNEAU_BAS * scale;
|
||
const zoneGauche = colW * (cartesAssistants.length - 2);
|
||
const zoneLargeur = colW * 2;
|
||
const zoneCentreX = zoneGauche + zoneLargeur / 2;
|
||
const zoneCentreY = zoneTop + (zoneBas - zoneTop) / 2;
|
||
|
||
const panneauPolicePx = BUREAU_PANNEAU_POLICE_FRAC * dispW;
|
||
ctxMesure.font = '700 ' + panneauPolicePx + 'px "Segoe UI","Arial",sans-serif';
|
||
const texteLargeur = ctxMesure.measureText(panneauBureau.textContent).width;
|
||
const padH = BUREAU_PANNEAU_PAD_H * scale;
|
||
const padV = BUREAU_PANNEAU_PAD_V * scale;
|
||
const panneauLargeur = texteLargeur + 2 * padH;
|
||
const panneauHauteur = panneauPolicePx * 1.15 + 2 * padV;
|
||
|
||
panneauBureau.style.display = 'flex';
|
||
panneauBureau.style.left = (zoneCentreX - panneauLargeur / 2) + 'px';
|
||
panneauBureau.style.top = (zoneCentreY - panneauHauteur / 2) + 'px';
|
||
panneauBureau.style.width = panneauLargeur + 'px';
|
||
panneauBureau.style.height = panneauHauteur + 'px';
|
||
panneauBureau.style.borderWidth = matPx + 'px';
|
||
panneauBureau.style.fontSize = panneauPolicePx + 'px';
|
||
|
||
} else {
|
||
panneauBureau.style.display = 'none';
|
||
}
|
||
} else {
|
||
panneauBureau.style.display = 'none';
|
||
}
|
||
}
|
||
// -------- Fonds interchangeables (8 septembre 2026) --------
|
||
// 3 fonds codés en dur (décision de Jean-Marc, pas de liste extensible
|
||
// pour l'instant) : liège actuel, tasseaux gris (futur écran assistants,
|
||
// présentation pas encore décidée), tableau blanc (futurs tableaux de
|
||
// bord). Toujours fondIndex=0 au chargement, aucune mémorisation d'une
|
||
// session à l'autre (décision explicite de Jean-Marc).
|
||
const FONDS = ['fond_bureau_1.jpg', 'fond_bureau_2.jpg', 'fond_bureau_3.jpg'];
|
||
let fondActuel = 0;
|
||
let glissementEnCours = false;
|
||
const DUREE_GLISSEMENT_MS = 450;
|
||
|
||
// Conteneur des post-its personnels, attaché au fond liège (index 0) —
|
||
// voir commentaire CSS #postits-layer plus haut. Créé une seule fois, vide
|
||
// au départ (chargerMesPostits() le remplit plus bas, de façon asynchrone,
|
||
// qu'il soit affiché ou parqué à ce moment-là — sans incidence puisque
|
||
// positionner() peut être appelée sans risque sur un élément hors DOM
|
||
// visible). Une variable JS, jamais document.getElementById() : l'élément
|
||
// reste valable même temporairement détaché du document (parqué dans une
|
||
// ancienne slide retirée), ce que getElementById ne retrouverait pas de la
|
||
// même façon si la recherche se faisait après un tel retrait.
|
||
const couchePostits = document.createElement('div');
|
||
couchePostits.id = 'postits-layer';
|
||
|
||
// Portraits des assistants (écran 2, 9 septembre 2026) : même principe que
|
||
// couchePostits ci-dessus — un noeud créé une seule fois, rempli de façon
|
||
// asynchrone par chargerAgentsEcranAssistants() plus bas, déplacé (jamais
|
||
// dupliqué) entre les slides par creerFondSlide().
|
||
const coucheAssistants = document.createElement('div');
|
||
coucheAssistants.id = 'assistants-layer';
|
||
|
||
// Panneau "Bureau des assistants" (voir commentaire CSS .bureau-panneau et
|
||
// constantes BUREAU_PANNEAU_* plus haut) : contenu statique, créé une
|
||
// seule fois ici et jamais recréé par creerCartesAssistants() (qui ne
|
||
// touche qu'aux .assistant-carte) — seule sa position/taille est
|
||
// recalculée à chaque appel de positionner().
|
||
const panneauBureau = document.createElement('div');
|
||
panneauBureau.className = 'bureau-panneau';
|
||
panneauBureau.textContent = "L'équipe des collaborateurs";
|
||
coucheAssistants.appendChild(panneauBureau);
|
||
|
||
|
||
function creerFondSlide(index) {
|
||
const slide = document.createElement('div');
|
||
slide.className = 'fond-slide';
|
||
slide.style.backgroundImage = "url('" + FONDS[index] + "')";
|
||
// Le fond liège (index 0) embarque la couche des post-its, le fond
|
||
// tasseaux gris (index 1, écran assistants) la couche des portraits : un
|
||
// simple appendChild déplace le noeud existant (le retire au passage de
|
||
// son ancien parent, s'il en avait un) — pas de duplication, pas de perte
|
||
// du contenu déjà chargé.
|
||
if (index === 0) {
|
||
slide.appendChild(couchePostits);
|
||
} else if (index === 1) {
|
||
slide.appendChild(coucheAssistants);
|
||
}
|
||
return slide;
|
||
}
|
||
|
||
// Affiche le tout premier fond au chargement, sans transition.
|
||
function initialiserFonds() {
|
||
const conteneur = document.getElementById('fonds-container');
|
||
conteneur.appendChild(creerFondSlide(fondActuel));
|
||
}
|
||
|
||
// direction : +1 (flèche "suivant", le fond n+1 entre par la droite en
|
||
// poussant l'actuel vers la gauche) ou -1 (flèche "précédent", l'inverse)
|
||
// — sens du glissement lié à la flèche pressée, consigne explicite de
|
||
// Jean-Marc. Boucle sur les 3 fonds (n+1 après le 3ᵉ revient au 1er).
|
||
function changerFond(direction) {
|
||
if (glissementEnCours) return;
|
||
glissementEnCours = true;
|
||
const conteneur = document.getElementById('fonds-container');
|
||
const ancienneSlide = conteneur.querySelector('.fond-slide');
|
||
const nouvelIndex = (fondActuel + direction + FONDS.length) % FONDS.length;
|
||
const nouvelleSlide = creerFondSlide(nouvelIndex);
|
||
// Position de départ de la nouvelle slide (hors champ, côté d'où elle
|
||
// doit entrer), posée sans transition avant d'être ajoutée au DOM.
|
||
nouvelleSlide.style.transform = 'translateX(' + (direction > 0 ? '100%' : '-100%') + ')';
|
||
conteneur.appendChild(nouvelleSlide);
|
||
// Forcer un reflow avant d'activer la transition, sinon le navigateur
|
||
// regroupe la position de départ et la position d'arrivée en une seule
|
||
// étape (pas de glissement visible).
|
||
void nouvelleSlide.offsetWidth;
|
||
nouvelleSlide.style.transition = 'transform ' + DUREE_GLISSEMENT_MS + 'ms ease';
|
||
ancienneSlide.style.transition = 'transform ' + DUREE_GLISSEMENT_MS + 'ms ease';
|
||
requestAnimationFrame(function () {
|
||
nouvelleSlide.style.transform = 'translateX(0)';
|
||
ancienneSlide.style.transform = 'translateX(' + (direction > 0 ? '-100%' : '100%') + ')';
|
||
});
|
||
ancienneSlide.addEventListener('transitionend', function fin() {
|
||
ancienneSlide.removeEventListener('transitionend', fin);
|
||
ancienneSlide.remove();
|
||
glissementEnCours = false;
|
||
});
|
||
fondActuel = nouvelIndex;
|
||
}
|
||
|
||
document.getElementById('fleche-precedente').addEventListener('click', function () { changerFond(-1); });
|
||
document.getElementById('fleche-suivante').addEventListener('click', function () { changerFond(1); });
|
||
|
||
initialiserFonds();
|
||
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;
|
||
// Bouton "BO" (11 septembre 2026) : n'apparaît que si la session porte
|
||
// l'accès (USR_BackOffice, posé par aspic.php depuis MIR_BO) — voir
|
||
// commentaire CSS #bouton-bo.
|
||
if (prefs.USR_BackOffice) {
|
||
document.getElementById('bouton-bo').hidden = false;
|
||
}
|
||
} catch (erreur) {
|
||
console.error('Erreur de chargement de preferences.php (identité) :', erreur);
|
||
}
|
||
}
|
||
chargerIdentiteUtilisateur();
|
||
document.getElementById('bouton-bo').addEventListener('click', function () {
|
||
window.location.href = 'backoffice.html';
|
||
});
|
||
|
||
// 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);
|
||
|
||
// Attaché à couchePostits (fond liège), pas à document.body depuis
|
||
// le 8 septembre 2026 — voir commentaire sur couchePostits plus haut.
|
||
couchePostits.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();
|
||
|
||
// Portraits des assistants, écran 2 (9 septembre 2026 — voir journal et
|
||
// réflexions stratégiques). Source : agents.php, qui sert déjà
|
||
// AGN_Prenom/AGN_Fonction pour le pilotage vocal — AGN_Photo (nom de
|
||
// fichier, ex. "assistant_matilda.jpg") et le tri par AGN_OrdreAffichage
|
||
// s'y ajoutent pour cet écran (voir commentaire d'en-tête de agents.php). Un agent sans
|
||
// AGN_Photo reste affiché (repli visuel "?", voir .assistant-photo-repli) :
|
||
// jamais de colonne manquante silencieusement. Nombre de colonnes non codé
|
||
// en dur : une carte par agent réellement renvoyé (voir positionner()).
|
||
function creerCartesAssistants() {
|
||
// Ne retire que les cartes (pas le panneau "Bureau des assistants",
|
||
// ajouté une seule fois à la création de coucheAssistants — voir
|
||
// panneauBureau plus haut).
|
||
coucheAssistants.querySelectorAll('.assistant-carte').forEach(function (el) { el.remove(); });
|
||
(agentsEcranAssistants || []).forEach(function (agent) {
|
||
const carte = document.createElement('div');
|
||
carte.className = 'assistant-carte';
|
||
|
||
const cadre = document.createElement('div');
|
||
cadre.className = 'assistant-cadre';
|
||
if (agent.AGN_Photo) {
|
||
const img = document.createElement('img');
|
||
img.className = 'assistant-photo';
|
||
img.alt = agent.AGN_Prenom || '';
|
||
img.src = agent.AGN_Photo;
|
||
cadre.appendChild(img);
|
||
} else {
|
||
const repli = document.createElement('div');
|
||
repli.className = 'assistant-photo-repli';
|
||
repli.textContent = '?';
|
||
cadre.appendChild(repli);
|
||
}
|
||
carte.appendChild(cadre);
|
||
|
||
const nom = document.createElement('div');
|
||
nom.className = 'assistant-nom';
|
||
nom.textContent = agent.AGN_Prenom || '';
|
||
carte.appendChild(nom);
|
||
|
||
if (agent.AGN_Fonction) {
|
||
const fonction = document.createElement('div');
|
||
fonction.className = 'assistant-fonction';
|
||
fonction.textContent = agent.AGN_Fonction;
|
||
carte.appendChild(fonction);
|
||
}
|
||
|
||
coucheAssistants.appendChild(carte);
|
||
});
|
||
positionner(); // place les cartes qui viennent d'être (re)créées
|
||
}
|
||
|
||
let agentsEcranAssistants = [];
|
||
async function chargerAgentsEcranAssistants() {
|
||
try {
|
||
const reponse = await fetch('agents.php');
|
||
if (!reponse.ok) throw new Error('HTTP ' + reponse.status);
|
||
const data = await reponse.json();
|
||
agentsEcranAssistants = data.agents || [];
|
||
creerCartesAssistants();
|
||
} catch (erreur) {
|
||
console.error('Erreur de chargement de agents.php (écran assistants) :', erreur);
|
||
}
|
||
}
|
||
chargerAgentsEcranAssistants();
|
||
|
||
|
||
// 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>
|