| 154 | } |
| 155 | |
| 156 | function buildPanels() { |
| 157 | const grid = document.getElementById('grid'); |
| 158 | for (const panel of PANELS) { |
| 159 | const section = el('section', `panel span-${panel.span}`); |
| 160 | section.id = `panel-${panel.id}`; |
| 161 | section.dataset.panel = panel.id; |
| 162 | section.dataset.state = 'loading'; |
| 163 | |
| 164 | const head = el('div', 'panel-head'); |
| 165 | head.append(el('h2', null, panel.title)); |
| 166 | const figure = el('p', 'panel-figure'); |
| 167 | figure.dataset.role = 'figure'; |
| 168 | head.append(figure); |
| 169 | section.append(head); |
| 170 | |
| 171 | if (panel.note) section.append(el('p', 'panel-note', panel.note)); |
| 172 | |
| 173 | const body = el('div', 'panel-body'); |
| 174 | body.dataset.role = 'body'; |
| 175 | if (panel.kind === 'chart') { |
| 176 | const wrap = el('div', 'chart-wrap'); |
| 177 | const canvas = document.createElement('canvas'); |
| 178 | canvas.dataset.role = 'canvas'; |
| 179 | // Chart.js renders to canvas, so the accessible copy is the table twin |
| 180 | // below — say so rather than leaving a bare graphic. |
| 181 | canvas.setAttribute('role', 'img'); |
| 182 | canvas.setAttribute('aria-label', `${panel.title}. The same data is in the table below.`); |
| 183 | wrap.append(canvas); |
| 184 | body.append(wrap); |
| 185 | } else if (panel.kind === 'stat') { |
| 186 | const stat = el('div', 'stat'); |
| 187 | stat.dataset.role = 'stat'; |
| 188 | stat.append(el('p', 'stat-value'), el('p', 'stat-caption')); |
| 189 | body.append(stat); |
| 190 | } else if (panel.kind === 'funnel') { |
| 191 | const funnel = el('div', 'funnel'); |
| 192 | funnel.dataset.role = 'funnel'; |
| 193 | body.append(funnel); |
| 194 | } |
| 195 | |
| 196 | const status = el('p', 'panel-state'); |
| 197 | status.dataset.role = 'state'; |
| 198 | body.append(status); |
| 199 | section.append(body); |
| 200 | |
| 201 | const toggle = el('button', 'link', 'Show numbers'); |
| 202 | toggle.type = 'button'; |
| 203 | toggle.dataset.role = 'toggle'; |
| 204 | toggle.setAttribute('aria-expanded', 'false'); |
| 205 | const table = el('div', 'table-wrap'); |
| 206 | table.dataset.role = 'table'; |
| 207 | table.hidden = true; |
| 208 | toggle.addEventListener('click', () => { |
| 209 | const open = table.hidden; |
| 210 | table.hidden = !open; |
| 211 | toggle.textContent = open ? 'Hide numbers' : 'Show numbers'; |
| 212 | toggle.setAttribute('aria-expanded', String(open)); |
| 213 | if (open) state.openTables.add(panel.id); |