fix: improve demo performance and fix broken component animations

- Add IntersectionObserver-based visibility pausing to AnimationLoopService
  (registerWithElement) so canvas components auto-pause when off-screen
- Fix voronoi-mesh: increase step size, add cell cache with invalidation
- Fix particle-field: replace O(n²) connections with spatial grid partitioning
- Fix ambient-light: remove GPU-killing blur filters, use inset -20% instead
- Tune demo: reduce particle/seed counts, remove always-on floating-layers
- Fix celebration components: move effect() inside afterNextRender so canvas
  is ready, pass [active]="true", render as fixed overlays instead of inline
- Fix one-shot text effects (typewriter, scramble-reveal, counter-rollup):
  defer animation until element is visible via IntersectionObserver

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Giuliano Silvestro
2026-03-09 14:21:13 +10:00
parent daf6182e94
commit 033cb7e10e
25 changed files with 1230 additions and 89 deletions

833
demo/src/app.component.ts Normal file
View File

@@ -0,0 +1,833 @@
import { Component } from '@angular/core';
import {
// Scroll & Progress
FlScrollProgressComponent,
// Backgrounds & Fields
FlParticleFieldComponent,
FlFlowFieldComponent,
FlMatrixRainComponent,
FlAuroraComponent,
FlVoronoiMeshComponent,
FlFractalLandscapeComponent,
FlAmbientLightComponent,
FlFloatingLayersComponent,
// Text Effects
FlGradientTextComponent,
FlTypewriterComponent,
FlTextShimmerComponent,
FlGlitchTextComponent,
FlScrambleRevealComponent,
FlCounterRollupComponent,
// Cards
FlTiltCardComponent,
FlHolographicCardComponent,
FlSpotlightCardComponent,
// Generative Art
FlBlobComponent,
FlGenerativePatternComponent,
FlGradientMeshComponent,
// Dividers
FlWaveDividerComponent,
FlGradientDividerComponent,
FlGlowDividerComponent,
// Celebrations
FlConfettiComponent,
FlFireworksComponent,
FlSparkleComponent,
FlEmojiRainComponent,
FlCheckmarkFlourishComponent,
// Patterns & Textures
FlDotGridComponent,
FlBlueprintGridComponent,
FlTopographicLinesComponent,
FlCircuitBoardComponent,
// Status Indicators
FlBreathingDotComponent,
FlLiveIndicatorComponent,
FlSignalStrengthComponent,
FlHeartbeatLineComponent,
// Directives
FlHoverLiftDirective,
FlMagneticDirective,
FlRippleDirective,
FlNeonGlowDirective,
FlCursorGlowDirective,
FlRevealOnScrollDirective,
FlPressSquishDirective,
FlTextGradientDirective,
} from '@sda/flair-elements-ui';
@Component({
selector: 'app-root',
standalone: true,
imports: [
// Scroll & Progress
FlScrollProgressComponent,
// Backgrounds & Fields
FlParticleFieldComponent,
FlFlowFieldComponent,
FlMatrixRainComponent,
FlAuroraComponent,
FlVoronoiMeshComponent,
FlFractalLandscapeComponent,
FlAmbientLightComponent,
// Text Effects
FlGradientTextComponent,
FlTypewriterComponent,
FlTextShimmerComponent,
FlGlitchTextComponent,
FlScrambleRevealComponent,
FlCounterRollupComponent,
// Cards
FlTiltCardComponent,
FlHolographicCardComponent,
FlSpotlightCardComponent,
// Generative Art
FlBlobComponent,
FlGenerativePatternComponent,
FlGradientMeshComponent,
// Dividers
FlWaveDividerComponent,
FlGradientDividerComponent,
FlGlowDividerComponent,
// Celebrations
FlConfettiComponent,
FlFireworksComponent,
FlSparkleComponent,
FlEmojiRainComponent,
FlCheckmarkFlourishComponent,
// Patterns & Textures
FlDotGridComponent,
FlBlueprintGridComponent,
FlTopographicLinesComponent,
FlCircuitBoardComponent,
// Status Indicators
FlBreathingDotComponent,
FlLiveIndicatorComponent,
FlSignalStrengthComponent,
FlHeartbeatLineComponent,
// Directives
FlHoverLiftDirective,
FlMagneticDirective,
FlRippleDirective,
FlNeonGlowDirective,
FlCursorGlowDirective,
FlRevealOnScrollDirective,
FlPressSquishDirective,
FlTextGradientDirective,
],
template: `
<div class="showcase">
<!-- Ambient Background Effects -->
<fl-ambient-light class="ambient-bg" [opacity]="0.15" [blur]="60"></fl-ambient-light>
<!-- 1. HERO SECTION -->
<fl-scroll-progress class="scroll-progress"></fl-scroll-progress>
<section class="hero">
<fl-particle-field class="hero-particles" [count]="50"></fl-particle-field>
<div class="hero-content">
<fl-gradient-text
[colors]="['#ff0080', '#7928ca', '#0070f3']"
class="hero-title">
Flair Elements UI
</fl-gradient-text>
<fl-typewriter
[text]="'Stunning visual effects and animations for modern web applications'"
[speed]="50"
class="hero-subtitle">
</fl-typewriter>
</div>
</section>
<fl-wave-divider></fl-wave-divider>
<!-- 2. INTERACTIVE CARDS -->
<section class="section">
<h2 class="section-title" flRevealOnScroll>Interactive Cards</h2>
<div class="cards-grid">
<fl-tilt-card>
<div class="card-content">
<h3>Tilt Card</h3>
<p>3D tilt effect that follows your cursor</p>
</div>
</fl-tilt-card>
<fl-holographic-card>
<div class="card-content">
<h3>Holographic Card</h3>
<p>Iridescent holographic shimmer effect</p>
</div>
</fl-holographic-card>
<fl-spotlight-card>
<div class="card-content">
<h3>Spotlight Card</h3>
<p>Dynamic spotlight that follows your mouse</p>
</div>
</fl-spotlight-card>
</div>
</section>
<fl-gradient-divider [colors]="['#ff0080', '#7928ca']"></fl-gradient-divider>
<!-- 3. TEXT EFFECTS -->
<section class="section">
<h2 class="section-title" flRevealOnScroll>Text Effects</h2>
<div class="text-effects">
<div class="text-effect-item">
<label>Gradient Text:</label>
<fl-gradient-text
[colors]="['#f093fb', '#f5576c']">
Beautiful Gradients
</fl-gradient-text>
</div>
<div class="text-effect-item">
<label>Shimmer:</label>
<fl-text-shimmer>Shimmering Effect</fl-text-shimmer>
</div>
<div class="text-effect-item">
<label>Glitch:</label>
<fl-glitch-text [text]="'Glitch Effect'"></fl-glitch-text>
</div>
<div class="text-effect-item">
<label>Scramble Reveal:</label>
<fl-scramble-reveal [text]="'Decoded Message'"></fl-scramble-reveal>
</div>
<div class="text-effect-item">
<label>Counter:</label>
<fl-counter-rollup
[value]="9999"
[duration]="2000">
</fl-counter-rollup>
</div>
</div>
</section>
<fl-glow-divider [color]="'#0070f3'"></fl-glow-divider>
<!-- 4. CANVAS BACKGROUNDS -->
<section class="section">
<h2 class="section-title" flRevealOnScroll>Canvas Backgrounds</h2>
<div class="canvas-grid">
<div class="canvas-box">
<fl-flow-field [particleCount]="400"></fl-flow-field>
<span class="canvas-label">Flow Field</span>
</div>
<div class="canvas-box">
<fl-matrix-rain></fl-matrix-rain>
<span class="canvas-label">Matrix Rain</span>
</div>
<div class="canvas-box">
<fl-aurora></fl-aurora>
<span class="canvas-label">Aurora</span>
</div>
<div class="canvas-box">
<fl-voronoi-mesh [seedCount]="15"></fl-voronoi-mesh>
<span class="canvas-label">Voronoi Mesh</span>
</div>
<div class="canvas-box">
<fl-fractal-landscape></fl-fractal-landscape>
<span class="canvas-label">Fractal Landscape</span>
</div>
</div>
</section>
<fl-wave-divider></fl-wave-divider>
<!-- 5. GENERATIVE ART -->
<section class="section">
<h2 class="section-title" flRevealOnScroll>Generative Art</h2>
<div class="generative-grid">
<div class="generative-box">
<fl-blob></fl-blob>
<span class="canvas-label">Blob</span>
</div>
<div class="generative-box">
<fl-generative-pattern></fl-generative-pattern>
<span class="canvas-label">Generative Pattern</span>
</div>
<div class="generative-box">
<fl-gradient-mesh [points]="3" [blur]="40"></fl-gradient-mesh>
<span class="canvas-label">Gradient Mesh</span>
</div>
</div>
</section>
<fl-gradient-divider [colors]="['#0070f3', '#00dfd8']"></fl-gradient-divider>
<!-- 7. CELEBRATIONS -->
<section class="section celebrations-section">
<h2 class="section-title" flRevealOnScroll>Celebrations</h2>
<div class="celebrations">
<button (click)="triggerConfetti = true" flPressSquish class="celebration-btn">
Confetti
</button>
<button (click)="triggerFireworks = true" flPressSquish class="celebration-btn">
Fireworks
</button>
<button (click)="triggerSparkle = true" flPressSquish class="celebration-btn">
Sparkle
</button>
<button (click)="triggerEmoji = true" flPressSquish class="celebration-btn">
Emoji Rain
</button>
<button (click)="triggerCheckmark = true" flPressSquish class="celebration-btn">
Checkmark
</button>
</div>
@if (triggerConfetti) {
<fl-confetti class="celebration-overlay" [active]="true"></fl-confetti>
}
@if (triggerFireworks) {
<fl-fireworks class="celebration-overlay" [active]="true"></fl-fireworks>
}
@if (triggerSparkle) {
<fl-sparkle class="celebration-overlay" [active]="true"></fl-sparkle>
}
@if (triggerEmoji) {
<fl-emoji-rain class="celebration-overlay" [active]="true" [emojis]="['🎉', '🎊', '⭐', '💫']"></fl-emoji-rain>
}
@if (triggerCheckmark) {
<fl-checkmark-flourish class="celebration-overlay" [active]="true"></fl-checkmark-flourish>
}
</section>
<fl-glow-divider [color]="'#7928ca'"></fl-glow-divider>
<!-- 8. DIRECTIVES -->
<section class="section">
<h2 class="section-title" flRevealOnScroll>Interactive Directives</h2>
<div class="directives-grid">
<div class="directive-card" flHoverLift>
<h4>Hover Lift</h4>
<p>Elevates on hover</p>
</div>
<div class="directive-card" flMagnetic>
<h4>Magnetic</h4>
<p>Attracted to cursor</p>
</div>
<div class="directive-card" flRipple>
<h4>Ripple</h4>
<p>Click for ripple effect</p>
</div>
<div class="directive-card" flNeonGlow>
<h4>Neon Glow</h4>
<p>Glowing neon border</p>
</div>
<div class="directive-card" flCursorGlow>
<h4>Cursor Glow</h4>
<p>Glow follows cursor</p>
</div>
<div class="directive-card" flRevealOnScroll>
<h4>Reveal on Scroll</h4>
<p>Animates into view</p>
</div>
<div class="directive-card" flPressSquish>
<h4>Press Squish</h4>
<p>Click to squish</p>
</div>
<div class="directive-card">
<h4 flTextGradient>Text Gradient</h4>
<p>Gradient text directive</p>
</div>
</div>
</section>
<fl-wave-divider></fl-wave-divider>
<!-- 9. PATTERNS & TEXTURES -->
<section class="section">
<h2 class="section-title" flRevealOnScroll>Patterns & Textures</h2>
<div class="patterns-grid">
<div class="pattern-box">
<fl-dot-grid></fl-dot-grid>
<span class="canvas-label">Dot Grid</span>
</div>
<div class="pattern-box">
<fl-blueprint-grid></fl-blueprint-grid>
<span class="canvas-label">Blueprint Grid</span>
</div>
<div class="pattern-box">
<fl-topographic-lines></fl-topographic-lines>
<span class="canvas-label">Topographic Lines</span>
</div>
<div class="pattern-box">
<fl-circuit-board></fl-circuit-board>
<span class="canvas-label">Circuit Board</span>
</div>
</div>
</section>
<fl-gradient-divider [colors]="['#ff0080', '#00dfd8']"></fl-gradient-divider>
<!-- 10. STATUS INDICATORS -->
<section class="section">
<h2 class="section-title" flRevealOnScroll>Status Indicators</h2>
<div class="status-grid">
<div class="status-item">
<fl-breathing-dot [color]="'#00ff00'"></fl-breathing-dot>
<span>Breathing Dot</span>
</div>
<div class="status-item">
<fl-live-indicator></fl-live-indicator>
<span>Live Indicator</span>
</div>
<div class="status-item">
<fl-signal-strength [level]="4"></fl-signal-strength>
<span>Signal Strength</span>
</div>
<div class="status-item">
<fl-heartbeat-line [color]="'#ff0080'"></fl-heartbeat-line>
<span>Heartbeat Line</span>
</div>
</div>
</section>
<fl-glow-divider [color]="'#ff0080'"></fl-glow-divider>
<!-- 11. AMBIENT -->
<section class="section final-section">
<h2 class="section-title" flRevealOnScroll>Ambient Effects</h2>
<p class="ambient-note">
Ambient Light is active throughout this entire showcase,
creating subtle atmospheric effects in the background.
</p>
</section>
<footer class="footer">
<fl-gradient-text
[colors]="['#ff0080', '#7928ca', '#0070f3']">
Built with Flair Elements UI
</fl-gradient-text>
</footer>
</div>
`,
styles: [`
:host {
display: block;
background: #0a0a0f;
color: #ffffff;
min-height: 100vh;
}
.showcase {
position: relative;
overflow-x: hidden;
}
/* Ambient Backgrounds */
.ambient-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
opacity: 0.3;
}
/* Scroll Progress */
.scroll-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
}
/* Hero Section */
.hero {
position: relative;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.hero-particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
.hero-content {
position: relative;
z-index: 2;
text-align: center;
padding: 2rem;
max-width: 1200px;
}
.hero-title {
font-size: 5rem;
font-weight: 900;
margin-bottom: 2rem;
line-height: 1.2;
}
.hero-subtitle {
font-size: 1.5rem;
opacity: 0.9;
max-width: 800px;
margin: 0 auto;
}
/* Section Styles */
.section {
position: relative;
z-index: 1;
padding: 6rem 2rem;
max-width: 1400px;
margin: 0 auto;
}
.section-title {
font-size: 3rem;
font-weight: 800;
text-align: center;
margin-bottom: 4rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Cards Grid */
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-bottom: 2rem;
}
.card-content {
padding: 3rem 2rem;
text-align: center;
background: rgba(255, 255, 255, 0.05);
border-radius: 1rem;
backdrop-filter: blur(10px);
}
.card-content h3 {
font-size: 1.8rem;
margin-bottom: 1rem;
color: #fff;
}
.card-content p {
font-size: 1rem;
opacity: 0.8;
line-height: 1.6;
}
/* Text Effects */
.text-effects {
display: flex;
flex-direction: column;
gap: 2rem;
max-width: 800px;
margin: 0 auto;
}
.text-effect-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 2rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.text-effect-item label {
font-weight: 600;
font-size: 1.1rem;
opacity: 0.7;
min-width: 150px;
}
/* Canvas Grid */
.canvas-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.canvas-box {
position: relative;
height: 300px;
border-radius: 1rem;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.5);
}
.canvas-label {
position: absolute;
bottom: 1rem;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
padding: 0.5rem 1.5rem;
border-radius: 2rem;
font-size: 0.9rem;
font-weight: 600;
z-index: 10;
backdrop-filter: blur(10px);
}
/* Generative Grid */
.generative-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
}
.generative-box {
position: relative;
height: 350px;
border-radius: 1rem;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.5);
}
/* Celebrations */
.celebrations-section {
position: relative;
}
.celebrations {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: center;
align-items: center;
padding: 2rem;
}
.celebration-overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 100;
}
.celebration-btn {
padding: 1rem 2rem;
font-size: 1.1rem;
font-weight: 600;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 0.75rem;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
}
.celebration-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6);
}
/* Directives Grid */
.directives-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.directive-card {
padding: 2.5rem 2rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.directive-card h4 {
font-size: 1.3rem;
margin-bottom: 0.75rem;
color: #fff;
}
.directive-card p {
font-size: 0.95rem;
opacity: 0.7;
line-height: 1.5;
}
.directive-card:hover {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.2);
}
/* Patterns Grid */
.patterns-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.pattern-box {
position: relative;
height: 300px;
border-radius: 1rem;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.5);
}
/* Status Grid */
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 3rem;
padding: 2rem;
}
.status-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
padding: 2rem;
background: rgba(255, 255, 255, 0.03);
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.status-item span {
font-size: 1rem;
font-weight: 600;
opacity: 0.8;
}
/* Final Section */
.final-section {
min-height: 50vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.ambient-note {
max-width: 700px;
text-align: center;
font-size: 1.2rem;
line-height: 1.8;
opacity: 0.8;
padding: 2rem;
background: rgba(255, 255, 255, 0.05);
border-radius: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Footer */
.footer {
text-align: center;
padding: 4rem 2rem;
font-size: 1.5rem;
position: relative;
z-index: 1;
}
/* Responsive Design */
@media (max-width: 768px) {
.hero-title {
font-size: 3rem;
}
.hero-subtitle {
font-size: 1.2rem;
}
.section-title {
font-size: 2rem;
}
.cards-grid,
.canvas-grid,
.generative-grid,
.directives-grid,
.patterns-grid {
grid-template-columns: 1fr;
}
.text-effect-item {
flex-direction: column;
gap: 1rem;
text-align: center;
}
.text-effect-item label {
min-width: auto;
}
}
`]
})
export class AppComponent {
triggerConfetti = false;
triggerFireworks = false;
triggerSparkle = false;
triggerEmoji = false;
triggerCheckmark = false;
}