Polarlicht
Weiche Lichtschleier in der Akzentfarbe, die langsam ziehen. Hell zurückhaltend, dunkel leuchtend.
Eine von 6 Components in Backgrounds
Variants
Details
Was Polarlicht mitbringt, aus Registry und Quellcode gelesen.
- Kategorie
- Backgrounds
- Bereich
- Elements
- Variants
- 3
- Version
- 1.0.0 · seit 1.0.0
- Datei
- components/ui/aurora.tsx
- Bringt mit
- utilskit
- 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 AuroraProps aus components/ui/aurora.tsx. Beschreibungen stammen aus den Kommentaren im Code und sind auf Englisch.
intensityoptionalAuroraIntensitysoftmediumvivid
How strong the veils are. Light mode always stays quieter than dark.
placementoptionalAuroraPlacementtopcenterbottom
Where the light hangs: from the top edge, behind the middle or rising from the bottom
speedoptionalnumberSeconds for one slow sway of the veils, default 22
parallaxoptionalnumberPixels the veils drift against the page over a full scroll pass, a quiet depth. Default 36, 0 is off.
Barrierefreiheit
Aus dem Quellcode der Component abgeleitet.
- Der Server rendert den Startzustand vollständig, JavaScript ergänzt nur die Interaktion.
Bewegung
- Bewegt sich mit dem Scrollen, der Fortschritt folgt der Position auf der Seite.
- Bringt eigene Animationen mit, ruhig getaktet und ohne Wackeln.
- Bei reduzierter Bewegung (prefers-reduced-motion) bleibt sie ruhig.
Quellcode
Die Datei, die aiw add in dein Projekt legt. Sie gehört danach dir.
Datei anzeigen, 174 Zeilen
'use client';
import { useEffect, useRef } from 'react';
import { cn, prefersReducedMotion } from '@/lib/kit';
export type AuroraIntensity = 'soft' | 'medium' | 'vivid';
export type AuroraPlacement = 'top' | 'center' | 'bottom';
export interface AuroraProps {
/** How strong the veils are. Light mode always stays quieter than dark. */
intensity?: AuroraIntensity;
/** Where the light hangs: from the top edge, behind the middle or rising from the bottom */
placement?: AuroraPlacement;
/** Seconds for one slow sway of the veils, default 22 */
speed?: number;
/** Pixels the veils drift against the page over a full scroll pass, a quiet depth. Default 36, 0 is off. */
parallax?: number;
className?: string;
}
const CSS = `
@keyframes kitfx-aurora-sway {
0%, 100% { transform: translate3d(-5%, 0, 0) rotate(var(--kitfx-r, 0deg)) scaleY(1); }
50% { transform: translate3d(5%, var(--kitfx-y, 8%), 0) rotate(calc(var(--kitfx-r, 0deg) * -0.6)) scaleY(1.25); }
}
@keyframes kitfx-aurora-breathe { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }
.kitfx-aurora * { animation-play-state: var(--kitfx-play, running); }
@media (prefers-reduced-motion: reduce) { .kitfx-aurora * { animation: none !important; } .kitfx-aurora-depth { transform: none !important; } }
/* Tones. Fallback without relative colors: thin mixes of the accent */
.kitfx-aurora {
--kitfx-a1: color-mix(in oklab, var(--primary) 22%, transparent);
--kitfx-a2: color-mix(in oklab, var(--primary) 11%, transparent);
--kitfx-a3: color-mix(in oklab, var(--primary) 16%, transparent);
}
.dark .kitfx-aurora {
--kitfx-a1: color-mix(in oklab, var(--primary) 50%, transparent);
--kitfx-a2: color-mix(in oklab, var(--primary) 26%, transparent);
--kitfx-a3: color-mix(in oklch, color-mix(in oklab, var(--primary) 70%, var(--foreground)) 36%, transparent);
}
/*
* --kitfx-n says how neutral the accent is: 1 for the grey default, 0 for any
* real color. Grey veils on white look like smudges, so a neutral accent turns
* into a pearl light: mid lightness, a breath of chroma, and the veils fan out
* in hue (lavender, rose, sky). A colored accent keeps its hue and strength.
*/
@supports (color: oklch(from red l c h)) {
.kitfx-aurora {
--kitfx-n: clamp(0, 1 - c / 0.06, 1);
--kitfx-a1: oklch(from var(--primary) calc(l + (0.72 - l) * var(--kitfx-n)) max(c, 0.08) h / calc(0.22 + 0.16 * var(--kitfx-n)));
--kitfx-a2: oklch(from var(--primary) calc(l + (0.78 - l) * var(--kitfx-n)) max(c, 0.075) calc(h + 60 * var(--kitfx-n)) / calc(0.11 + 0.2 * var(--kitfx-n)));
--kitfx-a3: oklch(from var(--primary) calc(l + (0.78 - l) * var(--kitfx-n)) max(c, 0.07) calc(h - 78 * var(--kitfx-n)) / calc(0.16 + 0.17 * var(--kitfx-n)));
}
.dark .kitfx-aurora {
--kitfx-a1: oklch(from var(--primary) calc(l + (0.8 - l) * var(--kitfx-n)) max(c, 0.07) h / 0.5);
--kitfx-a2: oklch(from var(--primary) calc(l + (0.76 - l) * var(--kitfx-n)) max(c, 0.07) calc(h + 60 * var(--kitfx-n)) / calc(0.26 + 0.08 * var(--kitfx-n)));
--kitfx-a3: oklch(from var(--primary) calc(l + (0.88 - l) * 0.45) max(c * 0.75, 0.06) calc(h - 78 * var(--kitfx-n)) / 0.38);
}
}
/* In dark mode the veils add light instead of laying a grey film over the page */
.dark .kitfx-aurora { mix-blend-mode: plus-lighter; }
`;
/* Peak strength per intensity, light and dark. Light stays a sheen, never a cloud. */
const INTENSITY: Record<AuroraIntensity, string> = {
soft: 'opacity-[0.55] dark:opacity-[0.55]',
medium: 'opacity-[0.8] dark:opacity-[0.78]',
vivid: 'opacity-[0.9] dark:opacity-[0.88]',
};
interface Ribbon { at: number; h: number; w: number; x: number; r: number; tone: 'a1' | 'a2' | 'a3'; blur: number; t: number; y: number }
/* The veils, measured from the anchored edge (top, middle or bottom) */
const RIBBONS: Ribbon[] = [
{ at: 4, h: 34, w: 120, x: -14, r: -7, tone: 'a2', blur: 46, t: 1, y: 10 },
{ at: 16, h: 18, w: 96, x: -4, r: -4, tone: 'a1', blur: 30, t: 1.35, y: -8 },
{ at: 10, h: 26, w: 80, x: 34, r: 6, tone: 'a3', blur: 40, t: 1.7, y: 12 },
{ at: 26, h: 10, w: 70, x: 10, r: -3, tone: 'a1', blur: 18, t: 1.15, y: 6 },
];
/* Fine vertical rays, like the curtain of a real aurora */
const RAYS = 'repeating-linear-gradient(90deg, #000 0px, rgb(0 0 0 / 0.66) 13px, #000 27px, rgb(0 0 0 / 0.8) 38px, #000 50px, rgb(0 0 0 / 0.58) 61px, #000 74px)';
const EDGE: Record<AuroraPlacement, string> = {
top: 'radial-gradient(ellipse 80% 90% at 50% 0%, #000 30%, transparent 78%)',
center: 'radial-gradient(ellipse 72% 52% at 50% 50%, #000 40%, transparent 100%), radial-gradient(ellipse 34% 30% at 50% 50%, rgb(0 0 0 / 0.22), #000)',
bottom: 'radial-gradient(ellipse 80% 90% at 50% 100%, #000 30%, transparent 78%)',
};
/**
* Scroll pass. While `on`, writes --kitfx-p on the element: 0 when its top
* meets the bottom of the window, 1 when its bottom leaves at the top. Never
* with reduced motion, the CSS then falls back to the resting value.
*/
function useScrollPass(ref: { current: HTMLElement | null }, on: boolean) {
useEffect(() => {
const el = ref.current;
if (!el || !on || prefersReducedMotion()) return;
let frame = 0;
const update = () => {
frame = 0;
const r = el.getBoundingClientRect();
const p = Math.min(1, Math.max(0, (window.innerHeight - r.top) / (r.height + window.innerHeight)));
el.style.setProperty('--kitfx-p', p.toFixed(4));
};
const onScroll = () => {
if (!frame) frame = requestAnimationFrame(update);
};
update();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll);
return () => {
cancelAnimationFrame(frame);
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
el.style.removeProperty('--kitfx-p');
};
}, [ref, on]);
}
/**
* Soft moving veils of light in the accent color. Fills its parent
* (give the parent `relative isolate overflow-hidden`, content after it with
* `relative`). Decorative, pauses out of view, still image with reduced motion.
*/
export function Aurora({ intensity = 'medium', placement = 'top', speed = 22, parallax = 36, className }: AuroraProps) {
const ref = useRef<HTMLDivElement>(null);
useScrollPass(ref, parallax !== 0);
useEffect(() => {
const el = ref.current;
if (!el || typeof IntersectionObserver === 'undefined') return;
const io = new IntersectionObserver(([e]) => el.style.setProperty('--kitfx-play', e.isIntersecting ? 'running' : 'paused'));
io.observe(el);
return () => io.disconnect();
}, []);
const edge = EDGE[placement];
return (
<div
ref={ref}
aria-hidden
data-clip-ok
className={cn('kitfx-aurora pointer-events-none absolute inset-0 overflow-hidden', INTENSITY[intensity], placement === 'center' && 'max-sm:opacity-[0.6] max-sm:dark:opacity-[0.55]', className)}
style={{ maskImage: edge, WebkitMaskImage: edge, maskComposite: 'intersect', WebkitMaskComposite: 'source-in' }}
>
<style dangerouslySetInnerHTML={{ __html: CSS }} />
{/* The veils sit a little deeper than the page: they drift against the scroll */}
<div className="kitfx-aurora-depth absolute inset-0" style={parallax ? { transform: `translate3d(0, calc((var(--kitfx-p, 0.5) - 0.5) * ${parallax}px), 0)` } : undefined}>
<div className="absolute inset-x-[-10%] inset-y-0" style={{ maskImage: RAYS, WebkitMaskImage: RAYS, transform: 'skewX(-12deg)' }}>
{RIBBONS.map((r, i) => {
const at = placement === 'center' ? { top: `${18 + r.at * 1.3}%` } : placement === 'bottom' ? { bottom: `${r.at}%` } : { top: `${r.at}%` };
return (
<div
key={i}
className="absolute rounded-[50%]"
style={{
...at,
left: `${r.x}%`,
width: `${r.w}%`,
height: `${r.h}%`,
background: `linear-gradient(90deg, transparent, var(--kitfx-${r.tone}) 28%, var(--kitfx-${r.tone}) 62%, transparent)`,
filter: `blur(${r.blur}px)`,
['--kitfx-r' as string]: `${r.r}deg`,
['--kitfx-y' as string]: `${placement === 'bottom' ? -r.y : r.y}%`,
transform: `rotate(${r.r}deg)`,
animation: `kitfx-aurora-sway ${Math.round(speed * r.t)}s ease-in-out ${-i * 5}s infinite, kitfx-aurora-breathe ${Math.round(speed * r.t * 0.8)}s ease-in-out ${-i * 3}s infinite`,
}}
/>
);
})}
</div>
</div>
</div>
);
}Häufige Fragen
Wie installiere ich Polarlicht?
Mit aiw add @ai-website/aurora. Der Befehl legt die Datei components/ui/aurora.tsx in dein Projekt, der Code gehört danach dir. Weitere npm-Pakete braucht sie nicht.
Welche Varianten hat Polarlicht?
3 Varianten, Sanft von oben, Kräftig in der Mitte und Horizont.
Kann ein KI-Assistent Polarlicht einbauen?
Ja. Über den MCP-Server der aiw CLI finden Claude, Cursor, GitHub Copilot, Windsurf, Codex und Gemini Polarlicht mit aiw_components und installieren sie mit aiw_add @ai-website/aurora.