diff --git a/.gitignore b/.gitignore
index c89c2f7..90ae61e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,10 @@ dist/
node_modules/
/xpi/
v2-planning.md
+themes-v2.md
+
+# Firefox Update-Manifest (wird auf hellion-media.de gehostet)
+updates.json
# Persönliche Backup-Dateien (nicht ins Repo)
favorites_*.html
diff --git a/README.md b/README.md
index 3a75a8e..421839e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# ⬡ Hellion Dashboard v1.5.2
+# ⬡ Hellion Dashboard v1.9.0
-
+



diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..91b94d4
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,163 @@
+# Hellion Dashboard — Architecture
+
+## Overview
+
+Hellion Dashboard is a browser extension (NewTab replacement) built with **Vanilla JavaScript ES2020**, **CSS Custom Properties**, and **zero dependencies**. No build step, no framework, no bundler — files are loaded directly via `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+**Rule:** A module may only reference modules loaded before it.
+
+---
+
+## Z-Index Hierarchy
+
+| Layer | z-index | Elements |
+|---|---|---|
+| Background | 0-2 | `#bgLayer`, boards |
+| Search bar | 90 | `.search-bar-wrapper` |
+| Widgets + Toolbar | 100+ | `.widget`, `.widget-toolbar` |
+| Header | 100 | `#header` |
+| Settings panel | 200 | `#settingsPanel` |
+| Dialogs / Modals | 300 | `.hellion-dialog-overlay`, modals |
+| Onboarding | 400 | `#onboardingOverlay` |
+
+Widgets use incrementing z-index (`WidgetManager._topZ++`) to stack above each other on click.
+
+---
+
+## Storage Keys
+
+| Key | Type | Content |
+|---|---|---|
+| `boards` | Array | Board objects with bookmarks |
+| `settings` | Object | User preferences (theme, toggles, etc.) |
+| `widgetStates` | Object | All widget data (see [widget-schema.md](widget-schema.md)) |
+| `onboardingDone` | Boolean | Whether onboarding has been completed |
+| `lastBackupReminder` | Number | Timestamp of last backup reminder |
+
+---
+
+## Browser Compatibility
+
+| Browser | Engine | Manifest |
+|---|---|---|
+| Chrome | Chromium MV3 | `manifest.json` |
+| Edge | Chromium MV3 | `manifest.json` |
+| Brave | Chromium MV3 | `manifest.json` |
+| Vivaldi | Chromium MV3 | `manifest.json` |
+| Opera / GX | Chromium MV3 | `manifest.opera.json` |
+| Firefox | Gecko MV3 | `manifest.firefox.json` |
+
+Changes affecting manifest fields must be synchronized across all three manifest files.
diff --git a/docs/patterns.md b/docs/patterns.md
new file mode 100644
index 0000000..aa9d1d5
--- /dev/null
+++ b/docs/patterns.md
@@ -0,0 +1,310 @@
+# Hellion Dashboard — Code Patterns & Conventions
+
+## Core Principles
+
+- **Vanilla JS ES2020** — No frameworks, no TypeScript, no build step
+- **Zero dependencies** — Everything is built from scratch
+- **`createElement` only** — Never use `innerHTML` (XSS prevention)
+- **CSS Custom Properties** — No hardcoded colors, everything through `var(--name)`
+- **Event delegation** — One listener per container, not per element
+- **Storage abstraction** — All storage access through `Store.get()` / `Store.set()`
+
+---
+
+## Pattern: Storage Abstraction
+
+**File:** `src/js/storage.js`
+
+All persistent data goes through the `Store` object. Never access `chrome.storage` or `localStorage` directly.
+
+```javascript
+// Reading
+const boards = await Store.get('boards'); // Returns null if not found
+const settings = await Store.get('settings');
+
+// Writing
+await Store.set('boards', boards);
+await Store.set('settings', settings);
+
+// Quota check (chrome.storage only, 10 MB limit)
+await Store.checkQuota();
+```
+
+**Why?** The `Store` handles the chrome.storage / localStorage fallback transparently. It also provides unified error handling (shows a dialog when storage is full).
+
+---
+
+## Pattern: Event Delegation
+
+Instead of attaching listeners to each element, attach one to the container and use `closest()` to find the target.
+
+```javascript
+// GOOD — one listener, handles all bookmarks
+container.addEventListener('click', (e) => {
+ const bmItem = e.target.closest('.bm-item');
+ if (!bmItem) return;
+ const id = bmItem.dataset.id;
+ // Handle click
+});
+
+// BAD — listener per element
+bookmarks.forEach(bm => {
+ bm.addEventListener('click', handler); // Don't do this!
+});
+```
+
+**Used in:** `boards.js` (board/bookmark events), `notes.js` (toolbar), `calculator.js` (button grid)
+
+---
+
+## Pattern: createElement over innerHTML
+
+Always build DOM with `document.createElement()`. This prevents XSS and is the project's #1 security rule.
+
+```javascript
+// GOOD
+const link = document.createElement('a');
+link.href = bookmark.url;
+link.textContent = bookmark.title;
+container.appendChild(link);
+
+// BAD — XSS risk!
+container.innerHTML = `${title}`;
+```
+
+---
+
+## Pattern: Shared Storage Key
+
+Multiple widget modules share the `widgetStates` key. Every module must read-before-write and preserve other modules' data.
+
+```javascript
+async save() {
+ const data = await Store.get('widgetStates') || {};
+
+ // Write your own data
+ data.yourKey = { /* ... */ };
+
+ // DON'T overwrite — the key already contains other modules' data
+ await Store.set('widgetStates', data);
+}
+```
+
+See [widget-schema.md](widget-schema.md) for the full `widgetStates` structure.
+
+---
+
+## Pattern: Widget Lifecycle Hooks
+
+Single-instance widgets (Calculator, Timer) need to know when they're closed, minimized, or reopened. They wrap `WidgetManager` methods in their `init()`:
+
+```javascript
+async init() {
+ // Wrap close
+ const prevClose = WidgetManager.close;
+ const self = this;
+ WidgetManager.close = function(id) {
+ prevClose.call(WidgetManager, id);
+ if (id === self.WIDGET_ID) {
+ self.onClose();
+ }
+ };
+
+ // Wrap minimize
+ const prevMinimize = WidgetManager.minimize;
+ WidgetManager.minimize = async function(id) {
+ await prevMinimize.call(WidgetManager, id);
+ if (id === self.WIDGET_ID) {
+ self._isOpen = false;
+ await self.save();
+ }
+ };
+}
+```
+
+**Important:** Multiple widgets chain these wraps. Calculator wraps first, Timer wraps Calculator's already-wrapped version, and so on. The chain must not break.
+
+---
+
+## Pattern: Debounced Save
+
+For frequent updates (typing in notes, moving widgets), use debounced saves to avoid excessive storage writes:
+
+```javascript
+_saveTimer: null,
+
+_debouncedSave() {
+ clearTimeout(this._saveTimer);
+ this._saveTimer = setTimeout(() => this.save(), 500);
+}
+
+// Usage: call _debouncedSave() instead of save() for frequent events
+textarea.addEventListener('input', () => {
+ noteData.content = textarea.value;
+ this._debouncedSave();
+});
+```
+
+**Used in:** `notes.js` (text editing), `image-ref.js` (label editing)
+
+---
+
+## Pattern: Theme System
+
+All themes use CSS Custom Properties defined in `[data-theme="name"]` blocks:
+
+```css
+[data-theme="nebula"] {
+ --bg-primary: #0a0e17;
+ --bg-board: rgba(15, 20, 35, 0.65);
+ --text-primary: #e0e6f0;
+ --accent: #7db3ff;
+ --border: rgba(125, 179, 255, 0.12);
+ /* ... more variables */
+}
+```
+
+**Never hardcode colors in JS.** Use CSS classes or variables:
+
+```javascript
+// GOOD — let CSS handle colors
+element.classList.add('active');
+
+// BAD — hardcoded color
+element.style.color = '#7db3ff';
+```
+
+8 themes are available: Nebula, Crescent, Event Horizon, Merchantman, Julia & Jin, SC Sunset, Hellion HUD, Hellion Energy.
+
+---
+
+## Pattern: Onboarding Slides
+
+The onboarding system (`onboarding.js`) uses a data-driven slide array. Each slide is an object with rendering hints:
+
+```javascript
+{
+ hero: '🎮', // Large emoji/icon
+ title: 'Slide Title', // Heading
+ text: 'Description...', // Optional text paragraph
+ features: ['Item 1', ...], // Optional bullet list
+ showThemes: true, // Optional theme grid
+ interactive: 'gaming-board' // Optional custom buttons
+}
+```
+
+The `_render()` method reads these properties and builds the DOM. To add a new slide, just add an object to the `slides` array.
+
+---
+
+## Pattern: Dialog System
+
+Custom dialogs replace native `alert()` and `confirm()`:
+
+```javascript
+// Alert (informational)
+await HellionDialog.alert('Message text', {
+ type: 'info', // 'info', 'success', 'warning', 'danger'
+ title: 'Title'
+});
+
+// Confirm (yes/no)
+const ok = await HellionDialog.confirm('Are you sure?', {
+ type: 'danger',
+ title: 'Delete',
+ confirmText: 'Delete', // Custom button text
+ cancelText: 'Cancel'
+});
+if (ok) { /* user confirmed */ }
+```
+
+---
+
+## Pattern: Pointer Events for Drag
+
+Widget dragging and board reordering use the Pointer Events API (not mouse events):
+
+```javascript
+element.addEventListener('pointerdown', (e) => {
+ element.setPointerCapture(e.pointerId);
+
+ function onMove(ev) {
+ // Update position
+ }
+
+ function onUp() {
+ element.releasePointerCapture(e.pointerId);
+ element.removeEventListener('pointermove', onMove);
+ element.removeEventListener('pointerup', onUp);
+ }
+
+ element.addEventListener('pointermove', onMove);
+ element.addEventListener('pointerup', onUp);
+});
+```
+
+**Why Pointer Events over Mouse Events?** They work with both mouse and touch, and `setPointerCapture` ensures events continue even if the cursor leaves the element.
+
+---
+
+## Pattern: Canvas API Image Processing
+
+The image reference widget converts uploaded images to WebP for smaller size:
+
+```javascript
+_processFile(file) {
+ return new Promise((resolve, reject) => {
+ const objectUrl = URL.createObjectURL(file);
+ const img = new Image();
+
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+ const ctx = canvas.getContext('2d');
+ ctx.drawImage(img, 0, 0);
+ const webpUrl = canvas.toDataURL('image/webp', 0.85);
+ URL.revokeObjectURL(objectUrl);
+ resolve(webpUrl);
+ };
+
+ img.onerror = () => {
+ URL.revokeObjectURL(objectUrl);
+ reject(new Error('Image could not be loaded'));
+ };
+
+ img.src = objectUrl;
+ });
+}
+```
+
+**Important:** Always call `URL.revokeObjectURL()` to free memory.
+
+---
+
+## Coding Rules Summary
+
+| Rule | Rationale |
+|---|---|
+| `createElement` only, never `innerHTML` | XSS prevention |
+| All storage through `Store` | Browser compatibility |
+| CSS variables, no hardcoded colors | Theme support |
+| Event delegation | Performance, dynamic content |
+| `const`/`let`, never `var` | Block scoping |
+| No external dependencies | Extension simplicity |
+| No build step | Direct development |
+| JSDoc comments on public functions | Documentation |
+| URL validation before `href` | Security |
+| Error handling on storage operations | Graceful failure |
+
+---
+
+## Manifest Synchronization
+
+Three manifest files must stay in sync:
+
+- `manifest.json` — Chrome, Edge, Brave, Vivaldi
+- `manifest.firefox.json` — Firefox
+- `manifest.opera.json` — Opera, Opera GX
+
+When changing version numbers, permissions, or content script entries, update all three files.
diff --git a/docs/widget-schema.md b/docs/widget-schema.md
new file mode 100644
index 0000000..04415a5
--- /dev/null
+++ b/docs/widget-schema.md
@@ -0,0 +1,330 @@
+# Hellion Dashboard — Widget Schema
+
+## Overview
+
+The widget system provides draggable, resizable floating panels managed by `WidgetManager` (`src/js/widgets.js`). Each widget type has its own module that handles content rendering and state management.
+
+---
+
+## Widget Types
+
+| Type | Module | Instance | Max | Storage |
+|---|---|---|---|---|
+| `note` | `notes.js` | Multi | 5 | Persistent (`widgetStates.notes`) |
+| `calculator` | `calculator.js` | Single | 1 | Persistent (`widgetStates.calculator`) |
+| `timer` | `timer.js` | Single | 1 | Persistent (`widgetStates.timer`) |
+| `image` | `image-ref.js` | Multi | 3 | Meta: persistent, Image data: sessionStorage |
+
+---
+
+## WidgetManager API
+
+### `create(type, config) → string`
+
+Creates a widget and appends it to the DOM.
+
+```javascript
+const id = WidgetManager.create('note', {
+ id: 'note_abc123', // Optional, auto-generated if omitted
+ title: 'My Note', // Default: 'Note'
+ x: 120, // Left position in px
+ y: 80, // Top position in px
+ width: 280, // Width in px (min: 200)
+ height: 220, // Height in px (min: 150)
+ open: true // Visible state (default: true)
+});
+```
+
+### `getBody(id) → HTMLElement | null`
+
+Returns the `.widget-body` element for content rendering.
+
+```javascript
+const body = WidgetManager.getBody('widget_calculator');
+if (body) Calculator.renderBody(body);
+```
+
+### `getState(id) → Object | null`
+
+Returns the current widget state (position, size, open status).
+
+```javascript
+const state = WidgetManager.getState('widget_timer');
+// → { id, type, title, x, y, width, height, open }
+```
+
+### `close(id)`
+
+Permanently removes a widget from the DOM and registry.
+
+### `minimize(id)`
+
+Hides a widget with animation. Widget remains in registry with `open: false`.
+
+### `openWidget(id)`
+
+Restores a minimized widget with animation.
+
+### `bringToFront(id)`
+
+Increments z-index to bring widget above all others.
+
+### `save() → Array`
+
+Returns an array of all `type: 'note'` widget states. Used by `Notes.save()` to merge with note content data.
+
+### `restore(renderCallback)`
+
+Loads widget states from storage and recreates all note widgets. Only handles notes — single-instance widgets (calculator, timer) restore themselves in their own `init()`.
+
+---
+
+## Shared Storage Key: `widgetStates`
+
+All widget modules share a single storage key. Each module's `save()` method must preserve other modules' data.
+
+```javascript
+// Structure of widgetStates
+{
+ notes: [
+ {
+ id: 'note_abc123',
+ title: 'My Note',
+ content: 'Hello world',
+ template: 'text', // 'text' or 'checklist'
+ x: 120, y: 80,
+ width: 280, height: 220,
+ open: true,
+ checklistItems: [], // For checklist template
+ checkedItems: [] // Checked item IDs
+ }
+ ],
+ calculator: {
+ x: 400, y: 120,
+ width: 280, height: 400,
+ open: false,
+ history: [
+ { expr: '2 + 3', result: '5' }
+ ]
+ },
+ timer: {
+ x: 600, y: 80,
+ width: 260, height: 360,
+ open: false,
+ muted: false,
+ presets: [
+ { name: 'Forschung', seconds: 2700 }
+ ]
+ },
+ imageRef: {
+ images: [
+ {
+ id: 'image_0',
+ label: 'Bauplan',
+ x: 200, y: 120,
+ width: 320, height: 280,
+ open: true
+ }
+ ]
+ }
+}
+```
+
+### Save Pattern — Preserving Other Modules' Data
+
+Every module that saves to `widgetStates` must read existing data first and preserve keys it doesn't own:
+
+```javascript
+// Example from notes.js
+async save() {
+ const existing = await Store.get(this.STORAGE_KEY);
+ const saveData = { notes: mergedNotes };
+
+ // Preserve other modules
+ if (existing && existing.calculator) saveData.calculator = existing.calculator;
+ if (existing && existing.timer) saveData.timer = existing.timer;
+ if (existing && existing.imageRef) saveData.imageRef = existing.imageRef;
+
+ await Store.set(this.STORAGE_KEY, saveData);
+}
+```
+
+---
+
+## Creating a New Widget Type
+
+### Step 1: Choose Single or Multi-Instance
+
+- **Single-instance** (like Calculator, Timer): One widget with a fixed ID. `toggle()` opens/closes.
+- **Multi-instance** (like Notes, ImageRef): Multiple widgets with dynamic IDs. `create()` adds new ones.
+
+### Step 2: Create the Module (`src/js/your-widget.js`)
+
+```javascript
+const YourWidget = {
+ WIDGET_ID: 'widget_yourwidget', // Fixed ID for single-instance
+ STORAGE_KEY: 'widgetStates',
+ _isOpen: false,
+
+ // Load state from storage
+ async load() {
+ const data = await Store.get(this.STORAGE_KEY);
+ if (data && data.yourWidget) {
+ // Restore your state
+ }
+ },
+
+ // Save state, preserving other modules
+ async save() {
+ const data = await Store.get(this.STORAGE_KEY) || {};
+ if (data.notes === undefined) data.notes = [];
+
+ const widgetState = WidgetManager.getState(this.WIDGET_ID);
+ data.yourWidget = {
+ x: widgetState ? widgetState.x : 400,
+ y: widgetState ? widgetState.y : 120,
+ width: widgetState ? widgetState.width : 280,
+ height: widgetState ? widgetState.height : 300,
+ open: this._isOpen,
+ // ... your custom data
+ };
+
+ await Store.set(this.STORAGE_KEY, data);
+ },
+
+ // Open widget
+ async open() {
+ if (this._isOpen) {
+ WidgetManager.bringToFront(this.WIDGET_ID);
+ return;
+ }
+
+ const data = await Store.get(this.STORAGE_KEY);
+ const saved = (data && data.yourWidget) ? data.yourWidget : {};
+
+ WidgetManager.create('yourwidget', {
+ id: this.WIDGET_ID,
+ title: 'Your Widget',
+ x: saved.x || 400,
+ y: saved.y || 120,
+ width: saved.width || 280,
+ height: saved.height || 300,
+ open: true
+ });
+
+ const body = WidgetManager.getBody(this.WIDGET_ID);
+ if (body) this.renderBody(body);
+
+ this._isOpen = true;
+ await this.save();
+ },
+
+ // Toggle open/close
+ async toggle() {
+ if (this._isOpen) {
+ const entry = WidgetManager._widgets.get(this.WIDGET_ID);
+ if (entry && entry.state.open) {
+ await WidgetManager.minimize(this.WIDGET_ID);
+ this._isOpen = false;
+ await this.save();
+ } else if (entry) {
+ await WidgetManager.openWidget(this.WIDGET_ID);
+ this._isOpen = true;
+ await this.save();
+ }
+ } else {
+ await this.open();
+ }
+ },
+
+ // Render widget content
+ renderBody(bodyEl) {
+ bodyEl.textContent = '';
+ // Build your UI with createElement (never innerHTML!)
+ },
+
+ // Initialize and hook into lifecycle
+ async init() {
+ await this.load();
+
+ // Restore if was open last time
+ const data = await Store.get(this.STORAGE_KEY);
+ if (data && data.yourWidget && data.yourWidget.open) {
+ await this.open();
+ }
+
+ // Hook into close event
+ const self = this;
+ const prevClose = WidgetManager.close;
+ WidgetManager.close = function(id) {
+ prevClose.call(WidgetManager, id);
+ if (id === self.WIDGET_ID) {
+ self._isOpen = false;
+ self.save();
+ }
+ };
+
+ // Hook into minimize event
+ const prevMinimize = WidgetManager.minimize;
+ WidgetManager.minimize = async function(id) {
+ await prevMinimize.call(WidgetManager, id);
+ if (id === self.WIDGET_ID) {
+ self._isOpen = false;
+ await self.save();
+ }
+ };
+
+ // Hook into open event
+ const prevOpen = WidgetManager.openWidget;
+ WidgetManager.openWidget = async function(id) {
+ await prevOpen.call(WidgetManager, id);
+ if (id === self.WIDGET_ID) {
+ self._isOpen = true;
+ const body = WidgetManager.getBody(self.WIDGET_ID);
+ if (body && body.children.length === 0) {
+ self.renderBody(body);
+ }
+ await self.save();
+ }
+ };
+ }
+};
+```
+
+### Step 3: Integration Checklist
+
+1. **`newtab.html`** — Add `