Compare commits

..

1 Commits

Author SHA1 Message Date
renovate-bot 6a9e54fdae chore(deps): update github/codeql-action digest to 9e0d7b8
Security / scan (pull_request) Successful in 39s
2026-05-18 00:30:55 +00:00
17 changed files with 236 additions and 481 deletions
@@ -1,22 +1,10 @@
# Release — creates ZIP packages for Chrome, Firefox and Opera on new tag
name: Release
# Wird bei einem vX.Y.Z-Tag-Push ausgeloest. Baut die drei Web-Extension-ZIPs
# (Chrome/Firefox/Opera) und haengt sie ans passende Gitea-Release.
#
# Portiert von GitHub Actions auf Gitea Actions (2026-06): der fruehere
# softprops/action-gh-release-Step ist GitHub-spezifisch und laeuft auf Gitea
# nicht. Ersetzt durch die Gitea-native release-action (volle gitea.com-URL,
# da DEFAULT_ACTIONS_URL=github nackte Namen sonst von github.com zieht).
# Muster uebernommen aus HellionChat/.gitea/workflows/release.yml.
on:
push:
tags:
- 'v*'
# Manueller Recovery-Trigger: in Gitea "Run workflow" und den Tag (z.B. v2.2.0)
# im Ref-Dropdown waehlen, NICHT master. Der Validate-Step unten failt hart
# bei einem Nicht-Tag-Ref, weil die release-action GITHUB_REF direkt liest.
workflow_dispatch:
permissions:
contents: write
@@ -25,20 +13,7 @@ jobs:
build-release:
name: Build & Release
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
# release-action liest GITHUB_REF direkt (kein tag_name-Input). Vorab
# validieren, damit manuelle Dispatches von einem Branch-Ref hier laut
# scheitern statt nach einem vollen Build.
- name: Validate tag ref
run: |
if [[ "${GITHUB_REF}" != refs/tags/v* ]]; then
echo "::error::Release-Workflow muss auf einem v*-Tag laufen, got ${GITHUB_REF}"
echo "::error::Tag pushen, oder im workflow_dispatch-Ref-Dropdown den Tag (nicht master) waehlen."
exit 1
fi
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
@@ -51,7 +26,7 @@ jobs:
mkdir -p dist
zip -r "dist/hellion-newtab-${{ steps.version.outputs.tag }}-chrome.zip" \
manifest.json newtab.html src/js/*.js src/css/ assets/ _locales/ \
-x "*.git*" "dist/*" ".github/*" ".gitea/*" "src/js/opera/*"
-x "*.git*" "dist/*" ".github/*" "src/js/opera/*"
- name: Create Firefox ZIP (Manifest V3)
run: |
@@ -59,7 +34,7 @@ jobs:
cp manifest.firefox.json manifest.json
zip -r "dist/hellion-newtab-${{ steps.version.outputs.tag }}-firefox.zip" \
manifest.json newtab.html src/js/*.js src/css/ assets/ _locales/ \
-x "*.git*" "dist/*" ".github/*" ".gitea/*" "manifest.chrome-backup.json" "manifest.firefox.json" "src/js/opera/*"
-x "*.git*" "dist/*" ".github/*" "manifest.chrome-backup.json" "manifest.firefox.json" "src/js/opera/*"
mv manifest.chrome-backup.json manifest.json
- name: Create Opera/Opera GX ZIP (Manifest V3 + workaround)
@@ -68,7 +43,7 @@ jobs:
cp manifest.opera.json manifest.json
zip -r "dist/hellion-newtab-${{ steps.version.outputs.tag }}-opera.zip" \
manifest.json newtab.html src/js/*.js src/js/opera/ src/css/ assets/ _locales/ \
-x "*.git*" "dist/*" ".github/*" ".gitea/*" "manifest.chrome-backup.json" "manifest.opera.json"
-x "*.git*" "dist/*" ".github/*" "manifest.chrome-backup.json" "manifest.opera.json"
mv manifest.chrome-backup.json manifest.json
- name: Generate SHA256 checksums
@@ -77,19 +52,10 @@ jobs:
sha256sum *.zip > checksums-sha256.txt
cat checksums-sha256.txt
# Gitea-native Release-Action. Legt das Release an, falls der Tag noch
# keins hat, oder aktualisiert das bestehende und haengt die Assets an.
# Der auto-injizierte GITHUB_TOKEN auf Gitea Actions hat Gitea-API-Scope
# und reicht fuer Release-Write.
- name: Attach to Gitea release
uses: https://gitea.com/actions/release-action@main
- name: Create GitHub Release
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3
with:
files: |-
dist/hellion-newtab-${{ steps.version.outputs.tag }}-chrome.zip
dist/hellion-newtab-${{ steps.version.outputs.tag }}-firefox.zip
dist/hellion-newtab-${{ steps.version.outputs.tag }}-opera.zip
dist/checksums-sha256.txt
api_key: ${{ secrets.GITHUB_TOKEN }}
name: "Hellion NewTab ${{ steps.version.outputs.tag }}"
body: |
## Hellion NewTab ${{ steps.version.outputs.tag }}
@@ -98,7 +64,13 @@ jobs:
- **Firefox:** `hellion-newtab-${{ steps.version.outputs.tag }}-firefox.zip`
- **Opera / Opera GX:** `hellion-newtab-${{ steps.version.outputs.tag }}-opera.zip`
Vollstaendige Installationsanleitung siehe README.
See [README](README.md) for the full installation instructions.
### Checksums
`checksums-sha256.txt` zum Verifizieren der Dateiintegritaet.
See `checksums-sha256.txt` to verify file integrity.
files: |
dist/hellion-newtab-${{ steps.version.outputs.tag }}-chrome.zip
dist/hellion-newtab-${{ steps.version.outputs.tag }}-firefox.zip
dist/hellion-newtab-${{ steps.version.outputs.tag }}-opera.zip
dist/checksums-sha256.txt
generate_release_notes: true
+42
View File
@@ -0,0 +1,42 @@
# Sicherheitsprüfung — läuft bei Push und PR auf main/master
name: Security Scan
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
schedule:
# Wöchentlich Montag 06:00 UTC
- cron: '0 6 * * 1'
permissions:
contents: read
security-events: write
jobs:
codeql:
name: CodeQL Analysis
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Initialize CodeQL
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
with:
languages: javascript
- name: Run CodeQL Analysis
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Dependency Review
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
-17
View File
@@ -6,23 +6,6 @@ All notable changes per version. Format based on [Keep a Changelog](https://keep
---
## [2.2.0] — 2026-06-13
### Added
- **View Transitions** — Native cross-fade on theme switch and central modals (Settings, Theme-Picker, custom dialogs, bookmark import, add-board, add-bookmark, rename). Feature-detected via `document.startViewTransition`, instant swap on older browsers. Widgets, notebook sidebar and onboarding deliberately excluded.
- **`color-scheme: dark`** — Declares the dark UA scheme so native scrollbars and form controls match the dark themes.
- **Accessibility pass** — `role="dialog"` / `aria-modal` / `aria-labelledby` on Settings and Theme-Picker with new focus trap, Escape handling and focus return; `role="toolbar"` + per-button `aria-label` on the widget toolbar; keyboard-operable theme cards (`role="button"`, `tabindex`, Enter/Space); `role="switch"` + `aria-checked` on settings toggles; focusable boards and bookmarks; visible `:focus-visible` ring tinted in the theme accent. New ARIA strings run through the i18n pipeline. Verified with Lighthouse and the axe DevTools extension, not a formal WCAG 2.2 AA audit.
### Changed
- **`color-mix()` token refactor** — Accent-derived color tokens now computed via `color-mix()` from `var(--accent)`, classified per theme (formula vs. override). Theme-specific alpha values and real special colors stay overrides; no visible theme change. `--border-accent` `:root` drift (179,92,255 → 179,89,255) fixed at both the Nebula block and the `:root` default.
- **`@layer` cascade ordering** — CSS reorganized into six layers (base / theme / layout / components / theme-overrides / utilities) so theme component overrides win deterministically instead of relying on selector specificity.
- **`clamp()` fluid typography** — Clock, logo, board titles and main spacing scale fluidly via `clamp()`. Existing 768px / 480px breakpoints kept as a safety net.
### Accessibility
- **`prefers-reduced-motion`** — Unlayered `@media` block disables transitions and animations, including the `::view-transition-*` pseudo-elements. The 350ms widget teardown fallback timer is retained so widgets still close when `transitionend` no longer fires under reduced motion.
---
## [2.1.0] — 2026-04-16
### Added
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "__MSG_extName__",
"default_locale": "en",
"version": "2.2.0",
"version": "2.1.0",
"description": "__MSG_extDesc__",
"author": "Hellion Online Media - Florian Wathling",
"homepage_url": "https://hellion-media.de",
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "__MSG_extName__",
"default_locale": "en",
"version": "2.2.0",
"version": "2.1.0",
"description": "__MSG_extDesc__",
"author": "Hellion Online Media - Florian Wathling",
"homepage_url": "https://hellion-media.de",
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "__MSG_extName__",
"default_locale": "en",
"version": "2.2.0",
"version": "2.1.0",
"description": "__MSG_extDesc__",
"author": "Hellion Online Media - Florian Wathling",
"homepage_url": "https://hellion-media.de",
+24 -24
View File
@@ -60,7 +60,7 @@
</div>
<!-- WIDGET TOOLBAR -->
<div class="widget-toolbar" id="widgetToolbar" role="toolbar" aria-orientation="vertical" data-i18n-aria-label="toolbar.label">
<div class="widget-toolbar" id="widgetToolbar">
<button class="widget-toolbar-btn" data-action="new-note" data-i18n-title="toolbar.note" title="Note erstellen">
<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>
</button>
@@ -103,9 +103,9 @@
<!-- SETTINGS PANEL -->
<div class="panel-overlay" id="settingsOverlay"></div>
<aside class="settings-panel" id="settingsPanel" role="dialog" aria-modal="true" aria-labelledby="settingsPanelTitle" aria-hidden="true">
<aside class="settings-panel" id="settingsPanel">
<div class="panel-header">
<span id="settingsPanelTitle" data-i18n="settings.title">Einstellungen</span>
<span data-i18n="settings.title">Einstellungen</span>
<button class="btn-close" id="btnCloseSettings" data-i18n-title="dialog.close"></button>
</div>
<div class="panel-body">
@@ -154,7 +154,7 @@
<span class="setting-desc" data-i18n="settings.image_ref.desc">Bilder als Referenz anzeigen (nur aktuelle Session)</span>
</div>
<label class="toggle">
<input type="checkbox" id="settingImageRef" role="switch" aria-checked="false">
<input type="checkbox" id="settingImageRef">
<span class="slider"></span>
</label>
</div>
@@ -223,7 +223,7 @@
<div class="panel-footer">
<div class="about-block">
<div class="about-logo" data-i18n="about.title">⬡ HELLION NEWTAB</div>
<div class="about-version">Version 2.2.0 · by Hellion Online Media</div>
<div class="about-version">Version 2.1.0 · by Hellion Online Media</div>
<div class="about-links">
<a href="https://hellion-media.de/impressum" target="_blank" class="about-link">
@@ -291,63 +291,63 @@
<!-- THEME PICKER MODAL -->
<div class="modal-overlay" id="themeOverlay">
<div class="theme-modal" id="themeModal" role="dialog" aria-modal="true" aria-labelledby="themeModalTitle" aria-hidden="true">
<div class="theme-modal" id="themeModal">
<div class="modal-header">
<span id="themeModalTitle" data-i18n="modal.theme_header">Darstellung</span>
<span data-i18n="modal.theme_header">Darstellung</span>
<button class="btn-close" id="btnCloseTheme" data-i18n-title="dialog.close"></button>
</div>
<div class="theme-grid">
<div class="theme-card active" data-value="nebula" role="button" tabindex="0" aria-pressed="true" data-i18n-aria-label="theme.card.nebula">
<div class="theme-card active" data-value="nebula">
<img class="theme-card-img" src="assets/themes/bg-nebula.webp" alt="Nebula" />
<span class="theme-card-label">Nebula</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="crescent" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.crescent">
<div class="theme-card" data-value="crescent">
<img class="theme-card-img" src="assets/themes/bg-crescent.webp" alt="Crescent" />
<span class="theme-card-label">Crescent</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="event-horizon" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.event_horizon">
<div class="theme-card" data-value="event-horizon">
<img class="theme-card-img" src="assets/themes/bg-event-horizon.webp" alt="Event Horizon" />
<span class="theme-card-label">Event Horizon</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="merchantman" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.merchantman">
<div class="theme-card" data-value="merchantman">
<img class="theme-card-img" src="assets/themes/bg-merchantman.webp" alt="Merchantman" />
<span class="theme-card-label">Merchantman</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="julia-jin" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.julia_jin">
<div class="theme-card" data-value="julia-jin">
<img class="theme-card-img" src="assets/themes/bg-julia-jin.webp" alt="Julia &amp; Jin" />
<span class="theme-card-label">Julia &amp; Jin</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="sc-sunset" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.sc_sunset">
<div class="theme-card" data-value="sc-sunset">
<img class="theme-card-img" src="assets/themes/bg-sc-sunset.webp" alt="SC Sunset" />
<span class="theme-card-label">SC Sunset</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="hellion-hud" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.hellion_hud">
<div class="theme-card" data-value="hellion-hud">
<img class="theme-card-img" src="assets/themes/bg-hellion-hud.webp" alt="Hellion HUD" />
<span class="theme-card-label">HUD</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="hellion-energy" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.hellion_energy">
<div class="theme-card" data-value="hellion-energy">
<img class="theme-card-img" src="assets/themes/bg-hellion-energy.webp" alt="Hellion Energy" />
<span class="theme-card-label">Energy</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="satisfactory" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.satisfactory">
<div class="theme-card" data-value="satisfactory">
<img class="theme-card-img" src="assets/themes/bg-satisfactory.webp" alt="Satisfactory" />
<span class="theme-card-label">Satisfactory</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="avorion" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.avorion">
<div class="theme-card" data-value="avorion">
<img class="theme-card-img" src="assets/themes/bg-avorion.webp" alt="Avorion" />
<span class="theme-card-label">Avorion</span>
<span class="theme-card-check"></span>
</div>
<div class="theme-card" data-value="hellion-stealth" role="button" tabindex="0" aria-pressed="false" data-i18n-aria-label="theme.card.hellion_stealth">
<div class="theme-card" data-value="hellion-stealth">
<img class="theme-card-img" src="assets/themes/bg-scPolaris.webp" alt="Hellion Stealth" />
<span class="theme-card-label">Stealth</span>
<span class="theme-card-check"></span>
@@ -382,42 +382,42 @@
<span class="setting-label" data-i18n="settings.compact">Kompaktmodus</span>
<span class="setting-desc" data-i18n="settings.compact.desc">Weniger Abstand für mehr Bookmarks</span>
</div>
<label class="toggle"><input type="checkbox" id="settingCompact" role="switch" aria-checked="false" /><span class="slider"></span></label>
<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" data-i18n="settings.shorten">Lange Titel kürzen</span>
<span class="setting-desc" data-i18n="settings.shorten.desc">Titel auf eine Zeile mit „…" kürzen</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShorten" role="switch" aria-checked="false" /><span class="slider"></span></label>
<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" data-i18n="settings.search">Suchleiste anzeigen</span>
<span class="setting-desc" data-i18n="settings.search.desc">Suchleiste unter dem Header ein/aus</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShowSearch" role="switch" aria-checked="true" checked /><span class="slider"></span></label>
<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" data-i18n="settings.newtab">Links in neuem Tab</span>
<span class="setting-desc" data-i18n="settings.newtab.desc">Bookmarks in neuem Browser-Tab öffnen</span>
</div>
<label class="toggle"><input type="checkbox" id="settingNewTab" role="switch" aria-checked="true" checked /><span class="slider"></span></label>
<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" data-i18n="settings.showdesc">Beschreibungen anzeigen</span>
<span class="setting-desc" data-i18n="settings.showdesc.desc">Gespeicherte Beschreibung unter Bookmarks</span>
</div>
<label class="toggle"><input type="checkbox" id="settingShowDesc" role="switch" aria-checked="false" /><span class="slider"></span></label>
<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" data-i18n="settings.hideextra">Bookmarks ausblenden</span>
<span class="setting-desc" data-i18n="settings.hideextra.desc">Überzählige Bookmarks in langen Boards verstecken</span>
</div>
<label class="toggle"><input type="checkbox" id="settingHideExtra" role="switch" aria-checked="false" /><span class="slider"></span></label>
<label class="toggle"><input type="checkbox" id="settingHideExtra" /><span class="slider"></span></label>
</div>
<div class="setting-row" id="visibleCountRow">
<div class="setting-info">
+119 -172
View File
@@ -1,12 +1,3 @@
/* =============================================
CASCADE LAYERS (v2.2) — Reihenfolge = Prioritaet (spaeter gewinnt)
theme-overrides MUSS nach components stehen, sonst verlieren die
theme-spezifischen Komponenten-Regeln (Board-Blur, Cinzel-Logos,
Hover-Tints) und alle 11 Themes brechen.
prefers-reduced-motion bleibt bewusst UNGESCHICHTET (Phase 4).
============================================= */
@layer base, theme, layout, components, theme-overrides, utilities;
/* =============================================
HELLION Dashboard — Theme System
Themes: nebula | crescent | event-horizon | merchantman | julia-jin | sc-sunset | hellion-hud | hellion-energy
@@ -18,7 +9,6 @@
- Inter: Fließtext und allgemeine Lesbarkeit
- Cinzel: Alternative Display-Schriftart für bestimmte Themes
============================================= */
@layer base {
/* Rajdhani - Lokal */
@font-face {
font-family: 'Rajdhani';
@@ -56,15 +46,9 @@
/* ---- BASE VARIABLES (Nebula = Default) ---- */
:root {
--accent: #b359ff;
/* Akzent-Töne als Formel aus --accent abgeleitet (Spec Block 1, color-mix Mittelweg).
--*-pct ist die Pro-Theme-Alpha-Variable; Default = häufigster Wert über alle 11 Themes.
Themes mit abweichendem Alpha überschreiben nur die Prozent-Variable im theme-Layer. */
--accent-dim-pct: 12%;
--accent-glow-pct: 8%;
--border-accent-pct: 25%;
--accent-dim: color-mix(in srgb, var(--accent) var(--accent-dim-pct), transparent);
--accent-glow: color-mix(in srgb, var(--accent) var(--accent-glow-pct), transparent);
--border-accent: color-mix(in srgb, var(--accent) var(--border-accent-pct), transparent);
--accent-dim: rgba(179, 89, 255, 0.12);
--accent-glow: rgba(179, 89, 255, 0.05);
--border-accent: rgba(179, 92, 255, 0.25);
--bg-primary: #050308;
--bg-board: rgba(10, 6, 14, 0.55);
--border: rgba(255, 255, 255, 0.06);
@@ -77,18 +61,14 @@
--radius: 8px;
--radius-sm: 5px;
--board-width: 240px;
--spacing: clamp(0.5rem, 0.4583rem + 0.14vw, 0.625rem);
--spacing: 10px;
--spacing-compact: 5px;
--overlay-bg: radial-gradient(circle at center, rgba(10,6,14,0.3) 0%, rgba(5,3,8,0.85) 100%);
--header-bg: rgba(10,6,14,0.90);
--board-hover-border-pct: 22%;
--logo-shadow-pct: 45%;
--toggle-on-bg-pct: 20%;
--board-hover-border: color-mix(in srgb, var(--accent) var(--board-hover-border-pct), transparent);
--board-hover-border: rgba(179,89,255,0.18);
--toggle-on-bg: rgba(214,92,255,0.22);
--logo-shadow: color-mix(in srgb, var(--accent) var(--logo-shadow-pct), transparent);
--logo-shadow: rgba(179,89,255,0.35);
--bg-solid-fallback: #0a060e;
color-scheme: dark;
}
/* Fallback fuer Browser die backdrop-filter blockieren (z.B. Brave Shields) */
@@ -102,17 +82,15 @@
background-color: var(--bg-solid-fallback, var(--bg-primary));
}
}
}
@layer theme {
/* ============================================
THEME: NEBULA (magenta / cosmic nebula)
============================================ */
[data-theme="nebula"] {
--accent: #b359ff;
--accent-glow-pct: 5%;
--board-hover-border-pct: 18%;
--logo-shadow-pct: 35%;
--accent-dim: rgba(179, 89, 255, 0.12);
--accent-glow: rgba(179, 89, 255, 0.05);
--border-accent: rgba(179, 92, 255, 0.25);
--bg-primary: #050308;
--bg-board: rgba(10, 6, 14, 0.55);
--border: rgba(255, 255, 255, 0.055);
@@ -123,18 +101,22 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: radial-gradient(circle at center, rgba(10,6,14,0.3) 0%, rgba(5,3,8,0.85) 100%);
--header-bg: rgba(10,6,14,0.92);
--board-hover-border: rgba(179,89,255,0.18);
--toggle-on-bg: rgba(214,92,255,0.22);
--logo-shadow: rgba(179,89,255,0.35);
--bg-solid-fallback: #0a060e;
}
[data-theme="nebula"] .board { border-color: rgba(214,92,255,0.10); }
[data-theme="nebula"] .bm-item:hover { background: rgba(214,92,255,0.05); }
/* ============================================
THEME: CRESCENT (gold / minimalist night)
============================================ */
[data-theme="crescent"] {
--accent: #d4bd8a;
--accent-glow-pct: 5%;
--board-hover-border-pct: 20%;
--logo-shadow-pct: 40%;
--accent-dim: rgba(212, 189, 138, 0.12);
--accent-glow: rgba(212, 189, 138, 0.05);
--border-accent: rgba(212, 189, 138, 0.25);
--bg-primary: #06080f;
--bg-board: rgba(8, 12, 22, 0.45);
--border: rgba(212, 189, 138, 0.10);
@@ -145,17 +127,27 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: radial-gradient(circle at center, rgba(6,8,15,0.2) 0%, rgba(6,8,15,0.9) 100%);
--header-bg: rgba(6,8,15,0.95);
--board-hover-border: rgba(212, 189, 138, 0.20);
--toggle-on-bg: rgba(200,168,74,0.22);
--logo-shadow: rgba(212, 189, 138, 0.40);
--bg-solid-fallback: #080c16;
letter-spacing: 0.5px;
}
[data-theme="crescent"] .logo { font-family: 'Cinzel', serif; letter-spacing: 4px; }
[data-theme="crescent"] .clock { font-family: 'Cinzel', serif; }
[data-theme="crescent"] .board-title { letter-spacing: 2px; }
[data-theme="crescent"] .bm-item:hover { background: rgba(200,168,74,0.05); }
[data-theme="crescent"] .board { border-color: rgba(200,168,74,0.10); }
/* ============================================
THEME: EVENT HORIZON (Cosmic Purple / deep space)
============================================ */
[data-theme="event-horizon"] {
--accent: #9d5cff;
--border-accent-pct: 28%;
--accent-dim: rgba(157, 92, 255, 0.12);
--accent-glow: rgba(157, 92, 255, 0.08);
--border-accent: rgba(157, 92, 255, 0.28);
--bg-primary: #040308;
--bg-board: rgba(8, 5, 15, 0.45);
--border: rgba(157, 92, 255, 0.12);
@@ -166,17 +158,23 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: radial-gradient(circle at center, rgba(4,3,8,0.2) 0%, rgba(4,3,8,0.95) 100%);
--header-bg: rgba(4,3,8,0.96);
--board-hover-border: rgba(157, 92, 255, 0.22);
--toggle-on-bg: rgba(224,128,48,0.22);
--logo-shadow: rgba(157, 92, 255, 0.45);
--bg-solid-fallback: #08050f;
}
[data-theme="event-horizon"] .board { border-color: rgba(157, 92, 255, 0.15); }
[data-theme="event-horizon"] .bm-item:hover { background: rgba(157, 92, 255, 0.08); }
/* ============================================
THEME: MERCHANTMAN (Emerald / industrial sci-fi)
============================================ */
[data-theme="merchantman"] {
--accent: #2eb8b8;
--accent-glow-pct: 6%;
--board-hover-border-pct: 20%;
--accent-dim: rgba(46, 184, 184, 0.12);
--accent-glow: rgba(46, 184, 184, 0.06);
--border-accent: rgba(46, 184, 184, 0.25);
--bg-primary: #040808;
--bg-board: rgba(6, 10, 10, 0.58);
--border: rgba(46, 184, 184, 0.10);
@@ -187,17 +185,22 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: radial-gradient(circle at 30% 40%, rgba(4,8,8,0.2) 0%, rgba(4,8,8,0.92) 100%);
--header-bg: rgba(4,8,8,0.95);
--board-hover-border: rgba(46, 184, 184, 0.20);
--toggle-on-bg: rgba(78,207,207,0.22);
--logo-shadow: rgba(46, 184, 184, 0.45);
--bg-solid-fallback: #060a0a;
}
[data-theme="merchantman"] .board { border-color: rgba(46, 184, 184, 0.12); }
[data-theme="merchantman"] .bm-item:hover { background: rgba(46, 184, 184, 0.06); }
/* ============================================
THEME: JULIA & JIN (Aetherial Night / FFXIV)
============================================ */
[data-theme="julia-jin"] {
--accent: #7db3ff;
--border-accent-pct: 30%;
--logo-shadow-pct: 50%;
--accent-dim: rgba(125, 179, 255, 0.12);
--accent-glow: rgba(125, 179, 255, 0.08);
--border-accent: rgba(125, 179, 255, 0.30);
--bg-primary: #03050a;
--bg-board: rgba(7, 10, 20, 0.60);
--border: rgba(125, 179, 255, 0.12);
@@ -208,16 +211,25 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: linear-gradient(180deg, rgba(3,5,10,0.85) 0%, rgba(3,5,10,0.25) 50%, rgba(3,5,10,0.92) 100%);
--header-bg: rgba(3,5,10,0.94);
--board-hover-border: rgba(125, 179, 255, 0.22);
--toggle-on-bg: rgba(91,159,255,0.22);
--logo-shadow: rgba(125, 179, 255, 0.50);
--bg-solid-fallback: #070a14;
}
[data-theme="julia-jin"] .logo { font-family: 'Cinzel', serif; letter-spacing: 5px; text-transform: uppercase; }
[data-theme="julia-jin"] .clock { font-family: 'Cinzel', serif; font-weight: 600; }
[data-theme="julia-jin"] .board-title { letter-spacing: 2px; font-weight: 500; }
[data-theme="julia-jin"] .board { border-color: rgba(125, 179, 255, 0.15); backdrop-filter: blur(4px); }
[data-theme="julia-jin"] .bm-item:hover { background: rgba(125, 179, 255, 0.08); }
/* ============================================
THEME: SC Sunset - Horizon Glow
============================================ */
[data-theme="sc-sunset"] {
--accent: #ff8c3d;
--border-accent-pct: 28%;
--accent-dim: rgba(255, 140, 61, 0.12);
--accent-glow: rgba(255, 140, 61, 0.08);
--border-accent: rgba(255, 140, 61, 0.28);
--bg-primary: #080503;
--bg-board: rgba(15, 10, 8, 0.55);
--border: rgba(255, 140, 61, 0.10);
@@ -228,19 +240,25 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: radial-gradient(circle at 50% 60%, rgba(8,5,3,0.1) 0%, rgba(8,5,3,0.92) 100%);
--header-bg: rgba(8,5,3,0.94);
--board-hover-border: rgba(255, 140, 61, 0.22);
--toggle-on-bg: rgba(240,124,48,0.22);
--logo-shadow: rgba(255, 140, 61, 0.45);
--bg-solid-fallback: #0f0a08;
}
[data-theme="sc-sunset"] .board {
border-color: rgba(255, 140, 61, 0.15);
backdrop-filter: blur(6px);}
[data-theme="sc-sunset"] .bm-item:hover {
background: rgba(255, 140, 61, 0.08); }
/* ============================================
THEME: HELLION HUD (circuit board / red+green)
============================================ */
[data-theme="hellion-hud"] {
--accent: #32ff6a;
--accent-dim-pct: 10%;
--accent-glow-pct: 5%;
--board-hover-border-pct: 20%;
--logo-shadow-pct: 40%;
--accent-dim: rgba(50, 255, 106, 0.10);
--accent-glow: rgba(50, 255, 106, 0.05);
--border-accent: rgba(50, 255, 106, 0.25);
--bg-primary: #030503;
--bg-board: rgba(5, 8, 5, 0.65);
--border: rgba(50, 255, 106, 0.12);
@@ -251,19 +269,31 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: radial-gradient(circle at center, rgba(3,5,3,0.15) 0%, rgba(3,5,3,0.95) 100%);
--header-bg: rgba(3,5,3,0.96);
--board-hover-border: rgba(50, 255, 106, 0.20);
--toggle-on-bg: rgba(34,204,68,0.20);
--logo-shadow: rgba(50, 255, 106, 0.40);
--bg-solid-fallback: #050805;
--danger: #ff4d4d;
}
[data-theme="hellion-hud"] .board {
border-color: rgba(50, 255, 106, 0.15);
backdrop-filter: blur(8px) brightness(1.1);
}
[data-theme="hellion-hud"] .clock {
font-family: 'Rajdhani', sans-serif;
font-weight: 700;
text-shadow: 0 0 10px var(--accent-glow);
}
/* ============================================
THEME: HELLION ENERGY (matrix / tactical green)
============================================ */
[data-theme="hellion-energy"] {
--accent: #1eff8e;
--border-accent-pct: 30%;
--board-hover-border-pct: 25%;
--logo-shadow-pct: 60%;
--accent-dim: rgba(30, 255, 142, 0.12);
--accent-glow: rgba(30, 255, 142, 0.08);
--border-accent: rgba(30, 255, 142, 0.30);
--bg-primary: #020503;
--bg-board: rgba(4, 7, 5, 0.60);
--border: rgba(30, 255, 142, 0.12);
@@ -274,18 +304,29 @@
--font-body: 'Inter', sans-serif;
--overlay-bg: radial-gradient(circle at center, rgba(2,5,3,0.1) 0%, rgba(2,5,3,0.95) 100%);
--header-bg: rgba(2,5,3,0.96);
--board-hover-border: rgba(30, 255, 142, 0.25);
--toggle-on-bg: rgba(0,232,122,0.18);
--logo-shadow: rgba(30, 255, 142, 0.60);
--bg-solid-fallback: #040705;
}
[data-theme="hellion-energy"] .board {
border-color: rgba(30, 255, 142, 0.15);
backdrop-filter: blur(10px) saturate(1.2);
}
[data-theme="hellion-energy"] .bm-item:hover {
background: rgba(30, 255, 142, 0.10);
box-shadow: inset 0 0 10px rgba(30, 255, 142, 0.05);
}
/* ============================================
THEME: SATISFACTORY (Industrial Desert)
============================================ */
[data-theme="satisfactory"] {
--accent: #00b4d8;
--border-accent-pct: 35%;
--board-hover-border-pct: 25%;
--logo-shadow-pct: 40%;
--accent-dim: rgba(0, 180, 216, 0.12);
--accent-glow: rgba(0, 180, 216, 0.08);
--border-accent: rgba(0, 180, 216, 0.35);
--bg-primary: #1a0f08;
--bg-board: rgba(26, 15, 8, 0.65);
--border: rgba(0, 180, 216, 0.15);
@@ -299,17 +340,25 @@
rgba(26,15,8,0.15) 50%,
rgba(26,15,8,0.90) 100%);
--header-bg: rgba(26,15,8,0.95);
--toggle-on-bg: color-mix(in srgb, var(--accent) var(--toggle-on-bg-pct), transparent);
--board-hover-border: rgba(0, 180, 216, 0.25);
--toggle-on-bg: rgba(0, 180, 216, 0.20);
--logo-shadow: rgba(0, 180, 216, 0.40);
--bg-solid-fallback: #1a0f08;
}
[data-theme="satisfactory"] .logo { font-family: 'Rajdhani', sans-serif; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; }
[data-theme="satisfactory"] .clock { font-family: 'Rajdhani', sans-serif; font-weight: 600; color: var(--accent); }
[data-theme="satisfactory"] .board-title { font-family: 'Rajdhani', sans-serif; letter-spacing: 1.5px; text-transform: uppercase; }
[data-theme="satisfactory"] .board { border-color: rgba(0, 180, 216, 0.20); backdrop-filter: blur(12px); }
[data-theme="satisfactory"] .bm-item:hover { background: rgba(0, 180, 216, 0.10); }
/* ============================================
THEME: AVORION (Deep Void)
============================================ */
[data-theme="avorion"] {
--accent: #2ec4a0;
--border-accent-pct: 30%;
--logo-shadow-pct: 50%;
--accent-dim: rgba(46, 196, 160, 0.12);
--accent-glow: rgba(46, 196, 160, 0.08);
--border-accent: rgba(46, 196, 160, 0.30);
--bg-primary: #020d0c;
--bg-board: rgba(2, 13, 12, 0.60);
--border: rgba(46, 196, 160, 0.12);
@@ -322,18 +371,25 @@
transparent 0%,
rgba(2, 13, 12, 0.95) 100%);
--header-bg: rgba(2, 13, 12, 0.94);
--toggle-on-bg-pct: 18%;
--toggle-on-bg: color-mix(in srgb, var(--accent) var(--toggle-on-bg-pct), transparent);
--board-hover-border: rgba(46, 196, 160, 0.22);
--toggle-on-bg: rgba(46, 196, 160, 0.18);
--logo-shadow: rgba(46, 196, 160, 0.50);
--bg-solid-fallback: #020d0c;
}
[data-theme="avorion"] .logo { font-family: 'Rajdhani', sans-serif; font-weight: 500; letter-spacing: 6px; text-transform: uppercase; }
[data-theme="avorion"] .clock { font-family: 'Rajdhani', sans-serif; font-weight: 400; color: var(--accent); }
[data-theme="avorion"] .board-title { font-family: 'Rajdhani', sans-serif; font-weight: 500; text-transform: uppercase; }
[data-theme="avorion"] .board { border-color: rgba(46, 196, 160, 0.15); backdrop-filter: blur(8px); }
[data-theme="avorion"] .bm-item:hover { background: rgba(46, 196, 160, 0.08); }
/* ============================================
THEME: HELLION STEALTH (Tactical Recon)
============================================ */
[data-theme="hellion-stealth"] {
--accent: #5ec2ff;
--border-accent-pct: 35%;
--board-hover-border-pct: 25%;
--accent-dim: rgba(94, 194, 255, 0.12);
--accent-glow: rgba(94, 194, 255, 0.08);
--border-accent: rgba(94, 194, 255, 0.35);
--bg-primary: #0d0f12;
--bg-board: rgba(13, 15, 18, 0.70);
--border: rgba(94, 194, 255, 0.15);
@@ -346,80 +402,21 @@
transparent 0%,
rgba(13, 15, 18, 0.90) 100%);
--header-bg: rgba(13, 15, 18, 0.96);
--toggle-on-bg: color-mix(in srgb, var(--accent) var(--toggle-on-bg-pct), transparent);
--board-hover-border: rgba(94, 194, 255, 0.25);
--toggle-on-bg: rgba(94, 194, 255, 0.20);
--logo-shadow: rgba(94, 194, 255, 0.45);
--bg-solid-fallback: #0d0f12;
}
}
/* ============================================
THEME-SCOPED KOMPONENTEN-REGELN (Sammelblock fuer @layer theme-overrides)
Aus den 11 [data-theme]-Bloecken extrahiert. Muss spaeter als components
greifen, sonst verlieren Board-Blur, Cinzel-Logos und Hover-Tints.
============================================ */
@layer theme-overrides {
[data-theme="nebula"] .board { border-color: rgba(214,92,255,0.10); }
[data-theme="nebula"] .bm-item:hover { background: rgba(214,92,255,0.05); }
[data-theme="crescent"] .logo { font-family: 'Cinzel', serif; letter-spacing: 4px; }
[data-theme="crescent"] .clock { font-family: 'Cinzel', serif; }
[data-theme="crescent"] .board-title { letter-spacing: 2px; }
[data-theme="crescent"] .bm-item:hover { background: rgba(200,168,74,0.05); }
[data-theme="crescent"] .board { border-color: rgba(200,168,74,0.10); }
[data-theme="event-horizon"] .board { border-color: rgba(157, 92, 255, 0.15); }
[data-theme="event-horizon"] .bm-item:hover { background: rgba(157, 92, 255, 0.08); }
[data-theme="merchantman"] .board { border-color: rgba(46, 184, 184, 0.12); }
[data-theme="merchantman"] .bm-item:hover { background: rgba(46, 184, 184, 0.06); }
[data-theme="julia-jin"] .logo { font-family: 'Cinzel', serif; letter-spacing: 5px; text-transform: uppercase; }
[data-theme="julia-jin"] .clock { font-family: 'Cinzel', serif; font-weight: 600; }
[data-theme="julia-jin"] .board-title { letter-spacing: 2px; font-weight: 500; }
[data-theme="julia-jin"] .board { border-color: rgba(125, 179, 255, 0.15); backdrop-filter: blur(4px); }
[data-theme="julia-jin"] .bm-item:hover { background: rgba(125, 179, 255, 0.08); }
[data-theme="sc-sunset"] .board {
border-color: rgba(255, 140, 61, 0.15);
backdrop-filter: blur(6px);}
[data-theme="sc-sunset"] .bm-item:hover {
background: rgba(255, 140, 61, 0.08); }
[data-theme="hellion-hud"] .board {
border-color: rgba(50, 255, 106, 0.15);
backdrop-filter: blur(8px) brightness(1.1);
}
[data-theme="hellion-hud"] .clock {
font-family: 'Rajdhani', sans-serif;
font-weight: 700;
text-shadow: 0 0 10px var(--accent-glow);
}
[data-theme="hellion-energy"] .board {
border-color: rgba(30, 255, 142, 0.15);
backdrop-filter: blur(10px) saturate(1.2);
}
[data-theme="hellion-energy"] .bm-item:hover {
background: rgba(30, 255, 142, 0.10);
box-shadow: inset 0 0 10px rgba(30, 255, 142, 0.05);
}
[data-theme="satisfactory"] .logo { font-family: 'Rajdhani', sans-serif; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; }
[data-theme="satisfactory"] .clock { font-family: 'Rajdhani', sans-serif; font-weight: 600; color: var(--accent); }
[data-theme="satisfactory"] .board-title { font-family: 'Rajdhani', sans-serif; letter-spacing: 1.5px; text-transform: uppercase; }
[data-theme="satisfactory"] .board { border-color: rgba(0, 180, 216, 0.20); backdrop-filter: blur(12px); }
[data-theme="satisfactory"] .bm-item:hover { background: rgba(0, 180, 216, 0.10); }
[data-theme="avorion"] .logo { font-family: 'Rajdhani', sans-serif; font-weight: 500; letter-spacing: 6px; text-transform: uppercase; }
[data-theme="avorion"] .clock { font-family: 'Rajdhani', sans-serif; font-weight: 400; color: var(--accent); }
[data-theme="avorion"] .board-title { font-family: 'Rajdhani', sans-serif; font-weight: 500; text-transform: uppercase; }
[data-theme="avorion"] .board { border-color: rgba(46, 196, 160, 0.15); backdrop-filter: blur(8px); }
[data-theme="avorion"] .bm-item:hover { background: rgba(46, 196, 160, 0.08); }
[data-theme="hellion-stealth"] .logo { font-family: 'Rajdhani', sans-serif; font-weight: 700; letter-spacing: 4px; }
[data-theme="hellion-stealth"] .clock { font-family: 'Rajdhani', sans-serif; font-weight: 600; color: var(--accent); }
[data-theme="hellion-stealth"] .board-title { text-transform: uppercase; font-size: 0.85rem; letter-spacing: 2px; }
[data-theme="hellion-stealth"] .board { border-color: rgba(94, 194, 255, 0.15); backdrop-filter: blur(10px); }
[data-theme="hellion-stealth"] .bm-item:hover { background: rgba(94, 194, 255, 0.10); border-left: 2px solid var(--accent); }
}
/* ============================================
BASE STYLES
============================================ */
@layer base {
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
@@ -431,9 +428,7 @@ html, body {
overflow-x: hidden;
transition: background 0.5s;
}
}
@layer layout {
.bg-layer {
position: fixed; inset: 0; z-index: 0;
background-size: cover; background-position: center;
@@ -467,7 +462,7 @@ html, body {
.logo {
font-family: var(--font-display);
font-size: clamp(0.875rem, 0.8125rem + 0.21vw, 1.0625rem); font-weight: 700; letter-spacing: 3px;
font-size: 17px; font-weight: 700; letter-spacing: 3px;
color: var(--accent);
text-shadow: 0 0 24px var(--logo-shadow);
transition: color 0.5s, text-shadow 0.5s, font-family 0.1s;
@@ -480,7 +475,7 @@ html, body {
.clock {
font-family: var(--font-display);
font-size: clamp(1rem, 0.9167rem + 0.28vw, 1.25rem); font-weight: 500; letter-spacing: 2px;
font-size: 20px; font-weight: 500; letter-spacing: 2px;
color: var(--text-secondary);
transition: color 0.5s, font-family 0.1s;
line-height: 1;
@@ -520,9 +515,7 @@ html, body {
padding: 110px 40px 40px;
min-height: 100vh;
}
}
@layer components {
.board {
width: var(--board-width);
background: var(--bg-board);
@@ -562,7 +555,7 @@ html, body {
.board-title {
font-family: var(--font-display);
font-size: clamp(0.6875rem, 0.6458rem + 0.14vw, 0.8125rem); font-weight: 600; letter-spacing: 1.5px;
font-size: 13px; font-weight: 600; letter-spacing: 1.5px;
color: var(--accent); text-transform: uppercase;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
max-width: 160px;
@@ -852,11 +845,9 @@ body.show-desc .bm-desc { display: block; }
background: var(--accent-dim);
flex-shrink: 0;
}
}
/* ---- BOARD BLUR (Private Mode) ---- */
@layer utilities {
.board.blurred .board-list,
.board.blurred .show-more-btn,
.board.blurred .add-bm-btn {
@@ -899,10 +890,8 @@ body.show-desc .bm-desc { display: block; }
}
.btn-blur-board:hover { background: var(--accent-dim); color: var(--accent); }
.board.blurred .btn-blur-board { color: var(--accent); opacity: 0.7; }
}
@layer components {
/* ---- ABOUT BLOCK ---- */
.about-block {
padding: 4px 18px 14px;
@@ -2354,12 +2343,10 @@ body.show-desc .bm-desc { display: block; }
.settings-section.open .section-content {
max-height: 800px;
}
}
/* ============================================
UTILITY CLASSES
============================================ */
@layer utilities {
.hidden { display: none; }
.accent-text { color: var(--accent); }
.dim { opacity: 0.4; }
@@ -2373,27 +2360,6 @@ body.show-desc .bm-desc { display: block; }
.about-info-label-block { display: block; margin-bottom: 6px; }
.about-link-subtle { color: var(--text-secondary); text-decoration: none; }
.modal-input-spaced { margin-top: 8px; }
}
/* ============================================
A11Y FOCUS RING (v2.2)
============================================ */
@layer utilities {
/* A11y: sichtbarer Fokus-Ring, getoent im Theme-Akzent.
Liegt bewusst im spaeten utilities-Layer, damit er die 9 outline:none-Regeln
aus dem components-Layer ueber die Kaskaden-Ordnung schlaegt (Spezifitaet
zwischen Layern irrelevant). Nur :focus-visible, damit Maus-Klicks keinen Ring zeigen. */
:focus-visible {
outline: 2px solid color-mix(in srgb, var(--accent) 70%, white 30%);
outline-offset: 2px;
border-radius: 3px;
}
/* Bookmarks/Cards: Ring leicht enger, da sie eigene Radien haben */
.bm-item:focus-visible,
.theme-card:focus-visible {
outline-offset: 1px;
}
}
/* ============================================
RESPONSIVE — Mobile & Tablet
@@ -2464,22 +2430,3 @@ body.show-desc .bm-desc { display: block; }
.modal { width: calc(100vw - 32px); }
}
/* =============================================
prefers-reduced-motion — UNGESCHICHTET (kein @layer).
Ungeschichtete Regeln gewinnen ueber alle Layer.
Kappt alle Transitions/Animationen inkl. der
View-Transition-Pseudo-Elemente (der *-Selektor
trifft sie nicht zuverlaessig, daher explizit).
============================================= */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
transition: none !important;
animation: none !important;
}
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
}
+1 -1
View File
@@ -105,7 +105,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: '2.2.0', exported: new Date().toISOString(), boards, settings, notes: notesData, calculator: calcHistory, timerPresets };
const data = { version: '2.1.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');
+2 -25
View File
@@ -214,9 +214,6 @@ function createBmEl(bm) {
li.dataset.bmId = bm.id;
li.dataset.bmUrl = bm.url;
li.draggable = true;
li.setAttribute('role', 'link');
li.setAttribute('tabindex', '0');
li.setAttribute('aria-label', bm.title);
const favicon = document.createElement('div');
favicon.className = 'bm-favicon-local';
@@ -270,31 +267,11 @@ function bindBoardListEvents(list, board) {
window.open(url, settings.newTab ? '_blank' : '_self', 'noopener,noreferrer');
}
});
// Tastatur: Enter oeffnet den Bookmark wie ein Klick. role="link" erwartet
// nur Enter (Space ist Button-Semantik). Der Delete-Button bleibt ein echter
// <button> und feuert seinen eigenen Klick ueber Space/Enter selbst.
list.addEventListener('keydown', e => {
if (e.key !== 'Enter') return;
const bmItem = e.target.closest('.bm-item');
if (!bmItem || e.target !== bmItem) return; // nur wenn der li selbst fokussiert ist
e.preventDefault();
const url = bmItem.dataset.bmUrl;
if (url) {
window.open(url, settings.newTab ? '_blank' : '_self', 'noopener,noreferrer');
}
});
}
// ---- MODALS ----
// reduced-motion kappt das Fade ueber den ungeschichteten @media-Block.
// Feature-Detection-Fallback (Firefox < 144): instant.
function openModal(id) {
withViewTransition(() => document.getElementById(id).classList.add('active'));
}
function closeModal(id) {
withViewTransition(() => document.getElementById(id).classList.remove('active'));
}
function openModal(id) { document.getElementById(id).classList.add('active'); }
function closeModal(id) { document.getElementById(id).classList.remove('active'); }
function openAddBoardModal() {
document.getElementById('newBoardName').value = '';
+3 -5
View File
@@ -196,18 +196,16 @@ const BrowserBookmarkImport = {
overlay.appendChild(modal);
document.body.appendChild(overlay);
// View-Transition-Fade
withViewTransition(() => overlay.classList.add('active'));
// Animation
requestAnimationFrame(() => overlay.classList.add('active'));
},
/** Schliesst das Modal */
_closeModal() {
const overlay = document.getElementById('bmImportOverlay');
if (!overlay) return;
withViewTransition(() => {
overlay.classList.remove('active');
overlay.remove();
});
setTimeout(() => overlay.remove(), 250);
},
/**
+1 -1
View File
@@ -28,7 +28,7 @@ function initDataButtons() {
btnExport.addEventListener('click', async () => {
const widgetData = await Store.get('widgetStates');
const data = {
version: '2.2.0',
version: '2.1.0',
exported: new Date().toISOString(),
boards,
settings,
+4 -30
View File
@@ -40,34 +40,23 @@ const HellionDialog = {
*/
_show(config) {
return new Promise(resolve => {
const prevFocus = document.activeElement;
const overlay = document.createElement('div');
overlay.className = 'dialog-overlay';
const box = document.createElement('div');
box.className = 'dialog-box';
box.setAttribute('role', config.isConfirm ? 'alertdialog' : 'dialog');
box.setAttribute('aria-modal', 'true');
// Eindeutige IDs pro Dialog-Instanz: kurz gestapelte Dialoge (timer.js/
// image-ref.js feuern teils ohne await) duerfen sich keine festen IDs
// teilen, sonst liest der Screenreader ueber aria-* den falschen Titel.
const uid = 'dlg-' + Date.now().toString(36) + '-' + (HellionDialog._seq = (HellionDialog._seq || 0) + 1);
box.setAttribute('aria-labelledby', uid + '-title');
box.setAttribute('aria-describedby', uid + '-body');
// Header
const header = document.createElement('div');
header.className = 'dialog-header';
header.appendChild(this._createIcon(config.type));
const titleSpan = document.createElement('span');
titleSpan.id = uid + '-title';
titleSpan.textContent = config.title;
header.appendChild(titleSpan);
// Body
const body = document.createElement('div');
body.className = 'dialog-body';
body.id = uid + '-body';
body.textContent = config.message;
// Actions
@@ -75,12 +64,9 @@ const HellionDialog = {
actions.className = 'dialog-actions';
function cleanup(result) {
document.removeEventListener('keydown', keyHandler);
withViewTransition(() => {
overlay.classList.remove('active');
overlay.remove();
});
if (prevFocus && typeof prevFocus.focus === 'function') prevFocus.focus();
document.removeEventListener('keydown', keyHandler);
setTimeout(() => overlay.remove(), 200);
resolve(result);
}
@@ -118,24 +104,12 @@ const HellionDialog = {
e.preventDefault();
cleanup(config.isConfirm ? false : undefined);
}
if (e.key === 'Tab') {
// Fokus-Falle: nur die Buttons im actions-Container sind fokussierbar
const items = Array.from(actions.querySelectorAll('button'));
if (items.length === 0) return;
const first = items[0];
const last = items[items.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault(); last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault(); first.focus();
}
}
}
document.addEventListener('keydown', keyHandler);
document.body.appendChild(overlay);
// View-Transition uebernimmt das Fade; Fokus bleibt erhalten
withViewTransition(() => {
// Nächster Frame für CSS-Transition
requestAnimationFrame(() => {
overlay.classList.add('active');
confirmBtn.focus();
});
-30
View File
@@ -387,19 +387,6 @@ const STRINGS = {
'modal.rename_placeholder': 'Neuer Name...',
'modal.rename_confirm': 'Umbenennen',
'modal.theme_header': 'Darstellung',
// Theme-Picker-Cards (ARIA)
'theme.card.nebula': 'Theme Nebula wählen',
'theme.card.crescent': 'Theme Crescent wählen',
'theme.card.event_horizon': 'Theme Event Horizon wählen',
'theme.card.merchantman': 'Theme Merchantman wählen',
'theme.card.julia_jin': 'Theme Julia & Jin wählen',
'theme.card.sc_sunset': 'Theme SC Sunset wählen',
'theme.card.hellion_hud': 'Theme Hellion HUD wählen',
'theme.card.hellion_energy': 'Theme Hellion Energy wählen',
'theme.card.satisfactory': 'Theme Satisfactory wählen',
'theme.card.avorion': 'Theme Avorion wählen',
'theme.card.hellion_stealth': 'Theme Hellion Stealth wählen',
'toolbar.label': 'Widget-Werkzeugleiste',
// About
'about.title': '⬡ HELLION NEWTAB',
@@ -812,19 +799,6 @@ const STRINGS = {
'modal.rename_placeholder': 'New name...',
'modal.rename_confirm': 'Rename',
'modal.theme_header': 'Theme',
// Theme picker cards (ARIA)
'theme.card.nebula': 'Select Nebula theme',
'theme.card.crescent': 'Select Crescent theme',
'theme.card.event_horizon': 'Select Event Horizon theme',
'theme.card.merchantman': 'Select Merchantman theme',
'theme.card.julia_jin': 'Select Julia & Jin theme',
'theme.card.sc_sunset': 'Select SC Sunset theme',
'theme.card.hellion_hud': 'Select Hellion HUD theme',
'theme.card.hellion_energy': 'Select Hellion Energy theme',
'theme.card.satisfactory': 'Select Satisfactory theme',
'theme.card.avorion': 'Select Avorion theme',
'theme.card.hellion_stealth': 'Select Hellion Stealth theme',
'toolbar.label': 'Widget toolbar',
// About
'about.title': '⬡ HELLION NEWTAB',
@@ -891,10 +865,6 @@ function applyLanguage() {
el.title = text;
el.setAttribute('aria-label', text);
});
// aria-label ohne sichtbaren title (z.B. Theme-Cards)
document.querySelectorAll('[data-i18n-aria-label]').forEach(el => {
el.setAttribute('aria-label', t(el.dataset.i18nAriaLabel));
});
}
/**
+7 -104
View File
@@ -3,105 +3,24 @@
Settings Panel, Theme-Modal, Accordion, Toggles
============================================= */
// ---- A11Y: Fokus-Management fuer Modals ----
// Merkt sich das vor dem Oeffnen fokussierte Element, damit wir es beim
// Schliessen restaurieren koennen. Pro offenem Modal eine Closure-Variable.
const _focusReturn = { settings: null, theme: null };
/** Liefert die fokussierbaren Elemente innerhalb eines Containers. */
function _focusable(container) {
return Array.from(container.querySelectorAll(
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
)).filter(el => el.offsetParent !== null);
}
/** Tab/Shift+Tab im Container einfangen + Escape schliesst. */
function _makeTrap(container, closeFn) {
return function trap(e) {
// Ein offener HellionDialog (z.B. Reset-All-Confirm oder BG-URL-Alert aus
// dem Panel) hat Vorrang: sein eigener keydown-Handler uebernimmt Escape/Tab.
// Sonst schloessen beide Listener gleichzeitig und die Dialog-Fokusfalle wird loechrig.
if (document.querySelector('.dialog-overlay')) return;
if (e.key === 'Escape') { e.preventDefault(); closeFn(); return; }
if (e.key !== 'Tab') return;
const items = _focusable(container);
if (items.length === 0) return;
const first = items[0];
const last = items[items.length - 1];
if (e.shiftKey && document.activeElement === first) {
e.preventDefault(); last.focus();
} else if (!e.shiftKey && document.activeElement === last) {
e.preventDefault(); first.focus();
}
};
}
// ---- SETTINGS PANEL ----
// Hinweis: withViewTransition (Phase 4) bleibt fuer das Fade erhalten; das
// Fokus-Management (merken, Falle, Rueckgabe) liegt bewusst ausserhalb des
// Transition-Callbacks. activeElement wird vor der Mutation gelesen.
let _settingsTrap = null;
function openSettings() {
const panel = document.getElementById('settingsPanel');
_focusReturn.settings = document.activeElement;
withViewTransition(() => {
panel.classList.add('open');
document.getElementById('settingsPanel').classList.add('open');
document.getElementById('settingsOverlay').classList.add('active');
});
panel.setAttribute('aria-hidden', 'false');
_settingsTrap = _makeTrap(panel, closeSettings);
document.addEventListener('keydown', _settingsTrap);
const first = _focusable(panel)[0];
if (first) first.focus();
}
function closeSettings() {
const panel = document.getElementById('settingsPanel');
withViewTransition(() => {
panel.classList.remove('open');
document.getElementById('settingsPanel').classList.remove('open');
document.getElementById('settingsOverlay').classList.remove('active');
});
panel.setAttribute('aria-hidden', 'true');
if (_settingsTrap) { document.removeEventListener('keydown', _settingsTrap); _settingsTrap = null; }
if (_focusReturn.settings) { _focusReturn.settings.focus(); _focusReturn.settings = null; }
}
// ---- THEME MODAL ----
let _themeTrap = null;
function openThemeModal() {
const overlay = document.getElementById('themeOverlay');
const modal = document.getElementById('themeModal');
_focusReturn.theme = document.activeElement;
withViewTransition(() => {
overlay.classList.add('active');
});
modal.setAttribute('aria-hidden', 'false');
_themeTrap = _makeTrap(modal, closeThemeModal);
document.addEventListener('keydown', _themeTrap);
const first = _focusable(modal)[0];
if (first) first.focus();
}
function closeThemeModal() {
const overlay = document.getElementById('themeOverlay');
const modal = document.getElementById('themeModal');
withViewTransition(() => {
overlay.classList.remove('active');
});
modal.setAttribute('aria-hidden', 'true');
if (_themeTrap) { document.removeEventListener('keydown', _themeTrap); _themeTrap = null; }
if (_focusReturn.theme) { _focusReturn.theme.focus(); _focusReturn.theme = null; }
}
/**
* Wechselt das Theme mit nativem Cross-Fade (View Transitions API).
* Wrap sitzt bewusst hier am User-Ausloeser, NICHT in applyTheme(),
* sonst fadet jeder neue Tab beim Initial-Load (settings.js:101).
* Feature-Detection-Fallback: aeltere Browser (z.B. Firefox < 144)
* schalten instant um, ohne Bruch.
* @param {string} name - Theme-Name
*/
function switchTheme(name) {
const swap = () => applyTheme(name, false); // false: Theme-BG anwenden (kein User-bgUrl-Schutz hier noetig, bgUrl wurde geleert)
withViewTransition(swap);
}
/**
@@ -170,11 +89,6 @@ function applySettings() {
const imgRefBtn = document.querySelector('[data-action="image-ref"]');
if (imgRefBtn) imgRefBtn.classList.toggle('hidden', !settings.imageRefEnabled);
// A11y: aria-checked aller role=switch-Toggles an den realen checked-State angleichen
document.querySelectorAll('.toggle input[role="switch"]').forEach(cb => {
cb.setAttribute('aria-checked', cb.checked ? 'true' : 'false');
});
// Toolbar-Position
document.body.classList.toggle('toolbar-left', settings.toolbarPos === 'left');
const toolbarPosEl = document.getElementById('settingToolbarPos');
@@ -208,25 +122,15 @@ function bindSettingsEvents() {
});
// Theme-Picker (Cards im Theme-Modal)
const themeCards = document.querySelectorAll('.theme-card');
function selectThemeCard(card) {
document.querySelectorAll('.theme-card').forEach(card => {
card.addEventListener('click', async () => {
const name = card.dataset.value;
if (!name || name === settings.theme) return Promise.resolve();
if (!name || name === settings.theme) return;
settings.theme = name;
settings.bgUrl = '';
document.getElementById('bgUrlInput').value = '';
// aria-pressed synchron halten — applyTheme/switchTheme pflegt nur die .active-Klasse, nicht ARIA
themeCards.forEach(c => c.setAttribute('aria-pressed', c === card ? 'true' : 'false'));
switchTheme(name); // WICHTIG: switchTheme aus Phase 4 (View-Transition-Wrapper), NICHT applyTheme direkt — sonst geht der Theme-Fade verloren
return saveSettings();
}
themeCards.forEach(card => {
card.addEventListener('click', () => selectThemeCard(card));
card.addEventListener('keydown', e => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
selectThemeCard(card);
}
applyTheme(name, false);
await saveSettings();
});
});
@@ -259,7 +163,6 @@ function bindSettingsEvents() {
const el = document.getElementById(id);
if (el) {
el.addEventListener('change', async e => {
e.target.setAttribute('aria-checked', e.target.checked ? 'true' : 'false');
fn(e.target.checked);
await saveSettings();
});
-11
View File
@@ -55,14 +55,3 @@ async function saveBoards() {
async function saveSettings() {
await Store.set('settings', settings);
}
// ---- VIEW TRANSITIONS ----
// Fuehrt eine synchrone DOM-Mutation mit nativem View-Transition-Fade aus.
// Feature-Detection-Fallback (Firefox < 144): instant. reduced-motion kappt das Fade ueber den ungeschichteten @media-Block.
function withViewTransition(mutate) {
if (document.startViewTransition) {
document.startViewTransition(mutate);
} else {
mutate();
}
}