mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 09:19:56 -05:00
Fix: SplashScreen Scaling on Windows (WIP)
This commit is contained in:
@@ -17,8 +17,11 @@
|
|||||||
onDismiss?: () => void;
|
onDismiss?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { mode = "loading", ringFull = false, failed = false, notConfigured = false,
|
let {
|
||||||
showCards = true, showFps = false, onReady, onRetry, onBypass, onDismiss }: Props = $props();
|
mode = "loading", ringFull = false, failed = false,
|
||||||
|
notConfigured = false, showCards = true, showFps = false,
|
||||||
|
onReady, onRetry, onBypass, onDismiss,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
const lockEnabled = $derived(
|
const lockEnabled = $derived(
|
||||||
store.settings.appLockEnabled && (store.settings.appLockPin?.length ?? 0) >= 4
|
store.settings.appLockEnabled && (store.settings.appLockPin?.length ?? 0) >= 4
|
||||||
@@ -28,6 +31,21 @@
|
|||||||
let pinShake = $state(false);
|
let pinShake = $state(false);
|
||||||
let pinUnlocked = $state(false);
|
let pinUnlocked = $state(false);
|
||||||
let pinVisible = $state(false);
|
let pinVisible = $state(false);
|
||||||
|
let uiScale = $state(1);
|
||||||
|
let fpsEl = $state<HTMLSpanElement | undefined>(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() {
|
function submitPin() {
|
||||||
if (pinEntry === store.settings.appLockPin) {
|
if (pinEntry === store.settings.appLockPin) {
|
||||||
@@ -37,7 +55,7 @@
|
|||||||
} else {
|
} else {
|
||||||
pinShake = true;
|
pinShake = true;
|
||||||
pinEntry = "";
|
pinEntry = "";
|
||||||
setTimeout(() => pinShake = false, 500);
|
setTimeout(() => (pinShake = false), 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,9 +68,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRetry() { onRetry?.(); }
|
|
||||||
function handleBypass() { onBypass?.(); }
|
|
||||||
|
|
||||||
const EXIT_MS = 320;
|
const EXIT_MS = 320;
|
||||||
const PHASE1_TARGET = 0.85;
|
const PHASE1_TARGET = 0.85;
|
||||||
const PHASE1_MS = 3000;
|
const PHASE1_MS = 3000;
|
||||||
@@ -64,8 +79,6 @@
|
|||||||
let exiting = $state(false);
|
let exiting = $state(false);
|
||||||
let exitLock = false;
|
let exitLock = false;
|
||||||
|
|
||||||
let fpsEl = $state<HTMLSpanElement | undefined>(undefined);
|
|
||||||
|
|
||||||
function triggerExit(cb?: () => void) {
|
function triggerExit(cb?: () => void) {
|
||||||
if (exitLock) return;
|
if (exitLock) return;
|
||||||
exitLock = true;
|
exitLock = true;
|
||||||
@@ -81,18 +94,14 @@
|
|||||||
if (exitLock) return;
|
if (exitLock) return;
|
||||||
if (animStart === null) animStart = ts;
|
if (animStart === null) animStart = ts;
|
||||||
const elapsed = ts - animStart;
|
const elapsed = ts - animStart;
|
||||||
|
|
||||||
if (animPhase === 1) {
|
if (animPhase === 1) {
|
||||||
const t = Math.min(elapsed / PHASE1_MS, 1);
|
const t = Math.min(elapsed / PHASE1_MS, 1);
|
||||||
const eased = 1 - Math.pow(1 - t, 3);
|
ringProg = 0.025 + (1 - Math.pow(1 - t, 3)) * (PHASE1_TARGET - 0.025);
|
||||||
ringProg = 0.025 + eased * (PHASE1_TARGET - 0.025);
|
|
||||||
if (t >= 1) { animPhase = 2; animStart = ts; }
|
if (t >= 1) { animPhase = 2; animStart = ts; }
|
||||||
} else if (animPhase === 2) {
|
} else {
|
||||||
const t = Math.min(elapsed / PHASE2_MS, 1);
|
const t = Math.min(elapsed / PHASE2_MS, 1);
|
||||||
const eased = 1 - Math.pow(1 - t, 4);
|
ringProg = PHASE1_TARGET + (1 - Math.pow(1 - t, 4)) * (PHASE2_TARGET - PHASE1_TARGET);
|
||||||
ringProg = PHASE1_TARGET + eased * (PHASE2_TARGET - PHASE1_TARGET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
animFrame = requestAnimationFrame(animateRing);
|
animFrame = requestAnimationFrame(animateRing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,26 +113,39 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (ringFull) {
|
if (!ringFull) return;
|
||||||
cancelAnimationFrame(animFrame);
|
cancelAnimationFrame(animFrame);
|
||||||
ringProg = 1;
|
ringProg = 1;
|
||||||
if (lockEnabled && !pinUnlocked) {
|
if (lockEnabled && !pinUnlocked) {
|
||||||
setTimeout(() => { pinVisible = true; }, 400);
|
setTimeout(() => (pinVisible = true), 400);
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => triggerExit(onReady), 650);
|
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(() => {
|
const dotsInterval = setInterval(() => {
|
||||||
dots = dots.length >= 3 ? "" : dots + ".";
|
dots = dots.length >= 3 ? "" : dots + ".";
|
||||||
}, 420);
|
}, 420);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(async () => {
|
||||||
|
const win = getCurrentWindow();
|
||||||
|
uiScale = await win.scaleFactor();
|
||||||
|
|
||||||
if (mode === "idle" && onDismiss) {
|
if (mode === "idle" && onDismiss) {
|
||||||
if (lockEnabled) {
|
if (lockEnabled) return () => clearInterval(dotsInterval);
|
||||||
return () => clearInterval(dotsInterval);
|
|
||||||
}
|
|
||||||
const handler = () => triggerExit(onDismiss);
|
const handler = () => triggerExit(onDismiss);
|
||||||
const t = setTimeout(() => {
|
const t = setTimeout(() => {
|
||||||
window.addEventListener("keydown", handler, { once: true });
|
window.addEventListener("keydown", handler, { once: true });
|
||||||
@@ -143,6 +165,7 @@
|
|||||||
|
|
||||||
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 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 = [
|
const LAYER_CFG = [
|
||||||
{ wMin: 26, wMax: 40, speedMin: 30, speedMax: 50, alpha: 0.22 },
|
{ wMin: 26, wMax: 40, speedMin: 30, speedMax: 50, alpha: 0.22 },
|
||||||
@@ -159,7 +182,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildCards(vw: number, vh: number) {
|
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++) {
|
for (let layer = 0; layer < 3; layer++) {
|
||||||
const cfg = LAYER_CFG[layer];
|
const cfg = LAYER_CFG[layer];
|
||||||
for (let col = 0; col < COLS; col++) {
|
for (let col = 0; col < COLS; col++) {
|
||||||
@@ -170,10 +194,14 @@
|
|||||||
const travel = vh + h + BUF;
|
const travel = vh + h + BUF;
|
||||||
cards.push({
|
cards.push({
|
||||||
cx: (col + 0.5) * laneW + (hash(seed + 2) * 2 - 1) * Math.max(0, (laneW - w) / 2 - 2),
|
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,
|
cycleSec: travel / speed,
|
||||||
phase: ((col / COLS) + hash(seed + 6) * 0.6 + layer * 0.23) % 1,
|
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,
|
angleStart: hash(seed + 3) * 50 - 25,
|
||||||
tilt: (hash(seed + 4) * 2 - 1) * 18,
|
tilt: (hash(seed + 4) * 2 - 1) * 18,
|
||||||
});
|
});
|
||||||
@@ -205,11 +233,12 @@
|
|||||||
const ctx = oc.getContext("2d")!;
|
const ctx = oc.getContext("2d")!;
|
||||||
ctx.scale(dpr, dpr);
|
ctx.scale(dpr, dpr);
|
||||||
const x0 = STAMP_PAD, y0 = STAMP_PAD;
|
const x0 = STAMP_PAD, y0 = STAMP_PAD;
|
||||||
const coverH = (c.w * 0.72) * 1.05;
|
const coverH = c.w * 0.72 * 1.05;
|
||||||
const lineY0 = y0 + 3 + coverH + 5;
|
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(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.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.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.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(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++) {
|
for (let li = 0; li < c.lines; li++) {
|
||||||
@@ -221,12 +250,17 @@
|
|||||||
|
|
||||||
function buildVignette(vw: number, vh: number, dpr: number): HTMLCanvasElement {
|
function buildVignette(vw: number, vh: number, dpr: number): HTMLCanvasElement {
|
||||||
const oc = document.createElement("canvas");
|
const oc = document.createElement("canvas");
|
||||||
oc.width = Math.round(vw * dpr); oc.height = Math.round(vh * dpr);
|
oc.width = Math.round(vw * dpr);
|
||||||
|
oc.height = Math.round(vh * dpr);
|
||||||
const ctx = oc.getContext("2d")!;
|
const ctx = oc.getContext("2d")!;
|
||||||
ctx.scale(dpr, dpr);
|
ctx.scale(dpr, dpr);
|
||||||
const g = ctx.createRadialGradient(vw / 2, vh / 2, 0, vw / 2, vh / 2, Math.max(vw, vh) * 0.65);
|
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)");
|
g.addColorStop(0, "rgba(0,0,0,0)");
|
||||||
ctx.fillStyle = g; ctx.fillRect(0, 0, vw, vh);
|
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;
|
return oc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,10 +281,11 @@
|
|||||||
const sin = tg.sinA * Math.cos(delta) + tg.cosA * Math.sin(delta);
|
const sin = tg.sinA * Math.cos(delta) + tg.cosA * Math.sin(delta);
|
||||||
ctx.globalAlpha = alpha;
|
ctx.globalAlpha = alpha;
|
||||||
ctx.setTransform(cos * dpr, sin * dpr, -sin * dpr, cos * dpr, c.cx * dpr, cy * dpr);
|
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.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);
|
ctx.drawImage(vignette, 0, 0, cw, ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +294,8 @@
|
|||||||
fpsFrames++;
|
fpsFrames++;
|
||||||
if (now - fpsLast >= 500) {
|
if (now - fpsLast >= 500) {
|
||||||
fps = Math.round(fpsFrames / ((now - fpsLast) / 1000));
|
fps = Math.round(fpsFrames / ((now - fpsLast) / 1000));
|
||||||
fpsFrames = 0; fpsLast = now;
|
fpsFrames = 0;
|
||||||
|
fpsLast = now;
|
||||||
if (fpsEl) fpsEl.textContent = `${fps} fps`;
|
if (fpsEl) fpsEl.textContent = `${fps} fps`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,10 +303,6 @@
|
|||||||
function mountCanvas(el: HTMLCanvasElement) {
|
function mountCanvas(el: HTMLCanvasElement) {
|
||||||
const win = getCurrentWindow();
|
const win = getCurrentWindow();
|
||||||
const ctx = el.getContext("2d")!;
|
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 live: RenderState | null = null;
|
||||||
let lastLogW = 0, lastLogH = 0, lastScale = 0, buildGen = 0;
|
let lastLogW = 0, lastLogH = 0, lastScale = 0, buildGen = 0;
|
||||||
|
|
||||||
@@ -289,7 +321,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ro = new ResizeObserver(() => syncSize());
|
const ro = new ResizeObserver(() => syncSize());
|
||||||
ro.observe(el); syncSize();
|
ro.observe(el);
|
||||||
|
syncSize();
|
||||||
|
|
||||||
let raf = 0, t0 = -1;
|
let raf = 0, t0 = -1;
|
||||||
function frame(now: number) {
|
function frame(now: number) {
|
||||||
@@ -303,30 +336,6 @@
|
|||||||
raf = requestAnimationFrame(frame);
|
raf = requestAnimationFrame(frame);
|
||||||
return () => { cancelAnimationFrame(raf); ro.disconnect(); };
|
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));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="splash" class:exiting style="cursor: {mode === 'idle' && !lockEnabled ? 'pointer' : 'default'}">
|
<div class="splash" class:exiting style="cursor: {mode === 'idle' && !lockEnabled ? 'pointer' : 'default'}">
|
||||||
@@ -339,9 +348,9 @@
|
|||||||
|
|
||||||
{#if mode === "idle" && lockEnabled}
|
{#if mode === "idle" && lockEnabled}
|
||||||
<div style="z-index:1;display:flex;flex-direction:column;align-items:center;gap:var(--sp-6)">
|
<div style="z-index:1;display:flex;flex-direction:column;align-items:center;gap:var(--sp-6)">
|
||||||
<div style="position:relative;width:96px;height:96px">
|
<div style="position:relative;width:{logoLockSize}px;height:{logoLockSize}px">
|
||||||
<div class="logo-glow"></div>
|
<div class="logo-glow"></div>
|
||||||
<img src={logoUrl} alt="Moku" class="logo-breathe" style="width:96px;height:96px;border-radius:22px;display:block;position:relative" />
|
<img src={logoUrl} alt="Moku" class="logo-breathe" style="width:{logoLockSize}px;height:{logoLockSize}px;border-radius:22px;display:block;position:relative" />
|
||||||
</div>
|
</div>
|
||||||
<div class="pin-block">
|
<div class="pin-block">
|
||||||
<div class="pin-dots" class:pin-shake={pinShake}>
|
<div class="pin-dots" class:pin-shake={pinShake}>
|
||||||
@@ -355,15 +364,15 @@
|
|||||||
|
|
||||||
{:else if mode === "idle"}
|
{:else if mode === "idle"}
|
||||||
<div style="z-index:1;display:flex;flex-direction:column;align-items:center">
|
<div style="z-index:1;display:flex;flex-direction:column;align-items:center">
|
||||||
<div style="position:relative;width:128px;height:128px;margin-bottom:32px">
|
<div style="position:relative;width:{logoIdleSize}px;height:{logoIdleSize}px;margin-bottom:32px">
|
||||||
<div class="logo-glow"></div>
|
<div class="logo-glow"></div>
|
||||||
<img src={logoUrl} alt="Moku" class="logo-breathe" style="width:128px;height:128px;border-radius:28px;display:block;position:relative" />
|
<img src={logoUrl} alt="Moku" class="logo-breathe" style="width:{logoIdleSize}px;height:{logoIdleSize}px;border-radius:28px;display:block;position:relative" />
|
||||||
</div>
|
</div>
|
||||||
<p class="hint">press any key to continue</p>
|
<p class="hint">press any key to continue</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{:else}
|
{:else}
|
||||||
<div style="position:relative;width:140px;height:140px;margin-bottom:20px;z-index:1">
|
<div style="position:relative;width:{logoLoadingSize}px;height:{logoLoadingSize}px;margin-bottom:20px;z-index:1">
|
||||||
{#if !failed && !notConfigured}
|
{#if !failed && !notConfigured}
|
||||||
<svg width={ringSize} height={ringSize}
|
<svg width={ringSize} height={ringSize}
|
||||||
class="loading-ring"
|
class="loading-ring"
|
||||||
@@ -377,7 +386,7 @@
|
|||||||
style="transition: stroke-dasharray 0.4s cubic-bezier(0.4,0,0.2,1)" />
|
style="transition: stroke-dasharray 0.4s cubic-bezier(0.4,0,0.2,1)" />
|
||||||
</svg>
|
</svg>
|
||||||
{/if}
|
{/if}
|
||||||
<img src={logoUrl} alt="Moku" style="width:140px;height:140px;border-radius:32px;display:block" />
|
<img src={logoUrl} alt="Moku" style="width:{logoLoadingSize}px;height:{logoLoadingSize}px;border-radius:32px;display:block" />
|
||||||
</div>
|
</div>
|
||||||
<p class="title-label">moku</p>
|
<p class="title-label">moku</p>
|
||||||
|
|
||||||
@@ -385,12 +394,10 @@
|
|||||||
<div class="status-slot" class:status-slot-hide={lockEnabled && pinVisible}>
|
<div class="status-slot" class:status-slot-hide={lockEnabled && pinVisible}>
|
||||||
{#if failed || notConfigured}
|
{#if failed || notConfigured}
|
||||||
<div class="error-box">
|
<div class="error-box">
|
||||||
<p class="error-label">
|
<p class="error-label">{failed ? "Could not reach server" : "Server not configured"}</p>
|
||||||
{failed ? "Could not reach server" : "Server not configured"}
|
|
||||||
</p>
|
|
||||||
<div class="error-actions">
|
<div class="error-actions">
|
||||||
<button class="err-btn" onclick={handleRetry}>Retry</button>
|
<button class="err-btn" onclick={() => onRetry?.()}>Retry</button>
|
||||||
<button class="err-btn err-btn--primary" onclick={handleBypass}>Enter app</button>
|
<button class="err-btn err-btn--primary" onclick={() => onBypass?.()}>Enter app</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -415,16 +422,20 @@
|
|||||||
<style>
|
<style>
|
||||||
.splash { position: fixed; inset: 0; z-index: 9999; background: var(--bg-base); overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; animation: spIn 0.35s cubic-bezier(0,0,0.2,1) both; }
|
.splash { position: fixed; inset: 0; z-index: 9999; background: var(--bg-base); overflow: hidden; display: flex; flex-direction: column; align-items: center; justify-content: center; animation: spIn 0.35s cubic-bezier(0,0,0.2,1) both; }
|
||||||
.splash.exiting { animation: spOut 320ms cubic-bezier(0.4,0,1,1) both; }
|
.splash.exiting { animation: spOut 320ms cubic-bezier(0.4,0,1,1) both; }
|
||||||
|
|
||||||
@keyframes spIn { from { opacity:0; transform:scale(1.015) } to { opacity:1; transform:scale(1) } }
|
@keyframes spIn { from { opacity:0; transform:scale(1.015) } to { opacity:1; transform:scale(1) } }
|
||||||
@keyframes spOut { from { opacity:1; transform:scale(1) } to { opacity:0; transform:scale(0.96) } }
|
@keyframes spOut { from { opacity:1; transform:scale(1) } to { opacity:0; transform:scale(0.96) } }
|
||||||
@keyframes logoBreathe { 0%,100% { transform:scale(1); filter:drop-shadow(0 0 0px rgba(255,255,255,0)) } 50% { transform:scale(1.04); filter:drop-shadow(0 0 18px rgba(255,255,255,0.12)) } }
|
@keyframes logoBreathe { 0%,100% { transform:scale(1); filter:drop-shadow(0 0 0px rgba(255,255,255,0)) } 50% { transform:scale(1.04); filter:drop-shadow(0 0 18px rgba(255,255,255,0.12)) } }
|
||||||
@keyframes hintFade { 0%,100% { opacity:0.35 } 50% { opacity:0.7 } }
|
@keyframes hintFade { 0%,100% { opacity:0.35 } 50% { opacity:0.7 } }
|
||||||
|
@keyframes errIn { from { opacity:0; transform:translateY(4px) } to { opacity:1; transform:translateY(0) } }
|
||||||
|
@keyframes pinShake { 0%,100% { transform:translateX(0) } 20%,60% { transform:translateX(-6px) } 40%,80% { transform:translateX(6px) } }
|
||||||
|
|
||||||
.logo-glow { position: absolute; inset: -20px; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,0.06) 0%, transparent 70%); animation: logoBreathe 4s ease-in-out infinite; }
|
.logo-glow { position: absolute; inset: -20px; border-radius: 50%; background: radial-gradient(circle, rgba(255,255,255,0.06) 0%, transparent 70%); animation: logoBreathe 4s ease-in-out infinite; }
|
||||||
.logo-breathe { animation: logoBreathe 4s ease-in-out infinite; }
|
.logo-breathe { animation: logoBreathe 4s ease-in-out infinite; }
|
||||||
.hint { font-family: var(--font-ui); font-size: 10px; color: var(--text-faint); letter-spacing: 0.22em; text-transform: uppercase; margin: 0; user-select: none; animation: hintFade 3.5s ease-in-out infinite; }
|
.hint { font-family: var(--font-ui); font-size: 10px; color: var(--text-faint); letter-spacing: 0.22em; text-transform: uppercase; margin: 0; user-select: none; animation: hintFade 3.5s ease-in-out infinite; }
|
||||||
.title-label { font-family: var(--font-ui); font-size: 11px; font-weight: 500; letter-spacing: 0.26em; text-transform: uppercase; color: var(--text-secondary); margin: 0 0 8px; z-index: 1; user-select: none; }
|
.title-label { font-family: var(--font-ui); font-size: 11px; font-weight: 500; letter-spacing: 0.26em; text-transform: uppercase; color: var(--text-secondary); margin: 0 0 8px; z-index: 1; user-select: none; }
|
||||||
|
|
||||||
.error-box { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px 20px; border-radius: var(--radius-lg); background: var(--bg-surface); border: 1px solid var(--border-base); min-width: 200px; text-align: center; animation: errIn 0.25s cubic-bezier(0,0,0.2,1) both; }
|
.error-box { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 16px 20px; border-radius: var(--radius-lg); background: var(--bg-surface); border: 1px solid var(--border-base); min-width: 200px; text-align: center; animation: errIn 0.25s cubic-bezier(0,0,0.2,1) both; }
|
||||||
@keyframes errIn { from { opacity:0; transform:translateY(4px) } to { opacity:1; transform:translateY(0) } }
|
|
||||||
.error-label { font-family: var(--font-ui); font-size: 11px; font-weight: 500; color: var(--text-muted); letter-spacing: 0.06em; margin: 0; }
|
.error-label { font-family: var(--font-ui); font-size: 11px; font-weight: 500; color: var(--text-muted); letter-spacing: 0.06em; margin: 0; }
|
||||||
.error-actions { display: flex; gap: 6px; }
|
.error-actions { display: flex; gap: 6px; }
|
||||||
.err-btn { padding: 5px 14px; border-radius: var(--radius-md); border: 1px solid var(--border-base); background: transparent; color: var(--text-muted); cursor: pointer; font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.04em; transition: border-color 0.15s, color 0.15s; }
|
.err-btn { padding: 5px 14px; border-radius: var(--radius-md); border: 1px solid var(--border-base); background: transparent; color: var(--text-muted); cursor: pointer; font-family: var(--font-ui); font-size: 11px; letter-spacing: 0.04em; transition: border-color 0.15s, color 0.15s; }
|
||||||
@@ -438,13 +449,13 @@
|
|||||||
.status-text { font-family: var(--font-ui); font-size: 10px; color: var(--text-faint); letter-spacing: 0.12em; margin: 0; min-width: 160px; text-align: center; }
|
.status-text { font-family: var(--font-ui); font-size: 10px; color: var(--text-faint); letter-spacing: 0.12em; margin: 0; min-width: 160px; text-align: center; }
|
||||||
.loading-ring { transition: opacity 0.5s ease; }
|
.loading-ring { transition: opacity 0.5s ease; }
|
||||||
.ring-hide { opacity: 0; }
|
.ring-hide { opacity: 0; }
|
||||||
|
|
||||||
.pin-slot { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); position: absolute; opacity: 0; transform: translateY(4px); transition: opacity 0.4s ease, transform 0.4s ease; pointer-events: none; }
|
.pin-slot { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); position: absolute; opacity: 0; transform: translateY(4px); transition: opacity 0.4s ease, transform 0.4s ease; pointer-events: none; }
|
||||||
.pin-slot-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
|
.pin-slot-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
|
||||||
.pin-block { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); position: relative; }
|
.pin-block { display: flex; flex-direction: column; align-items: center; gap: var(--sp-3); position: relative; }
|
||||||
.pin-dots { display: flex; gap: 12px; align-items: center; }
|
.pin-dots { display: flex; gap: 12px; align-items: center; }
|
||||||
.pin-dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid var(--border-strong); background: transparent; transition: background 0.12s, border-color 0.12s; }
|
.pin-dot { width: 10px; height: 10px; border-radius: 50%; border: 1px solid var(--border-strong); background: transparent; transition: background 0.12s, border-color 0.12s; }
|
||||||
.pin-dot-filled { background: var(--accent); border-color: var(--accent); }
|
.pin-dot-filled { background: var(--accent); border-color: var(--accent); }
|
||||||
@keyframes pinShake { 0%,100% { transform:translateX(0) } 20%,60% { transform:translateX(-6px) } 40%,80% { transform:translateX(6px) } }
|
|
||||||
.pin-shake { animation: pinShake 0.42s ease; }
|
.pin-shake { animation: pinShake 0.42s ease; }
|
||||||
.pin-submit-btn { opacity: 0; width: 1px; height: 1px; overflow: hidden; padding: 0; border: none; background: none; cursor: pointer; pointer-events: auto; position: absolute; }
|
.pin-submit-btn { opacity: 0; width: 1px; height: 1px; overflow: hidden; padding: 0; border: none; background: none; cursor: pointer; pointer-events: auto; position: absolute; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user