feat(app): Onboarding, Settings-Redesign und Docs für v1.9.0

- Onboarding mit Widget-Toolbar Slide und Gaming Starter Board
- Settings in Darstellung-Modal und schlankes Settings-Panel
- About-Block als fixierten Footer im Settings-Panel
- Dropdown-Optionen an Theme-Farben anpassen
- Projekt-Dokumentation (Architektur, Widget-Schema, Patterns)
- Firefox Update-URL für Store-Veröffentlichung
- Versions-Bump auf 1.9.0 in allen Manifests
This commit is contained in:
2026-03-22 08:54:57 +01:00
parent 51947b229c
commit 198171b6c2
14 changed files with 1035 additions and 197 deletions
+4
View File
@@ -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
+2 -2
View File
@@ -1,6 +1,6 @@
# ⬡ Hellion Dashboard v1.5.2
# ⬡ Hellion Dashboard v1.9.0
![Version](https://img.shields.io/badge/Version-1.5.2-blue)
![Version](https://img.shields.io/badge/Version-1.9.0-blue)
![JavaScript](https://img.shields.io/badge/JavaScript-Vanilla%20ES2020-F7DF1E?logo=javascript&logoColor=black)
![Manifest](https://img.shields.io/badge/Manifest-V3-green)
![License](https://img.shields.io/badge/License-CC%20BY--NC--SA%204.0-orange)
+163
View File
@@ -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 `<script>` tags.
**Storage:** `chrome.storage.local` with `localStorage` fallback.
**Manifest:** V3 for Chromium browsers, V3 for Firefox (separate manifest).
---
## File Structure
```
HOM_NewTab_Project/
├── newtab.html # Single HTML entry point
├── manifest.json # Chrome/Edge/Brave/Vivaldi (MV3)
├── manifest.firefox.json # Firefox (MV3)
├── manifest.opera.json # Opera/Opera GX (MV3 + workarounds)
├── src/
│ ├── css/
│ │ └── main.css # All styles, themes, responsive breakpoints
│ └── js/
│ ├── storage.js # Storage abstraction layer
│ ├── state.js # Global state, defaults, helpers
│ ├── themes.js # Theme definitions & application
│ ├── boards.js # Board/bookmark rendering & events
│ ├── drag.js # Drag & drop (Pointer Events API)
│ ├── settings.js # Settings panel, toggles, theme picker
│ ├── search.js # Search bar (Google, DuckDuckGo, Bing)
│ ├── widgets.js # Widget manager (registry, drag, resize)
│ ├── notes.js # Notes/checklists (multi-instance widgets)
│ ├── calculator.js # Calculator widget (single-instance)
│ ├── timer.js # Timer/countdown widget (single-instance)
│ ├── image-ref.js # Image reference widget (multi-instance)
│ ├── onboarding.js # First-run onboarding flow
│ ├── data.js # JSON export/import (backup & restore)
│ ├── app.js # Init, clock, global events (entry point)
│ └── dialog.js # Custom dialog system (alert, confirm)
├── assets/
│ ├── icons/ # Extension icons (16-512px)
│ └── themes/ # Theme background images
└── docs/ # Documentation (you are here)
```
---
## Module Responsibilities
Each module has exactly one responsibility. They communicate through global references (no import/export — this is a browser extension without a bundler).
| Module | Responsibility |
|---|---|
| `storage.js` | **Only** place that touches `chrome.storage` / `localStorage`. All other modules go through `Store.get()` / `Store.set()`. |
| `state.js` | Global `boards` and `settings` arrays, default values, `uid()`, `escHtml()`, `getFaviconUrl()`. |
| `themes.js` | Theme CSS variable application. 8 themes, each with its own `[data-theme]` block in CSS. |
| `boards.js` | Renders boards and bookmarks. Event delegation on board containers. |
| `drag.js` | Board and bookmark reordering via Pointer Events API. |
| `settings.js` | Settings panel UI, toggle handlers, theme modal, background upload. |
| `search.js` | Search bar with engine switching (Google, DuckDuckGo, Bing). |
| `widgets.js` | Widget manager — creates DOM, handles drag/resize/z-index, provides registry. See [widget-schema.md](widget-schema.md). |
| `notes.js` | Notes and checklists as widgets. Multi-instance (max 5). Notebook sidebar. Also handles widget toolbar events. |
| `calculator.js` | Calculator widget. Single-instance. Shunting-yard expression parser (no `eval()`). |
| `timer.js` | Timer/countdown widget. Single-instance. Presets, Web Audio API alarm, tab-title blink. |
| `image-ref.js` | Image reference widget. Multi-instance (max 3). Canvas API WebP conversion, sessionStorage for image data. |
| `onboarding.js` | Multi-slide onboarding flow. Gaming starter board opt-in. |
| `data.js` | JSON export/import with validation. Handles boards, notes, calculator history, timer presets. |
| `app.js` | Entry point. Calls `init()` on DOMContentLoaded. Clock, global event binding. |
| `dialog.js` | `HellionDialog.alert()` and `HellionDialog.confirm()` — custom styled dialogs replacing native browser dialogs. |
---
## Init Sequence
```
DOMContentLoaded
→ init()
→ Store.get('boards') # Load saved boards
→ Store.get('settings') # Load saved settings
→ applySettings() # Apply theme, toggles, etc.
→ renderBoards() # Render all boards
→ startClock() # Start clock/date display
→ bindGlobalEvents() # Header buttons, modals
→ bindSettingsEvents() # Settings toggles, theme picker
→ initSearch() # Search bar
→ migrateSticky() # Legacy sticky note migration
→ Notes.init() # Notes + widget toolbar
→ Calculator.init() # Calculator widget
→ Timer.init() # Timer widget
→ ImageRef.init() # Image reference widget
→ initDataButtons() # Export/import buttons
→ Onboarding check # First-run onboarding
```
---
## Script Load Order
Scripts are loaded in `newtab.html` in dependency order:
```html
<script src="src/js/dialog.js"></script>
<script src="src/js/storage.js"></script>
<script src="src/js/state.js"></script>
<script src="src/js/themes.js"></script>
<script src="src/js/boards.js"></script>
<script src="src/js/drag.js"></script>
<script src="src/js/settings.js"></script>
<script src="src/js/search.js"></script>
<script src="src/js/onboarding.js"></script>
<script src="src/js/widgets.js"></script>
<script src="src/js/notes.js"></script>
<script src="src/js/calculator.js"></script>
<script src="src/js/timer.js"></script>
<script src="src/js/image-ref.js"></script>
<script src="src/js/data.js"></script>
<script src="src/js/app.js"></script>
```
**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.
+310
View File
@@ -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 = `<a href="${url}">${title}</a>`;
```
---
## 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.
+330
View File
@@ -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 `<script>` tag (after `widgets.js`, before `data.js`)
2. **`newtab.html`** — Add toolbar button: `<button class="widget-toolbar-btn" data-action="your-action">`
3. **`notes.js`** — Add toolbar handler in `initToolbar()`: `} else if (action === 'your-action') { YourWidget.toggle(); }`
4. **`notes.js`** — Preserve your data in `save()`: `if (existing && existing.yourWidget) saveData.yourWidget = existing.yourWidget;`
5. **`app.js`** — Add `await YourWidget.init();` to the init sequence
6. **`src/css/main.css`** — Add widget-specific CSS styles
7. **`data.js`** — Add export/import logic (if data should be included in backups)
---
## Widget DOM Structure
Every widget created by `WidgetManager.create()` has this structure:
```html
<div class="widget" data-widget-id="widget_abc123"
style="left: 120px; top: 80px; width: 280px; height: 220px;">
<div class="widget-header">
<span class="widget-title">Title</span>
<div class="widget-actions">
<button class="widget-btn widget-minimize"></button>
<button class="widget-btn widget-close"></button>
</div>
</div>
<div class="widget-body">
<!-- Your content goes here (via renderBody) -->
</div>
<div class="widget-resize-handle"></div>
</div>
```
- **Header** is the drag handle (Pointer Events)
- **Title** supports double-click to edit (contentEditable, max 20 chars)
- **Body** is where your module renders content
- **Resize handle** appears on hover (bottom-right corner)
+2 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Hellion NewTab",
"version": "1.5.2",
"version": "1.9.0",
"description": "Personal bookmark dashboard — local, private, no account needed. By Hellion Online Media.",
"author": "Hellion Online Media - Florian Wathling",
"homepage_url": "https://hellion-media.de",
@@ -18,6 +18,7 @@
"browser_specific_settings": {
"gecko": {
"id": "hellion-newtab@hellion-media.de",
"update_url": "https://hellion-media.de/extensions/firefox/updates.json",
"strict_min_version": "142.0",
"data_collection_permissions": {
"required": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Hellion NewTab",
"version": "1.5.2",
"version": "1.9.0",
"description": "Personal bookmark dashboard — local, private, no account needed. By Hellion Online Media.",
"author": "Hellion Online Media - Florian Wathling",
"homepage_url": "https://hellion-media.de",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Hellion Dashboard (GX Native)",
"version": "1.5.2",
"version": "1.9.0",
"description": "Ersetzt die Opera GX Startseite durch dein persönliches, leistungsoptimiertes Hellion Dashboard. Schnell, sauber und werbefrei.",
"author": "Hellion Online Media - Florian Wathling",
"homepage_url": "https://hellion-media.de",
+145 -178
View File
@@ -35,9 +35,9 @@
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/></svg>
Note
</button>
<button class="btn-icon" id="btnTheme" title="Theme wählen">
<button class="btn-icon" id="btnTheme" title="Darstellung & Theme">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 100 20 4 4 0 01-1-7.9 1 1 0 011-.1h1a2 2 0 002-2V7a5 5 0 00-3-4.5"/><circle cx="7" cy="10" r="1.5"/><circle cx="13" cy="6" r="1.5"/><circle cx="17" cy="10" r="1.5"/><circle cx="9" cy="17" r="1.5"/></svg>
Theme
Darstellung
</button>
<button class="btn-icon" id="btnSettings" title="Einstellungen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
@@ -105,84 +105,11 @@
<div class="panel-overlay" id="settingsOverlay"></div>
<aside class="settings-panel" id="settingsPanel">
<div class="panel-header">
<span>Settings</span>
<span>Einstellungen</span>
<button class="btn-close" id="btnCloseSettings"></button>
</div>
<div class="panel-body">
<!-- APPEARANCE -->
<section class="settings-section" data-section="appearance">
<button class="settings-section-title" type="button">
<span class="section-chevron"></span>
APPEARANCE
</button>
<div class="section-content">
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Compact mode</span>
<span class="setting-desc">Reduce spacing to show more bookmarks</span>
</div>
<label class="toggle"><input type="checkbox" id="settingCompact" /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Shorten long titles</span>
<span class="setting-desc">Shorten title to one line with "…"</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShorten" /><span class="slider"></span></label>
</div>
</div>
</section>
<!-- BEHAVIOR -->
<section class="settings-section" data-section="behavior">
<button class="settings-section-title" type="button">
<span class="section-chevron"></span>
BEHAVIOR
</button>
<div class="section-content">
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Open links in new tab</span>
<span class="setting-desc">Open bookmarks in a new browser tab</span>
</div>
<label class="toggle"><input type="checkbox" id="settingNewTab" checked /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Show bookmark descriptions</span>
<span class="setting-desc">Display saved descriptions below bookmark titles</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShowDesc" /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Hide extra bookmarks in long boards</span>
<span class="setting-desc">Automatically hides extra bookmarks in long boards</span>
</div>
<label class="toggle"><input type="checkbox" id="settingHideExtra" /><span class="slider"></span></label>
</div>
<div class="setting-row" id="visibleCountRow">
<div class="setting-info">
<span class="setting-label">Visible bookmarks before hide</span>
<span class="setting-desc">Choose how many bookmarks are shown</span>
</div>
<select class="select-input" id="settingVisibleCount">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="20">20</option>
</select>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Suchleiste anzeigen</span>
<span class="setting-desc">Suchleiste unter dem Header ein/aus</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShowSearch" checked /><span class="slider"></span></label>
</div>
</div>
</section>
<!-- WIDGETS -->
<section class="settings-section" data-section="widgets">
<button class="settings-section-title" type="button">
@@ -193,7 +120,7 @@
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Toolbar-Position</span>
<span class="setting-desc">Widget-Toolbar links oder rechts</span>
<span class="setting-desc">Widget-Toolbar links oder rechts anzeigen</span>
</div>
<select class="select-input" id="settingToolbarPos">
<option value="right" selected>Rechts</option>
@@ -213,38 +140,28 @@
</div>
</section>
<!-- DATA -->
<!-- DATEN & HILFE -->
<section class="settings-section" data-section="data">
<button class="settings-section-title" type="button">
<span class="section-chevron"></span>
DATA
DATEN & HILFE
</button>
<div class="section-content">
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Export Boards</span>
<span class="setting-desc">Alle Boards als JSON sichern</span>
<span class="setting-label">Backup exportieren</span>
<span class="setting-desc">Alle Boards, Notes und Einstellungen als JSON sichern</span>
</div>
<button class="btn-small" id="btnExportJSON">Export</button>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Import Boards</span>
<span class="setting-label">Backup importieren</span>
<span class="setting-desc">JSON-Backup wiederherstellen</span>
</div>
<button class="btn-small" id="btnImportJSON">Import</button>
<input type="file" id="jsonImportInput" accept=".json" class="hidden" />
</div>
</div>
</section>
<!-- HELP -->
<section class="settings-section" data-section="help">
<button class="settings-section-title" type="button">
<span class="section-chevron"></span>
HELP
</button>
<div class="section-content">
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Onboarding wiederholen</span>
@@ -255,81 +172,6 @@
</div>
</section>
<!-- ABOUT / IMPRESSUM -->
<section class="settings-section" data-section="about">
<button class="settings-section-title" type="button">
<span class="section-chevron"></span>
ABOUT
</button>
<div class="section-content">
<div class="about-block">
<div class="about-logo">⬡ HELLION NEWTAB</div>
<div class="about-version">Version 1.5.2 · by Hellion Online Media</div>
<div class="about-links">
<a href="https://hellion-media.de/impressum" target="_blank" class="about-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Impressum
</a>
<a href="https://hellion-media.de" target="_blank" class="about-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
hellion-media.de
</a>
</div>
<div class="about-divider"></div>
<div class="about-info-row">
<span class="about-info-label">Entwickler</span>
<span class="about-info-value">Florian Wathling</span>
</div>
<div class="about-info-row">
<span class="about-info-label">Unternehmen</span>
<span class="about-info-value">Hellion Online Media</span>
</div>
<div class="about-info-row">
<span class="about-info-label">Lizenz</span>
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" class="about-info-value about-link-subtle">CC BY-NC-SA 4.0</a>
</div>
<div class="about-info-row">
<span class="about-info-label">Datenspeicherung</span>
<span class="about-info-value">100% lokal · Kein Server · Kein Account</span>
</div>
<div class="about-divider"></div>
<div class="about-bugreport">
<span class="about-info-label about-info-label-block">Bug Report / Feedback</span>
<a href="mailto:kontakt@hellion-media.de?subject=Hellion NewTab Bug Report" class="about-link-mail">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
kontakt@hellion-media.de
</a>
</div>
<div class="about-bugreport">
<span class="about-info-label about-info-label-block">Support</span>
<a href="https://ko-fi.com/hellionmedia" target="_blank" class="about-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
Ko-fi — hellionmedia
</a>
</div>
<div class="about-browsers">
<span class="about-info-label about-info-label-block">Kompatible Browser</span>
<div class="about-browser-tags">
<span class="browser-tag">Chrome</span>
<span class="browser-tag">Edge</span>
<span class="browser-tag">Firefox</span>
<span class="browser-tag">Opera</span>
<span class="browser-tag">Opera GX</span>
<span class="browser-tag">Brave</span>
<span class="browser-tag">Vivaldi</span>
</div>
</div>
</div>
</div>
</section>
<!-- DANGER ZONE -->
<section class="settings-section" data-section="danger">
<button class="settings-section-title danger" type="button">
@@ -339,8 +181,8 @@
<div class="section-content">
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Reset all data</span>
<span class="setting-desc">Deletes all boards and bookmarks</span>
<span class="setting-label">Alles zurücksetzen</span>
<span class="setting-desc">Löscht alle Boards, Notes und Einstellungen</span>
</div>
<button class="btn-danger" id="btnResetAll">Reset</button>
</div>
@@ -348,13 +190,82 @@
</section>
</div>
<!-- ABOUT — fixiert am unteren Rand -->
<div class="panel-footer">
<div class="about-block">
<div class="about-logo">⬡ HELLION NEWTAB</div>
<div class="about-version">Version 1.9.0 · by Hellion Online Media</div>
<div class="about-links">
<a href="https://hellion-media.de/impressum" target="_blank" class="about-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
Impressum
</a>
<a href="https://hellion-media.de" target="_blank" class="about-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
hellion-media.de
</a>
</div>
<div class="about-divider"></div>
<div class="about-info-row">
<span class="about-info-label">Entwickler</span>
<span class="about-info-value">Florian Wathling</span>
</div>
<div class="about-info-row">
<span class="about-info-label">Unternehmen</span>
<span class="about-info-value">Hellion Online Media</span>
</div>
<div class="about-info-row">
<span class="about-info-label">Lizenz</span>
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" target="_blank" class="about-info-value about-link-subtle">CC BY-NC-SA 4.0</a>
</div>
<div class="about-info-row">
<span class="about-info-label">Datenspeicherung</span>
<span class="about-info-value">100% lokal · Kein Server · Kein Account</span>
</div>
<div class="about-divider"></div>
<div class="about-bugreport">
<span class="about-info-label about-info-label-block">Bug Report / Feedback</span>
<a href="mailto:kontakt@hellion-media.de?subject=Hellion NewTab Bug Report" class="about-link-mail">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg>
kontakt@hellion-media.de
</a>
</div>
<div class="about-bugreport">
<span class="about-info-label about-info-label-block">Support</span>
<a href="https://ko-fi.com/hellionmedia" target="_blank" class="about-link">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
Ko-fi — hellionmedia
</a>
</div>
<div class="about-browsers">
<span class="about-info-label about-info-label-block">Kompatible Browser</span>
<div class="about-browser-tags">
<span class="browser-tag">Chrome</span>
<span class="browser-tag">Edge</span>
<span class="browser-tag">Firefox</span>
<span class="browser-tag">Opera</span>
<span class="browser-tag">Opera GX</span>
<span class="browser-tag">Brave</span>
<span class="browser-tag">Vivaldi</span>
</div>
</div>
</div>
</div>
</aside>
<!-- THEME PICKER MODAL -->
<div class="modal-overlay" id="themeOverlay">
<div class="theme-modal" id="themeModal">
<div class="modal-header">
<span>Theme wählen</span>
<span>Darstellung</span>
<button class="btn-close" id="btnCloseTheme"></button>
</div>
<div class="theme-grid">
@@ -400,27 +311,83 @@
</div>
</div>
<div class="theme-modal-section">
<h3 class="settings-section-title">BACKGROUND</h3>
<h3 class="settings-section-title">HINTERGRUND</h3>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Background image URL</span>
<span class="setting-desc">Custom wallpaper URL</span>
<span class="setting-label">Bild-URL</span>
<span class="setting-desc">Eigenes Hintergrundbild per URL</span>
</div>
<button class="btn-small" id="btnChangeBg">Change</button>
<button class="btn-small" id="btnChangeBg">Ändern</button>
</div>
<div class="setting-row hidden" id="bgInputRow">
<input type="text" class="text-input full-width" id="bgUrlInput" placeholder="https://... or leave empty for default" />
<button class="btn-small" id="btnApplyBg">Apply</button>
<input type="text" class="text-input full-width" id="bgUrlInput" placeholder="https://... oder leer für Standard" />
<button class="btn-small" id="btnApplyBg">Übernehmen</button>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Background file upload</span>
<span class="setting-desc">Use a local image as background</span>
<span class="setting-label">Datei hochladen</span>
<span class="setting-desc">Lokales Bild als Hintergrund verwenden</span>
</div>
<button class="btn-small" id="btnBgFile">Upload</button>
<input type="file" id="bgFileInput" accept="image/*" class="hidden" />
</div>
</div>
<div class="theme-modal-section">
<h3 class="settings-section-title">DARSTELLUNG</h3>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Kompaktmodus</span>
<span class="setting-desc">Weniger Abstand für mehr Bookmarks</span>
</div>
<label class="toggle"><input type="checkbox" id="settingCompact" /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Lange Titel kürzen</span>
<span class="setting-desc">Titel auf eine Zeile mit „…" kürzen</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShorten" /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Suchleiste anzeigen</span>
<span class="setting-desc">Suchleiste unter dem Header ein/aus</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShowSearch" checked /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Links in neuem Tab</span>
<span class="setting-desc">Bookmarks in neuem Browser-Tab öffnen</span>
</div>
<label class="toggle"><input type="checkbox" id="settingNewTab" checked /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Beschreibungen anzeigen</span>
<span class="setting-desc">Gespeicherte Beschreibung unter Bookmarks</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShowDesc" /><span class="slider"></span></label>
</div>
<div class="setting-row">
<div class="setting-info">
<span class="setting-label">Bookmarks ausblenden</span>
<span class="setting-desc">Überzählige Bookmarks in langen Boards verstecken</span>
</div>
<label class="toggle"><input type="checkbox" id="settingHideExtra" /><span class="slider"></span></label>
</div>
<div class="setting-row" id="visibleCountRow">
<div class="setting-info">
<span class="setting-label">Sichtbare Bookmarks</span>
<span class="setting-desc">Anzahl vor dem Ausblenden</span>
</div>
<select class="select-input" id="settingVisibleCount">
<option value="5">5</option>
<option value="10" selected>10</option>
<option value="20">20</option>
</select>
</div>
</div>
</div>
</div>
+9 -2
View File
@@ -537,7 +537,13 @@ body.show-desc .bm-desc { display: block; }
font-family: var(--font-display); font-size: 15px; font-weight: 600;
letter-spacing: 2px; color: var(--accent); text-transform: uppercase;
}
.panel-body { flex: 1; overflow-y: auto; padding: 12px 0; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.panel-body { flex: 1; overflow-y: auto; padding: 12px 0; scrollbar-width: thin; scrollbar-color: var(--border) transparent; min-height: 0; }
.panel-footer {
flex-shrink: 0; border-top: 1px solid var(--border);
max-height: 45vh; overflow-y: auto;
scrollbar-width: thin; scrollbar-color: var(--border) transparent;
}
.panel-footer .about-block { padding: 12px 18px 16px; }
.settings-section { margin-bottom: 4px; }
.settings-section-title {
@@ -639,11 +645,12 @@ body.show-desc .bm-desc { display: block; }
/* INPUTS */
.select-input {
padding: 5px 8px; background: rgba(255,255,255,0.06);
padding: 5px 8px; background: var(--bg-board, rgba(255,255,255,0.06));
border: 1px solid var(--border); border-radius: var(--radius-sm);
color: var(--text-primary); font-family: var(--font-body); font-size: 12px;
cursor: pointer; min-width: 70px;
}
.select-input option { background: var(--bg-primary, #0a0e17); color: var(--text-primary); }
.select-input:focus { outline: none; border-color: var(--border-accent); }
.text-input {
+1 -1
View File
@@ -103,7 +103,7 @@ async function checkBackupReminder() {
const notesData = (widgetData && Array.isArray(widgetData.notes)) ? widgetData.notes : [];
const calcHistory = (widgetData && widgetData.calculator) ? widgetData.calculator.history || [] : [];
const timerPresets = (widgetData && widgetData.timer) ? widgetData.timer.presets || [] : [];
const data = { version: '1.7.0', exported: new Date().toISOString(), boards, settings, notes: notesData, calculator: calcHistory, timerPresets };
const data = { version: '1.9.0', exported: new Date().toISOString(), boards, settings, notes: notesData, calculator: calcHistory, timerPresets };
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
+1 -1
View File
@@ -13,7 +13,7 @@ function initDataButtons() {
btnExport.addEventListener('click', async () => {
const widgetData = await Store.get('widgetStates');
const data = {
version: '1.7.0',
version: '1.9.0',
exported: new Date().toISOString(),
boards,
settings,
+65 -9
View File
@@ -25,17 +25,19 @@ const Onboarding = {
{
hero: '\uD83C\uDFA8',
title: '8 handgefertigte Themes',
text: 'Klicke auf den „Theme" Button im Header um dein Theme zu wählen. Jedes hat seinen eigenen Stil und Farbpalette.',
text: 'Klicke auf den \u201ETheme\u201C Button im Header um dein Theme zu w\u00E4hlen. Jedes hat seinen eigenen Stil und Farbpalette.',
showThemes: true
},
{
hero: '\u26A1',
title: 'Weitere Features',
hero: '\uD83E\uDDF0',
title: 'Widget-Toolbar',
features: [
'Suchleiste mit Google, DuckDuckGo oder Bing',
'Widget-Toolbar rechts \u2014 Notes und Checklisten erstellen',
'Notebook-Sidebar \u00FCber den \u201ENote\u201C Button oder die Toolbar',
'Funktioniert komplett offline \u2014 alles lokal gespeichert'
'Die schwebenden Buttons rechts \u00F6ffnen Widgets',
'Notes und Checklisten f\u00FCr schnelle Notizen',
'Taschenrechner mit History',
'Timer/Countdown mit speicherbaren Presets',
'Bild-Referenz Widgets (aktivierbar in Settings)',
'Notebook-Sidebar zeigt alle Notes auf einen Blick'
]
},
{
@@ -43,10 +45,16 @@ const Onboarding = {
title: 'Backups nicht vergessen!',
text: 'Deine Daten sind lokal im Browser gespeichert. Wenn du Browserdaten l\u00F6schst, gehen sie verloren! Sichere regelm\u00E4\u00DFig \u00FCber Settings \u2192 Data \u2192 Export. Wir erinnern dich alle 7 Tage daran.'
},
{
hero: '\uD83C\uDFAE',
title: 'Gaming Starter Board',
text: 'Spielst du Games wie Satisfactory, Factorio oder Star Citizen? Ich kann ein Board mit n\u00FCtzlichen Community-Links anlegen.',
interactive: 'gaming-board'
},
{
hero: '\uD83D\uDE80',
title: 'Bereit!',
text: 'Klicke auf \u201E+ Board\u201C um dein erstes Board zu erstellen, oder nutze den \u201EImport\u201C Button im Header um deine Browser-Lesezeichen zu importieren.'
text: 'Erstelle dein erstes Board mit \u201E+ Board\u201C oder importiere deine Browser-Lesezeichen \u00FCber den Import-Button im Header. Viel Spa\u00DF!'
}
],
@@ -160,7 +168,27 @@ const Onboarding = {
nav.appendChild(backBtn);
}
if (isLast) {
if (slide.interactive === 'gaming-board') {
// Interaktive Slide: Zwei Buttons statt "Weiter"
const noBtn = document.createElement('button');
noBtn.className = 'btn-secondary';
noBtn.textContent = 'Nein danke';
noBtn.addEventListener('click', () => {
this.currentSlide++;
this._render();
});
const yesBtn = document.createElement('button');
yesBtn.className = 'btn-primary';
yesBtn.textContent = 'Ja, gerne';
yesBtn.addEventListener('click', async () => {
await this._createGamingBoard();
this.currentSlide++;
this._render();
});
nav.append(noBtn, yesBtn);
} else if (isLast) {
const startBtn = document.createElement('button');
startBtn.className = 'btn-primary';
startBtn.textContent = 'Los geht\u2019s!';
@@ -181,6 +209,34 @@ const Onboarding = {
modal.appendChild(footer);
},
/**
* Gaming Starter Board erstellen
* Vorbefuelltes Board mit Community-Links fuer Factory/Space Games
*/
async _createGamingBoard() {
const gamingBoard = {
id: uid(),
title: '\uD83C\uDFAE Gaming',
bookmarks: [
{ id: uid(), title: 'Satisfactory Wiki', url: 'https://satisfactory.wiki.gg', desc: '' },
{ id: uid(), title: 'Satisfactory Calculator', url: 'https://satisfactorytools.com', desc: '' },
{ id: uid(), title: 'Factorio Wiki', url: 'https://wiki.factorio.com', desc: '' },
{ id: uid(), title: 'Factorio Cheatsheet', url: 'https://factoriocheatsheet.com', desc: '' },
{ id: uid(), title: 'Avorion Wiki', url: 'https://wiki.avorion.net', desc: '' },
{ id: uid(), title: 'Minecraft Wiki', url: 'https://minecraft.wiki', desc: '' },
{ id: uid(), title: 'Modrinth (Mods)', url: 'https://modrinth.com', desc: '' },
{ id: uid(), title: 'Star Citizen Wiki', url: 'https://starcitizen.tools', desc: '' },
{ id: uid(), title: 'UEX Corp (Trading)', url: 'https://uexcorp.space', desc: '' },
{ id: uid(), title: 'Hellion TradeCenter', url: 'https://hellion-initiative.online/tradecenter', desc: 'Trade Center f\u00FCr Star Citizen' }
],
blurred: false
};
boards.push(gamingBoard);
await saveBoards();
renderBoards();
},
/** Keyboard-Navigation */
_bindKeyboard() {
this._keyHandler = (e) => {
+1 -1
View File
@@ -25,7 +25,7 @@ function closeThemeModal() {
// ---- ACCORDION ----
function initAccordion() {
const defaultOpen = new Set(['appearance', 'behavior', 'widgets', 'data', 'help']);
const defaultOpen = new Set(['widgets']);
const sections = document.querySelectorAll('.settings-section[data-section]');
sections.forEach(section => {