feat(field-app): refonte accessibilité terrain (gros texte/contraste/cibles + flux guidé)
Priorité accessibilité (usage terrain, gants, luminosité variable — utilisable par une personne âgée / focus TDAH) : - Type scale +, contraste renforcé (sous-textes #94a3b8→#aebccf), cibles tactiles ≥54-58px (action principale, boutons d'action, cartes). - Liste : emphase du PROCHAIN arrêt (ruban vert + bordure/halo), compteur « X de Y terminé(s) », arrêts terminés atténués. - Détail : NAV + action principale au-dessus de la ligne de flottaison — « 🧭 Démarrer l'itinéraire » (bouton bleu proéminent) puis « 📍 Je suis arrivé » (grand vert) + phrase d'aide selon l'état ; carte descendue en référence ; Street View en lien discret ; boutons Commenter/Photo/Appareil agrandis (icône+libellé empilés). - focus-visible + micro-feedback tactile (reduced-motion respecté). Vérifié live (Playwright mobile, token tech réel) : liste + détail rendus, carte OSM/MapLibre (0 Mapbox). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5dd38eb79b
commit
c1a240023b
|
|
@ -5,28 +5,37 @@
|
||||||
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel=stylesheet>
|
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel=stylesheet>
|
||||||
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
|
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
|
||||||
<style>
|
<style>
|
||||||
*{box-sizing:border-box}body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;margin:0;background:#0f172a;color:#e2e8f0}
|
/* Accessibilité terrain (usage par temps/gants/lumière variables) : gros texte, fort contraste, grandes cibles tactiles,
|
||||||
.wrap{max-width:640px;margin:0 auto;padding:12px;padding-bottom:80px}
|
une action principale claire par écran. */
|
||||||
.card{background:#1e293b;border-radius:14px;padding:14px;margin-bottom:10px}
|
*{box-sizing:border-box}body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;margin:0;background:#0f172a;color:#e8eef6;font-size:17px;line-height:1.45;-webkit-text-size-adjust:100%}
|
||||||
h1{font-size:17px;margin:0 0 2px}.sub{color:#94a3b8;font-size:13px}
|
.wrap{max-width:640px;margin:0 auto;padding:14px;padding-bottom:88px}
|
||||||
.badge{font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px}
|
.card{background:#1e293b;border-radius:14px;padding:16px;margin-bottom:12px}
|
||||||
.b-todo{background:#334155;color:#cbd5e1}.b-run{background:#064e3b;color:#6ee7b7}.b-done{background:#1e3a8a;color:#bfdbfe}
|
h1{font-size:21px;margin:0 0 3px;font-weight:800}.sub{color:#aebccf;font-size:14.5px}
|
||||||
.job{display:flex;gap:10px;align-items:center;cursor:pointer}
|
.badge{font-size:12.5px;font-weight:700;padding:3px 11px;border-radius:20px;white-space:nowrap}
|
||||||
.map{height:230px;border-radius:10px;overflow:hidden;margin:8px 0}
|
.b-todo{background:#334155;color:#e2e8f0}.b-run{background:#065f46;color:#a7f3d0}.b-done{background:#1e3a8a;color:#bfdbfe}
|
||||||
.big{font-size:16px;font-weight:700;padding:15px;border-radius:12px;text-align:center;border:0;width:100%;color:#fff}
|
.job{display:flex;gap:12px;align-items:center;cursor:pointer;min-height:60px}
|
||||||
|
.job-next{border:2px solid #16a34a;box-shadow:0 0 0 3px rgba(22,163,74,.15)}
|
||||||
|
.job-done{opacity:.55}
|
||||||
|
.nextrib{display:inline-block;font-size:11px;font-weight:800;letter-spacing:.06em;color:#052e16;background:#4ade80;border-radius:6px;padding:1px 7px;margin-bottom:5px}
|
||||||
|
.map{height:190px;border-radius:12px;overflow:hidden;margin:10px 0}
|
||||||
|
.big{font-size:19px;font-weight:800;padding:18px;border-radius:14px;text-align:center;border:0;width:100%;color:#fff;min-height:58px}
|
||||||
.start{background:#16a34a}.stop{background:#ea580c}.doneb{background:#1e40af}
|
.start{background:#16a34a}.stop{background:#ea580c}.doneb{background:#1e40af}
|
||||||
.dnote{display:none;margin-top:8px;background:#fff7ed;border:1px solid #fdba74;border-radius:10px;padding:10px;color:#7c2d12;font-size:14px;line-height:1.4}
|
.hint{text-align:center;color:#aebccf;font-size:13.5px;margin-top:8px}
|
||||||
.dnote .skl{margin-top:5px;font-size:12px;color:#9a3412}
|
.navbtn{display:block;width:100%;background:#2563eb;color:#fff;font-weight:800;font-size:17px;padding:16px;border:0;border-radius:14px;text-align:center;text-decoration:none;margin:10px 0;min-height:54px}
|
||||||
.acts{display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-top:10px}
|
.dnote{display:none;margin-top:10px;background:#fff7ed;border:1px solid #fdba74;border-radius:12px;padding:12px;color:#7c2d12;font-size:15px;line-height:1.45}
|
||||||
.acts a,.acts button{background:#334155;color:#e2e8f0;border:0;border-radius:10px;padding:12px 6px;font-size:13px;text-align:center;text-decoration:none;font-weight:600}
|
.dnote .skl{margin-top:6px;font-size:13.5px;color:#9a3412}
|
||||||
.top{display:flex;align-items:center;gap:10px;margin-bottom:8px}
|
.acts{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;margin-top:12px}
|
||||||
.lnk{color:#60a5fa;text-decoration:none;font-size:13px}
|
.acts a,.acts button{background:#334155;color:#e8eef6;border:0;border-radius:12px;padding:15px 6px;font-size:14.5px;text-align:center;text-decoration:none;font-weight:700;min-height:56px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px}
|
||||||
.geo{font-size:12px;color:#94a3b8;margin-top:6px}.warn{color:#fca5a5}
|
.top{display:flex;align-items:center;gap:12px;margin-bottom:10px}
|
||||||
.modal{position:fixed;inset:0;background:rgba(0,0,0,.85);display:none;flex-direction:column;padding:14px;z-index:50}
|
.lnk{color:#7cb0ff;text-decoration:none;font-size:15px;font-weight:600}
|
||||||
.modal video{width:100%;border-radius:10px;background:#000}
|
.geo{font-size:13.5px;color:#aebccf;margin-top:8px}.warn{color:#fca5a5;font-weight:600}
|
||||||
.modal input{width:100%;padding:12px;border-radius:8px;border:1px solid #475569;background:#0f172a;color:#fff;font-size:15px;margin-top:8px}
|
.modal{position:fixed;inset:0;background:rgba(0,0,0,.9);display:none;flex-direction:column;padding:16px;z-index:50}
|
||||||
.toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:#0b3d2e;color:#6ee7b7;padding:10px 16px;border-radius:10px;font-size:13px;display:none;z-index:60}
|
.modal video{width:100%;border-radius:12px;background:#000}
|
||||||
.hbtn{background:#334155;border:0;color:#e2e8f0;border-radius:8px;padding:8px 10px;font-size:14px}
|
.modal input{width:100%;padding:14px;border-radius:10px;border:1px solid #475569;background:#0f172a;color:#fff;font-size:16px;margin-top:10px}
|
||||||
|
.toast{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);background:#0b3d2e;color:#a7f3d0;padding:12px 18px;border-radius:12px;font-size:14.5px;display:none;z-index:60;font-weight:600}
|
||||||
|
.hbtn{background:#334155;border:0;color:#e8eef6;border-radius:10px;padding:11px 14px;font-size:15px;font-weight:600}
|
||||||
|
:focus-visible{outline:3px solid #7cb0ff;outline-offset:2px}
|
||||||
|
@media (prefers-reduced-motion:no-preference){.big{transition:transform .08s}.big:active{transform:scale(.98)}}
|
||||||
</style></head>
|
</style></head>
|
||||||
<body>
|
<body>
|
||||||
<div class=wrap>
|
<div class=wrap>
|
||||||
|
|
@ -43,16 +52,17 @@
|
||||||
<h1 id=dsub></h1><div class=sub id=dcust></div>
|
<h1 id=dsub></h1><div class=sub id=dcust></div>
|
||||||
<div class=sub id=daddr style=margin-top:4px></div>
|
<div class=sub id=daddr style=margin-top:4px></div>
|
||||||
<div id=dnote class=dnote></div>
|
<div id=dnote class=dnote></div>
|
||||||
<div style=margin-top:6px>
|
<!-- Nav + action principale D'ABORD (au-dessus de la ligne de flottaison) ; carte = référence en dessous. -->
|
||||||
<a class=lnk id=ditin target=_blank>🧭 Itinéraire</a> <a class=lnk id=dsv target=_blank>👁 Street View</a>
|
<a class=navbtn id=ditin target=_blank>🧭 Démarrer l'itinéraire</a>
|
||||||
</div>
|
|
||||||
<div class=map id=map></div>
|
|
||||||
<div class=geo id=dgeo></div>
|
|
||||||
<button id=chrono class="big start" onclick=toggleChrono()>📍 Je suis arrivé</button>
|
<button id=chrono class="big start" onclick=toggleChrono()>📍 Je suis arrivé</button>
|
||||||
|
<div class=hint id=chronohint></div>
|
||||||
|
<div class=geo id=dgeo></div>
|
||||||
|
<div class=map id=map></div>
|
||||||
|
<div style="text-align:center;margin-top:8px"><a class=lnk id=dsv target=_blank>👁 Voir en Street View</a></div>
|
||||||
<div class=acts>
|
<div class=acts>
|
||||||
<button onclick=openComment()>💬 Commenter</button>
|
<button onclick=openComment()>💬<span>Commenter</span></button>
|
||||||
<button onclick=document.getElementById('photo').click()>📷 Photo</button>
|
<button onclick=document.getElementById('photo').click()>📷<span>Photo</span></button>
|
||||||
<button onclick=openScan()>📦 Appareil</button>
|
<button onclick=openScan()>📦<span>Appareil</span></button>
|
||||||
</div>
|
</div>
|
||||||
<div id=cmtbox style="display:none;margin-top:8px;background:#0f172a;border:1px solid #334155;border-radius:10px;padding:10px;text-align:left">
|
<div id=cmtbox style="display:none;margin-top:8px;background:#0f172a;border:1px solid #334155;border-radius:10px;padding:10px;text-align:left">
|
||||||
<div style="font-size:12px;font-weight:700;color:#94a3b8;margin-bottom:4px">💬 Fil du ticket</div>
|
<div style="font-size:12px;font-weight:700;color:#94a3b8;margin-bottom:4px">💬 Fil du ticket</div>
|
||||||
|
|
@ -104,10 +114,13 @@ async function load(){
|
||||||
$('jobs').innerHTML='<span class=warn>Lien invalide ou expiré.</span>';
|
$('jobs').innerHTML='<span class=warn>Lien invalide ou expiré.</span>';
|
||||||
}
|
}
|
||||||
function renderList(){
|
function renderList(){
|
||||||
if(!JOBS.length){$('jobs').innerHTML='Aucune intervention.';return}
|
if(!JOBS.length){$('jobs').innerHTML='<div class=sub>Aucune intervention aujourd\'hui.</div>';$('bestroute').style.display='none';return}
|
||||||
$('jobs').innerHTML='';
|
const done=JOBS.filter(j=>j.ended).length; const nextIdx=JOBS.findIndex(j=>!j.ended); // 1er arrêt non terminé = « prochain »
|
||||||
JOBS.forEach((j,i)=>{const d=document.createElement('div');d.className='card job';d.onclick=()=>openDetail(i);
|
$('jobs').innerHTML='<div class=sub style="margin:2px 0 12px">'+done+' de '+JOBS.length+' terminé(s)</div>';
|
||||||
d.innerHTML='<div style=flex:1><div style=font-weight:600>'+esc(dec(j.subject)||'Job')+'</div><div class=sub>'+(j.start?j.start+' · ':'')+esc(dec(j.address))+'</div></div>'+badge(j);
|
JOBS.forEach((j,i)=>{const d=document.createElement('div');
|
||||||
|
d.className='card job'+(i===nextIdx?' job-next':'')+(j.ended?' job-done':'');
|
||||||
|
d.onclick=()=>openDetail(i);
|
||||||
|
d.innerHTML='<div style=flex:1>'+(i===nextIdx?'<div class=nextrib>PROCHAIN</div>':'')+'<div style="font-weight:700;font-size:16px">'+esc(dec(j.subject)||'Job')+'</div><div class=sub>'+(j.start?j.start+' · ':'')+esc(dec(j.address))+'</div></div>'+badge(j);
|
||||||
$('jobs').appendChild(d)});
|
$('jobs').appendChild(d)});
|
||||||
// Bouton « meilleur trajet » dès qu'il reste ≥2 arrêts géolocalisés non terminés (sinon 1 seul → « Itinéraire » du détail suffit).
|
// Bouton « meilleur trajet » dès qu'il reste ≥2 arrêts géolocalisés non terminés (sinon 1 seul → « Itinéraire » du détail suffit).
|
||||||
const stops=JOBS.filter(j=>!j.ended&&j.lat&&Math.abs(+j.lat)>0.01);
|
const stops=JOBS.filter(j=>!j.ended&&j.lat&&Math.abs(+j.lat)>0.01);
|
||||||
|
|
@ -144,9 +157,10 @@ function openDetail(i){
|
||||||
$('dsv').href=hasLL?('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+CUR.lat+','+CUR.lon):'#';
|
$('dsv').href=hasLL?('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+CUR.lat+','+CUR.lon):'#';
|
||||||
setChrono();initMap(hasLL);startWatch(hasLL);
|
setChrono();initMap(hasLL);startWatch(hasLL);
|
||||||
}
|
}
|
||||||
function setChrono(){const b=$('chrono');if(CUR.ended){b.className='big doneb';b.textContent='✅ Terminé'+(CUR.minutes!=null?' · '+CUR.minutes+' min':'');b.disabled=true}
|
function setChrono(){const b=$('chrono');const h=$('chronohint');
|
||||||
else if(CUR.started){b.className='big stop';b.textContent='⏹ J\'ai terminé';b.disabled=false}
|
if(CUR.ended){b.className='big doneb';b.textContent='✅ Terminé'+(CUR.minutes!=null?' · '+CUR.minutes+' min':'');b.disabled=true;if(h)h.textContent='Intervention terminée.'}
|
||||||
else{b.className='big start';b.textContent='📍 Je suis arrivé';b.disabled=false}}
|
else if(CUR.started){b.className='big stop';b.textContent='⏹ J\'ai terminé';b.disabled=false;if(h)h.textContent='Appuyez quand le travail est terminé.'}
|
||||||
|
else{b.className='big start';b.textContent='📍 Je suis arrivé';b.disabled=false;if(h)h.textContent='Appuyez en arrivant (détecté aussi automatiquement).'}}
|
||||||
async function toggleChrono(){
|
async function toggleChrono(){
|
||||||
let pos=null;try{pos=await getPos()}catch(e){}
|
let pos=null;try{pos=await getPos()}catch(e){}
|
||||||
const type=CUR.started?'geo_exit':'geo_enter';
|
const type=CUR.started?'geo_exit':'geo_enter';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user