v0.1.0-pre.045

This commit is contained in:
2026-07-25 18:35:51 +02:00
parent c2dbd207d3
commit 01764a485a
9 changed files with 329 additions and 132 deletions

View File

@@ -1,8 +1,117 @@
/* file: kb-app-demo-desktop/frontend/sass/splash.scss */
/* version: 1 */
html, body { width: 100%; height: 100%; margin: 0; background: transparent; overflow: hidden; }
body { display: grid; place-items: center; font-family: sans-serif; }
#splash-container { width: 100%; height: 100%; display: grid; place-content: center; justify-items: center; gap: 1rem; opacity: 0; background: radial-gradient(circle, #1f2937, #030712); color: #f9fafb; }
#splash-container img { width: min(55vw, 420px); max-height: 55vh; object-fit: contain; }
#splash-container h1 { margin: 0; letter-spacing: .08em; }
#messages-container { min-height: 1.5rem; color: #d1d5db; }
// file: kb-app-demo-desktop/frontend/sass/splash.scss
// version: 2
@font-face {
font-family: 'Dos Amazigh';
src: url('../fonts/DOS_Amazigh.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: transparent;
overflow: hidden;
}
body {
display: flex;
justify-content: center;
align-items: center;
font-family: Arial, sans-serif;
}
#splash-container {
position: relative;
width: 960px;
height: 637px;
opacity: 0;
will-change: opacity;
}
#splash-image {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
z-index: 1;
}
#app-name {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-family: 'Dos Amazigh', sans-serif;
font-size: 96px;
font-weight: bold;
color: #fff;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
z-index: 2;
text-align: center;
}
#debug-info,
#messages-container {
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
}
#debug-info::-webkit-scrollbar,
#messages-container::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
}
#debug-info {
position: absolute;
top: 0;
left: 0;
width: 50%;
max-height: 30%;
color: white;
font-family: monospace;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.1);
padding: 8px;
z-index: 3;
box-sizing: border-box;
}
#messages-container {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
max-height: 30%;
background-color: rgba(0, 0, 0, 0.1);
padding: 10px 10px 40px;
z-index: 2;
box-sizing: border-box;
}
.splash-message {
margin-bottom: 8px;
padding: 6px 10px;
border-radius: 4px;
animation: fadeIn 0.3s ease-in-out;
font-weight: bold;
color: #fff;
}
.splash-message.info { background-color: rgba(33, 150, 243, 0.2); }
.splash-message.warning { background-color: rgba(255, 152, 0, 0.2); }
.splash-message.error { background-color: rgba(244, 67, 54, 0.2); }
.splash-message.success { background-color: rgba(76, 175, 80, 0.2); }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}

View File

@@ -1,19 +1,23 @@
<!-- file: kb-app-demo-desktop/frontend/splash.html -->
<!-- version: 1 -->
<!doctype html>
<!-- version: 2 -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chargement — Khadhroony Bot3</title>
<link rel="stylesheet" href="sass/splash.scss" />
</head>
<body>
<main id="splash-container">
<img src="imgs/splash.png" alt="Khadhroony Bot3" />
<h1>Khadhroony Bot3</h1>
<div id="messages-container" aria-live="polite"></div>
</main>
<script type="module" src="ts/splash.ts"></script>
</body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Chargement — Khadhroony Bot3</title>
<link rel="stylesheet" href="sass/splash.scss" />
</head>
<body>
<div id="splash-container" style="opacity: 0;">
<img id="splash-image" src="imgs/splash.png" alt="Chargement de Khadhroony Bot3" />
<div id="app-name">Khadhroony</div>
<div id="debug-info" aria-live="polite"></div>
<div id="messages-container" aria-live="polite"></div>
</div>
<script type="module" src="ts/splash.ts" defer></script>
</body>
</html>

View File

