(area: HTMLElement)
| 180 | // ── Overview ── |
| 181 | |
| 182 | function renderOverview(area: HTMLElement): void { |
| 183 | const { ready, total } = getTotalProgress(); |
| 184 | const pct = total > 0 ? (ready / total) * 100 : 0; |
| 185 | const circumference = 2 * Math.PI * 40; |
| 186 | const dashOffset = circumference - (pct / 100) * circumference; |
| 187 | const ringColor = ready === total ? 'var(--settings-green)' : ready > 0 ? 'var(--settings-blue)' : 'var(--settings-yellow)'; |
| 188 | |
| 189 | const wmState = getSecretState('WORLDMONITOR_API_KEY'); |
| 190 | const wmStatusText = wmState.present ? 'Active' : 'Not set'; |
| 191 | const wmStatusClass = wmState.present ? 'ok' : 'warn'; |
| 192 | const alreadyRegistered = false; // Force-show form for email testing |
| 193 | |
| 194 | const catCards = SETTINGS_CATEGORIES.map(cat => { |
| 195 | const { ready: catReady, total: catTotal } = getFeatureStatusCounts(cat); |
| 196 | const cls = catReady === catTotal ? 'ov-cat-ok' : catReady > 0 ? 'ov-cat-partial' : 'ov-cat-warn'; |
| 197 | return `<button class="settings-ov-cat ${cls}" data-section="${cat.id}"> |
| 198 | <span class="settings-ov-cat-label">${escapeHtml(cat.label)}</span> |
| 199 | <span class="settings-ov-cat-count">${catReady}/${catTotal} ready</span> |
| 200 | </button>`; |
| 201 | }).join(''); |
| 202 | |
| 203 | area.innerHTML = ` |
| 204 | <div class="settings-overview"> |
| 205 | <div class="settings-ov-progress"> |
| 206 | <svg class="settings-ov-ring" viewBox="0 0 100 100" width="120" height="120"> |
| 207 | <circle cx="50" cy="50" r="40" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="8"/> |
| 208 | <circle cx="50" cy="50" r="40" fill="none" stroke="${ringColor}" stroke-width="8" |
| 209 | stroke-linecap="round" stroke-dasharray="${circumference}" stroke-dashoffset="${dashOffset}" |
| 210 | transform="rotate(-90 50 50)" style="transition:stroke-dashoffset 0.6s ease"/> |
| 211 | </svg> |
| 212 | <div class="settings-ov-ring-text"> |
| 213 | <span class="settings-ov-ring-num">${ready}</span> |
| 214 | <span class="settings-ov-ring-label">of ${total} ready</span> |
| 215 | </div> |
| 216 | </div> |
| 217 | <div class="settings-ov-cats">${catCards}</div> |
| 218 | </div> |
| 219 | |
| 220 | <div class="settings-ov-license"> |
| 221 | <section class="wm-section"> |
| 222 | <h2 class="wm-section-title">${t('modals.settingsWindow.worldMonitor.apiKey.title')}</h2> |
| 223 | <p class="wm-section-desc">${t('modals.settingsWindow.worldMonitor.apiKey.description')}</p> |
| 224 | <div class="wm-key-row"> |
| 225 | <div class="wm-input-wrap"> |
| 226 | <input type="password" class="wm-input" data-wm-key-input |
| 227 | placeholder="${t('modals.settingsWindow.worldMonitor.apiKey.placeholder')}" |
| 228 | autocomplete="off" spellcheck="false" |
| 229 | ${wmState.present ? `value="${MASKED_SENTINEL}"` : ''} /> |
| 230 | <button type="button" class="wm-toggle-vis" data-wm-toggle title="Show/hide">👁</button> |
| 231 | </div> |
| 232 | <span class="wm-badge ${wmStatusClass}">${wmStatusText}</span> |
| 233 | </div> |
| 234 | </section> |
| 235 | |
| 236 | <div class="wm-divider"><span>${t('modals.settingsWindow.worldMonitor.dividerOr')}</span></div> |
| 237 | |
| 238 | <section class="wm-section"> |
| 239 | <h2 class="wm-section-title">${t('modals.settingsWindow.worldMonitor.register.title')}</h2> |
no test coverage detected