style(FileHistoryPanel): enhance loading animation and styles

This update improves the loading animation for the File History Panel,
making it more visually appealing and responsive. The CSS has been
refactored to introduce new styles and animations, while the Svelte
component has been updated to utilize these changes effectively.

- Added new loading animations and styles for better user experience
- Refactored CSS to improve maintainability and visual design
- Updated Svelte component to integrate new loading elements
This commit is contained in:
Christoph Brandau
2026-07-08 14:36:09 +02:00
parent ba2065d3c4
commit 61a22987ba
2 changed files with 120 additions and 61 deletions
+108 -45
View File
@@ -2147,73 +2147,136 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 12px; gap: 10px;
min-height: 140px; min-height: 156px;
padding: 20px; padding: 22px;
overflow: hidden;
background:
radial-gradient(circle at center 42%, rgba(151, 118, 255, 0.12), transparent 52%),
linear-gradient(180deg, rgba(15, 18, 31, 0.55), transparent);
} }
.file-history-loading-graph { width: 56px; height: 56px; overflow: visible; } .file-history-loading-mark {
position: relative;
.file-history-loading-graph .fhl-ring { display: grid;
fill: none; place-items: center;
stroke: rgba(100, 108, 255, 0.22); width: 86px;
stroke-width: 3; height: 86px;
stroke-dasharray: 26 18; isolation: isolate;
transform-origin: 60px 60px;
animation: fhl-ring-spin 5s linear infinite;
} }
.file-history-loading-graph .fhl-trunk, .file-history-loading-halo {
.file-history-loading-graph .fhl-branch { position: absolute;
inset: 10px;
border: 1px solid rgba(151, 118, 255, 0.24);
border-radius: 18px;
transform: rotate(45deg);
animation: fhl-halo-breathe 2.4s ease-in-out infinite;
}
.file-history-loading-traces {
position: absolute;
inset: -12px;
width: 110px;
height: 110px;
overflow: visible;
}
.file-history-loading-traces .fhl-trace {
fill: none; fill: none;
stroke: url(#fhl-line); stroke-width: 2.5;
stroke-width: 5;
stroke-linecap: round; stroke-linecap: round;
stroke-dasharray: 92;
stroke-dashoffset: 92;
filter: drop-shadow(0 0 6px rgba(151, 118, 255, 0.5));
animation: fhl-trace-draw 2.4s ease-in-out infinite;
}
.file-history-loading-traces .fhl-trace-main {
stroke: #9b70ff;
}
.file-history-loading-traces .fhl-trace-branch {
stroke: #ff6d26;
animation-delay: 0.28s;
} }
.file-history-loading-graph .fhl-branch { .file-history-loading-icon {
stroke-dasharray: 90; position: relative;
stroke-dashoffset: 90; z-index: 1;
animation: fhl-branch-draw 2.4s ease-in-out infinite; width: 64px;
height: 64px;
object-fit: contain;
filter:
drop-shadow(0 10px 14px rgba(0, 0, 0, 0.45))
drop-shadow(0 0 12px rgba(151, 118, 255, 0.28));
animation: fhl-icon-float 2.4s ease-in-out infinite;
} }
.file-history-loading-graph .fhl-node { .file-history-loading-sheen {
fill: var(--color-surface-alt); position: absolute;
stroke: url(#fhl-line); z-index: 2;
stroke-width: 5; width: 54px;
animation: fhl-node-pulse 2.4s ease-in-out infinite; height: 82px;
border-radius: 50%;
background: linear-gradient(90deg, transparent 8%, rgba(255, 255, 255, 0.16), transparent 72%);
mix-blend-mode: screen;
transform: translateX(-48px) rotate(16deg);
animation: fhl-icon-sheen 2.8s ease-in-out infinite;
} }
.file-history-loading-graph .fhl-n1 { animation-delay: 0s; }
.file-history-loading-graph .fhl-n2 { animation-delay: 0.5s; }
.file-history-loading-graph .fhl-n3 { animation-delay: 1s; }
.file-history-loading-graph .fhl-n4 { animation-delay: 1.5s; }
.file-history-loading-label { .file-history-loading-label {
color: var(--color-ink-faint); color: var(--color-ink-faint);
font-size: 12.5px; font-size: 12.5px;
font-weight: 600; font-weight: 800;
letter-spacing: 0.01em;
} }
@keyframes fhl-ring-spin { to { transform: rotate(360deg); } } .file-history-loading-bar {
@keyframes fhl-branch-draw { position: relative;
0% { stroke-dashoffset: 90; opacity: 0.35; } width: 118px;
45% { stroke-dashoffset: 0; opacity: 1; } height: 3px;
100% { stroke-dashoffset: 0; opacity: 1; } overflow: hidden;
border-radius: 999px;
background: rgba(151, 118, 255, 0.14);
} }
@keyframes fhl-node-pulse { .file-history-loading-bar span {
0%, 100% { fill: var(--color-surface-alt); filter: none; } position: absolute;
50% { inset: 0;
fill: var(--color-primary); width: 46%;
filter: drop-shadow(0 0 6px rgba(100, 108, 255, 0.8)); border-radius: inherit;
background: linear-gradient(90deg, transparent, #9b70ff 42%, #ff6d26 74%, transparent);
box-shadow: 0 0 12px rgba(255, 109, 38, 0.26);
animation: fhl-bar-slide 1.35s ease-in-out infinite;
} }
@keyframes fhl-halo-breathe {
0%, 100% { opacity: 0.35; transform: rotate(45deg) scale(0.94); }
50% { opacity: 0.8; transform: rotate(45deg) scale(1.04); }
}
@keyframes fhl-icon-float {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-3px) scale(1.02); }
}
@keyframes fhl-icon-sheen {
0%, 18% { transform: translateX(-48px) rotate(16deg); opacity: 0; }
44% { opacity: 0.8; }
68%, 100% { transform: translateX(50px) rotate(16deg); opacity: 0; }
}
@keyframes fhl-trace-draw {
0% { stroke-dashoffset: 92; opacity: 0; }
36% { opacity: 1; }
64%, 100% { stroke-dashoffset: 0; opacity: 0.72; }
}
@keyframes fhl-bar-slide {
0% { transform: translateX(-120%); }
100% { transform: translateX(320%); }
} }
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.file-history-loading-graph .fhl-ring, .file-history-loading-halo,
.file-history-loading-graph .fhl-branch, .file-history-loading-traces .fhl-trace,
.file-history-loading-graph .fhl-node { animation: none; } .file-history-loading-icon,
.file-history-loading-graph .fhl-branch { stroke-dashoffset: 0; } .file-history-loading-sheen,
.file-history-loading-bar span { animation: none; }
.file-history-loading-traces .fhl-trace { stroke-dashoffset: 0; opacity: 0.72; }
} }
/* --- Git graph --- */ /* --- Git graph --- */
+10 -14
View File
@@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { GitCompare, History, RotateCcw } from "@lucide/svelte"; import { GitCompare, History, RotateCcw } from "@lucide/svelte";
import iconUrl from "../../../src-tauri/icons/icon.png";
import type { GitCommit } from "../types"; import type { GitCommit } from "../types";
interface Props { interface Props {
@@ -93,22 +94,17 @@
<div class="blank-state">Select a file in Explorer.</div> <div class="blank-state">Select a file in Explorer.</div>
{:else if isLoading} {:else if isLoading}
<div class="file-history-loading" role="status" aria-live="polite"> <div class="file-history-loading" role="status" aria-live="polite">
<svg class="file-history-loading-graph" viewBox="0 0 120 120" aria-hidden="true"> <div class="file-history-loading-mark" aria-hidden="true">
<defs> <span class="file-history-loading-halo"></span>
<linearGradient id="fhl-line" x1="0" y1="0" x2="1" y2="1"> <svg class="file-history-loading-traces" viewBox="0 0 120 120">
<stop offset="0%" stop-color="#646cff" /> <path class="fhl-trace fhl-trace-main" d="M14 82 C38 60, 48 58, 60 60 S84 66, 106 36" />
<stop offset="100%" stop-color="#41d1ff" /> <path class="fhl-trace fhl-trace-branch" d="M28 36 C52 48, 70 70, 91 94" />
</linearGradient>
</defs>
<circle class="fhl-ring" cx="60" cy="60" r="52" />
<path class="fhl-trunk" d="M42 22 L42 98" />
<path class="fhl-branch" d="M42 44 C42 62, 82 58, 82 76 L82 88" />
<circle class="fhl-node fhl-n1" cx="42" cy="30" r="6" />
<circle class="fhl-node fhl-n2" cx="42" cy="60" r="6" />
<circle class="fhl-node fhl-n3" cx="82" cy="88" r="6" />
<circle class="fhl-node fhl-n4" cx="42" cy="90" r="6" />
</svg> </svg>
<img src={iconUrl} alt="" class="file-history-loading-icon" />
<span class="file-history-loading-sheen"></span>
</div>
<span class="file-history-loading-label">Loading history…</span> <span class="file-history-loading-label">Loading history…</span>
<span class="file-history-loading-bar" aria-hidden="true"><span></span></span>
</div> </div>
{:else if fileHistory.length === 0} {:else if fileHistory.length === 0}
<div class="blank-state">No history returned for this selection.</div> <div class="blank-state">No history returned for this selection.</div>