3D-Punktkugel
Eine drehende Kugel aus gleichmäßig verteilten Punkten, mit WebGL berechnet. Helle Vorderseite, schwache Rückseite, einige Punkte leuchten im Akzent, ein Lichtkranz umgibt sie. Neigt sich mit dem Zeiger.
Ein Globus aus Lichtpunkten, der sich weiterdreht
Eine Punktkugel dreht einen Globus aus gleichmäßig verteilten Punkten hinter oder neben deinem Text, mit WebGL per Raytracing gezeichnet, mit Akzentfunken und weichem Halo. Sie sagt global, verbunden oder Netzwerk, ohne Karte und ohne ein einziges Bild, das laden muss.
„Hero mit Kugel daneben“ setzt den Text links und die drehende Kugel rechts, am Telefon liegt sie unter dem Text. Der „Horizont“ lässt eine große Kugel wie einen Planeten von der Unterkante aufsteigen, der Text steht darüber. Die „Hervorgehobene Karte“ lässt eine Kugel oben in die Ecke der Karte ragen. „Hinter einem Call to Action“ rahmt die Abschlussfläche mit einer großen, ruhigen Kugel, deren Rand um den Text leuchtet.
Nimm die geteilte Fassung bei langem Text und den Horizont bei einer kurzen, starken Überschrift. Die Funken übernehmen deine Akzentfarbe, halte weitere Akzente auf demselben Bildschirm deshalb sparsam.
Details
Was Punktkugel mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- 3D-Hintergründe
- Bereich
- Visuals
- Variants
- 4
- Version
- 1.0.0 · seit 1.2.0
- Datei
- components/ui/dot-sphere.tsx
- Bringt mit
- utilskitwebgl
- npm-Pakete
- Keine
- Sprachen
- Texte kommen über Props
- Studio
- Im Code anpassen
- Mit KI bauen
- Per MCP in Claude, Cursor, Copilot und Codex
Props
Die Schnittstelle DotSphereProps aus components/ui/dot-sphere.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch. Nimmt außerdem alle Props von ShaderLayerProps an.
placementoptionalDotSpherePlacementcenterrighthorizoncorner
center behind the copy, right next to it (from 900 px box width, below the copy on narrower boxes), horizon rising from the bottom edge, or corner peeking in at the top right (for cards)
sizeoptionalnumberSize, 1 is the designed size
ringsoptionalnumberRings of dots from pole to pole, default 30
sparksoptionalnumberShare of dots that glow in the accent, 0 to 0.5, default 0.12
halooptionalnumberStrength of the halo, default 1
Barrierefreiheit
Aus dem Quellcode der Component abgeleitet.
- Kommt ohne Client-State aus. Der Server schreibt sie vollständig, sie ist auch ohne JavaScript lesbar.
Bewegung
- Ohne Animationen, sie steht sofort ruhig da.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 143 Zeilen
'use client';
import { cn } from '@/lib/kit';
import { GLSL_NOISE, ShaderBackdrop, ShaderCanvas, type ShaderLayerProps } from '@/components/ui/webgl';
/*
* A sphere made of evenly spaced dots, ray traced per pixel. Rings of dots
* run from pole to pole, each ring with as many dots as fit its circumference,
* so the spacing stays even. Front dots are lit, back dots shine through
* faintly, a few dots glow in the accent and a halo surrounds the sphere. The
* pointer tilts it, the scroll turns it further. Abstract on purpose: no land,
* no map, no arcs (that is the Globe).
*/
export type DotSpherePlacement = 'center' | 'right' | 'horizon' | 'corner';
export interface DotSphereProps extends ShaderLayerProps {
/** center behind the copy, right next to it (from 900 px box width, below the copy on narrower boxes), horizon rising from the bottom edge, or corner peeking in at the top right (for cards) */
placement?: DotSpherePlacement;
/** Size, 1 is the designed size */
size?: number;
/** Rings of dots from pole to pole, default 30 */
rings?: number;
/** Share of dots that glow in the accent, 0 to 0.5, default 0.12 */
sparks?: number;
/** Strength of the halo, default 1 */
halo?: number;
}
const PLACE: Record<DotSpherePlacement, number> = { center: 0, right: 1, horizon: 2, corner: 3 };
const FRAG = `${GLSL_NOISE}
uniform float u_place;uniform float u_size;uniform float u_rings;uniform float u_sparks;uniform float u_halo;
const float PI=3.14159265;
vec3 dots(vec3 n,float aa,float back){
float N=u_rings;
float lat=asin(clamp(n.y,-1.,1.));float lon=atan(n.z,n.x);
float ri=floor((lat/PI+.5)*N+.5);float la=(ri/N-.5)*PI;
float cnt=max(1.,floor(2.*N*cos(la)+.5));
float li=floor((lon/(2.*PI)+.5)*cnt+.5);float lo=(li/cnt-.5)*2.*PI;
vec3 dc=vec3(cos(la)*cos(lo),sin(la),cos(la)*sin(lo));
float ang=length(n-dc);
float rr=PI/N*.21;
float m=smoothstep(rr+aa,rr-aa,ang);
float h=hash(vec2(ri,mod(li,cnt))+3.1);
float spark=step(h,u_sparks)*(.55+.45*sin(u_time*1.6+h*90.));
return vec3(m,m*spark,back);
}
void main(){
float m=min(u_res.x,u_res.y);
vec2 p=(gl_FragCoord.xy-.5*u_res)/m;
float hw=.5*u_res.x/m,hh=.5*u_res.y/m;
vec2 c=vec2(0.);float R=.34;
if(u_place>.5&&u_place<1.5){if(u_res.x/u_dpr>=900.){c=vec2(hw*.5,0.);R=.36;}else{R=min(.36,150.*u_dpr/m);c=vec2(0.,-hh+R+.07);}}
if(u_place>1.5&&u_place<2.5){R=max(hw*1.05,.95);c=vec2(0.,-hh-R*.62);}
if(u_place>2.5){R=min(min(.42,hh*.62),100.*u_dpr/m);c=vec2(hw-R*.72,hh-R*.68);}
R*=u_size;
vec2 d=p-c;float r2=dot(d,d);float len=sqrt(r2);
float spin=u_time*.12+u_scroll*1.4+(u_mouse.x-.5)*.7*u_hover;
float tilt=-.38+(u_mouse.y-.5)*.5*u_hover;
float cs=cos(spin),sn=sin(spin),ct=cos(tilt),st=sin(tilt);
vec3 col=vec3(0.);float al=0.;
vec3 base=mix(u_c3,u_c0,(1.-u_dark)*.45);vec3 acc=u_c0;
if(len<R){
float z=sqrt(R*R-r2);float face=z/R;
float aa=1.3/(R*m)/max(face,.18);
for(int k=0;k<2;k++){
float s=k==0?1.:-1.;
vec3 n=vec3(d,s*z)/R;
n=vec3(n.x,n.y*ct-n.z*st,n.y*st+n.z*ct);
n=vec3(n.x*cs-n.z*sn,n.y,n.x*sn+n.z*cs);
vec3 g=dots(n,aa,float(k));
float lit=(k==0?(.32+.68*face):.16*(1.-face*.4))*mix(.62,1.,u_dark);
vec3 dc=mix(base,acc,min(1.,g.y*1.4));
float a=g.x*lit*(1.+g.y*.8);
col+=dc*a;al+=a;
}
float body=.06*face*u_dark;col+=acc*body*.6;al+=body;
}
float edge=len-R;
float halo=u_halo*(exp(-max(edge,0.)*7.)*.5*step(0.,edge)+exp(-abs(edge)*60.)*.35);
halo*=mix(.55,1.,u_dark);
col+=acc*halo;al+=halo;
float k=mix(.85,1.,u_dark)*u_intensity;
al=clamp(al*k,0.,1.);
gl_FragColor=vec4(min(col*k,vec3(1.)),al);
}`;
/* Still picture: a disc of dots with a soft halo, placed like the live sphere */
const SPOT: Record<DotSpherePlacement, string> = {
center: 'left-1/2 top-1/2 aspect-square w-[min(68%,68vh)] -translate-x-1/2 -translate-y-1/2',
right: 'max-lg:left-1/2 max-lg:top-[62%] max-lg:aspect-square max-lg:w-[min(80%,48vh)] max-lg:-translate-x-1/2 lg:right-[6%] lg:top-1/2 lg:aspect-square lg:w-[min(42%,72vh)] lg:-translate-y-1/2',
horizon: 'left-1/2 top-[64%] aspect-square w-[max(110%,62rem)] -translate-x-1/2',
corner: 'right-0 top-0 aspect-square h-[62%] translate-x-[28%] -translate-y-[32%]',
};
function Still({ placement }: { placement: DotSpherePlacement }) {
const dots = 'radial-gradient(circle, color-mix(in oklab, var(--primary) 45%, var(--foreground)) 0 1.6px, transparent 2.2px) 0 0 / 14px 14px';
return (
<div className={cn('absolute rounded-full', SPOT[placement])} style={{ boxShadow: '0 0 80px 6px color-mix(in oklab, var(--primary) 30%, transparent)' }}>
<div
className="absolute inset-0 rounded-full"
style={{
background: dots,
maskImage: 'radial-gradient(circle at 42% 38%, #000 0%, rgb(0 0 0 / 0.55) 55%, rgb(0 0 0 / 0.2) 71%, transparent 71.5%)',
WebkitMaskImage: 'radial-gradient(circle at 42% 38%, #000 0%, rgb(0 0 0 / 0.55) 55%, rgb(0 0 0 / 0.2) 71%, transparent 71.5%)',
}}
/>
</div>
);
}
/**
* A turning sphere of dots behind or beside any content. With children it
* renders a box, without children only the layer for a `relative` parent.
*/
export function DotSphere({ intensity = 1, speed = 1, colors, interactive = true, placement = 'center', size = 1, rings = 30, sparks = 0.12, halo = 1, fallback, scrim = 'none', scrimStrength, scrimColor, children, className, contentClassName, layerClassName }: DotSphereProps) {
return (
<ShaderBackdrop
scrim={scrim}
scrimStrength={scrimStrength}
scrimColor={scrimColor}
className={className}
contentClassName={contentClassName}
layer={
<ShaderCanvas
fragment={FRAG}
colors={colors ?? ['var(--primary)', 'var(--primary)', 'var(--primary)', 'color-mix(in oklab, var(--primary) 28%, var(--foreground))']}
uniforms={{ u_place: PLACE[placement], u_size: size, u_rings: Math.max(8, Math.min(60, Math.round(rings))), u_sparks: Math.min(0.5, Math.max(0, sparks)), u_halo: halo }}
speed={speed}
intensity={intensity}
maxDpr={2}
phoneDpr={1.5}
pointer={interactive}
fallback={fallback ?? <Still placement={placement} />}
className={layerClassName}
/>
}
>
{children}
</ShaderBackdrop>
);
}Häufige Fragen
Wie installiere ich Punktkugel?
Mit aiw add @ai-website/dot-sphere. Der Befehl legt die Datei components/ui/dot-sphere.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind webgl. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Punktkugel?
4 Varianten, Hero mit Kugel daneben, Horizont, Hervorgehobene Karte und Hinter einem Call to Action.
Kann ein KI-Assistent Punktkugel einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Punktkugel mit aiw_components und installieren sie mit aiw_add @ai-website/dot-sphere.