From 67a9f0b944f9e81af6959fdbcfe19b9b35969a00 Mon Sep 17 00:00:00 2001 From: Youwes09 Date: Mon, 6 Apr 2026 00:39:16 -0500 Subject: [PATCH] Fix: SplashScreen Scaling on Windows (WIP) --- src/components/chrome/SplashScreen.svelte | 271 +++++++++++----------- 1 file changed, 141 insertions(+), 130 deletions(-) diff --git a/src/components/chrome/SplashScreen.svelte b/src/components/chrome/SplashScreen.svelte index 5315907..f6d1358 100644 --- a/src/components/chrome/SplashScreen.svelte +++ b/src/components/chrome/SplashScreen.svelte @@ -17,8 +17,11 @@ onDismiss?: () => void; } - let { mode = "loading", ringFull = false, failed = false, notConfigured = false, - showCards = true, showFps = false, onReady, onRetry, onBypass, onDismiss }: Props = $props(); + let { + mode = "loading", ringFull = false, failed = false, + notConfigured = false, showCards = true, showFps = false, + onReady, onRetry, onBypass, onDismiss, + }: Props = $props(); const lockEnabled = $derived( store.settings.appLockEnabled && (store.settings.appLockPin?.length ?? 0) >= 4 @@ -28,6 +31,21 @@ let pinShake = $state(false); let pinUnlocked = $state(false); let pinVisible = $state(false); + let uiScale = $state(1); + let fpsEl = $state(undefined); + + const logoLoadingSize = 140; + const logoIdleSize = 128; + const logoLockSize = 96; + + const ringR = $derived(70); + const ringPad = $derived(12); + const ringSize = $derived((ringR + ringPad) * 2); + const ringC = $derived(ringR + ringPad); + const ringCirc = $derived(2 * Math.PI * ringR); + const ringArc = $derived(ringCirc * Math.min(Math.max(ringProg, 0.025), 0.999)); + const ringTop = $derived(-((ringSize - logoLoadingSize) / 2)); + const ringLeft = $derived(-((ringSize - logoLoadingSize) / 2)); function submitPin() { if (pinEntry === store.settings.appLockPin) { @@ -36,13 +54,13 @@ if (mode === "idle") triggerExit(onDismiss); } else { pinShake = true; - pinEntry = ""; - setTimeout(() => pinShake = false, 500); + pinEntry = ""; + setTimeout(() => (pinShake = false), 500); } } function onPinKey(e: KeyboardEvent) { - if (e.key === "Enter") { submitPin(); return; } + if (e.key === "Enter") { submitPin(); return; } if (e.key === "Backspace") { pinEntry = pinEntry.slice(0, -1); return; } if (/^\d$/.test(e.key)) { pinEntry = (pinEntry + e.key).slice(0, 8); @@ -50,10 +68,7 @@ } } - function handleRetry() { onRetry?.(); } - function handleBypass() { onBypass?.(); } - - const EXIT_MS = 320; + const EXIT_MS = 320; const PHASE1_TARGET = 0.85; const PHASE1_MS = 3000; const PHASE2_TARGET = 0.95; @@ -64,8 +79,6 @@ let exiting = $state(false); let exitLock = false; - let fpsEl = $state(undefined); - function triggerExit(cb?: () => void) { if (exitLock) return; exitLock = true; @@ -81,18 +94,14 @@ if (exitLock) return; if (animStart === null) animStart = ts; const elapsed = ts - animStart; - if (animPhase === 1) { const t = Math.min(elapsed / PHASE1_MS, 1); - const eased = 1 - Math.pow(1 - t, 3); - ringProg = 0.025 + eased * (PHASE1_TARGET - 0.025); + ringProg = 0.025 + (1 - Math.pow(1 - t, 3)) * (PHASE1_TARGET - 0.025); if (t >= 1) { animPhase = 2; animStart = ts; } - } else if (animPhase === 2) { + } else { const t = Math.min(elapsed / PHASE2_MS, 1); - const eased = 1 - Math.pow(1 - t, 4); - ringProg = PHASE1_TARGET + eased * (PHASE2_TARGET - PHASE1_TARGET); + ringProg = PHASE1_TARGET + (1 - Math.pow(1 - t, 4)) * (PHASE2_TARGET - PHASE1_TARGET); } - animFrame = requestAnimationFrame(animateRing); } @@ -104,26 +113,39 @@ }); $effect(() => { - if (ringFull) { - cancelAnimationFrame(animFrame); - ringProg = 1; - if (lockEnabled && !pinUnlocked) { - setTimeout(() => { pinVisible = true; }, 400); - } else { - setTimeout(() => triggerExit(onReady), 650); - } + if (!ringFull) return; + cancelAnimationFrame(animFrame); + ringProg = 1; + if (lockEnabled && !pinUnlocked) { + setTimeout(() => (pinVisible = true), 400); + } else { + setTimeout(() => triggerExit(onReady), 650); } }); + $effect(() => { + const needsPin = + (mode === "idle" && lockEnabled) || + (mode === "loading" && lockEnabled && ringFull && !pinUnlocked); + if (!needsPin) return; + window.addEventListener("keydown", onPinKey); + return () => window.removeEventListener("keydown", onPinKey); + }); + + $effect(() => { + if (pinUnlocked && mode !== "idle") triggerExit(onReady); + }); + const dotsInterval = setInterval(() => { dots = dots.length >= 3 ? "" : dots + "."; }, 420); - onMount(() => { + onMount(async () => { + const win = getCurrentWindow(); + uiScale = await win.scaleFactor(); + if (mode === "idle" && onDismiss) { - if (lockEnabled) { - return () => clearInterval(dotsInterval); - } + if (lockEnabled) return () => clearInterval(dotsInterval); const handler = () => triggerExit(onDismiss); const t = setTimeout(() => { window.addEventListener("keydown", handler, { once: true }); @@ -141,8 +163,9 @@ return () => clearInterval(dotsInterval); }); - interface CardDef { cx: number; w: number; h: number; lines: number; alpha: number; speed: number; cycleSec: number; phase: number; travel: number; yStart: number; angleStart: number; tilt: number; } + interface CardDef { cx: number; w: number; h: number; lines: number; alpha: number; speed: number; cycleSec: number; phase: number; travel: number; yStart: number; angleStart: number; tilt: number; } interface CardTrig { cosA: number; sinA: number; tiltRad: number; } + interface RenderState { cards: CardDef[]; trigs: CardTrig[]; stamps: HTMLCanvasElement[]; vignette: HTMLCanvasElement; CW: number; CH: number; scale: number; } const LAYER_CFG = [ { wMin: 26, wMax: 40, speedMin: 30, speedMax: 50, alpha: 0.22 }, @@ -159,29 +182,34 @@ } function buildCards(vw: number, vh: number) { - const cards: CardDef[] = [], laneW = vw / COLS; + const cards: CardDef[] = []; + const laneW = vw / COLS; for (let layer = 0; layer < 3; layer++) { const cfg = LAYER_CFG[layer]; for (let col = 0; col < COLS; col++) { - const seed = col * 31 + layer * 97 + 7; - const w = cfg.wMin + hash(seed + 1) * (cfg.wMax - cfg.wMin); - const h = w * 1.44; - const speed = cfg.speedMin + hash(seed + 5) * (cfg.speedMax - cfg.speedMin); + const seed = col * 31 + layer * 97 + 7; + const w = cfg.wMin + hash(seed + 1) * (cfg.wMax - cfg.wMin); + const h = w * 1.44; + const speed = cfg.speedMin + hash(seed + 5) * (cfg.speedMax - cfg.speedMin); const travel = vh + h + BUF; cards.push({ cx: (col + 0.5) * laneW + (hash(seed + 2) * 2 - 1) * Math.max(0, (laneW - w) / 2 - 2), - w, h, lines: 1 + Math.floor(hash(seed + 7) * 3), alpha: cfg.alpha, speed, + w, h, + lines: 1 + Math.floor(hash(seed + 7) * 3), + alpha: cfg.alpha, + speed, cycleSec: travel / speed, phase: ((col / COLS) + hash(seed + 6) * 0.6 + layer * 0.23) % 1, - travel, yStart: vh + h / 2 + BUF / 2, + travel, + yStart: vh + h / 2 + BUF / 2, angleStart: hash(seed + 3) * 50 - 25, tilt: (hash(seed + 4) * 2 - 1) * 18, }); } } const trigs: CardTrig[] = cards.map(c => ({ - cosA: Math.cos(c.angleStart * (Math.PI / 180)), - sinA: Math.sin(c.angleStart * (Math.PI / 180)), + cosA: Math.cos(c.angleStart * (Math.PI / 180)), + sinA: Math.sin(c.angleStart * (Math.PI / 180)), tiltRad: c.tilt * (Math.PI / 180), })); return { cards, trigs }; @@ -189,29 +217,30 @@ function rrect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) { ctx.beginPath(); - ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r); + ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.arcTo(x + w, y, x + w, y + r, r); ctx.lineTo(x + w, y + h - r); ctx.arcTo(x + w, y + h, x + w - r, y + h, r); - ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r); - ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r); + ctx.lineTo(x + r, y + h); ctx.arcTo(x, y + h, x, y + h - r, r); + ctx.lineTo(x, y + r); ctx.arcTo(x, y, x + r, y, r); ctx.closePath(); } const STAMP_PAD = 6; function buildStamp(c: CardDef, dpr: number): HTMLCanvasElement { - const oc = document.createElement("canvas"); + const oc = document.createElement("canvas"); oc.width = Math.round(Math.ceil(c.w + STAMP_PAD * 2) * dpr); oc.height = Math.round(Math.ceil(c.h + STAMP_PAD * 2) * dpr); const ctx = oc.getContext("2d")!; ctx.scale(dpr, dpr); - const x0 = STAMP_PAD, y0 = STAMP_PAD; - const coverH = (c.w * 0.72) * 1.05; + const x0 = STAMP_PAD, y0 = STAMP_PAD; + const coverH = c.w * 0.72 * 1.05; const lineY0 = y0 + 3 + coverH + 5; - ctx.fillStyle = "rgba(0,0,0,0.5)"; rrect(ctx, x0 + 2, y0 + 2, c.w, c.h, 4); ctx.fill(); - ctx.fillStyle = "rgba(255,255,255,0.07)"; rrect(ctx, x0, y0, c.w, c.h, 4); ctx.fill(); - ctx.strokeStyle = "rgba(255,255,255,0.75)"; ctx.lineWidth = 1.2; rrect(ctx, x0, y0, c.w, c.h, 4); ctx.stroke(); - ctx.fillStyle = "rgba(255,255,255,0.15)"; rrect(ctx, x0 + 3, y0 + 3, c.w - 6, coverH, 3); ctx.fill(); - ctx.fillStyle = "rgba(255,255,255,0.08)"; rrect(ctx, x0 + 3, y0 + 3, (c.w - 6) * 0.45, coverH, 3); ctx.fill(); + ctx.fillStyle = "rgba(0,0,0,0.5)"; rrect(ctx, x0 + 2, y0 + 2, c.w, c.h, 4); ctx.fill(); + ctx.fillStyle = "rgba(255,255,255,0.07)"; rrect(ctx, x0, y0, c.w, c.h, 4); ctx.fill(); + ctx.strokeStyle = "rgba(255,255,255,0.75)"; + ctx.lineWidth = 1.2; rrect(ctx, x0, y0, c.w, c.h, 4); ctx.stroke(); + ctx.fillStyle = "rgba(255,255,255,0.15)"; rrect(ctx, x0 + 3, y0 + 3, c.w - 6, coverH, 3); ctx.fill(); + ctx.fillStyle = "rgba(255,255,255,0.08)"; rrect(ctx, x0 + 3, y0 + 3, (c.w - 6) * 0.45, coverH, 3); ctx.fill(); for (let li = 0; li < c.lines; li++) { ctx.fillStyle = li === 0 ? "rgba(255,255,255,0.35)" : "rgba(255,255,255,0.20)"; ctx.fillRect(x0 + 4, lineY0 + li * 8, (c.w - 8) * (li === 0 ? 0.78 : 0.52), li === 0 ? 3 : 2); @@ -220,13 +249,18 @@ } function buildVignette(vw: number, vh: number, dpr: number): HTMLCanvasElement { - const oc = document.createElement("canvas"); - oc.width = Math.round(vw * dpr); oc.height = Math.round(vh * dpr); + const oc = document.createElement("canvas"); + oc.width = Math.round(vw * dpr); + oc.height = Math.round(vh * dpr); const ctx = oc.getContext("2d")!; ctx.scale(dpr, dpr); const g = ctx.createRadialGradient(vw / 2, vh / 2, 0, vw / 2, vh / 2, Math.max(vw, vh) * 0.65); - g.addColorStop(0, "rgba(0,0,0,0)"); g.addColorStop(0.4, "rgba(0,0,0,0)"); g.addColorStop(0.7, "rgba(0,0,0,0.25)"); g.addColorStop(1, "rgba(0,0,0,0.65)"); - ctx.fillStyle = g; ctx.fillRect(0, 0, vw, vh); + g.addColorStop(0, "rgba(0,0,0,0)"); + g.addColorStop(0.4, "rgba(0,0,0,0)"); + g.addColorStop(0.7, "rgba(0,0,0,0.25)"); + g.addColorStop(1, "rgba(0,0,0,0.65)"); + ctx.fillStyle = g; + ctx.fillRect(0, 0, vw, vh); return oc; } @@ -236,21 +270,22 @@ ) { ctx.clearRect(0, 0, cw, ch); for (let i = 0; i < cards.length; i++) { - const c = cards[i]; - const p = ((t / c.cycleSec) + c.phase) % 1; + const c = cards[i]; + const p = ((t / c.cycleSec) + c.phase) % 1; const alpha = p < 0.07 ? (p / 0.07) * c.alpha : p > 0.86 ? ((1 - p) / 0.14) * c.alpha : c.alpha; if (alpha < 0.005) continue; - const cy = c.yStart - p * c.travel; - const tg = trigs[i]; + const cy = c.yStart - p * c.travel; + const tg = trigs[i]; const delta = tg.tiltRad * p; - const cos = tg.cosA * Math.cos(delta) - tg.sinA * Math.sin(delta); - const sin = tg.sinA * Math.cos(delta) + tg.cosA * Math.sin(delta); + const cos = tg.cosA * Math.cos(delta) - tg.sinA * Math.sin(delta); + const sin = tg.sinA * Math.cos(delta) + tg.cosA * Math.sin(delta); ctx.globalAlpha = alpha; ctx.setTransform(cos * dpr, sin * dpr, -sin * dpr, cos * dpr, c.cx * dpr, cy * dpr); - const sw = stamps[i].width, sh = stamps[i].height; + const sw = stamps[i].width / dpr, sh = stamps[i].height / dpr; ctx.drawImage(stamps[i], -sw / 2, -sh / 2, sw, sh); } - ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.globalAlpha = 1; + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.globalAlpha = 1; ctx.drawImage(vignette, 0, 0, cw, ch); } @@ -258,8 +293,9 @@ function tickFps(now: number) { fpsFrames++; if (now - fpsLast >= 500) { - fps = Math.round(fpsFrames / ((now - fpsLast) / 1000)); - fpsFrames = 0; fpsLast = now; + fps = Math.round(fpsFrames / ((now - fpsLast) / 1000)); + fpsFrames = 0; + fpsLast = now; if (fpsEl) fpsEl.textContent = `${fps} fps`; } } @@ -267,10 +303,6 @@ function mountCanvas(el: HTMLCanvasElement) { const win = getCurrentWindow(); const ctx = el.getContext("2d")!; - interface RenderState { - cards: CardDef[]; trigs: CardTrig[]; stamps: HTMLCanvasElement[]; - vignette: HTMLCanvasElement; CW: number; CH: number; scale: number; - } let live: RenderState | null = null; let lastLogW = 0, lastLogH = 0, lastScale = 0, buildGen = 0; @@ -289,7 +321,8 @@ } const ro = new ResizeObserver(() => syncSize()); - ro.observe(el); syncSize(); + ro.observe(el); + syncSize(); let raf = 0, t0 = -1; function frame(now: number) { @@ -303,30 +336,6 @@ raf = requestAnimationFrame(frame); return () => { cancelAnimationFrame(raf); ro.disconnect(); }; } - - $effect(() => { - const needsPin = - (mode === "idle" && lockEnabled) || - (mode === "loading" && lockEnabled && ringFull && !pinUnlocked); - if (!needsPin) return; - window.addEventListener("keydown", onPinKey); - return () => window.removeEventListener("keydown", onPinKey); - }); - - $effect(() => { - if (pinUnlocked && mode !== "idle") { - triggerExit(onReady); - } - }); - - const ringR = $derived(70); - const ringPad = $derived(12); - const ringSize = $derived((ringR + ringPad) * 2); - const ringC = $derived(ringR + ringPad); - const ringCirc = $derived(2 * Math.PI * ringR); - const ringArc = $derived(ringCirc * Math.min(Math.max(ringProg, 0.025), 0.999)); - const ringTop = $derived(-((ringSize - 140) / 2)); - const ringLeft = $derived(-((ringSize - 140) / 2));
@@ -339,9 +348,9 @@ {#if mode === "idle" && lockEnabled}
-
+
- Moku + Moku
@@ -355,15 +364,15 @@ {:else if mode === "idle"}
-
+
- Moku + Moku

press any key to continue

{:else} -
+
{#if !failed && !notConfigured} {/if} - Moku + Moku

moku

@@ -385,12 +394,10 @@
{#if failed || notConfigured}
-

- {failed ? "Could not reach server" : "Server not configured"} -

+

{failed ? "Could not reach server" : "Server not configured"}

- - + +
{:else} @@ -415,36 +422,40 @@