Dotted Rotating Globe
A dotted-sphere globe that turns slowly under a fixed highlight - pure CSS and inline SVG, no WebGL.
3 frameworksBeginner
A rotating globe ringed by KPI badges that overlap it on desktop and drop below it on phones.
<!--
KPI badges float around a spinning globe. The badges are real content (a <dl>
each) and sit outside the aria-hidden globe, so a screen reader reads the
numbers and skips the decoration. They overlap the globe on wide screens and
drop below it on phones - absolute positioning only kicks in at sm:.
-->
<style>
@keyframes gso-spin { to { transform: rotate(1turn); } }
</style>
<div class="mx-auto w-full max-w-md px-4 py-8">
<div class="relative mx-auto aspect-square w-full max-w-[260px]" aria-hidden="true">
<svg viewBox="0 0 200 200" class="h-full w-full text-emerald-600/55 [animation:gso-spin_34s_linear_infinite] motion-reduce:[animation:none] dark:text-emerald-400/55">
<defs>
<pattern id="gso-dots" width="8" height="8" patternUnits="userSpaceOnUse">
<circle cx="1.6" cy="1.6" r="1.3" fill="currentColor" />
</pattern>
<clipPath id="gso-clip"><circle cx="100" cy="100" r="92" /></clipPath>
</defs>
<g clip-path="url(#gso-clip)">
<rect width="200" height="200" fill="url(#gso-dots)" />
<ellipse cx="100" cy="100" rx="92" ry="20" fill="none" stroke="currentColor" stroke-opacity="0.4" />
<ellipse cx="100" cy="100" rx="46" ry="92" fill="none" stroke="currentColor" stroke-opacity="0.3" />
</g>
<circle cx="100" cy="100" r="92" fill="none" stroke="currentColor" stroke-opacity="0.5" />
</svg>
<div class="pointer-events-none absolute inset-0 rounded-full bg-[radial-gradient(circle_at_34%_30%,rgba(255,255,255,0.5),transparent_55%)]"></div>
</div>
<div class="mt-6 flex flex-wrap justify-center gap-3 sm:mt-0 sm:block">
<dl class="rounded-xl border border-gray-200 bg-white/90 px-3 py-2 text-center shadow-sm backdrop-blur sm:absolute sm:left-2 sm:top-8 dark:border-gray-800 dark:bg-gray-900/90">
<dt class="text-lg font-bold text-gray-900 dark:text-gray-100">99.99%</dt>
<dd class="text-xs text-gray-500 dark:text-gray-400">Uptime</dd>
</dl>
<dl class="rounded-xl border border-gray-200 bg-white/90 px-3 py-2 text-center shadow-sm backdrop-blur sm:absolute sm:right-2 sm:top-16 dark:border-gray-800 dark:bg-gray-900/90">
<dt class="text-lg font-bold text-gray-900 dark:text-gray-100">34</dt>
<dd class="text-xs text-gray-500 dark:text-gray-400">Regions</dd>
</dl>
<dl class="rounded-xl border border-gray-200 bg-white/90 px-3 py-2 text-center shadow-sm backdrop-blur sm:absolute sm:bottom-8 sm:left-8 dark:border-gray-800 dark:bg-gray-900/90">
<dt class="text-lg font-bold text-gray-900 dark:text-gray-100">28ms</dt>
<dd class="text-xs text-gray-500 dark:text-gray-400">p50 latency</dd>
</dl>
</div>
</div>| Prop | Type | Default | Description |
|---|---|---|---|
statsrequired | GlobeStat[] | - | Stats |
className | string | - | Additional classes merged onto the root element. |
Drive the numbers with `stats` of `{ value, label }`; each is a `<dl>` sitting outside the `aria-hidden` globe, so a screen reader reads the metrics and skips the decoration. Absolute anchor positions only apply from `sm:` up - below that the badges wrap in normal flow instead of colliding.