Muster
Hintergrundmuster in den Farben des Themes. Punkte, Raster, Linien, Höhenlinien, Korn, Lichtfelder, Wellen, Raster-Punkte und Ringe, jeweils mit Ton, Ausblendung, Seed und einer sehr langsamen Drift.
Eine von 3 Components in Muster und Formen
Variants
Details
Was Muster mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- Muster und Formen
- Bereich
- Visuals
- Variants
- 8
- Version
- 1.0.0 · seit 1.0.0
- Datei
- components/ui/pattern.tsx
- Bringt mit
- utilskitmotion-scene
- 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 PatternProps aus components/ui/pattern.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.
kindoptionalPatternKinddotsgridlinesdiagonalcrosswavescontoursnoisegradientmeshhalftonerings
What is drawn. Default dots.
sizeoptionalnumberScale in pixels: the tile for dots, grid, lines, diagonal, cross and waves, the line spacing for contours and rings, the dot pitch for halftone and the grain for noise. Each kind has its own default.
toneoptionalPatternTonemutedaccent
muted (default) draws in the foreground at low strength, accent in the accent color
fadeoptionalPatternFadenoneradialtopbottomedges
Where the pattern fades out. top and bottom name the edge that fades. Default none.
seedoptionalnumberPicks positions and shapes for contours, halftone, rings, gradient and mesh
animateoptionalbooleanA very slow drift or shimmer, paused out of view, off with reduced motion
strengthoptionalnumber0 to 1, how strong the lines are. Default 1.
styleoptionalReact.CSSProperties
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
- Bringt eigene Animationen mit, ruhig getaktet und ohne Wackeln.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 431 Zeilen
'use client';
import { useMemo } from 'react';
import { cn, round } from '@/lib/kit';
import { SCENE_CSS, rand, useScenePlay, useSceneId } from '@/components/ui/motion-scene';
/*
* Background patterns, drawn in SVG and CSS with the kit tokens. They fill
* their parent (absolute inset-0) and never take clicks. Every pattern is
* deterministic: the same kind, size and seed give the same picture on the
* server and in the browser.
*
* The optional drift is very slow, moves only transform or opacity, pauses
* out of view and stays off with reduced motion. Tiled patterns move by
* exactly one tile per loop, so the loop has no seam.
*/
export type PatternKind = 'dots' | 'grid' | 'lines' | 'diagonal' | 'cross' | 'waves' | 'contours' | 'noise' | 'gradient' | 'mesh' | 'halftone' | 'rings';
export type PatternTone = 'muted' | 'accent';
export type PatternFade = 'none' | 'radial' | 'top' | 'bottom' | 'edges';
export interface PatternProps {
/** What is drawn. Default dots. */
kind?: PatternKind;
/**
* Scale in pixels: the tile for dots, grid, lines, diagonal, cross and waves,
* the line spacing for contours and rings, the dot pitch for halftone and the
* grain for noise. Each kind has its own default.
*/
size?: number;
/** muted (default) draws in the foreground at low strength, accent in the accent color */
tone?: PatternTone;
/** Where the pattern fades out. top and bottom name the edge that fades. Default none. */
fade?: PatternFade;
/** Picks positions and shapes for contours, halftone, rings, gradient and mesh */
seed?: number;
/** A very slow drift or shimmer, paused out of view, off with reduced motion */
animate?: boolean;
/** 0 to 1, how strong the lines are. Default 1. */
strength?: number;
className?: string;
style?: React.CSSProperties;
}
const SIZE: Record<PatternKind, number> = { dots: 22, grid: 32, lines: 14, diagonal: 12, cross: 28, waves: 16, contours: 24, noise: 2, gradient: 1, mesh: 1, halftone: 14, rings: 34 };
const FADE: Record<PatternFade, React.CSSProperties> = {
none: {},
radial: { maskImage: 'radial-gradient(ellipse 72% 70% at 50% 50%, #000 22%, transparent 80%)' },
top: { maskImage: 'linear-gradient(to top, #000 30%, transparent 96%)' },
bottom: { maskImage: 'linear-gradient(to bottom, #000 30%, transparent 96%)' },
edges: {
maskImage: 'linear-gradient(to right, transparent, #000 16%, #000 84%, transparent), linear-gradient(to bottom, transparent, #000 18%, #000 82%, transparent)',
maskComposite: 'intersect',
WebkitMaskComposite: 'source-in',
} as React.CSSProperties,
};
/** The three ink strengths and the solid ink, as CSS variables on the root */
function inks(tone: PatternTone, strength: number): React.CSSProperties {
const s = Math.max(0, Math.min(1, strength));
const base = tone === 'accent' ? 'var(--primary)' : 'var(--foreground)';
const levels = tone === 'accent' ? [24, 42, 74] : [12, 20, 34];
return {
'--pt-1': `color-mix(in oklab, ${base} ${round(levels[0] * s, 1)}%, transparent)`,
'--pt-2': `color-mix(in oklab, ${base} ${round(levels[1] * s, 1)}%, transparent)`,
'--pt-3': `color-mix(in oklab, ${base} ${round(levels[2] * s, 1)}%, transparent)`,
'--pt-solid': base,
} as React.CSSProperties;
}
/* ------------------------------------------------------------------ */
/* Contour lines: marching squares over a seeded height field */
/* ------------------------------------------------------------------ */
const CW = 1200;
const CH = 800;
function contourPaths(seed: number, levels: number) {
const NX = 48;
const NY = 32;
const sx = CW / NX;
const sy = CH / NY;
const hills = Array.from({ length: 7 }, (_, i) => ({
x: rand(seed, i) * CW,
y: rand(seed, i + 11) * CH,
s: 140 + rand(seed, i + 23) * 260,
h: (rand(seed, i + 37) > 0.28 ? 1 : -0.7) * (0.45 + rand(seed, i + 41) * 0.8),
}));
const ph = rand(seed, 53) * 6.28;
const field = (x: number, y: number) => {
let v = 0.18 * Math.sin(x / 260 + ph) * Math.cos(y / 210 - ph);
for (const k of hills) {
const dx = x - k.x;
const dy = y - k.y;
v += k.h * Math.exp(-(dx * dx + dy * dy) / (2 * k.s * k.s));
}
return v;
};
const v: number[][] = [];
let min = Infinity;
let max = -Infinity;
for (let j = 0; j <= NY; j++) {
v[j] = [];
for (let i = 0; i <= NX; i++) {
const f = field(i * sx, j * sy);
v[j][i] = f;
if (f < min) min = f;
if (f > max) max = f;
}
}
const paths: { d: string; major: boolean }[] = [];
for (let l = 1; l <= levels; l++) {
const t = min + ((max - min) * l) / (levels + 1);
const pts = new Map<string, [number, number]>();
const links = new Map<string, string[]>();
const point = (key: string, p: [number, number]) => {
if (!pts.has(key)) pts.set(key, p);
return key;
};
const lerp = (a: number, b: number) => (t - a) / (b - a);
// edge points: h(i,j) on the top edge of row j between i and i+1, v(i,j) on the left edge of column i between j and j+1
const top = (i: number, j: number) => point(`h${i},${j}`, [(i + lerp(v[j][i], v[j][i + 1])) * sx, j * sy]);
const left = (i: number, j: number) => point(`v${i},${j}`, [i * sx, (j + lerp(v[j][i], v[j + 1][i])) * sy]);
const link = (a: string, b: string) => {
links.set(a, [...(links.get(a) ?? []), b]);
links.set(b, [...(links.get(b) ?? []), a]);
};
for (let j = 0; j < NY; j++) {
for (let i = 0; i < NX; i++) {
const a = v[j][i] > t ? 1 : 0;
const b = v[j][i + 1] > t ? 1 : 0;
const c = v[j + 1][i + 1] > t ? 1 : 0;
const d = v[j + 1][i] > t ? 1 : 0;
const code = a * 8 + b * 4 + c * 2 + d;
if (code === 0 || code === 15) continue;
const T = () => top(i, j);
const R = () => left(i + 1, j);
const B = () => top(i, j + 1);
const L = () => left(i, j);
const centre = (v[j][i] + v[j][i + 1] + v[j + 1][i + 1] + v[j + 1][i]) / 4 > t;
switch (code) {
case 1: case 14: link(L(), B()); break;
case 2: case 13: link(B(), R()); break;
case 3: case 12: link(L(), R()); break;
case 4: case 11: link(T(), R()); break;
case 6: case 9: link(T(), B()); break;
case 7: case 8: link(L(), T()); break;
case 5: if (centre) { link(L(), T()); link(B(), R()); } else { link(L(), B()); link(T(), R()); } break;
case 10: if (centre) { link(T(), R()); link(L(), B()); } else { link(L(), T()); link(B(), R()); } break;
}
}
}
// chain the segments into lines, open ends first
const used = new Set<string>();
const chains: [number, number][][] = [];
const walk = (start: string) => {
const chain: string[] = [start];
used.add(start);
let cur = start;
for (;;) {
const next = (links.get(cur) ?? []).find((n) => !used.has(n));
if (!next) break;
used.add(next);
chain.push(next);
cur = next;
}
const closed = chain.length > 2 && (links.get(cur) ?? []).includes(start);
const out = chain.map((k) => pts.get(k)!);
if (closed) out.push(out[0]);
if (out.length > 3) chains.push(out);
};
for (const [k, n] of links) if (n.length === 1 && !used.has(k)) walk(k);
for (const k of links.keys()) if (!used.has(k)) walk(k);
// smooth through the midpoints, the vertices are the control points
let d = '';
for (const c of chains) {
const m = (p: [number, number], q: [number, number]) => `${Math.round((p[0] + q[0]) / 2)} ${Math.round((p[1] + q[1]) / 2)}`;
d += `M${Math.round(c[0][0])} ${Math.round(c[0][1])}L${m(c[0], c[1])}`;
for (let k = 1; k < c.length - 1; k++) d += `Q${Math.round(c[k][0])} ${Math.round(c[k][1])} ${m(c[k], c[k + 1])}`;
d += `L${Math.round(c[c.length - 1][0])} ${Math.round(c[c.length - 1][1])}`;
}
if (d) paths.push({ d, major: l % 4 === 0 });
}
return paths;
}
/* ------------------------------------------------------------------ */
/* Grain */
/* ------------------------------------------------------------------ */
const grain = (freq: number) =>
`url("data:image/svg+xml;utf8,${encodeURIComponent(
`<svg xmlns='http://www.w3.org/2000/svg' width='240' height='240'><filter id='g' x='0' y='0'><feTurbulence type='fractalNoise' baseFrequency='${freq}' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2.4 -1.1'/></filter><rect width='100%' height='100%' filter='url(%23g)'/></svg>`,
).replace(/%2523/g, '%23')}")`;
/* ------------------------------------------------------------------ */
/* The component */
/* ------------------------------------------------------------------ */
/**
* A background pattern that fills its parent. Put it first inside a
* `relative` container, the content after it.
*
* <div className="relative isolate overflow-hidden">
* <Pattern kind="contours" tone="accent" fade="radial" />
* …
* </div>
*/
export function Pattern({ kind = 'dots', size, tone = 'muted', fade = 'none', seed = 1, animate = false, strength = 1, className, style }: PatternProps) {
const sid = useSceneId('kpt');
const play = useScenePlay<HTMLDivElement>('in-view');
const s = Math.max(1, size ?? SIZE[kind]);
const pid = `${sid}p`;
/* tiled kinds: one tile, the layer is one period larger than its box so the drift never shows an edge */
let period = 0;
let tile: React.ReactNode = null;
let drift: [number, number] = [0, 0];
if (kind === 'dots') {
period = s;
tile = <circle cx={round(s / 2, 2)} cy={round(s / 2, 2)} r={round(Math.max(1.1, s / 16), 2)} style={{ fill: 'var(--pt-3)' }} />;
drift = [s, s];
} else if (kind === 'grid') {
period = s * 4;
tile = (
<>
<path d={[1, 2, 3].map((k) => `M${k * s + 0.5} 0V${period}M0 ${k * s + 0.5}H${period}`).join('')} style={{ stroke: 'var(--pt-1)', fill: 'none' }} shapeRendering="crispEdges" />
<path d={`M0.5 0V${period}M0 0.5H${period}`} style={{ stroke: 'var(--pt-2)', fill: 'none' }} shapeRendering="crispEdges" />
<rect x="-1.5" y="-1.5" width="4" height="4" style={{ fill: 'var(--pt-3)' }} />
</>
);
drift = [period, period];
} else if (kind === 'lines') {
period = s * 6;
tile = (
<>
<path d={[1, 2, 3, 4, 5].map((k) => `M0 ${k * s + 0.5}H${period}`).join('')} style={{ stroke: 'var(--pt-1)' }} shapeRendering="crispEdges" />
<path d={`M0 0.5H${period}`} style={{ stroke: 'var(--pt-2)' }} shapeRendering="crispEdges" />
</>
);
drift = [0, period];
} else if (kind === 'diagonal') {
period = s;
tile = <path d={`M${round(s / 2, 2)} -1V${s + 1}`} style={{ stroke: 'var(--pt-2)', strokeWidth: 1 }} />;
drift = [round(s * Math.SQRT2, 3), 0];
} else if (kind === 'cross') {
period = s;
const c = round(s / 2, 2);
const a = round(Math.max(2.5, s / 7), 2);
tile = <path d={`M${round(c - a, 2)} ${c}H${round(c + a, 2)}M${c} ${round(c - a, 2)}V${round(c + a, 2)}`} style={{ stroke: 'var(--pt-3)', strokeWidth: 1.2, strokeLinecap: 'round', fill: 'none' }} />;
drift = [s, s];
} else if (kind === 'waves') {
period = s * 4;
const y = round(s / 2, 2);
const a = round(s * 0.62, 2);
tile = <path d={`M0 ${y}Q${s} ${round(y - a, 2)} ${s * 2} ${y}T${period} ${y}`} style={{ stroke: 'var(--pt-2)', strokeWidth: 1.1, fill: 'none' }} />;
drift = [period, 0];
}
const levels = Math.max(5, Math.min(32, Math.round(340 / s)));
const contours = useMemo(() => (kind === 'contours' ? contourPaths(seed, levels) : []), [kind, seed, levels]);
const on = animate ? play.state : undefined;
const css: string[] = [];
let body: React.ReactNode = null;
if (period) {
const w = period;
const h = kind === 'waves' ? s : period;
const patternTransform = kind === 'diagonal' ? 'rotate(45)' : undefined;
const pad = Math.ceil(Math.max(Math.abs(drift[0]), Math.abs(drift[1]), w, h)) + 2;
css.push(`@keyframes ${sid}d{from{transform:translate3d(0,0,0)}to{transform:translate3d(${drift[0]}px,${drift[1]}px,0)}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}m{animation:${sid}d ${round(Math.max(18, (Math.hypot(drift[0], drift[1]) / 2.2) * 1), 1)}s linear infinite}`);
body = (
<svg className={`${sid}m absolute`} style={{ inset: -pad, width: `calc(100% + ${pad * 2}px)`, height: `calc(100% + ${pad * 2}px)` }}>
<defs>
<pattern id={pid} width={w} height={h} patternUnits="userSpaceOnUse" patternTransform={patternTransform}>
{tile}
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${pid})`} />
</svg>
);
} else if (kind === 'contours') {
css.push(`@keyframes ${sid}d{0%{transform:scale(1.06) translate3d(0,0,0)}50%{transform:scale(1.1) translate3d(-1.2%,0.8%,0)}100%{transform:scale(1.06) translate3d(0,0,0)}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}m{animation:${sid}d 60s ease-in-out infinite}`);
css.push(`@keyframes ${sid}s{0%,100%{opacity:.55}50%{opacity:1}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}x{animation:${sid}s 9s ease-in-out infinite}`);
body = (
<svg viewBox={`0 0 ${CW} ${CH}`} preserveAspectRatio="xMidYMid slice" className={`${sid}m absolute inset-0 size-full`} style={{ transform: 'scale(1.06)' }}>
<g fill="none" strokeLinecap="round" strokeLinejoin="round">
{contours.map((p, i) => (
<path key={i} d={p.d} className={p.major ? `${sid}x` : undefined} style={{ stroke: p.major ? 'var(--pt-3)' : 'var(--pt-2)', strokeWidth: p.major ? 1.5 : 1, animationDelay: p.major ? `${-i * 0.7}s` : undefined }} vectorEffect="non-scaling-stroke" />
))}
</g>
</svg>
);
} else if (kind === 'rings') {
const cx = round(CW * (0.35 + rand(seed, 1) * 0.4));
const cy = round(CH * (0.3 + rand(seed, 2) * 0.4));
const step = s * 1.4;
const count = Math.ceil(Math.hypot(CW, CH) / step);
css.push(`@keyframes ${sid}r{0%,100%{opacity:.45}30%{opacity:1}60%{opacity:.45}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}c{animation:${sid}r 7s ease-in-out infinite;animation-delay:calc(var(--i) * 0.22s)}`);
body = (
<svg viewBox={`0 0 ${CW} ${CH}`} preserveAspectRatio="xMidYMid slice" className="absolute inset-0 size-full">
<g fill="none">
{Array.from({ length: count }, (_, i) => {
const major = i % 4 === 3;
const dashed = !major && rand(seed, i + 7) > 0.72;
return (
<circle
key={i}
cx={cx}
cy={cy}
r={round(step * (i + 1), 1)}
className={`${sid}c`}
style={{ '--i': i, stroke: major ? 'var(--pt-3)' : 'var(--pt-2)', strokeWidth: major ? 1.4 : 1, strokeDasharray: dashed ? '2 7' : undefined } as React.CSSProperties}
vectorEffect="non-scaling-stroke"
/>
);
})}
<circle cx={cx} cy={cy} r="4" style={{ fill: 'var(--pt-3)' }} />
</g>
</svg>
);
} else if (kind === 'halftone') {
// soft dots, weighted by a light field, then cut at half alpha: the dots grow where the field is strong
// the light sits toward one side, so text in the middle stays on small dots
const fx = round(rand(seed, 3) < 0.5 ? 0.1 + rand(seed, 5) * 0.2 : 0.7 + rand(seed, 5) * 0.2, 3);
const fy = round(0.2 + rand(seed, 4) * 0.6, 3);
const p = s;
css.push(`@keyframes ${sid}h{0%,100%{transform:translate3d(0,0,0)}50%{transform:translate3d(9%,-7%,0)}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}m{animation:${sid}h 34s ease-in-out infinite;transform-box:view-box}`);
body = (
<svg className="absolute inset-0 size-full" style={{ opacity: tone === 'accent' ? round(0.78 * strength, 2) : round(0.3 * strength, 2) }}>
<defs>
<radialGradient id={`${pid}d`}>
<stop offset="0" style={{ stopColor: 'var(--pt-solid)' }} />
<stop offset="1" style={{ stopColor: 'var(--pt-solid)', stopOpacity: 0 }} />
</radialGradient>
<pattern id={pid} width={p} height={p} patternUnits="userSpaceOnUse">
<circle cx={p / 2} cy={p / 2} r={round(p * 0.7, 2)} fill={`url(#${pid}d)`} />
</pattern>
<radialGradient id={`${pid}l`} cx={fx} cy={fy} r="0.72">
<stop offset="0" stopColor="#fff" />
<stop offset="0.55" stopColor="#fff" stopOpacity="0.62" />
<stop offset="1" stopColor="#fff" stopOpacity="0" />
</radialGradient>
<mask id={`${pid}m`} maskUnits="userSpaceOnUse" x="0" y="0" width="100%" height="100%">
<rect className={`${sid}m`} x="-20%" y="-20%" width="140%" height="140%" fill={`url(#${pid}l)`} />
</mask>
<filter id={`${pid}f`} x="0" y="0" width="1" height="1" colorInterpolationFilters="sRGB">
<feComponentTransfer>
<feFuncA type="linear" slope="16" intercept="-7.4" />
</feComponentTransfer>
</filter>
</defs>
<g filter={`url(#${pid}f)`}>
<g mask={`url(#${pid}m)`}>
<rect width="100%" height="100%" fill={`url(#${pid})`} />
</g>
</g>
</svg>
);
} else if (kind === 'noise') {
const freq = round(Math.min(1.2, Math.max(0.25, 1.8 / s)), 3);
css.push(`@keyframes ${sid}d{from{transform:translate3d(0,0,0)}to{transform:translate3d(240px,240px,0)}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}m{animation:${sid}d 40s steps(48) infinite}`);
const url = grain(freq);
body = (
<div
className={`${sid}m absolute`}
style={{ inset: -242, background: 'var(--pt-solid)', opacity: tone === 'accent' ? round(0.5 * strength, 2) : round(0.2 * strength, 2), maskImage: url, WebkitMaskImage: url, maskSize: '240px 240px', WebkitMaskSize: '240px 240px' }}
/>
);
} else if (kind === 'gradient') {
const fields = [
{ c: tone === 'accent' ? 'color-mix(in oklab, var(--primary) 34%, transparent)' : 'color-mix(in oklab, var(--foreground) 11%, transparent)', w: 70 },
{ c: tone === 'accent' ? 'color-mix(in oklab, var(--primary) 22%, color-mix(in oklab, var(--info) 24%, transparent))' : 'color-mix(in oklab, var(--foreground) 7%, transparent)', w: 58 },
{ c: tone === 'accent' ? 'color-mix(in oklab, var(--primary) 14%, color-mix(in oklab, var(--foreground) 6%, transparent))' : 'color-mix(in oklab, var(--foreground) 9%, transparent)', w: 50 },
{ c: tone === 'accent' ? 'color-mix(in oklab, var(--primary) 20%, transparent)' : 'color-mix(in oklab, var(--foreground) 5%, transparent)', w: 44 },
];
fields.forEach((_, i) => {
css.push(`@keyframes ${sid}g${i}{0%,100%{transform:translate3d(0,0,0) scale(1)}50%{transform:translate3d(${round((rand(seed, i + 60) - 0.5) * 16, 1)}%,${round((rand(seed, i + 70) - 0.5) * 14, 1)}%,0) scale(${round(1.04 + rand(seed, i + 80) * 0.08, 3)})}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}g${i}{animation:${sid}g${i} ${round(22 + i * 5 + rand(seed, i + 90) * 6, 1)}s ease-in-out infinite}`);
});
body = fields.map((f, i) => {
const x = round(rand(seed, i * 3 + 1) * 90 - 10, 1);
const y = round(rand(seed, i * 3 + 2) * 80 - 15, 1);
return (
<div
key={i}
className={`${sid}g${i} absolute aspect-square rounded-full`}
style={{ left: `${round(x - f.w / 2, 1)}%`, top: `${round(y - f.w / 2, 1)}%`, width: `${f.w}%`, background: `radial-gradient(closest-side, ${f.c}, transparent)` }}
/>
);
});
} else if (kind === 'mesh') {
const at = (i: number) => `${round(rand(seed, i * 2 + 101) * 100)}% ${round(rand(seed, i * 2 + 102) * 100)}%`;
const colors =
tone === 'accent'
? ['color-mix(in oklab, var(--primary) 78%, var(--background))', 'color-mix(in oklch, var(--primary) 55%, var(--info))', 'color-mix(in oklab, var(--primary) 30%, var(--background))', 'color-mix(in oklch, var(--primary) 60%, var(--success) 40%)', 'color-mix(in oklab, var(--primary) 45%, var(--foreground) 20%)']
: ['color-mix(in oklab, var(--foreground) 16%, var(--background))', 'color-mix(in oklab, var(--foreground) 6%, var(--background))', 'color-mix(in oklab, var(--foreground) 24%, var(--background))', 'color-mix(in oklab, var(--foreground) 10%, var(--background))', 'color-mix(in oklab, var(--foreground) 3%, var(--background))'];
const layers = colors.map((c, i) => `radial-gradient(${['60% 70%', '55% 60%', '70% 55%', '50% 65%', '65% 60%'][i]} at ${at(i)}, ${c}, transparent)`).join(',');
css.push(`@keyframes ${sid}d{0%,100%{transform:rotate(0deg) scale(1.15)}50%{transform:rotate(${round(4 + rand(seed, 9) * 6, 1)}deg) scale(1.24)}}`);
css.push(`.${sid}:not([data-kv="off"]) .${sid}m{animation:${sid}d 48s ease-in-out infinite}`);
const url = grain(0.85);
body = (
<>
<div className={`${sid}m absolute inset-0`} style={{ backgroundColor: 'var(--surface)', backgroundImage: layers, transform: 'scale(1.15)', opacity: round(Math.max(0.2, strength), 2) }} />
<div className="absolute inset-0" style={{ background: 'var(--foreground)', opacity: 0.07, maskImage: url, WebkitMaskImage: url, maskSize: '240px', WebkitMaskSize: '240px' }} />
</>
);
}
return (
<div
ref={play.ref}
aria-hidden
data-kv={on}
data-clip-ok
className={cn(sid, 'pointer-events-none absolute inset-0 -z-10 overflow-hidden', className)}
style={{ ...inks(tone, strength), ...FADE[fade], WebkitMaskImage: FADE[fade].maskImage, ...style }}
>
{animate && <style dangerouslySetInnerHTML={{ __html: SCENE_CSS + css.join('\n') }} />}
{body}
</div>
);
}Häufige Fragen
Wie installiere ich Muster?
Mit aiw add @ai-website/pattern. Der Befehl legt die Datei components/ui/pattern.tsx in dein Projekt, der Code gehört danach dir. Mit dabei sind motion-scene. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Muster?
8 Varianten, Punkte, Raster, Linien, Höhenlinien, Korn, Lichtfelder und Mesh, Wellen und Rasterpunkte.
Kann ein KI-Assistent Muster einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Muster mit aiw_components und installieren sie mit aiw_add @ai-website/pattern.