@@ -1,40 +1,112 @@
// file: kb-app-demo-desktop/frontend/ts/splash.ts
// version: 1
// version: 2
import { listen } from "@tauri-apps/api/event";
import type { SplashOrder } from "./bindings/kb_app_demo_desktop/splash/SplashOrder.ts";
type SplashOrder = {
order: string;
msg: string | null;
status: string | null;
duration_ms: number | null;
};
const defaultFadeDurationMs = 500;
let activeOpacityFrame: number | null = null;
const container = document.querySelector<HTMLElement>("#splash-container");
const messages = document.querySelector<HTMLElement>("#messages-container");
function duration(order: SplashOrder): number {
if (typeof order.duration_ms !== "number" || !Number.isFinite(order.duration_ms) || order.duration_ms <= 0) {
return 500;
function normalizeDurationMs(value: number | null | undefined): number {
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
return defaultFadeDurationMs;
}
return order.duration_ms;
return value;
}
async function applyOrder(order: SplashOrder): Promise<void> {
if (!container) {
function easeInOut(progress: number): number {
if (progress < 0.5) {
return 2 * progress * progress;
}
return 1 - Math.pow(-2 * progress + 2, 2) / 2;
}
async function animateOpacity(element: HTMLElement, fromOpacity: number, toOpacity: number, durationMs: number): Promise<void> {
if (activeOpacityFrame !== null) {
cancelAnimationFrame(activeOpacityFrame);
activeOpacityFrame = null;
}
element.style.opacity = fromOpacity.toString();
element.style.willChange = "opacity";
await new Promise<void>(resolve => requestAnimationFrame(() => resolve()));
await new Promise<void>(resolve => {
const startedAt = performance.now();
const opacityDelta = toOpacity - fromOpacity;
const updateOpacity = (currentTime: number): void => {
const elapsedMs = currentTime - startedAt;
const rawProgress = Math.min(elapsedMs / durationMs, 1);
const nextOpacity = fromOpacity + opacityDelta * easeInOut(rawProgress);
element.style.opacity = nextOpacity.toString();
if (rawProgress >= 1) {
element.style.opacity = toOpacity.toString();
element.style.willChange = "auto";
activeOpacityFrame = null;
resolve();
return;
}
activeOpacityFrame = requestAnimationFrame(updateOpacity);
};
activeOpacityFrame = requestAnimationFrame(updateOpacity);
});
}
function scrollToLatest(element: HTMLElement): void {
element.scrollTop = element.scrollHeight;
}
function addLogMessage(message: string): void {
const debugInfo = document.getElementById("debug-info");
if (!debugInfo) {
return;
}
if (order.order === "add_msg" && messages && order.msg) {
messages.textContent = order.msg;
messages.dataset.status = order.status ?? "info";
const line = document.createElement("div");
line.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
debugInfo.appendChild(line);
scrollToLatest(debugInfo);
}
function addMessage(message: string, status: string): void {
const messagesContainer = document.getElementById("messages-container");
if (!messagesContainer) {
return;
}
if (order.order === "fadein" || order.order === "fadeout") {
container.style.transition = `opacity ${duration(order)}ms ease-in-out`;
container.style.opacity = order.order === "fadein" ? "1" : "0";
const messageElement = document.createElement("div");
messageElement.className = `splash-message ${status}`;
messageElement.textContent = message;
messagesContainer.appendChild(messageElement);
scrollToLatest(messagesContainer);
}
async function handleSplashOrder(order: SplashOrder): Promise<void> {
const container = document.getElementById("splash-container");
if (order.order === "fadein" && container) {
await animateOpacity(container, 0, 1, normalizeDurationMs(order.duration_ms));
return;
}
if (order.order === "fadeout" && container) {
await animateOpacity(container, 1, 0, normalizeDurationMs(order.duration_ms));
return;
}
if (order.order === "add_msg" && order.msg && order.status) {
addMessage(order.msg, order.status);
return;
}
if (order.order === "add_log" && order.msg) {
addLogMessage(order.msg);
}
}
void listen<SplashOrder>("splash", event => {
void applyOrder(event.payload);
async function initializeSplash(): Promise<void> {
const container = document.getElementById("splash-container");
if (container) {
container.style.opacity = "0";
container.style.willChange = "opacity";
}
await listen<SplashOrder>("splash", event => {
void handleSplashOrder(event.payload);
});
}
document.addEventListener("DOMContentLoaded", () => {
void initializeSplash();
});