feat(pwa): loading screen in the colours of the last used theme
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
57a97a3750
commit
4a923eaf01
5 files changed
+52
-7
No files matched your search
@@ -11,6 +11,8 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V
|
|||||||
### Changed
|
### Changed
|
||||||
- Kanban maximieren: Nur noch das Board wird breiter, Listen-Reiter und „Neue Aufgabe anlegen“ behalten ihre Breite. Das Board wächst weich animiert, jede Spalte wird höchstens 380 px breit und das Board bleibt mittig (bei wenigen Spalten also schmaler als das Fenster).
|
- Kanban maximieren: Nur noch das Board wird breiter, Listen-Reiter und „Neue Aufgabe anlegen“ behalten ihre Breite. Das Board wächst weich animiert, jede Spalte wird höchstens 380 px breit und das Board bleibt mittig (bei wenigen Spalten also schmaler als das Fenster).
|
||||||
|
|
||||||
|
- Ladebildschirm: Statt immer in Blau erscheint er in den Farben des zuletzt auf dem Gerät genutzten Themes (Hintergrund, Logo in der Akzentfarbe, Statusleiste passend); beim allerersten Besuch bleibt er blau. Neu dafür ist `boot-theme.js` (eigene Datei wegen der Content-Security-Policy); die App-Caches werden einmal erneuert.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- App installieren am Handy: Meldet Chrome erst nach dem Öffnen der Anleitung, dass MindSift installierbar ist (z. B. kurz nach dem Entfernen der alten App), erscheint in der Anleitung jetzt ein Knopf „Jetzt installieren“ für die direkte Installation.
|
- App installieren am Handy: Meldet Chrome erst nach dem Öffnen der Anleitung, dass MindSift installierbar ist (z. B. kurz nach dem Entfernen der alten App), erscheint in der Anleitung jetzt ein Knopf „Jetzt installieren“ für die direkte Installation.
|
||||||
|
|
||||||
|
|||||||
+21
-5
@@ -3,8 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
<!-- Blue while loading (matches the splash screen); the app switches it to the theme background -->
|
<!-- Black like the system bars of the installed app; boot-theme.js and then the app switch it to the
|
||||||
|
theme background -->
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<script src="/boot-theme.js"></script>
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-title" content="MindSift" />
|
<meta name="apple-mobile-web-app-title" content="MindSift" />
|
||||||
@@ -43,11 +45,25 @@
|
|||||||
<script src="/pwa-install.js"></script>
|
<script src="/pwa-install.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-[#0b0f19] text-[#e3e3e3] antialiased selection:bg-[#8ab4f8]/30 selection:text-[#8ab4f8]">
|
<body class="bg-[#0b0f19] text-[#e3e3e3] antialiased selection:bg-[#8ab4f8]/30 selection:text-[#8ab4f8]">
|
||||||
<!-- Shown until React mounts and replaces it: full blue with the logo, no text, no white flash -->
|
<!-- Shown until React mounts and replaces it: the logo on the background of the theme used last on
|
||||||
|
this device (boot-theme.js), full blue on the first visit; no text, no white flash -->
|
||||||
<div id="root">
|
<div id="root">
|
||||||
<div style="position:fixed;inset:0;background:#2563eb;display:flex;align-items:center;justify-content:center">
|
<div style="position:fixed;inset:0;background:var(--boot-bg,#2563eb);display:flex;align-items:center;justify-content:center">
|
||||||
<!-- the icon's own blue square disappears on blue, so the visible brain is ~55% of this size -->
|
<!-- icon.svg inline, so its square takes the theme's accent (on blue it disappears in the background) -->
|
||||||
<img src="/icon.svg" width="128" height="128" alt="" style="display:block" />
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="128" height="128" style="display:block">
|
||||||
|
<rect width="512" height="512" rx="108" style="fill:var(--boot-accent,#2563eb)" />
|
||||||
|
<g transform="translate(256, 256) scale(12.5) translate(-12, -12)" fill="none" style="stroke:var(--boot-on-accent,#ffffff)" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z"/>
|
||||||
|
<path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z"/>
|
||||||
|
<path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4"/>
|
||||||
|
<path d="M17.599 6.5a3 3 0 0 0 .399-1.375"/>
|
||||||
|
<path d="M6.003 5.125A3 3 0 0 0 6.401 6.5"/>
|
||||||
|
<path d="M3.477 10.896a4 4 0 0 1 .585-.396"/>
|
||||||
|
<path d="M19.938 10.5a4 4 0 0 1 .585.396"/>
|
||||||
|
<path d="M6 18a4 4 0 0 1-1.967-.516"/>
|
||||||
|
<path d="M19.967 17.484A4 4 0 0 1 18 18"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// Runs before the first paint: the loading screen takes the colours of the theme used last on this
|
||||||
|
// device (stored by the app in lib/theme.ts), so a light or orange theme does not start in blue.
|
||||||
|
// A file instead of an inline script so the CSP can stay at script-src 'self'.
|
||||||
|
(function () {
|
||||||
|
try {
|
||||||
|
var colors = JSON.parse(localStorage.getItem('mindsift_boot_colors') || 'null');
|
||||||
|
if (!colors || typeof colors.bg !== 'string' || typeof colors.accent !== 'string') return;
|
||||||
|
var root = document.documentElement;
|
||||||
|
root.style.setProperty('--boot-bg', colors.bg);
|
||||||
|
root.style.setProperty('--boot-accent', colors.accent);
|
||||||
|
if (typeof colors.onAccent === 'string') root.style.setProperty('--boot-on-accent', colors.onAccent);
|
||||||
|
var meta = document.querySelector('meta[name="theme-color"]');
|
||||||
|
if (meta) meta.setAttribute('content', colors.bg);
|
||||||
|
} catch (e) {
|
||||||
|
// no storage: the loading screen stays blue
|
||||||
|
}
|
||||||
|
})();
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
// would hand it the old values), the cache only offline.
|
// would hand it the old values), the cache only offline.
|
||||||
// - API requests are never touched; offline data comes from the app's own cache (lib/offline.ts).
|
// - API requests are never touched; offline data comes from the app's own cache (lib/offline.ts).
|
||||||
// Raise VERSION when this file changes in a way that needs fresh caches.
|
// Raise VERSION when this file changes in a way that needs fresh caches.
|
||||||
const VERSION = 'v5';
|
const VERSION = 'v6';
|
||||||
const SHELL_CACHE = `mindsift-shell-${VERSION}`;
|
const SHELL_CACHE = `mindsift-shell-${VERSION}`;
|
||||||
const ASSET_CACHE = `mindsift-assets-${VERSION}`;
|
const ASSET_CACHE = `mindsift-assets-${VERSION}`;
|
||||||
const MAX_ASSETS = 80;
|
const MAX_ASSETS = 80;
|
||||||
@@ -23,6 +23,8 @@ const SHELL_URLS = [
|
|||||||
'/icon-512x512.png',
|
'/icon-512x512.png',
|
||||||
'/apple-touch-icon.png',
|
'/apple-touch-icon.png',
|
||||||
'/badge-96x96.png',
|
'/badge-96x96.png',
|
||||||
|
'/boot-theme.js',
|
||||||
|
'/pwa-install.js',
|
||||||
];
|
];
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener('install', (event) => {
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ export function getAccentColor(): string | null {
|
|||||||
export function setAccentColor(hex: string | null): void {
|
export function setAccentColor(hex: string | null): void {
|
||||||
store(ACCENT_KEY, hex);
|
store(ACCENT_KEY, hex);
|
||||||
applyAccent(hex);
|
applyAccent(hex);
|
||||||
|
syncThemeColorMeta(); // keeps the loading screen's colours current
|
||||||
}
|
}
|
||||||
|
|
||||||
function hexToRgb(hex: string): [number, number, number] {
|
function hexToRgb(hex: string): [number, number, number] {
|
||||||
@@ -352,15 +353,22 @@ function applyToDocument(themeId: ThemeId, customCss: string | null): void {
|
|||||||
|
|
||||||
// The status bar (theme-color) starts black (like the Android navigation bar of the installed app,
|
// The status bar (theme-color) starts black (like the Android navigation bar of the installed app,
|
||||||
// which only takes the manifest colour); once the app runs it follows the theme's background.
|
// which only takes the manifest colour); once the app runs it follows the theme's background.
|
||||||
|
// The colours are also kept for the next start: public/boot-theme.js paints the loading screen in them.
|
||||||
function syncThemeColorMeta(): void {
|
function syncThemeColorMeta(): void {
|
||||||
const meta = document.querySelector('meta[name="theme-color"]');
|
const meta = document.querySelector('meta[name="theme-color"]');
|
||||||
if (!meta || typeof requestAnimationFrame === 'undefined') return;
|
if (!meta || typeof requestAnimationFrame === 'undefined') return;
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-app').trim();
|
const css = getComputedStyle(document.documentElement);
|
||||||
|
const bg = css.getPropertyValue('--bg-app').trim();
|
||||||
if (bg) meta.setAttribute('content', bg);
|
if (bg) meta.setAttribute('content', bg);
|
||||||
|
const accent = css.getPropertyValue('--accent-primary').trim();
|
||||||
|
const onAccent = css.getPropertyValue('--text-on-accent').trim();
|
||||||
|
if (bg && accent) store(BOOT_COLORS_KEY, JSON.stringify({ bg, accent, onAccent }));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const BOOT_COLORS_KEY = 'mindsift_boot_colors';
|
||||||
|
|
||||||
function store(key: string, value: string | null): void {
|
function store(key: string, value: string | null): void {
|
||||||
try {
|
try {
|
||||||
if (value === null) localStorage.removeItem(key);
|
if (value === null) localStorage.removeItem(key);
|
||||||
|
|||||||
Reference in new issue
Block a user