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:
@@ -0,0 +1,19 @@
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.fl-fluid-container__svg {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.fl-fluid-container__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import {
|
||||
Component, ChangeDetectionStrategy, input, inject,
|
||||
DestroyRef, afterNextRender, signal,
|
||||
} from '@angular/core';
|
||||
import { ReducedMotionService } from '../../services/reduced-motion.service';
|
||||
import { AnimationLoopService } from '../../services/animation-loop.service';
|
||||
|
||||
@Component({
|
||||
selector: 'fl-fluid-container',
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<svg
|
||||
class="fl-fluid-container__svg"
|
||||
preserveAspectRatio="none"
|
||||
[attr.viewBox]="viewBox"
|
||||
>
|
||||
<defs>
|
||||
<clipPath [id]="clipId">
|
||||
<path [attr.d]="clipPath()" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
class="fl-fluid-container__content"
|
||||
[style.clip-path]="clipPathValue"
|
||||
[style.border]="borderStyle"
|
||||
>
|
||||
<ng-content />
|
||||
</div>
|
||||
`,
|
||||
styleUrl: './fl-fluid-container.component.scss',
|
||||
host: { 'class': 'fl-fluid-container' },
|
||||
})
|
||||
export class FlFluidContainerComponent {
|
||||
private reducedMotion = inject(ReducedMotionService);
|
||||
private animationLoop = inject(AnimationLoopService);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
// Inputs
|
||||
readonly borderColor = input('#ff6b6b');
|
||||
readonly speed = input(1);
|
||||
readonly amplitude = input(10);
|
||||
readonly borderWidth = input(2);
|
||||
|
||||
protected readonly clipPath = signal('');
|
||||
protected readonly clipId = `fluid-${Math.random().toString(36).slice(2)}`;
|
||||
protected readonly viewBox = '0 0 100 100';
|
||||
|
||||
private loopCleanup: (() => void) | null = null;
|
||||
private time = 0;
|
||||
|
||||
protected get clipPathValue(): string {
|
||||
return this.reducedMotion.reduced() ? 'none' : `url(#${this.clipId})`;
|
||||
}
|
||||
|
||||
protected get borderStyle(): string {
|
||||
return `${this.borderWidth()}px solid ${this.borderColor()}`;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
afterNextRender(() => {
|
||||
this.updatePath();
|
||||
|
||||
if (!this.reducedMotion.reduced()) {
|
||||
const id = `fluid-container-${Math.random().toString(36).slice(2)}`;
|
||||
this.loopCleanup = this.animationLoop.register(id, (delta) => this.tick(delta));
|
||||
}
|
||||
});
|
||||
|
||||
this.destroyRef.onDestroy(() => {
|
||||
this.loopCleanup?.();
|
||||
});
|
||||
}
|
||||
|
||||
private tick(delta: number): void {
|
||||
this.time += delta * this.speed();
|
||||
this.updatePath();
|
||||
}
|
||||
|
||||
private updatePath(): void {
|
||||
const amp = this.amplitude() / 10;
|
||||
const t = this.time;
|
||||
|
||||
// Create undulating border effect
|
||||
const topWave = this.createWave(0, 100, t, amp, 0);
|
||||
const rightWave = this.createWave(100, 100, t, amp, Math.PI / 2, true);
|
||||
const bottomWave = this.createWave(100, 0, t, amp, Math.PI);
|
||||
const leftWave = this.createWave(0, 0, t, amp, 3 * Math.PI / 2, true);
|
||||
|
||||
this.clipPath.set(`
|
||||
M ${topWave}
|
||||
L ${rightWave}
|
||||
L ${bottomWave}
|
||||
L ${leftWave}
|
||||
Z
|
||||
`);
|
||||
}
|
||||
|
||||
private createWave(
|
||||
start: number,
|
||||
end: number,
|
||||
time: number,
|
||||
amplitude: number,
|
||||
phase: number,
|
||||
vertical = false
|
||||
): string {
|
||||
const points: string[] = [];
|
||||
const steps = 20;
|
||||
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const t = i / steps;
|
||||
const pos = start + (end - start) * t;
|
||||
const wave = Math.sin(t * Math.PI * 4 + time + phase) * amplitude;
|
||||
|
||||
if (vertical) {
|
||||
points.push(`${pos + wave},${start + (end - start) * t}`);
|
||||
} else {
|
||||
points.push(`${start + (end - start) * t},${pos + wave}`);
|
||||
}
|
||||
}
|
||||
|
||||
return points.join(' L ');
|
||||
}
|
||||
}
|
||||
1
src/components/fl-fluid-container/index.ts
Normal file
1
src/components/fl-fluid-container/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './fl-fluid-container.component';
|
||||
Reference in New Issue
Block a user