/* ros-app.js — Scenario A interactivity */ (function () { const data = window.ROS_DATA; /* -------------------------------------------------- THEME TOGGLE */ const themeToggle = document.getElementById('themeToggle'); const themeLabel = document.getElementById('themeLabel'); const stored = localStorage.getItem('ros-theme'); if (stored) document.documentElement.setAttribute('data-theme', stored); function syncThemeLabel() { const t = document.documentElement.getAttribute('data-theme') || 'light'; themeLabel.textContent = t === 'dark' ? 'Lyst' : 'Mørkt'; } syncThemeLabel(); themeToggle.addEventListener('click', () => { const cur = document.documentElement.getAttribute('data-theme') || 'light'; const next = cur === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem('ros-theme', next); syncThemeLabel(); drawRadar(); // redraw since some colors are computed }); /* -------------------------------------------------- SCREEN ROUTING */ const tabs = document.querySelectorAll('.screen-tab'); const screens = document.querySelectorAll('.screen'); function showScreen(name) { tabs.forEach(t => t.setAttribute('aria-current', t.dataset.screen === name ? 'true' : 'false')); screens.forEach(s => s.dataset.active = s.dataset.screen === name ? 'true' : 'false'); history.replaceState(null, '', '#' + name); } tabs.forEach(t => t.addEventListener('click', () => showScreen(t.dataset.screen))); document.querySelectorAll('[data-goto]').forEach(b => b.addEventListener('click', () => showScreen(b.dataset.goto))); const initial = (location.hash || '#matrix').slice(1); if (['intake','matrix','findings','summary'].includes(initial)) showScreen(initial); else showScreen('matrix'); /* -------------------------------------------------- MATRIX */ // 5x5 grid + axis ticks. Bottom-left origin: row 5 = konsekvens 5 (highest at top) const matrix = document.getElementById('rosMatrix'); let showResidual = false; function buildMatrix() { matrix.innerHTML = ''; // For each row from konsekvens=5 down to 1 for (let k = 5; k >= 1; k--) { // Y-tick const tick = document.createElement('div'); tick.className = 'matrix__y-tick'; tick.textContent = k; matrix.appendChild(tick); // 5 cells for (let s = 1; s <= 5; s++) { const cell = document.createElement('button'); cell.type = 'button'; const score = s * k; cell.className = 'matrix__cell'; cell.dataset.score = score; cell.dataset.s = s; cell.dataset.k = k; cell.setAttribute('aria-label', `Sannsynlighet ${s}, konsekvens ${k}, score ${score}`); const scoreLabel = document.createElement('span'); scoreLabel.className = 'matrix__cell-score'; scoreLabel.textContent = score; cell.appendChild(scoreLabel); const bubbles = document.createElement('span'); bubbles.className = 'matrix__cell-bubbles'; // Find threats in this cell const threats = data.threats.filter(t => { const sa = showResidual ? t.restrisiko.sannsynlighet : t.sannsynlighet; const ko = showResidual ? t.restrisiko.konsekvens : t.konsekvens; return sa === s && ko === k; }); threats.slice(0, 3).forEach(t => { const b = document.createElement('span'); b.className = 'matrix__bubble'; b.textContent = t.id; b.title = t.tittel; bubbles.appendChild(b); }); // Aggregate count from cellCounts (only when not showing residual) const extra = !showResidual ? (data.cellCounts[`${s},${k}`] || 0) : 0; const overflow = (threats.length > 3) ? (threats.length - 3) : 0; const totalExtra = extra + overflow; if (totalExtra > 0) { const c = document.createElement('span'); c.className = 'matrix__bubble matrix__bubble--count'; c.textContent = '+' + totalExtra; bubbles.appendChild(c); } cell.appendChild(bubbles); cell.addEventListener('click', () => { // Pick first named threat in this cell, else show count info if (threats.length) openThreatPanel(threats[0].id); }); matrix.appendChild(cell); } } // Bottom row: corner + 5 x-ticks const corner = document.createElement('div'); corner.className = 'matrix__corner'; matrix.appendChild(corner); for (let s = 1; s <= 5; s++) { const xt = document.createElement('div'); xt.className = 'matrix__x-tick'; xt.textContent = s; matrix.appendChild(xt); } } buildMatrix(); document.getElementById('toggleResidual').addEventListener('click', (e) => { showResidual = !showResidual; e.target.textContent = showResidual ? 'Vis nåværende risiko' : 'Vis restrisiko etter tiltak'; buildMatrix(); }); /* -------------------------------------------------- RADAR */ function drawRadar() { const svg = document.querySelector('.radar__svg #radarGrid'); if (!svg) return; svg.innerHTML = ''; const axes = data.radarAxes; const N = axes.length; const R = 100; // Grid rings for (let r = 1; r <= 5; r++) { const radius = (R / 5) * r; const points = []; for (let i = 0; i < N; i++) { const a = (-Math.PI / 2) + (i / N) * Math.PI * 2; points.push((Math.cos(a) * radius).toFixed(2) + ',' + (Math.sin(a) * radius).toFixed(2)); } const poly = document.createElementNS('http://www.w3.org/2000/svg', 'polygon'); poly.setAttribute('points', points.join(' ')); poly.setAttribute('class', 'radar__grid-line'); svg.appendChild(poly); } // Axes for (let i = 0; i < N; i++) { const a = (-Math.PI / 2) + (i / N) * Math.PI * 2; const line = document.createElementNS('http://www.w3.org/2000/svg', 'line'); line.setAttribute('x1', 0); line.setAttribute('y1', 0); line.setAttribute('x2', (Math.cos(a) * R).toFixed(2)); line.setAttribute('y2', (Math.sin(a) * R).toFixed(2)); line.setAttribute('class', 'radar__axis'); svg.appendChild(line); // Label const lx = Math.cos(a) * (R + 22); const ly = Math.sin(a) * (R + 22); const txt = document.createElementNS('http://www.w3.org/2000/svg', 'text'); txt.setAttribute('x', lx.toFixed(2)); txt.setAttribute('y', (ly + 4).toFixed(2)); txt.setAttribute('class', 'radar__label'); txt.textContent = axes[i].label; svg.appendChild(txt); } // Series helper function series(values, klass) { const points = []; for (let i = 0; i < N; i++) { const a = (-Math.PI / 2) + (i / N) * Math.PI * 2; const r = (values[i] / 5) * R; points.push((Math.cos(a) * r).toFixed(2) + ',' + (Math.sin(a) * r).toFixed(2)); } const poly = document.createElementNS('http://www.w3.org/2000/svg', 'polygon'); poly.setAttribute('points', points.join(' ')); poly.setAttribute('class', klass); svg.appendChild(poly); } series(axes.map(a => a.target), 'radar__series radar__series--target'); series(axes.map(a => a.current), 'radar__series'); // Scores list const dl = document.getElementById('radarScores'); if (dl) { dl.innerHTML = ''; axes.forEach(a => { const row = document.createElement('div'); row.className = 'radar__score-row'; row.innerHTML = `
${t.kilde}
${t.sannsynlighetBegrunnelse}
${t.konsekvensBegrunnelse}
${t.kilde}