diff --git a/docs/superpowers/plans/2026-04-16-hardening-v2.0.1.md b/docs/superpowers/plans/2026-04-16-hardening-v2.0.1.md
new file mode 100644
index 0000000..b9a78b1
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-16-hardening-v2.0.1.md
@@ -0,0 +1,955 @@
+# Hellion NewTab v2.0.1 Hardening — Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Harden v2.0.0 with security fixes, widget event-system refactoring, i18n completeness, and code quality improvements.
+
+**Architecture:** Foundation-First — build the new widget event system first, then migrate widget modules onto it, then layer security, i18n, and quality fixes. Each task touches isolated files to avoid merge conflicts.
+
+**Tech Stack:** Vanilla JavaScript ES2020, CSS Custom Properties, Browser Extension Manifest V3, no build step, no npm.
+
+**Spec:** `docs/superpowers/specs/2026-04-16-hardening-v2.0.1-design.md`
+
+**Testing:** No automated test framework. Each task includes manual browser-based verification steps. Load the extension in Chrome (`chrome://extensions` → Developer mode → Load unpacked) after each task.
+
+---
+
+## File Map
+
+| File | Tasks | Changes |
+|---|---|---|
+| `src/js/widgets.js` | 1, 2 | Add event system (`_emitter`, `on`, `off`), dispatch events in `close`/`minimize`/`openWidget`, replace `setTimeout` with `transitionend` |
+| `src/js/calculator.js` | 3 | Replace monkey-patching (L692-728) with `WidgetManager.on()` listeners |
+| `src/js/timer.js` | 3 | Replace monkey-patching (L723-758) with `WidgetManager.on()` listeners |
+| `src/js/image-ref.js` | 3 | Replace monkey-patching (L463-498) with `WidgetManager.on()` listeners |
+| `src/js/settings.js` | 4 | Add `isValidBgUrl()`, validate in `applySettings()` and file upload + URL input handlers |
+| `src/js/data.js` | 5 | Add `isSafeUrl()`, immutable mapping, string length limits, Notes import via `Notes.init()` |
+| `src/js/state.js` | 6 | Remove `getFaviconUrl()` |
+| `src/js/boards.js` | 6 | Replace `
` favicon with local letter-div |
+| `src/css/main.css` | 6, 7 | Replace `.bm-favicon`/`.bm-favicon-fallback` with `.bm-favicon-local`, add `@supports not` fallback, add `--bg-solid-fallback` per theme |
+| `newtab.html` | 8 | Add 5x `data-i18n-title`, 3x `data-i18n` |
+| `src/js/i18n.js` | 8 | Add 10 new keys to `STRINGS.de` and `STRINGS.en` (8 i18n + 2 bgUrl validation) |
+| `src/js/app.js` | 9 | Store `setInterval` ID in variable |
+| `manifest.json` | 9 | Version bump to 2.0.1 |
+| `manifest.firefox.json` | 9 | Version bump to 2.0.1 |
+| `manifest.opera.json` | 9 | Version bump to 2.0.1 |
+| `CHANGELOG.md` | 9 | Add v2.0.1 entry |
+
+---
+
+### Task 1: Widget Event-System in WidgetManager
+
+**Files:**
+- Modify: `src/js/widgets.js:6-10` (add emitter + on/off)
+- Modify: `src/js/widgets.js:143-148` (close — dispatch event)
+
+- [ ] **Step 1: Add event emitter and on/off methods to WidgetManager**
+
+In `src/js/widgets.js`, add three new properties after `STORAGE_KEY: 'widgetStates',` (line 10):
+
+```javascript
+ /** @type {EventTarget} Internes Event-System fuer Widget-Lifecycle */
+ _emitter: new EventTarget(),
+
+ /**
+ * Event-Listener registrieren
+ * @param {string} event - z.B. 'widget:close', 'widget:minimize', 'widget:open'
+ * @param {Function} handler
+ */
+ on(event, handler) {
+ this._emitter.addEventListener(event, handler);
+ },
+
+ /**
+ * Event-Listener entfernen
+ * @param {string} event
+ * @param {Function} handler
+ */
+ off(event, handler) {
+ this._emitter.removeEventListener(event, handler);
+ },
+```
+
+- [ ] **Step 2: Dispatch `widget:close` event in close()**
+
+Replace the `close` method (lines 143-148):
+
+```javascript
+ close(id) {
+ const entry = this._widgets.get(id);
+ if (!entry) return;
+ entry.el.remove();
+ this._widgets.delete(id);
+ this._emitter.dispatchEvent(new CustomEvent('widget:close', { detail: { id } }));
+ },
+```
+
+Note: The event fires AFTER `el.remove()` and `_widgets.delete()`. Listeners must not access the widget entry.
+
+- [ ] **Step 3: Verify event system loads without errors**
+
+Reload the extension in the browser. Open the console (`F12`). Verify:
+- No JavaScript errors on load
+- `WidgetManager.on` is a function (type `WidgetManager.on` in console)
+- `WidgetManager._emitter` is an EventTarget
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/js/widgets.js
+git commit -m "refactor(widgets): add EventTarget-based lifecycle event system
+
+Add _emitter, on(), off() to WidgetManager. Dispatch widget:close event
+after close(). Foundation for removing monkey-patching from widget modules."
+```
+
+---
+
+### Task 2: Minimize with transitionend + openWidget event dispatch
+
+**Files:**
+- Modify: `src/js/widgets.js:154-163` (minimize)
+- Modify: `src/js/widgets.js:169-180` (openWidget)
+
+- [ ] **Step 1: Replace setTimeout with transitionend in minimize()**
+
+Replace the `minimize` method (lines 154-163):
+
+```javascript
+ async minimize(id) {
+ const entry = this._widgets.get(id);
+ if (!entry) return;
+ entry.state.open = false;
+ entry._minimizing = true;
+ entry.el.classList.add('widget-minimized');
+
+ entry.el.addEventListener('transitionend', function onEnd(e) {
+ if (e.target !== entry.el) return;
+ entry.el.removeEventListener('transitionend', onEnd);
+ if (entry._minimizing) {
+ entry.el.style.display = 'none';
+ }
+ entry._minimizing = false;
+ });
+
+ this._emitter.dispatchEvent(new CustomEvent('widget:minimize', { detail: { id } }));
+ await this.save();
+ },
+```
+
+- [ ] **Step 2: Add race-condition guard and event dispatch to openWidget()**
+
+Replace the `openWidget` method (lines 169-180):
+
+```javascript
+ async openWidget(id) {
+ const entry = this._widgets.get(id);
+ if (!entry) return;
+ entry._minimizing = false;
+ entry.state.open = true;
+ entry.el.style.display = 'flex';
+ requestAnimationFrame(() => {
+ entry.el.classList.remove('widget-minimized');
+ });
+ this.bringToFront(id);
+ this._emitter.dispatchEvent(new CustomEvent('widget:open', { detail: { id } }));
+ await this.save();
+ },
+```
+
+Key change: `entry._minimizing = false` cancels any in-flight minimize transition.
+
+- [ ] **Step 3: Verify minimize/open animation works**
+
+Reload extension. Test:
+1. Create a note → minimize it → verify it fades out and disappears
+2. Click the note in the widget toolbar to reopen → verify it appears smoothly
+3. Rapid test: minimize → immediately reopen before animation ends → verify no display glitch (the race condition fix)
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/js/widgets.js
+git commit -m "fix(widgets): replace setTimeout with transitionend in minimize
+
+Fixes race condition where openWidget() during the 250ms timeout would
+be overridden. Uses _minimizing flag to cancel in-flight transitions.
+Dispatches widget:minimize and widget:open events."
+```
+
+---
+
+### Task 3: Migrate Calculator, Timer, ImageRef to Event Listeners
+
+**Files:**
+- Modify: `src/js/calculator.js:692-728`
+- Modify: `src/js/timer.js:723-758`
+- Modify: `src/js/image-ref.js:463-498`
+
+- [ ] **Step 1: Replace monkey-patching in calculator.js**
+
+Replace lines 692-728 (the three monkey-patching blocks in `init()`) with:
+
+```javascript
+ // Widget-Lifecycle-Events
+ const self = this;
+ WidgetManager.on('widget:close', (e) => {
+ if (e.detail.id === self.WIDGET_ID) {
+ self.onClose();
+ }
+ });
+
+ WidgetManager.on('widget:minimize', (e) => {
+ if (e.detail.id === self.WIDGET_ID) {
+ self._isOpen = false;
+ self.save();
+ }
+ });
+
+ WidgetManager.on('widget:open', (e) => {
+ if (e.detail.id === self.WIDGET_ID) {
+ self._isOpen = true;
+ const body = WidgetManager.getBody(self.WIDGET_ID);
+ if (body && body.children.length === 0) {
+ self.renderBody(body);
+ }
+ const entry = WidgetManager._widgets.get(self.WIDGET_ID);
+ if (entry) self._bindKeyboard(entry.el);
+ self.save();
+ }
+ });
+```
+
+- [ ] **Step 2: Replace monkey-patching in timer.js**
+
+Replace lines 723-758 (the three monkey-patching blocks in `init()`) with:
+
+```javascript
+ // Widget-Lifecycle-Events
+ const self = this;
+ WidgetManager.on('widget:close', (e) => {
+ if (e.detail.id === self.WIDGET_ID) {
+ self.onClose();
+ }
+ });
+
+ WidgetManager.on('widget:minimize', (e) => {
+ if (e.detail.id === self.WIDGET_ID) {
+ self._isOpen = false;
+ self.save();
+ }
+ });
+
+ WidgetManager.on('widget:open', (e) => {
+ if (e.detail.id === self.WIDGET_ID) {
+ self._isOpen = true;
+ const body = WidgetManager.getBody(self.WIDGET_ID);
+ if (body && body.children.length === 0) {
+ self.renderBody(body);
+ }
+ const entry = WidgetManager._widgets.get(self.WIDGET_ID);
+ if (entry) self._bindKeyboard(entry.el);
+ self.save();
+ }
+ });
+```
+
+- [ ] **Step 3: Replace monkey-patching in image-ref.js**
+
+Replace lines 463-498 (the three monkey-patching blocks in `init()`) with:
+
+```javascript
+ // Widget-Lifecycle-Events
+ const self = this;
+ WidgetManager.on('widget:close', (e) => {
+ const isImage = self._images.some(img => img.id === e.detail.id);
+ if (isImage) {
+ self.onClose(e.detail.id);
+ }
+ });
+
+ WidgetManager.on('widget:minimize', (e) => {
+ const isImage = self._images.some(img => img.id === e.detail.id);
+ if (isImage) {
+ self.save();
+ }
+ });
+
+ WidgetManager.on('widget:open', (e) => {
+ const imgData = self._images.find(img => img.id === e.detail.id);
+ if (imgData) {
+ const body = WidgetManager.getBody(e.detail.id);
+ if (body && body.children.length === 0) {
+ const dataUrl = self._getSessionImage(e.detail.id);
+ self.renderBody(imgData, body, dataUrl);
+ }
+ self.save();
+ }
+ });
+```
+
+- [ ] **Step 4: Verify all three widget types work**
+
+Reload extension. Test each widget type:
+
+1. **Calculator:** Open → type a calculation → minimize → reopen → verify history is still there → close → reopen from toolbar
+2. **Timer:** Open → set a time → minimize → reopen → verify time is preserved → close
+3. **Image-Ref:** Enable in Settings → open image widget → add an image → minimize → reopen → verify image displays → close
+
+Check console for any errors during all operations.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/js/calculator.js src/js/timer.js src/js/image-ref.js
+git commit -m "refactor(widgets): migrate Calculator, Timer, ImageRef to event listeners
+
+Replace monkey-patching of WidgetManager.close/minimize/openWidget with
+WidgetManager.on() event listeners. Eliminates 3-deep closure chain."
+```
+
+---
+
+### Task 4: Security — URL Validation in settings.js
+
+**Files:**
+- Modify: `src/js/settings.js:52-95` (applySettings)
+- Modify: `src/js/settings.js:166-175` (btnApplyBg handler)
+- Modify: `src/js/settings.js:181-194` (bgFileInput handler)
+
+- [ ] **Step 1: Add isValidBgUrl() helper**
+
+Add this function at the top of `settings.js`, after the `closeThemeModal()` function (after line 24):
+
+```javascript
+/**
+ * Prueft ob eine Background-URL sicher fuer CSS-Einbettung ist.
+ * Erlaubt nur blob: und data:image/ Protokolle (aus File Upload).
+ * @param {string} url
+ * @returns {boolean}
+ */
+function isValidBgUrl(url) {
+ return typeof url === 'string' && url.length > 0 &&
+ (url.startsWith('blob:') || url.startsWith('data:image/'));
+}
+```
+
+- [ ] **Step 2: Add validation in applySettings()**
+
+Replace lines 92-94:
+
+```javascript
+ if (settings.bgUrl) {
+ document.getElementById('bgLayer').style.backgroundImage = `url('${settings.bgUrl}')`;
+ }
+```
+
+With:
+
+```javascript
+ if (settings.bgUrl && isValidBgUrl(settings.bgUrl)) {
+ document.getElementById('bgLayer').style.backgroundImage = `url('${settings.bgUrl}')`;
+ } else if (settings.bgUrl) {
+ // Ungueltige URL im Storage — bereinigen
+ settings.bgUrl = '';
+ }
+```
+
+- [ ] **Step 3: Add validation in the URL-input handler (btnApplyBg)**
+
+Replace lines 169-175:
+
+```javascript
+ document.getElementById('btnApplyBg').addEventListener('click', async () => {
+ const url = document.getElementById('bgUrlInput').value.trim();
+ settings.bgUrl = url;
+ document.getElementById('bgLayer').style.backgroundImage = url ? `url('${url}')` : '';
+ await saveSettings();
+ document.getElementById('bgInputRow').classList.add('hidden');
+ });
+```
+
+With:
+
+```javascript
+ document.getElementById('btnApplyBg').addEventListener('click', async () => {
+ const url = document.getElementById('bgUrlInput').value.trim();
+ if (url && !isValidBgUrl(url)) {
+ await HellionDialog.alert(t('settings.bg_invalid_url'), { type: 'danger', title: t('settings.bg_invalid_url.title') });
+ return;
+ }
+ settings.bgUrl = url;
+ document.getElementById('bgLayer').style.backgroundImage = url ? `url('${url}')` : '';
+ await saveSettings();
+ document.getElementById('bgInputRow').classList.add('hidden');
+ });
+```
+
+- [ ] **Step 4: Verify the file upload handler is already safe**
+
+Read `settings.js:181-194`. The `FileReader.readAsDataURL(file)` produces a `data:image/...` string, which passes `isValidBgUrl()`. The handler at line 186 sets `settings.bgUrl = ev.target.result` — this is already valid output. No change needed here.
+
+- [ ] **Step 5: Add i18n keys for the validation error dialog**
+
+These keys will be added in Task 8 together with all other i18n keys. For now, note that we need:
+- `settings.bg_invalid_url` — "Nur lokale Bilder (Upload) sind als Hintergrund erlaubt." / "Only local images (upload) are allowed as background."
+- `settings.bg_invalid_url.title` — "Ungültige URL" / "Invalid URL"
+
+- [ ] **Step 6: Verify background upload still works**
+
+Reload extension. Test:
+1. Open Theme Modal → upload a local image → verify it displays as background
+2. Try entering `javascript:alert(1)` in the URL input → verify it's rejected with a dialog
+3. Reload → verify the uploaded background persists
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add src/js/settings.js
+git commit -m "fix(security): validate background URL before CSS injection
+
+Add isValidBgUrl() that only allows blob: and data:image/ protocols.
+Applied in applySettings() and the manual URL input handler.
+Prevents CSS injection via manipulated bgUrl storage values."
+```
+
+---
+
+### Task 5: Security + Quality — Data Import Hardening
+
+**Files:**
+- Modify: `src/js/data.js:33-127`
+
+- [ ] **Step 1: Add isSafeUrl() helper at top of data.js**
+
+Add after the `initDataButtons` function declaration (after line 6, before the function body):
+
+Actually, add it inside the function before the event listeners, right after `if (!btnExport || !btnImport) return;` (after line 10):
+
+```javascript
+ /**
+ * Prueft ob eine URL ein sicheres Protokoll hat.
+ * Blockiert javascript:, data:, vbscript: etc.
+ * @param {string} url
+ * @returns {boolean}
+ */
+ function isSafeUrl(url) {
+ try {
+ const u = new URL(url);
+ return ['http:', 'https:', 'ftp:'].includes(u.protocol);
+ } catch {
+ return false;
+ }
+ }
+```
+
+- [ ] **Step 2: Replace the mutable board/bookmark filter with immutable mapping**
+
+Replace lines 41-52 (the `validBoards` filter block):
+
+```javascript
+ const validBoards = data.boards
+ .filter(b => b && typeof b.title === 'string' && Array.isArray(b.bookmarks))
+ .map(b => ({
+ id: b.id || uid(),
+ title: String(b.title).slice(0, 100),
+ blurred: !!b.blurred,
+ bookmarks: b.bookmarks
+ .filter(bm => bm && typeof bm.title === 'string' && isSafeUrl(bm.url))
+ .map(bm => ({
+ id: bm.id || uid(),
+ title: String(bm.title).slice(0, 200),
+ url: bm.url,
+ desc: String(bm.desc || '').slice(0, 500)
+ }))
+ }));
+```
+
+- [ ] **Step 3: Replace the mutable notes filter with immutable mapping**
+
+Replace lines 68-71 (the `importNotes` filter):
+
+```javascript
+ const importNotes = data.notes
+ .filter(n => n && n.id && n.template)
+ .map(n => ({
+ id: n.id,
+ template: ['note', 'checklist'].includes(n.template) ? n.template : 'note',
+ title: String(n.title || '').slice(0, 200),
+ content: String(n.content || '').slice(0, 5000),
+ x: typeof n.x === 'number' ? n.x : 120,
+ y: typeof n.y === 'number' ? n.y : 80,
+ width: typeof n.width === 'number' ? n.width : 280,
+ height: typeof n.height === 'number' ? n.height : 220,
+ open: n.open !== false,
+ checklistItems: Array.isArray(n.checklistItems) ? n.checklistItems : []
+ }));
+```
+
+- [ ] **Step 4: Replace direct Notes._notes mutation with Notes.init()**
+
+Replace lines 76-81:
+
+```javascript
+ if (toImport.length > 0) {
+ const merged = [...existingNotes, ...toImport];
+ existingWidgets.notes = merged;
+ Notes._notes = merged;
+ notesImported = toImport.length;
+ }
+```
+
+With:
+
+```javascript
+ if (toImport.length > 0) {
+ const merged = [...existingNotes, ...toImport];
+ existingWidgets.notes = merged;
+ notesImported = toImport.length;
+ }
+```
+
+Then after line 113 (`await Store.set('widgetStates', existingWidgets);`), add:
+
+```javascript
+ // Widget-Module neu aus Storage laden (kein direkter Zugriff auf Internals)
+ if (notesImported > 0) await Notes.init();
+ if (calcImported) await Calculator.load();
+ if (timerImported) await Timer.load();
+```
+
+And remove the direct mutations at lines 93 and 107:
+- Remove: `Calculator._history = existingWidgets.calculator.history;` (line 93)
+- Remove: `Timer._presets = existingWidgets.timer.presets;` (line 107)
+
+- [ ] **Step 5: Verify import functionality**
+
+Reload extension. Test:
+1. Export current data as JSON
+2. Edit the exported JSON: add a bookmark with `javascript:alert(1)` URL → import → verify the bad bookmark is silently skipped
+3. Import a normal JSON backup → verify boards, notes, calculator history, timer presets all appear correctly
+4. Verify no console errors
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/js/data.js
+git commit -m "fix(security): harden JSON import with URL validation and immutable mapping
+
+Add isSafeUrl() to block javascript:/data: URLs in imported bookmarks.
+Replace mutable object mutation with immutable .map() and string length limits.
+Use Notes.init()/Calculator.load()/Timer.load() instead of direct _notes/_history
+mutation after import."
+```
+
+---
+
+### Task 6: Remove Google Favicons — Local Letter Icons
+
+**Files:**
+- Modify: `src/js/state.js:36-43` (remove `getFaviconUrl`)
+- Modify: `src/js/boards.js:218-230` (replace favicon rendering)
+- Modify: `src/css/main.css:565-571` (replace CSS classes)
+
+- [ ] **Step 1: Remove getFaviconUrl() from state.js**
+
+Delete lines 36-43 in `src/js/state.js`:
+
+```javascript
+function getFaviconUrl(url) {
+ try {
+ const u = new URL(url);
+ return `https://www.google.com/s2/favicons?domain=${u.hostname}&sz=16`;
+ } catch {
+ return '';
+ }
+}
+```
+
+- [ ] **Step 2: Replace favicon rendering in boards.js**
+
+Replace lines 218-230 in `src/js/boards.js`:
+
+```javascript
+ const favicon = document.createElement('img');
+ favicon.className = 'bm-favicon';
+ favicon.width = 14;
+ favicon.height = 14;
+ favicon.src = getFaviconUrl(bm.url);
+ favicon.addEventListener('error', function() {
+ this.classList.add('hidden');
+ this.nextElementSibling.classList.remove('hidden');
+ });
+
+ const fallback = document.createElement('div');
+ fallback.className = 'bm-favicon-fallback hidden';
+ fallback.textContent = bm.title.charAt(0).toUpperCase();
+```
+
+With:
+
+```javascript
+ const favicon = document.createElement('div');
+ favicon.className = 'bm-favicon-local';
+ favicon.textContent = bm.title.charAt(0).toUpperCase();
+ const hue = (bm.title.charCodeAt(0) * 137) % 360;
+ favicon.style.backgroundColor = `hsl(${hue}, 45%, 35%)`;
+```
+
+Also update the `appendChild` calls below. The old code appends both `favicon` and `fallback`:
+
+Find the line that appends the fallback (should be near line 243-244):
+```javascript
+ li.append(favicon, fallback, textDiv, deleteBtn);
+```
+
+Replace with:
+```javascript
+ li.append(favicon, textDiv, deleteBtn);
+```
+
+- [ ] **Step 3: Replace CSS classes in main.css**
+
+Replace lines 565-571:
+
+```css
+.bm-favicon { width: 14px; height: 14px; flex-shrink: 0; border-radius: 2px; opacity: 0.85; }
+.bm-favicon-fallback {
+ width: 14px; height: 14px; flex-shrink: 0;
+ background: var(--accent-dim); border-radius: 2px;
+ display: flex; align-items: center; justify-content: center;
+ font-size: 8px; color: var(--accent);
+}
+```
+
+With:
+
+```css
+.bm-favicon-local {
+ width: 16px; height: 16px; flex-shrink: 0;
+ border-radius: 3px;
+ display: flex; align-items: center; justify-content: center;
+ font-size: 9px; font-weight: 600;
+ color: #fff;
+ line-height: 1;
+}
+```
+
+- [ ] **Step 4: Verify favicons display correctly**
+
+Reload extension. Check:
+1. All bookmarks show a colored letter icon
+2. Different bookmark titles produce different colors
+3. The icons are aligned and properly sized in all themes
+4. No network requests to google.com in the Network tab (F12 → Network)
+5. No console errors about `getFaviconUrl`
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/js/state.js src/js/boards.js src/css/main.css
+git commit -m "feat(privacy): replace Google Favicons with local letter icons
+
+Remove getFaviconUrl() and all external network requests. Bookmarks now
+show a colored letter icon with deterministic hue based on title.
+Eliminates privacy leak and Brave Shields compatibility issues."
+```
+
+---
+
+### Task 7: backdrop-filter Fallback for Brave Shields
+
+**Files:**
+- Modify: `src/css/main.css` (add `--bg-solid-fallback` per theme + `@supports not` block)
+
+- [ ] **Step 1: Add --bg-solid-fallback to each theme**
+
+Add the variable to each theme's `[data-theme]` block. The value is an opaque version of `--bg-board`:
+
+| Theme | Line | `--bg-solid-fallback` value |
+|---|---|---|
+| nebula | ~82 | `#0a060e` |
+| crescent | ~108 | `#0c0b08` |
+| event-horizon | ~137 | `#06040f` |
+| merchantman | ~163 | `#040d0d` |
+| julia-jin | ~189 | `#080c12` |
+| sc-sunset | ~216 | `#0e0808` |
+| hellion-hud | ~245 | `#04080c` |
+| hellion-energy | ~278 | `#040a08` |
+| satisfactory | ~310 | `#060a0c` |
+| avorion | ~341 | `#040c0a` |
+| hellion-stealth | ~371 | `#060a0e` |
+
+Add `--bg-solid-fallback: ;` as the last variable in each theme block.
+
+- [ ] **Step 2: Add @supports not block at the end of the general layout section**
+
+Add after the existing board/widget styles, before the theme-specific sections (around line 75, before the first `[data-theme]` block):
+
+```css
+/* Fallback fuer Browser die backdrop-filter blockieren (z.B. Brave Shields) */
+@supports not (backdrop-filter: blur(1px)) {
+ .board,
+ .widget,
+ .settings-panel,
+ .dialog-box,
+ .theme-modal,
+ .search-bar {
+ background-color: var(--bg-solid-fallback, var(--bg-primary));
+ }
+}
+```
+
+- [ ] **Step 3: Verify fallback works**
+
+Test in Brave with Shields set to aggressive. Or test by temporarily adding this CSS rule:
+```css
+.board { backdrop-filter: none !important; }
+```
+Verify that boards still have a visible background (opaque, not transparent).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/css/main.css
+git commit -m "fix(compat): add backdrop-filter fallback for Brave Shields
+
+Add --bg-solid-fallback CSS variable to all 11 themes and a
+@supports not (backdrop-filter) block. UI remains usable when
+Brave Shields or strict fingerprinting settings block backdrop-filter."
+```
+
+---
+
+### Task 8: Complete i18n Coverage
+
+**Files:**
+- Modify: `newtab.html:26-42` (add `data-i18n-title` to 5 header buttons)
+- Modify: `newtab.html:198, 215, 374` (add `data-i18n` to 3 setting buttons)
+- Modify: `src/js/i18n.js` (add 10 new keys — 8 from spec + 2 from Task 4)
+
+- [ ] **Step 1: Add data-i18n-title to header buttons in newtab.html**
+
+Line 26 — change:
+```html
+