UI Components Easy
AI Thinking Loader
Collection of 'Thinking…' animated loaders for AI interfaces: pulse dots, shimmer bar, orbit ring, and wave variants.
Open in Lab
MCP
css react tailwind vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f9fafb;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 16px;
}
.demo {
width: 100%;
max-width: 640px;
}
.loader-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.loader-card {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 14px;
padding: 24px 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
min-height: 120px;
justify-content: center;
}
.loader-card--wide {
grid-column: 1 / -1;
}
.loader-label {
font-size: 11px;
font-weight: 700;
color: #9ca3af;
text-transform: uppercase;
letter-spacing: 0.06em;
align-self: flex-start;
}
.loader-caption {
font-size: 12px;
color: #6b7280;
}
/* 1. Bouncing dots */
.thinking-dots {
display: flex;
gap: 6px;
align-items: center;
height: 24px;
}
.thinking-dots span {
width: 8px;
height: 8px;
background: #6366f1;
border-radius: 50%;
animation: bounce-dot 1.2s ease-in-out infinite;
}
.thinking-dots span:nth-child(2) {
animation-delay: 0.2s;
}
.thinking-dots span:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes bounce-dot {
0%,
80%,
100% {
transform: scale(0.8) translateY(0);
opacity: 0.5;
}
40% {
transform: scale(1) translateY(-8px);
opacity: 1;
}
}
/* 2. Pulse ring */
.thinking-pulse {
position: relative;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.pulse-ring {
position: absolute;
inset: 0;
border: 2px solid #6366f1;
border-radius: 50%;
animation: pulse-expand 2s ease-out infinite;
}
.pulse-ring--2 {
animation-delay: 0.8s;
}
.pulse-core {
width: 12px;
height: 12px;
background: #6366f1;
border-radius: 50%;
}
@keyframes pulse-expand {
0% {
transform: scale(0.5);
opacity: 1;
}
100% {
transform: scale(1.4);
opacity: 0;
}
}
/* 3. Shimmer bar */
.thinking-shimmer {
width: 100%;
display: flex;
flex-direction: column;
gap: 6px;
}
.shimmer-line {
height: 10px;
border-radius: 5px;
background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.shimmer-line--full {
width: 100%;
}
.shimmer-line--80 {
width: 80%;
}
.shimmer-line--60 {
width: 60%;
}
@keyframes shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* 4. Wave bars */
.thinking-wave {
display: flex;
gap: 4px;
align-items: flex-end;
height: 28px;
}
.thinking-wave span {
width: 4px;
background: #6366f1;
border-radius: 2px;
animation: wave-bar 1s ease-in-out infinite;
}
.thinking-wave span:nth-child(1) {
animation-delay: 0s;
height: 14px;
}
.thinking-wave span:nth-child(2) {
animation-delay: 0.1s;
height: 20px;
}
.thinking-wave span:nth-child(3) {
animation-delay: 0.2s;
height: 28px;
}
.thinking-wave span:nth-child(4) {
animation-delay: 0.3s;
height: 20px;
}
.thinking-wave span:nth-child(5) {
animation-delay: 0.4s;
height: 14px;
}
@keyframes wave-bar {
0%,
100% {
transform: scaleY(0.5);
opacity: 0.5;
}
50% {
transform: scaleY(1);
opacity: 1;
}
}
/* 5. Skeleton text */
.thinking-skeleton {
width: 100%;
display: flex;
flex-direction: column;
gap: 8px;
}
.skel-line {
height: 12px;
border-radius: 6px;
background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
.skel-line--100 {
width: 100%;
}
.skel-line--90 {
width: 90%;
}
.skel-line--75 {
width: 75%;
}
.skel-line--50 {
width: 50%;
}// Pure CSS — no JavaScript needed for these animations<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AI Thinking Loader</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="demo">
<div class="loader-grid">
<div class="loader-card">
<div class="loader-label">Bouncing Dots</div>
<div class="thinking-dots">
<span></span><span></span><span></span>
</div>
<div class="loader-caption">Thinking…</div>
</div>
<div class="loader-card">
<div class="loader-label">Pulse Ring</div>
<div class="thinking-pulse">
<div class="pulse-ring"></div>
<div class="pulse-ring pulse-ring--2"></div>
<div class="pulse-core"></div>
</div>
<div class="loader-caption">Processing…</div>
</div>
<div class="loader-card">
<div class="loader-label">Shimmer Bar</div>
<div class="thinking-shimmer">
<div class="shimmer-line shimmer-line--full"></div>
<div class="shimmer-line shimmer-line--80"></div>
<div class="shimmer-line shimmer-line--60"></div>
</div>
<div class="loader-caption">Generating…</div>
</div>
<div class="loader-card">
<div class="loader-label">Wave Bars</div>
<div class="thinking-wave">
<span></span><span></span><span></span><span></span><span></span>
</div>
<div class="loader-caption">Analyzing…</div>
</div>
<div class="loader-card loader-card--wide">
<div class="loader-label">Shimmer Text (skeleton)</div>
<div class="thinking-skeleton">
<div class="skel-line skel-line--100"></div>
<div class="skel-line skel-line--90"></div>
<div class="skel-line skel-line--75"></div>
<div class="skel-line skel-line--50"></div>
</div>
</div>
</div>
</div>
</body>
</html>import { useState } from "react";
/* ── Loader 1: Dots pulse ─────────────────────────────────────── */
function DotsPulse() {
return (
<div className="flex items-center gap-1.5">
{[0, 1, 2].map((i) => (
<span
key={i}
className="w-2 h-2 rounded-full bg-[#58a6ff] animate-bounce"
style={{ animationDelay: `${i * 150}ms`, animationDuration: "0.8s" }}
/>
))}
<span className="ml-1 text-[13px] text-[#8b949e]">Thinking…</span>
</div>
);
}
/* ── Loader 2: Shimmer bar ────────────────────────────────────── */
function ShimmerBar() {
return (
<div className="space-y-2 w-full max-w-[320px]">
{[100, 80, 60].map((w, i) => (
<div
key={i}
className="h-3 rounded-full bg-gradient-to-r from-[#21262d] via-[#30363d] to-[#21262d] bg-[length:200%_100%] animate-shimmer"
style={{ width: `${w}%` }}
/>
))}
<style>{`@keyframes shimmer{0%{background-position:100% 0}100%{background-position:-100% 0}}.animate-shimmer{animation:shimmer 1.4s ease-in-out infinite}`}</style>
</div>
);
}
/* ── Loader 3: Orbit ring ─────────────────────────────────────── */
function OrbitRing() {
return (
<div className="flex items-center gap-3">
<div className="relative w-8 h-8">
<div className="absolute inset-0 rounded-full border-2 border-[#30363d]" />
<div className="absolute inset-0 rounded-full border-2 border-transparent border-t-[#58a6ff] animate-spin" />
<div className="absolute inset-1 rounded-full bg-[#58a6ff]/10 flex items-center justify-center">
<svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="#58a6ff"
strokeWidth="2.5"
>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z" />
<path d="M12 8v4l3 3" />
</svg>
</div>
</div>
<span className="text-[13px] text-[#8b949e]">Processing…</span>
</div>
);
}
/* ── Loader 4: Wave bars ──────────────────────────────────────── */
function WaveBars() {
return (
<div className="flex items-center gap-3">
<div className="flex items-end gap-0.5 h-6">
{[0, 1, 2, 3, 4].map((i) => (
<div
key={i}
className="w-1 rounded-full bg-[#bc8cff]"
style={{
animation: `wave 0.9s ease-in-out ${i * 0.1}s infinite`,
height: "60%",
}}
/>
))}
</div>
<span className="text-[13px] text-[#8b949e]">Analyzing…</span>
<style>{`@keyframes wave{0%,100%{transform:scaleY(0.4)}50%{transform:scaleY(1.2)}}`}</style>
</div>
);
}
/* ── Loader 5: Typing cursor ──────────────────────────────────── */
function TypingCursor() {
return (
<div className="flex items-center gap-1">
<span className="text-[13px] text-[#8b949e]">Generating response</span>
<span
className="inline-block w-0.5 h-4 bg-[#58a6ff] animate-pulse rounded-sm"
style={{ animationDuration: "0.7s" }}
/>
</div>
);
}
/* ── Loader 6: Brain pulse (new) ─────────────────────────────── */
function BrainPulse() {
return (
<div className="flex items-center gap-3">
<div className="relative">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-[#58a6ff]/20 to-[#bc8cff]/20 border border-[#58a6ff]/20 flex items-center justify-center animate-pulse">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="#58a6ff"
strokeWidth="1.5"
>
<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-5 0v-15A2.5 2.5 0 0 1 9.5 2z" />
<path d="M14.5 2A2.5 2.5 0 0 1 17 4.5v15a2.5 2.5 0 0 1-5 0v-15A2.5 2.5 0 0 1 14.5 2z" />
<path d="M5 8.5a2.5 2.5 0 1 1 4 2" />
<path d="M15 8.5a2.5 2.5 0 1 0-4 2" />
</svg>
</div>
</div>
<div>
<p className="text-[13px] font-semibold text-[#e6edf3]">Deep reasoning</p>
<p className="text-[11px] text-[#484f58]">Working through the problem…</p>
</div>
</div>
);
}
/* ── Loader 7: Token stream (new) ───────────────────────────── */
function TokenStream() {
return (
<div className="space-y-1.5">
<div className="flex items-center gap-2">
<span className="text-[11px] text-[#484f58] font-mono">tokens/s</span>
<div className="flex-1 h-1 bg-[#21262d] rounded-full overflow-hidden">
<div
className="h-full bg-green-500 rounded-full"
style={{ animation: "tokenbar 2s ease-in-out infinite" }}
/>
</div>
<span className="text-[11px] text-green-400 font-mono">~48</span>
</div>
<div className="font-mono text-[12px] text-[#8b949e] overflow-hidden whitespace-nowrap">
{"The answer to your question involves ".split("").map((ch, i) => (
<span
key={i}
className="inline-block"
style={{ animation: `fade-in 0.05s ${i * 0.04}s both` }}
>
{ch}
</span>
))}
<span className="inline-block w-0.5 h-3.5 bg-[#58a6ff] align-middle animate-pulse ml-px" />
</div>
<style>{`
@keyframes tokenbar{0%{width:0}70%{width:100%}100%{width:100%}}
@keyframes fade-in{from{opacity:0}to{opacity:1}}
`}</style>
</div>
);
}
/* ── Loader 8: Step progress (new) ─────────────────────────── */
function StepProgress() {
const steps = ["Reading context", "Planning response", "Generating", "Reviewing"];
return (
<div className="space-y-1.5">
{steps.map((step, i) => (
<div key={step} className="flex items-center gap-2">
<div
className="w-4 h-4 rounded-full flex items-center justify-center flex-shrink-0"
style={{
animation: `step-reveal 0.3s ${i * 0.8}s both`,
opacity: 0,
}}
>
{i < 2 ? (
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="#7ee787"
strokeWidth="2.5"
>
<polyline points="20 6 9 17 4 12" />
</svg>
) : i === 2 ? (
<div className="w-3 h-3 rounded-full border-2 border-[#e3b341] border-t-transparent animate-spin" />
) : (
<div className="w-2 h-2 rounded-full bg-[#484f58]" />
)}
</div>
<span
className={`text-[12px] ${i < 2 ? "text-[#7ee787] line-through" : i === 2 ? "text-[#e3b341]" : "text-[#484f58]"}`}
style={{ animation: `step-reveal 0.3s ${i * 0.8}s both`, opacity: 0 }}
>
{step}
</span>
</div>
))}
<style>{`@keyframes step-reveal{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:none}}`}</style>
</div>
);
}
const LOADERS = [
{ label: "Dots Pulse", comp: DotsPulse },
{ label: "Shimmer Bar", comp: ShimmerBar },
{ label: "Orbit Ring", comp: OrbitRing },
{ label: "Wave Bars", comp: WaveBars },
{ label: "Typing Cursor", comp: TypingCursor },
{ label: "Brain Pulse", comp: BrainPulse },
{ label: "Token Stream", comp: TokenStream },
{ label: "Step Progress", comp: StepProgress },
];
export default function AiThinkingLoaderRC() {
const [active, setActive] = useState<number | null>(null);
return (
<div className="min-h-screen bg-[#0d1117] p-6 flex justify-center">
<div className="w-full max-w-[720px]">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
{LOADERS.map(({ label, comp: Comp }, i) => (
<div
key={label}
className={`bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors ${
active === i ? "border-[#58a6ff]/40" : "border-[#30363d] hover:border-[#8b949e]/40"
}`}
onClick={() => setActive(active === i ? null : i)}
>
<p className="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">
{label}
</p>
<Comp />
</div>
))}
</div>
</div>
</div>
);
}<script setup>
import { ref } from "vue";
const active = ref(null);
const LOADERS = [
"Dots Pulse",
"Shimmer Bar",
"Orbit Ring",
"Wave Bars",
"Typing Cursor",
"Brain Pulse",
"Token Stream",
"Step Progress",
];
const steps = ["Reading context", "Planning response", "Generating", "Reviewing"];
const tokenText = "The answer to your question involves ";
function toggle(i) {
active.value = active.value === i ? null : i;
}
</script>
<template>
<div class="min-h-screen bg-[#0d1117] p-6 flex justify-center">
<div class="w-full max-w-[720px]">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div
v-for="(label, i) in LOADERS"
:key="label"
:class="[
'bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors',
active === i ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'
]"
@click="toggle(i)"
>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">{{ label }}</p>
<!-- Dots Pulse -->
<div v-if="i === 0" class="flex items-center gap-1.5">
<span
v-for="d in [0, 1, 2]"
:key="d"
class="w-2 h-2 rounded-full bg-[#58a6ff] animate-bounce"
:style="{ animationDelay: d * 150 + 'ms', animationDuration: '0.8s' }"
></span>
<span class="ml-1 text-[13px] text-[#8b949e]">Thinking...</span>
</div>
<!-- Shimmer Bar -->
<div v-else-if="i === 1" class="space-y-2 w-full max-w-[320px]">
<div
v-for="w in [100, 80, 60]"
:key="w"
class="h-3 rounded-full bg-gradient-to-r from-[#21262d] via-[#30363d] to-[#21262d] bg-[length:200%_100%] animate-shimmer"
:style="{ width: w + '%' }"
></div>
</div>
<!-- Orbit Ring -->
<div v-else-if="i === 2" class="flex items-center gap-3">
<div class="relative w-8 h-8">
<div class="absolute inset-0 rounded-full border-2 border-[#30363d]"></div>
<div class="absolute inset-0 rounded-full border-2 border-transparent border-t-[#58a6ff] animate-spin"></div>
<div class="absolute inset-1 rounded-full bg-[#58a6ff]/10 flex items-center justify-center">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#58a6ff" stroke-width="2.5">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/>
<path d="M12 8v4l3 3"/>
</svg>
</div>
</div>
<span class="text-[13px] text-[#8b949e]">Processing...</span>
</div>
<!-- Wave Bars -->
<div v-else-if="i === 3" class="flex items-center gap-3">
<div class="flex items-end gap-0.5 h-6">
<div
v-for="b in [0, 1, 2, 3, 4]"
:key="b"
class="w-1 rounded-full bg-[#bc8cff]"
:style="{ animation: `wave 0.9s ease-in-out ${b * 0.1}s infinite`, height: '60%' }"
></div>
</div>
<span class="text-[13px] text-[#8b949e]">Analyzing...</span>
</div>
<!-- Typing Cursor -->
<div v-else-if="i === 4" class="flex items-center gap-1">
<span class="text-[13px] text-[#8b949e]">Generating response</span>
<span
class="inline-block w-0.5 h-4 bg-[#58a6ff] animate-pulse rounded-sm"
style="animation-duration: 0.7s;"
></span>
</div>
<!-- Brain Pulse -->
<div v-else-if="i === 5" class="flex items-center gap-3">
<div class="relative">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-[#58a6ff]/20 to-[#bc8cff]/20 border border-[#58a6ff]/20 flex items-center justify-center animate-pulse">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#58a6ff" stroke-width="1.5">
<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-5 0v-15A2.5 2.5 0 0 1 9.5 2z"/>
<path d="M14.5 2A2.5 2.5 0 0 1 17 4.5v15a2.5 2.5 0 0 1-5 0v-15A2.5 2.5 0 0 1 14.5 2z"/>
<path d="M5 8.5a2.5 2.5 0 1 1 4 2"/>
<path d="M15 8.5a2.5 2.5 0 1 0-4 2"/>
</svg>
</div>
</div>
<div>
<p class="text-[13px] font-semibold text-[#e6edf3]">Deep reasoning</p>
<p class="text-[11px] text-[#484f58]">Working through the problem...</p>
</div>
</div>
<!-- Token Stream -->
<div v-else-if="i === 6" class="space-y-1.5">
<div class="flex items-center gap-2">
<span class="text-[11px] text-[#484f58] font-mono">tokens/s</span>
<div class="flex-1 h-1 bg-[#21262d] rounded-full overflow-hidden">
<div
class="h-full bg-green-500 rounded-full"
style="animation: tokenbar 2s ease-in-out infinite;"
></div>
</div>
<span class="text-[11px] text-green-400 font-mono">~48</span>
</div>
<div class="font-mono text-[12px] text-[#8b949e] overflow-hidden whitespace-nowrap">
<span
v-for="(ch, ci) in tokenText.split('')"
:key="ci"
class="inline-block"
:style="{ animation: `fade-in 0.05s ${ci * 0.04}s both` }"
>{{ ch === ' ' ? '\u00a0' : ch }}</span>
<span class="inline-block w-0.5 h-3.5 bg-[#58a6ff] align-middle animate-pulse ml-px"></span>
</div>
</div>
<!-- Step Progress -->
<div v-else-if="i === 7" class="space-y-1.5">
<div v-for="(step, si) in steps" :key="step" class="flex items-center gap-2">
<div
class="w-4 h-4 rounded-full flex items-center justify-center flex-shrink-0"
:style="{ animation: `step-reveal 0.3s ${si * 0.8}s both`, opacity: 0 }"
>
<svg v-if="si < 2" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#7ee787" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
<div v-else-if="si === 2" class="w-3 h-3 rounded-full border-2 border-[#e3b341] border-t-transparent animate-spin"></div>
<div v-else class="w-2 h-2 rounded-full bg-[#484f58]"></div>
</div>
<span
:class="['text-[12px]', si < 2 ? 'text-[#7ee787] line-through' : si === 2 ? 'text-[#e3b341]' : 'text-[#484f58]']"
:style="{ animation: `step-reveal 0.3s ${si * 0.8}s both`, opacity: 0 }"
>
{{ step }}
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
@keyframes shimmer {
0% { background-position: 100% 0; }
100% { background-position: -100% 0; }
}
.animate-shimmer {
animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes wave {
0%, 100% { transform: scaleY(0.4); }
50% { transform: scaleY(1.2); }
}
@keyframes tokenbar {
0% { width: 0; }
70% { width: 100%; }
100% { width: 100%; }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes step-reveal {
from { opacity: 0; transform: translateX(-8px); }
to { opacity: 1; transform: none; }
}
</style><script>
let active = null;
function toggle(i) {
active = active === i ? null : i;
}
</script>
<div class="min-h-screen bg-[#0d1117] p-6 flex justify-center">
<div class="w-full max-w-[720px]">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<!-- Dots Pulse -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 0 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(0)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Dots Pulse</p>
<div class="flex items-center gap-1.5">
{#each [0, 1, 2] as i}
<span class="w-2 h-2 rounded-full bg-[#58a6ff] animate-bounce" style="animation-delay:{i * 150}ms;animation-duration:0.8s"></span>
{/each}
<span class="ml-1 text-[13px] text-[#8b949e]">Thinking…</span>
</div>
</div>
<!-- Shimmer Bar -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 1 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(1)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Shimmer Bar</p>
<div class="space-y-2 w-full max-w-[320px]">
{#each [100, 80, 60] as w}
<div class="h-3 rounded-full bg-gradient-to-r from-[#21262d] via-[#30363d] to-[#21262d] bg-[length:200%_100%] animate-shimmer" style="width:{w}%"></div>
{/each}
</div>
</div>
<!-- Orbit Ring -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 2 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(2)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Orbit Ring</p>
<div class="flex items-center gap-3">
<div class="relative w-8 h-8">
<div class="absolute inset-0 rounded-full border-2 border-[#30363d]"></div>
<div class="absolute inset-0 rounded-full border-2 border-transparent border-t-[#58a6ff] animate-spin"></div>
<div class="absolute inset-1 rounded-full bg-[#58a6ff]/10 flex items-center justify-center">
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#58a6ff" stroke-width="2.5"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z"/><path d="M12 8v4l3 3"/></svg>
</div>
</div>
<span class="text-[13px] text-[#8b949e]">Processing…</span>
</div>
</div>
<!-- Wave Bars -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 3 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(3)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Wave Bars</p>
<div class="flex items-center gap-3">
<div class="flex items-end gap-0.5 h-6">
{#each [0, 1, 2, 3, 4] as i}
<div class="w-1 rounded-full bg-[#bc8cff]" style="animation:wave 0.9s ease-in-out {i * 0.1}s infinite;height:60%"></div>
{/each}
</div>
<span class="text-[13px] text-[#8b949e]">Analyzing…</span>
</div>
</div>
<!-- Typing Cursor -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 4 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(4)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Typing Cursor</p>
<div class="flex items-center gap-1">
<span class="text-[13px] text-[#8b949e]">Generating response</span>
<span class="inline-block w-0.5 h-4 bg-[#58a6ff] animate-pulse rounded-sm" style="animation-duration:0.7s"></span>
</div>
</div>
<!-- Brain Pulse -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 5 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(5)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Brain Pulse</p>
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-[#58a6ff]/20 to-[#bc8cff]/20 border border-[#58a6ff]/20 flex items-center justify-center animate-pulse">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#58a6ff" stroke-width="1.5"><path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-5 0v-15A2.5 2.5 0 0 1 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 1 17 4.5v15a2.5 2.5 0 0 1-5 0v-15A2.5 2.5 0 0 1 14.5 2z"/><path d="M5 8.5a2.5 2.5 0 1 1 4 2"/><path d="M15 8.5a2.5 2.5 0 1 0-4 2"/></svg>
</div>
<div>
<p class="text-[13px] font-semibold text-[#e6edf3]">Deep reasoning</p>
<p class="text-[11px] text-[#484f58]">Working through the problem…</p>
</div>
</div>
</div>
<!-- Token Stream -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 6 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(6)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Token Stream</p>
<div class="space-y-1.5">
<div class="flex items-center gap-2">
<span class="text-[11px] text-[#484f58] font-mono">tokens/s</span>
<div class="flex-1 h-1 bg-[#21262d] rounded-full overflow-hidden"><div class="h-full bg-green-500 rounded-full animate-tokenbar"></div></div>
<span class="text-[11px] text-green-400 font-mono">~48</span>
</div>
<div class="font-mono text-[12px] text-[#8b949e] overflow-hidden whitespace-nowrap">
{#each "The answer to your question involves ".split("") as ch, i}
<span class="inline-block" style="animation:fade-in 0.05s {i * 0.04}s both">{ch}</span>
{/each}
<span class="inline-block w-0.5 h-3.5 bg-[#58a6ff] align-middle animate-pulse ml-px"></span>
</div>
</div>
</div>
<!-- Step Progress -->
<div class="bg-[#161b22] border rounded-xl px-5 py-4 cursor-pointer transition-colors {active === 7 ? 'border-[#58a6ff]/40' : 'border-[#30363d] hover:border-[#8b949e]/40'}" on:click={() => toggle(7)}>
<p class="text-[10px] font-bold text-[#484f58] uppercase tracking-wider mb-3">Step Progress</p>
<div class="space-y-1.5">
{#each ["Reading context", "Planning response", "Generating", "Reviewing"] as step, i}
<div class="flex items-center gap-2">
<div class="w-4 h-4 rounded-full flex items-center justify-center flex-shrink-0" style="animation:step-reveal 0.3s {i * 0.8}s both;opacity:0">
{#if i < 2}
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#7ee787" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
{:else if i === 2}
<div class="w-3 h-3 rounded-full border-2 border-[#e3b341] border-t-transparent animate-spin"></div>
{:else}
<div class="w-2 h-2 rounded-full bg-[#484f58]"></div>
{/if}
</div>
<span class="text-[12px] {i < 2 ? 'text-[#7ee787] line-through' : i === 2 ? 'text-[#e3b341]' : 'text-[#484f58]'}" style="animation:step-reveal 0.3s {i * 0.8}s both;opacity:0">{step}</span>
</div>
{/each}
</div>
</div>
</div>
</div>
</div>
<style>
@keyframes shimmer { 0% { background-position: 100% 0 } 100% { background-position: -100% 0 } }
.animate-shimmer { animation: shimmer 1.4s ease-in-out infinite; }
@keyframes wave { 0%, 100% { transform: scaleY(0.4) } 50% { transform: scaleY(1.2) } }
@keyframes tokenbar { 0% { width: 0 } 70% { width: 100% } 100% { width: 100% } }
.animate-tokenbar { animation: tokenbar 2s ease-in-out infinite; }
@keyframes fade-in { from { opacity: 0 } to { opacity: 1 } }
@keyframes step-reveal { from { opacity: 0; transform: translateX(-8px) } to { opacity: 1; transform: none } }
</style>Five CSS-only “thinking” loader variants for AI chat UIs: bouncing dots, shimmer skeleton bar, rotating orbit ring, wave bars, and a shimmer text effect. All pure CSS animations, zero JS.