diff --git a/frontend/src/lib/components/ui/LoadingScreen.svelte b/frontend/src/lib/components/ui/LoadingScreen.svelte index ec23944..ea307fc 100644 --- a/frontend/src/lib/components/ui/LoadingScreen.svelte +++ b/frontend/src/lib/components/ui/LoadingScreen.svelte @@ -1,61 +1,98 @@ -
- -
-
+
+ + -
- -
- {#if variant === 'connect'} - -
-
- - - - - {:else if variant === 'query'} - -
-
- - - - - +
+ {message} + {#if submessage} + {submessage} + {/if} +
+ {#if determinate} +
{:else} - -
- - - -
+
{/if}
- - -
-
-
- - -

{message}

- {#if submessage} -

{submessage}

- {/if}
@@ -63,175 +100,138 @@ .loading-root { position: absolute; inset: 0; + z-index: 30; display: flex; + flex-direction: column; align-items: center; justify-content: center; - z-index: 30; + gap: 30px; overflow: hidden; - /* Matches the app background */ - background: var(--bg); - animation: fadeIn 0.22s ease both; + background: + radial-gradient( + 1000px 600px at 50% -10%, + rgba(var(--glow-rgb), 0.1) 0%, + transparent 60% + ), + var(--bg); + animation: riseIn 0.4s both; } - @keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } + @keyframes riseIn { + from { + opacity: 0; + transform: translateY(14px); + } + to { + opacity: 1; + transform: none; + } } - /* ---- ambient blobs ---- */ - .blob { - position: absolute; - border-radius: 50%; - filter: blur(72px); - pointer-events: none; - } - .blob-a { - width: 420px; - height: 420px; - background: radial-gradient(circle, rgba(var(--glow-rgb), 0.18) 0%, transparent 70%); - top: -100px; - right: -60px; - animation: driftA 7s ease-in-out infinite alternate; - } - .blob-b { - width: 320px; - height: 320px; - background: radial-gradient(circle, rgba(var(--glow-rgb), 0.10) 0%, transparent 70%); - bottom: -80px; - left: -60px; - animation: driftB 9s ease-in-out infinite alternate; - } - @keyframes driftA { - from { transform: translate(0, 0) scale(1); } - to { transform: translate(-30px, 30px) scale(1.1); } - } - @keyframes driftB { - from { transform: translate(0, 0) scale(1); } - to { transform: translate(20px, -20px) scale(1.08); } + .mark { + overflow: visible; } - - /* ---- card ---- */ - .loading-card { - display: flex; - flex-direction: column; - align-items: center; - gap: 18px; - padding: 40px 48px; - background: var(--card); - border: 1px solid var(--border); - border-radius: var(--radius-xl, 26px); - box-shadow: var(--shadow-lg); - max-width: 340px; - width: 90%; - animation: cardPop 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both; - position: relative; - z-index: 1; - } - @keyframes cardPop { - from { opacity: 0; transform: scale(0.88) translateY(16px); } - to { opacity: 1; transform: scale(1) translateY(0); } + .mark > * { + stroke-dasharray: 1; + animation: signDraw 2.6s cubic-bezier(0.65, 0, 0.35, 1) infinite; } - - /* ---- icon area ---- */ - .icon-wrap { - position: relative; - width: 64px; - height: 64px; - display: flex; - align-items: center; - justify-content: center; + .mark > *:nth-child(2) { + animation-delay: 0.15s; } - - .icon-svg { - color: var(--brand); - position: relative; - z-index: 2; - animation: iconPulse 2.4s ease-in-out infinite; + .mark > *:nth-child(3) { + animation-delay: 0.3s; } - @keyframes iconPulse { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.75; transform: scale(0.92); } + .mark > *:nth-child(4) { + animation-delay: 0.45s; } - /* orbit rings */ - .orbit-ring { - position: absolute; - inset: 0; - border-radius: 50%; - border: 1.5px solid transparent; - border-top-color: var(--brand); - animation: orbit 1.5s linear infinite; - } - .ring-1 { - inset: 0; - opacity: 0.55; - } - .ring-2 { - inset: 8px; - animation-direction: reverse; - animation-duration: 2.2s; - border-top-color: var(--accent, var(--brand)); - opacity: 0.35; - } - @keyframes orbit { - to { transform: rotate(360deg); } + @keyframes signDraw { + 0%, + 100% { + stroke-dashoffset: 1; + } + 50% { + stroke-dashoffset: 0; + } } - /* ---- dots ---- */ - .dots { + .copy { display: flex; - gap: 8px; + flex-direction: column; align-items: center; + gap: 12px; + padding: 0 24px; } - .dot { - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--brand); - animation: dotBounce 1.2s ease-in-out infinite; + + .msg { + font-size: 14px; + font-weight: 600; + color: var(--ink-2); + letter-spacing: -0.005em; + text-align: center; } - .dot:nth-child(1) { animation-delay: 0s; } - .dot:nth-child(2) { animation-delay: 0.18s; } - .dot:nth-child(3) { animation-delay: 0.36s; } - @keyframes dotBounce { - 0%, 80%, 100% { transform: scale(0.7); opacity: 0.45; } - 40% { transform: scale(1.1); opacity: 1; } + + .sub { + font-size: 12.5px; + color: var(--faint); + line-height: 1.5; + text-align: center; + max-width: 260px; } - /* ---- progress bar ---- */ - .progress-track { - width: 100%; - height: 3px; - background: var(--surface-3, var(--border)); + .track { + width: 120px; + height: 1px; + background: var(--border); border-radius: 99px; overflow: hidden; } - .progress-bar { + + .fill { height: 100%; - width: 40%; - background: linear-gradient(90deg, transparent, var(--brand), var(--accent, var(--brand)), transparent); + background: var(--brand); border-radius: 99px; - animation: progressSweep 1.6s ease-in-out infinite; + transition: width 0.5s cubic-bezier(0.65, 0, 0.35, 1); } - @keyframes progressSweep { - 0% { transform: translateX(-200%); } - 100% { transform: translateX(350%); } + + .fill.sweep { + width: 40%; + animation: sweep 1.6s ease-in-out infinite; } - /* ---- text ---- */ - .loading-msg { - margin: 0; - font-size: 15px; - font-weight: 700; - color: var(--ink); - letter-spacing: -0.015em; - text-align: center; + @keyframes sweep { + 0% { + transform: translateX(-120%); + } + 100% { + transform: translateX(320%); + } } - .loading-sub { - margin: -8px 0 0; - font-size: 12.5px; - color: var(--muted); - text-align: center; - line-height: 1.5; + + /* + The mark is the only thing conveying "still working", so it keeps a gentle + pulse rather than freezing completely when motion is reduced. + */ + @media (prefers-reduced-motion: reduce) { + .loading-root { + animation: none; + } + .mark > * { + stroke-dasharray: none; + animation: fade 2.4s ease-in-out infinite; + } + .fill.sweep { + width: 100%; + animation: fade 2.4s ease-in-out infinite; + } + @keyframes fade { + 0%, + 100% { + opacity: 0.35; + } + 50% { + opacity: 1; + } + } }