From 42529043d323b5b7e13b7ad3650f0076d32d589c Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Wed, 24 Jun 2026 08:39:47 +0200 Subject: [PATCH] =?UTF-8?q?[FEAT]=20ARIA=20puntatore=20guida=20"Mostrami?= =?UTF-8?q?=20dov'=C3=A8"=20=E2=80=94=20spotlight=20verso=20il=20menu=20(F?= =?UTF-8?q?ase=20D)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Porting del pattern TRPG (CHANGE_aria_voice_pointer.md) sulla sidebar V2: - public/js/aria-guide.js (nuovo): DESTINATIONS (26 voci menu β†’ keyword IT), matchDestination(question), pointTo(nav,label) = overlay + spotlight pulsante + puntatore animato + coach-mark "Vai β†’"; Esc/click per chiudere; fallback navigate se la voce non Γ¨ visibile; rispetta prefers-reduced-motion; self-contained, no inline-CSS. - common-bi.js: data-nav="" sulle voci sidebar (aggancio puntatore). - common.js: inietta aria-guide.js dopo login; dopo ogni risposta ARIA, se la domanda combacia con una destinazione mostra il bottone "πŸ“ Mostrami dov'Γ¨: ". Frontend-only, additivo. Cache-buster common.js/common-bi.js -> 20260624f. Co-Authored-By: Claude Opus 4.8 (1M context) --- public/_app-bi-demo.html | 4 +- public/admin/index.html | 4 +- public/admin/organizations.html | 4 +- public/admin/users.html | 4 +- public/architecture.html | 4 +- public/assessment.html | 4 +- public/assets.html | 4 +- public/calendario.html | 4 +- public/companies.html | 4 +- public/competenze.html | 4 +- public/controlli-periodici.html | 4 +- public/cross-analysis.html | 4 +- public/dashboard.html | 4 +- public/guida.html | 4 +- public/incidents.html | 4 +- public/integrazioniext.html | 2 +- public/internal-audits.html | 4 +- public/isms.html | 4 +- public/js/aria-guide.js | 144 +++++++++++++++++++++++++++++ public/js/common-bi.js | 3 +- public/js/common.js | 26 ++++++ public/kb.html | 4 +- public/login.html | 2 +- public/management-review.html | 4 +- public/misure-requisiti.html | 4 +- public/mktg-api-doc.html | 2 +- public/normative.html | 4 +- public/onboarding.html | 2 +- public/organigramma.html | 4 +- public/policies.html | 4 +- public/raci.html | 4 +- public/register.html | 2 +- public/reports.html | 4 +- public/review-schedule.html | 4 +- public/risks.html | 4 +- public/settings.html | 4 +- public/stakeholder-activities.html | 4 +- public/stakeholders.html | 4 +- public/supply-chain.html | 4 +- public/training.html | 4 +- public/whistleblowing.html | 4 +- public/workflow.html | 4 +- 42 files changed, 245 insertions(+), 74 deletions(-) create mode 100644 public/js/aria-guide.js diff --git a/public/_app-bi-demo.html b/public/_app-bi-demo.html index 6c67608..9b0ec8e 100644 --- a/public/_app-bi-demo.html +++ b/public/_app-bi-demo.html @@ -71,8 +71,8 @@ - - + + - + @@ -328,7 +328,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -158,7 +158,7 @@ bootstrap.loadFonts('../vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -180,7 +180,7 @@ bootstrap.loadFonts('../vendor/bootstrap-italia/dist/fonts'); } - + - + - + - + @@ -165,7 +165,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -372,7 +372,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + - + - + - + - + - + diff --git a/public/controlli-periodici.html b/public/controlli-periodici.html index adcad53..8d9a6ee 100644 --- a/public/controlli-periodici.html +++ b/public/controlli-periodici.html @@ -137,9 +137,9 @@ - + - + - + @@ -393,7 +393,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -154,7 +154,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -1152,7 +1152,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -362,7 +362,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + + - + - + - + @@ -195,7 +195,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + diff --git a/public/js/aria-guide.js b/public/js/aria-guide.js new file mode 100644 index 0000000..6235701 --- /dev/null +++ b/public/js/aria-guide.js @@ -0,0 +1,144 @@ +/** + * NIS2 Agile - ARIA Guide (puntatore "dove andare") + * + * Porting del pattern TRPG (CHANGE_aria_voice_pointer.md): quando l'utente chiede + * "dove trovo X", ARIA mostra un PUNTATORE animato + spotlight sulla voce reale + * della sidebar V2 (common-bi.js) e offre "Vai β†’". + * + * Self-contained, vanilla, frontend-only. Nessuna dipendenza, niente inline-CSS. + * Aggancia le voci via [data-nav=""] (vedi common-bi.js). + * Rispetta prefers-reduced-motion. Espone window.AriaGuide. + */ +(function () { + 'use strict'; + if (window.AriaGuide) return; + + // Mappa destinazioni: nav (data-nav) β†’ { label, keywords[] }. label = voce reale del menu. + var DESTINATIONS = [ + { nav: 'assets', label: 'Inventario', section: 'Operativo', keywords: ['inventario', 'asset', 'beni', 'bene', 'sistemi informativi', 'rilevanza nis2'] }, + { nav: 'risks', label: 'Rischi', section: 'Gestione', keywords: ['rischio', 'rischi', 'valutazione del rischio', 'risk', 'matrice rischi', 'trattamento'] }, + { nav: 'incidents', label: 'Incidenti', section: 'Gestione', keywords: ['incidente', 'incidenti', 'notifica', 'art. 25', 'art.25', 'csirt', 'data breach', '24 ore', '72 ore'] }, + { nav: 'assessment', label: 'Gap Analysis', section: 'Principale', keywords: ['gap analysis', 'gap', 'assessment', 'questionario', 'autovalutazione', '80 domande'] }, + { nav: 'misure-requisiti', label: 'Misure e Requisiti', section: 'Principale', keywords: ['misure', 'requisiti', 'misura', 'requisito', 'controlli acn', 'determinazione acn'] }, + { nav: 'isms', label: 'Modello SGSI (ISO 27001)', section: 'Principale', keywords: ['sgsi', 'iso 27001', 'isms', 'soa', 'statement of applicability', 'modello organizzativo', 'modello sgsi'] }, + { nav: 'policies', label: 'Policy', section: 'Gestione', keywords: ['policy', 'politiche', 'politica', 'procedura', 'procedure', 'documento normativo'] }, + { nav: 'supply-chain', label: 'Supply Chain', section: 'Gestione', keywords: ['supply chain', 'fornitori', 'fornitore', 'catena di fornitura', 'terze parti'] }, + { nav: 'training', label: 'Formazione', section: 'Operativo', keywords: ['formazione', 'corsi', 'corso', 'training', 'awareness', 'sensibilizzazione'] }, + { nav: 'whistleblowing', label: 'Segnalazioni', section: 'Gestione', keywords: ['segnalazione', 'segnalazioni', 'whistleblowing', 'illeciti', 'canale segnalazione'] }, + { nav: 'normative', label: 'Normative', section: 'Gestione', keywords: ['normativa', 'normative', 'aggiornamenti normativi', 'decreto', 'feed normativo'] }, + { nav: 'stakeholders', label: 'Stakeholder', section: 'Gestione', keywords: ['stakeholder', 'portatori di interesse', 'mendelow', 'matrice stakeholder'] }, + { nav: 'stakeholder-activities', label: 'AttivitΓ  stakeholder', section: 'Gestione', keywords: ['attivitΓ  stakeholder', 'questionari stakeholder', 'azioni stakeholder'] }, + { nav: 'organigramma', label: 'Organigramma', section: 'Gestione', keywords: ['organigramma', 'ruoli', 'governance', 'raci', 'responsabilitΓ '] }, + { nav: 'competenze', label: 'Competenze', section: 'Gestione', keywords: ['competenze', 'skill', 'competenza', 'requisiti ruolo'] }, + { nav: 'dashboard', label: 'Dashboard', section: 'Principale', keywords: ['dashboard', 'panoramica', 'home', 'punteggio di compliance', 'compliance score', 'cruscotto'] }, + { nav: 'calendario', label: 'Calendario', section: 'Principale', keywords: ['calendario', 'scadenze', 'scadenziario', 'deadline'] }, + { nav: 'workflow', label: 'Compliance Journey', section: 'Principale', keywords: ['compliance journey', 'percorso di conformitΓ ', 'journey', 'roadmap'] }, + { nav: 'internal-audits', label: 'Audit interni', section: 'Operativo', keywords: ['audit interni', 'audit interno', '9.2', 'verifica interna'] }, + { nav: 'controlli-periodici', label: 'Controlli periodici', section: 'Operativo', keywords: ['controlli periodici', 'controllo periodico', 'monitoraggio controlli'] }, + { nav: 'management-review', label: 'Riesame di Direzione', section: 'Operativo', keywords: ['riesame di direzione', 'riesame', 'management review', '9.3'] }, + { nav: 'reports', label: 'Audit & Report', section: 'Operativo', keywords: ['report', 'reportistica', 'audit log', 'esporta', 'esportazione', 'audit & report'] }, + { nav: 'kb', label: 'Knowledge Base', section: 'Gestione', keywords: ['knowledge base', 'documenti caricati', 'carica documento', 'base di conoscenza'] }, + { nav: 'cross-analysis', label: 'AI Cross-Analysis', section: 'Gestione', keywords: ['cross analysis', 'cross-analysis', 'analisi incrociata'] }, + { nav: 'settings', label: 'Impostazioni', section: 'Sistema', keywords: ['impostazioni', 'settings', 'membri', 'permessi documentali', 'profilo'] }, + { nav: 'integrazioniext', label: 'Integrazioni Esterne', section: 'Sistema', keywords: ['integrazioni', 'integrazione', 'api esterne', 'webhook'] } + ]; + + function matchDestination(question) { + var q = ' ' + String(question || '').toLowerCase().replace(/[«»"'`]/g, ' ') + ' '; + var best = null, bestLen = 0; + for (var i = 0; i < DESTINATIONS.length; i++) { + var d = DESTINATIONS[i]; + for (var k = 0; k < d.keywords.length; k++) { + var kw = d.keywords[k]; + if (kw.length >= 3 && q.indexOf(kw) !== -1 && kw.length > bestLen) { + best = d; bestLen = kw.length; + } + } + } + return best; + } + + // ── CSS (iniettato una volta) ─────────────────────────────────────────── + var _css = false; + function injectCss() { + if (_css) return; _css = true; + var s = document.createElement('style'); + s.textContent = [ + '.aria-guide-ov{position:fixed;inset:0;z-index:10050;background:transparent}', + '.aria-guide-spot{position:fixed;z-index:10051;border:3px solid #0066CC;border-radius:10px;', + ' box-shadow:0 0 0 9999px rgba(15,23,42,.45),0 0 14px rgba(0,102,204,.7);pointer-events:none;', + ' transition:all .35s cubic-bezier(.4,0,.2,1);animation:aria-pulse 1.4s ease-in-out infinite}', + '@keyframes aria-pulse{0%,100%{box-shadow:0 0 0 9999px rgba(15,23,42,.45),0 0 0 0 rgba(0,102,204,.55)}', + ' 50%{box-shadow:0 0 0 9999px rgba(15,23,42,.45),0 0 0 8px rgba(0,102,204,0)}}', + '.aria-guide-ptr{position:fixed;z-index:10053;width:34px;height:34px;pointer-events:none;', + ' transition:left .6s cubic-bezier(.4,0,.2,1),top .6s cubic-bezier(.4,0,.2,1);filter:drop-shadow(0 2px 3px rgba(0,0,0,.4))}', + '.aria-guide-coach{position:fixed;z-index:10052;max-width:260px;background:#0b2a4a;color:#fff;', + ' padding:10px 12px;border-radius:10px;font:500 13px/1.45 system-ui,Arial,sans-serif;', + ' box-shadow:0 8px 24px rgba(0,0,0,.3)}', + '.aria-guide-coach b{color:#7ec6ff}', + '.aria-guide-coach .row{margin-top:8px;display:flex;gap:8px;justify-content:flex-end}', + '.aria-guide-coach button{border:none;border-radius:7px;padding:6px 12px;font:600 12px system-ui;cursor:pointer}', + '.aria-guide-go{background:#0066CC;color:#fff}.aria-guide-x{background:#1e3a5f;color:#cbd5e1}', + '@media (prefers-reduced-motion:reduce){.aria-guide-spot,.aria-guide-ptr{transition:none;animation:none}}' + ].join('\n'); + document.head.appendChild(s); + } + + var _open = null; + function dismiss() { + if (!_open) return; + _open.forEach(function (el) { if (el && el.parentNode) el.parentNode.removeChild(el); }); + document.removeEventListener('keydown', onKey, true); + _open = null; + } + function onKey(e) { if (e.key === 'Escape') dismiss(); } + + /** + * Mostra il puntatore sulla voce di menu `nav` (data-nav). Se la voce non Γ¨ + * visibile (sidebar mobile chiusa / pagina senza sidebar) naviga direttamente. + * @returns {boolean} true se ha mostrato la guida, false se ha fatto fallback. + */ + function pointTo(nav, label) { + dismiss(); + var el = document.querySelector('.sidebar-nav a[data-nav="' + nav + '"]'); + var href = el ? el.getAttribute('href') : (nav + '.html'); + var rect = el && el.getBoundingClientRect(); + // fallback: niente sidebar visibile β†’ vai diretto + if (!el || !rect || rect.width === 0 || rect.height === 0) { window.location.href = href; return false; } + + injectCss(); + var ov = document.createElement('div'); ov.className = 'aria-guide-ov'; + var spot = document.createElement('div'); spot.className = 'aria-guide-spot'; + var ptr = document.createElement('div'); ptr.className = 'aria-guide-ptr'; + ptr.innerHTML = ''; + var coach = document.createElement('div'); coach.className = 'aria-guide-coach'; + coach.innerHTML = 'Eccolo: ' + (label || nav) + '
È nel menu a sinistra. Vuoi che ti ci porti?' + + '
'; + + document.body.appendChild(ov); + document.body.appendChild(spot); + document.body.appendChild(ptr); + document.body.appendChild(coach); + + function place() { + var r = el.getBoundingClientRect(); + spot.style.left = (r.left - 4) + 'px'; spot.style.top = (r.top - 4) + 'px'; + spot.style.width = (r.width + 8) + 'px'; spot.style.height = (r.height + 8) + 'px'; + ptr.style.left = (r.right - 6) + 'px'; ptr.style.top = (r.top + r.height / 2) + 'px'; + var cTop = Math.min(r.top, window.innerHeight - 120); + coach.style.left = (r.right + 16) + 'px'; coach.style.top = Math.max(8, cTop) + 'px'; + } + // pointer parte dal centro schermo poi va alla voce (animazione) + ptr.style.left = (window.innerWidth / 2) + 'px'; ptr.style.top = (window.innerHeight / 2) + 'px'; + requestAnimationFrame(function () { requestAnimationFrame(place); }); + + ov.addEventListener('click', dismiss); + coach.querySelector('.aria-guide-x').addEventListener('click', dismiss); + coach.querySelector('.aria-guide-go').addEventListener('click', function () { window.location.href = href; }); + document.addEventListener('keydown', onKey, true); + _open = [ov, spot, ptr, coach]; + return true; + } + + window.AriaGuide = { pointTo: pointTo, matchDestination: matchDestination, dismiss: dismiss, DESTINATIONS: DESTINATIONS }; +})(); diff --git a/public/js/common-bi.js b/public/js/common-bi.js index 4ad9d3d..2fc41eb 100644 --- a/public/js/common-bi.js +++ b/public/js/common-bi.js @@ -136,7 +136,8 @@ var cls = isActive ? 'active' : ''; var aria = isActive ? ' aria-current="page"' : ''; var spanI18n = item.i18nKey ? ' data-i18n="' + item.i18nKey + '"' : ''; - html += '' + + var navKey = item.href.replace(/\.html$/, ''); // aggancio puntatore ARIA (aria-guide.js) + html += '' + item.icon + '' + item.name + '' + ''; }); diff --git a/public/js/common.js b/public/js/common.js index 2cf05dc..cbe5e8e 100644 --- a/public/js/common.js +++ b/public/js/common.js @@ -981,6 +981,15 @@ function switchLang(lang) { } } + // ── 1c. Inietta il modulo guida ARIA (puntatore/spotlight verso il menu) ── + if (!window.__ariaGuideLoaded) { + window.__ariaGuideLoaded = true; + var ag = document.createElement('script'); + ag.src = 'js/aria-guide.js?v=20260624f'; + ag.async = true; + document.body.appendChild(ag); + } + // ── 2. AI Chat FAB viola "ARIA" ───────────────────────────────────────── function ensureFab() { if (document.getElementById('ai-chat-fab')) return; @@ -1165,6 +1174,23 @@ function switchLang(lang) { pending = false; sessionStorage.setItem(STORAGE_KEY, JSON.stringify(history)); render(); + // Guida "Mostrami dov'Γ¨": se la domanda combacia con una voce di menu, + // offri il puntatore/spotlight verso la sidebar (porting TRPG aria-guide). + try { + var dest = (window.AriaGuide && AriaGuide.matchDestination) ? AriaGuide.matchDestination(text) : null; + if (dest) { + var msgsEl = document.getElementById('ai-chat-msgs'); + if (msgsEl) { + var cta = document.createElement('button'); + cta.type = 'button'; + cta.style.cssText = 'margin:6px 0 2px;align-self:flex-start;background:#eef5ff;color:#0066CC;border:1px solid #b3d4f5;border-radius:10px;padding:7px 12px;font:600 13px system-ui;cursor:pointer'; + cta.textContent = 'πŸ“ Mostrami dov’è: ' + dest.label; + cta.addEventListener('click', function () { if (window.AriaGuide) AriaGuide.pointTo(dest.nav, dest.label); }); + msgsEl.appendChild(cta); + msgsEl.scrollTop = msgsEl.scrollHeight; + } + } + } catch (e) { /* guida opzionale */ } } fab.onclick = function () { diff --git a/public/kb.html b/public/kb.html index 406e9b4..43df1da 100644 --- a/public/kb.html +++ b/public/kb.html @@ -151,7 +151,7 @@ - + @@ -161,7 +161,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + diff --git a/public/login.html b/public/login.html index d23c5b8..fa8157e 100644 --- a/public/login.html +++ b/public/login.html @@ -95,7 +95,7 @@ - + - + - + - + - + + diff --git a/public/normative.html b/public/normative.html index 735d618..11f935d 100644 --- a/public/normative.html +++ b/public/normative.html @@ -113,7 +113,7 @@ - + @@ -123,7 +123,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + - + - + diff --git a/public/policies.html b/public/policies.html index 0d91f85..174aeb7 100644 --- a/public/policies.html +++ b/public/policies.html @@ -334,7 +334,7 @@ - + @@ -344,7 +344,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + - + diff --git a/public/register.html b/public/register.html index 7a5dfa4..3f92cbc 100644 --- a/public/register.html +++ b/public/register.html @@ -269,7 +269,7 @@ - + - + @@ -454,7 +454,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + - + - + @@ -505,7 +505,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -699,7 +699,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + - + - + - + - + @@ -488,7 +488,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -303,7 +303,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + @@ -218,7 +218,7 @@ bootstrap.loadFonts('/vendor/bootstrap-italia/dist/fonts'); } - + - + - +