diff --git a/frontend/index.html b/frontend/index.html
index 91f2579..b924493 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -3,12 +3,180 @@
+
Cluedo Sheet
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Zauber-Detektiv Notizbogen
+
Magie wird vorbereitet…
+
+
+
+
+
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 84b88dd..8d4e48e 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -342,6 +342,14 @@ export default function App() {
setThemeKey(key);
applyTheme(key);
+ // ✅ sofort für nächsten Start merken (verhindert Flash)
+ try {
+ localStorage.setItem(`hpTheme:${(me?.email || "guest").toLowerCase()}`, key);
+ localStorage.setItem("hpTheme:guest", key); // fallback, falls noch nicht eingeloggt
+ } catch {
+ // ignore
+ }
+
try {
await api("/auth/theme", {
method: "PATCH",
@@ -351,6 +359,7 @@ export default function App() {
// theme locally already applied; ignore backend error
}
};
+
// ===== Stats (always fresh on open) =====
const openStatsModal = async () => {
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 19def02..c0a0bc2 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -1,14 +1,51 @@
import React from "react";
-import { createRoot } from "react-dom/client";
+import ReactDOM from "react-dom/client";
import App from "./App.jsx";
+import { applyTheme, DEFAULT_THEME_KEY } from "./styles/themes";
import { registerSW } from "virtual:pwa-register";
-createRoot(document.getElementById("root")).render();
-registerSW({ immediate: true });
-const updateSW = registerSW({
+async function bootstrap() {
+ // Theme sofort setzen
+ try {
+ const key = localStorage.getItem("hpTheme:guest") || DEFAULT_THEME_KEY;
+ applyTheme(key);
+ } catch {
+ applyTheme(DEFAULT_THEME_KEY);
+ }
+
+ // Fonts abwarten (verhindert Layout-Sprung)
+ try {
+ if (document.fonts?.ready) {
+ await document.fonts.ready;
+ }
+ } catch {}
+
+ // App rendern
+ ReactDOM.createRoot(document.getElementById("root")).render();
+
+ // Splash mind. 3 Sekunden anzeigen (3000ms)
+ const MIN_SPLASH_MS = 3000;
+ const tStart = performance.now();
+
+ const hideSplash = () => {
+ const splash = document.getElementById("app-splash");
+ if (!splash) return;
+ splash.classList.add("hide");
+ setTimeout(() => splash.remove(), 320);
+ };
+
+ const elapsed = performance.now() - tStart;
+ const remaining = Math.max(0, MIN_SPLASH_MS - elapsed);
+ setTimeout(hideSplash, remaining);
+
+ // Service Worker ohne Auto-Reload-Flash
+ registerSW({
immediate: true,
onNeedRefresh() {
- updateSW(true); // sofort neue Version aktivieren
- window.location.reload();
+ console.info("Neue Version verfügbar");
+ // optional: später Toast "Update verfügbar"
},
});
+}
+
+bootstrap();
diff --git a/frontend/src/styles/themes.js b/frontend/src/styles/themes.js
index ff01d9c..cb182c6 100644
--- a/frontend/src/styles/themes.js
+++ b/frontend/src/styles/themes.js
@@ -195,6 +195,32 @@ export const THEMES = {
export const DEFAULT_THEME_KEY = "default";
+export function setThemeColorMeta(color) {
+ try {
+ const safe = typeof color === "string" ? color.trim() : "";
+ if (!safe) return;
+
+ // only allow solid colors (hex, rgb, hsl); ignore urls/gradients/rgba overlays
+ const looksSolid =
+ safe.startsWith("#") ||
+ safe.startsWith("rgb(") ||
+ safe.startsWith("hsl(") ||
+ safe.startsWith("oklch(");
+
+ if (!looksSolid) return;
+
+ let meta = document.querySelector('meta[name="theme-color"]');
+ if (!meta) {
+ meta = document.createElement("meta");
+ meta.setAttribute("name", "theme-color");
+ document.head.appendChild(meta);
+ }
+ meta.setAttribute("content", safe);
+ } catch {
+ // ignore
+ }
+}
+
export function applyTheme(themeKey) {
const t = THEMES[themeKey] || THEMES[DEFAULT_THEME_KEY];
const root = document.documentElement;
@@ -202,6 +228,10 @@ export function applyTheme(themeKey) {
for (const [k, v] of Object.entries(t.tokens)) {
root.style.setProperty(`--hp-${k}`, v);
}
+
+ // ✅ PWA/Android Statusbar dynamisch an Theme anpassen
+ // Nimmt (falls vorhanden) statusBarColor, sonst pageBg
+ setThemeColorMeta(t.tokens.statusBarColor || t.tokens.pageBg || "#000000");
}
export function themeStorageKey(email) {
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index d23be78..2219097 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -20,7 +20,7 @@ export default defineConfig({
scope: "/",
display: "standalone",
background_color: "#1c140d",
- theme_color: "#caa45a",
+ theme_color: "#000000",
icons: [
{ src: "/icons/icon-512.png", sizes: "512x512", type: "image/png" }
]