-
-
- {#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;
+ }
+ }
}