feat: add @sda/flair-elements-ui library
Angular 19+ standalone library with 61 components, 15 directives, and 8 services for decorative visual effects, animations, and micro-interactions. Components include particle systems, flow fields, aurora effects, tilt/holographic/spotlight cards, text effects, dividers, scroll animations, generative art, celebrations, image treatments, morphing shapes, terminal output, and pattern textures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
20
src/components/fl-aurora/fl-aurora.component.scss
Normal file
20
src/components/fl-aurora/fl-aurora.component.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
::ng-deep > :not(svg) {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
157
src/components/fl-aurora/fl-aurora.component.ts
Normal file
157
src/components/fl-aurora/fl-aurora.component.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
Component, ChangeDetectionStrategy, input, output, inject,
|
||||
viewChild, ElementRef, afterNextRender, DestroyRef,
|
||||
} from '@angular/core';
|
||||
import { ReducedMotionService } from '../../services/reduced-motion.service';
|
||||
import { AnimationLoopService } from '../../services/animation-loop.service';
|
||||
import { createSvgElement, generateWavePath } from '../../utils/svg.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'fl-aurora',
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<svg #svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<filter id="aurora-blur" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feGaussianBlur stdDeviation="40" />
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
<ng-content />
|
||||
`,
|
||||
styleUrl: './fl-aurora.component.scss',
|
||||
host: { 'class': 'fl-aurora' },
|
||||
})
|
||||
export class FlAuroraComponent {
|
||||
private reducedMotion = inject(ReducedMotionService);
|
||||
private animationLoop = inject(AnimationLoopService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
protected svgRef = viewChild.required<ElementRef<SVGElement>>('svg');
|
||||
|
||||
// Inputs
|
||||
readonly colors = input<string[]>(['#22c55e', '#06b6d4', '#8b5cf6', '#6366f1']);
|
||||
readonly layers = input(4);
|
||||
readonly speed = input(1);
|
||||
readonly blur = input(60);
|
||||
readonly opacity = input(0.6);
|
||||
readonly responsive = input(true);
|
||||
|
||||
// Outputs
|
||||
readonly ready = output<void>();
|
||||
|
||||
private paths: SVGPathElement[] = [];
|
||||
private width = 0;
|
||||
private height = 0;
|
||||
private loopCleanup: (() => void) | null = null;
|
||||
private resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.initialize();
|
||||
});
|
||||
|
||||
this.destroyRef.onDestroy(() => {
|
||||
this.loopCleanup?.();
|
||||
this.resizeObserver?.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
private initialize(): void {
|
||||
const svg = this.svgRef().nativeElement;
|
||||
const parent = svg.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
this.resize();
|
||||
|
||||
if (this.responsive()) {
|
||||
this.resizeObserver = new ResizeObserver(() => this.resize());
|
||||
this.resizeObserver.observe(parent);
|
||||
}
|
||||
|
||||
// Update blur
|
||||
const blurFilter = svg.querySelector('feGaussianBlur');
|
||||
if (blurFilter) {
|
||||
blurFilter.setAttribute('stdDeviation', `${this.blur()}`);
|
||||
}
|
||||
|
||||
this.createLayers();
|
||||
|
||||
if (this.reducedMotion.reduced()) {
|
||||
this.renderStaticState();
|
||||
} else {
|
||||
const id = `aurora-${Math.random().toString(36).slice(2)}`;
|
||||
this.loopCleanup = this.animationLoop.register(id, (_, elapsed) => this.tick(elapsed));
|
||||
}
|
||||
|
||||
this.ready.emit();
|
||||
}
|
||||
|
||||
private resize(): void {
|
||||
const svg = this.svgRef().nativeElement;
|
||||
const parent = svg.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
this.width = parent.clientWidth;
|
||||
this.height = parent.clientHeight;
|
||||
svg.setAttribute('viewBox', `0 0 ${this.width} ${this.height}`);
|
||||
svg.setAttribute('width', `${this.width}`);
|
||||
svg.setAttribute('height', `${this.height}`);
|
||||
}
|
||||
|
||||
private createLayers(): void {
|
||||
const svg = this.svgRef().nativeElement;
|
||||
const colors = this.colors();
|
||||
const layerCount = this.layers();
|
||||
const opacity = this.opacity();
|
||||
|
||||
// Remove old paths
|
||||
for (const p of this.paths) p.remove();
|
||||
this.paths = [];
|
||||
|
||||
for (let i = 0; i < layerCount; i++) {
|
||||
const path = createSvgElement('path', {
|
||||
fill: colors[i % colors.length],
|
||||
opacity: `${opacity * (1 - i * 0.15)}`,
|
||||
filter: 'url(#aurora-blur)',
|
||||
});
|
||||
svg.appendChild(path);
|
||||
this.paths.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
private tick(elapsed: number): void {
|
||||
const w = this.width;
|
||||
const h = this.height;
|
||||
const speed = this.speed();
|
||||
const layerCount = this.layers();
|
||||
|
||||
for (let i = 0; i < this.paths.length; i++) {
|
||||
const layerOffset = i / layerCount;
|
||||
const amplitude = h * 0.15 * (1 + layerOffset * 0.5);
|
||||
const frequency = 1.5 + layerOffset;
|
||||
const phase = elapsed * speed * (0.3 + layerOffset * 0.2);
|
||||
const yOffset = h * (0.2 + layerOffset * 0.15);
|
||||
|
||||
const d = generateWavePath(w, h, amplitude, frequency, phase, yOffset);
|
||||
this.paths[i].setAttribute('d', d);
|
||||
}
|
||||
}
|
||||
|
||||
private renderStaticState(): void {
|
||||
const w = this.width;
|
||||
const h = this.height;
|
||||
const layerCount = this.layers();
|
||||
|
||||
for (let i = 0; i < this.paths.length; i++) {
|
||||
const layerOffset = i / layerCount;
|
||||
const amplitude = h * 0.15 * (1 + layerOffset * 0.5);
|
||||
const frequency = 1.5 + layerOffset;
|
||||
const yOffset = h * (0.2 + layerOffset * 0.15);
|
||||
|
||||
const d = generateWavePath(w, h, amplitude, frequency, 0, yOffset);
|
||||
this.paths[i].setAttribute('d', d);
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/components/fl-aurora/index.ts
Normal file
1
src/components/fl-aurora/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './fl-aurora.component';
|
||||
Reference in New Issue
Block a user