From bc02acf650d9d25c4fea907c74afc00ca72d6352 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sun, 9 Nov 2025 21:00:31 +0800 Subject: [PATCH] feat: Add professional glitch-effect loading animation with minimum duration control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement unified LoadingOverlay component with advanced glitch effects: Animation features: - Glitch text effect with RGB chromatic aberration - Dynamic clip-path based text slicing animations - Random flicker and screen tearing effects - Horizontal glitch block artifacts - CRT-style scan line with color gradient - Fractal noise overlay for authenticity Technical improvements: - Minimum display duration (600-800ms) to prevent flickering - Smooth fade-in/fade-out transitions - Consistent TERMIX branding across all loading states - Multiple animation layers with different timing - Mix-blend-mode and advanced CSS filters Applied to: - SSH terminal connection loading - File manager directory loading - Server metrics loading Brand enhancement: - Uses TERMIX monospace typography - Cyberpunk-style visual effects - Professional loading experience - Stronger brand recognition 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/ui/components/LoadingOverlay.tsx | 484 ++++++++++++++++++ .../apps/file-manager/FileManagerGrid.tsx | 21 +- src/ui/desktop/apps/server/Server.tsx | 21 +- src/ui/desktop/apps/terminal/Terminal.tsx | 19 +- 4 files changed, 511 insertions(+), 34 deletions(-) create mode 100644 src/ui/components/LoadingOverlay.tsx diff --git a/src/ui/components/LoadingOverlay.tsx b/src/ui/components/LoadingOverlay.tsx new file mode 100644 index 00000000..448ed382 --- /dev/null +++ b/src/ui/components/LoadingOverlay.tsx @@ -0,0 +1,484 @@ +import React, { useEffect, useState, useRef } from "react"; +import { cn } from "@/lib/utils"; + +interface LoadingOverlayProps { + visible: boolean; + minDuration?: number; // Minimum display duration in milliseconds + message?: string; + showLogo?: boolean; + className?: string; + backgroundColor?: string; +} + +export function LoadingOverlay({ + visible, + minDuration = 800, + message, + showLogo = true, + className, + backgroundColor, +}: LoadingOverlayProps) { + const [isShowing, setIsShowing] = useState(false); + const [isFadingOut, setIsFadingOut] = useState(false); + const showStartTimeRef = useRef(null); + const minDurationTimerRef = useRef(null); + + useEffect(() => { + if (visible) { + // Start showing immediately + setIsShowing(true); + setIsFadingOut(false); + showStartTimeRef.current = Date.now(); + + // Clear any existing timer + if (minDurationTimerRef.current) { + clearTimeout(minDurationTimerRef.current); + minDurationTimerRef.current = null; + } + } else if (isShowing) { + // Calculate how long it has been showing + const elapsed = showStartTimeRef.current + ? Date.now() - showStartTimeRef.current + : 0; + const remaining = Math.max(0, minDuration - elapsed); + + if (remaining > 0) { + // Wait for minimum duration before hiding + minDurationTimerRef.current = setTimeout(() => { + setIsFadingOut(true); + // Wait for fade-out animation to complete + setTimeout(() => { + setIsShowing(false); + setIsFadingOut(false); + showStartTimeRef.current = null; + }, 300); // Match fade-out duration + }, remaining); + } else { + // Minimum duration already passed, hide immediately + setIsFadingOut(true); + setTimeout(() => { + setIsShowing(false); + setIsFadingOut(false); + showStartTimeRef.current = null; + }, 300); + } + } + + return () => { + if (minDurationTimerRef.current) { + clearTimeout(minDurationTimerRef.current); + minDurationTimerRef.current = null; + } + }; + }, [visible, isShowing, minDuration]); + + if (!isShowing) { + return null; + } + + return ( + <> + + +
+
+
+ +
+
+ {/* TERMIX Glitch Text */} +
+ TERMIX +
+ + {/* Scan line effect */} +
+
+ + {message && ( +
+

+ {message} +

+
+ )} +
+
+ + ); +} diff --git a/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx b/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx index 5c1a0ac8..25c14cf4 100644 --- a/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx +++ b/src/ui/desktop/apps/file-manager/FileManagerGrid.tsx @@ -24,6 +24,7 @@ import { } from "lucide-react"; import { useTranslation } from "react-i18next"; import type { FileItem } from "../../../types/index.js"; +import { LoadingOverlay } from "@/ui/components/LoadingOverlay"; interface CreateIntent { id: string; @@ -871,19 +872,8 @@ export function FileManagerGrid({ onUndo, ]); - if (isLoading) { - return ( -
-
-
-

{t("common.loading")}

-
-
- ); - } - return ( -
+
); } diff --git a/src/ui/desktop/apps/server/Server.tsx b/src/ui/desktop/apps/server/Server.tsx index 21879e0f..4cf5be8c 100644 --- a/src/ui/desktop/apps/server/Server.tsx +++ b/src/ui/desktop/apps/server/Server.tsx @@ -17,6 +17,7 @@ import { type StatsConfig, DEFAULT_STATS_CONFIG, } from "@/types/stats-widgets"; +import { LoadingOverlay } from "@/ui/components/LoadingOverlay"; import { CpuWidget, MemoryWidget, @@ -443,17 +444,8 @@ export function Server({
{metricsEnabled && showStatsUI && ( -
- {isLoadingMetrics && !metrics ? ( -
-
-
- - {t("serverStats.loadingMetrics")} - -
-
- ) : !metrics && serverStatus === "offline" ? ( +
+ {!metrics && serverStatus === "offline" ? (
@@ -476,6 +468,13 @@ export function Server({ ))}
)} + +
)} diff --git a/src/ui/desktop/apps/terminal/Terminal.tsx b/src/ui/desktop/apps/terminal/Terminal.tsx index 8b5d5eff..21f0e4ad 100644 --- a/src/ui/desktop/apps/terminal/Terminal.tsx +++ b/src/ui/desktop/apps/terminal/Terminal.tsx @@ -31,6 +31,7 @@ import { useCommandTracker } from "@/ui/hooks/useCommandTracker"; import { useCommandHistory } from "@/ui/hooks/useCommandHistory"; import { CommandHistoryDialog } from "./CommandHistoryDialog"; import { CommandAutocomplete } from "./CommandAutocomplete"; +import { LoadingOverlay } from "@/ui/components/LoadingOverlay"; interface HostConfig { id?: number; @@ -1450,17 +1451,13 @@ export const Terminal = forwardRef( onSelect={handleAutocompleteSelect} /> - {isConnecting && ( -
-
-
- {t("terminal.connecting")} -
-
- )} +
); },