Documentation
v1.9
GETTING STARTED
DYNAMIC ISLAND
Premium element
Core Background
Aura Flow
Element provides a dynamic, colorful fluid animation effect within a container. This immersive effect mimics fluid motions and responds to user interactions like mouse movement, creating an engaging visual experience.
Preview
Open previewQuick Setup
data-fluid
Required
Activates the Aura Flow effect.
Available Presets
data-fluid-preset="preset-name"
Optional
ocean
sunset
pink
warm
rainbow
golden
mystic
tropical
neon
Custom Colors
Define your own color scheme using these attributes
data-fluid-color-1
Optional
Sets the first color in the fluid animation
Example: #FF0000, rgb(255,0,0), red
data-fluid-color-2
Optional
Example: #00FF00, rgb(0,255,0), green
Example: #00FF00, rgb(0,255,0), green
data-fluid-color-3
Optional
Example: #0000FF, rgb(0,0,255), blue
Example: #0000FF, rgb(0,0,255), blue
Advanced configuration
data-fluid-opacity
Optional
Sets the opacity level of the fluid animation
Default: 0.5
data-fluid-intensity
Optional
Controls the overall color intensity of the effect
Default: 0.8
data-fluid-glow
Optional
Adjusts the glow effect seen in the animation
Default: 0.8
data-fluid-radius
Optional
Specifies the radius of the effect reach
Default: 100
data-fluid-pulse-speed
Optional
Sets the speed at which the animation pulses
Default: 0.5
We're here to help
Choose how you'd like to connect with our support team
Contact Support
Get help from our team of experts
Response within 24 hours
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aura Flow Configurator</title>
<style>
:root {
--background: #121212;
--card-bg: #1e1e1e;
--card-bg-hover: #252525;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--accent: #ef6013;
--accent-hover: #c64c0c;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
--thumb: #ef6013;
--card-radius: 16px;
--input-radius: 8px;
--button-radius: 12px;
--transition: all 0.25s ease;
--font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
background-color: var(--background);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 2.5rem 1.5rem;
}
.content {
display: flex;
flex-wrap: wrap;
gap: 2.5rem;
}
.preview-section {
flex: 1;
min-width: 300px;
}
.controls-section {
flex: 1;
min-width: 300px;
max-width: 500px;
}
.card {
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 2rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.preview-container {
height: 500px;
width: 100%;
position: relative;
overflow: hidden;
border-radius: var(--card-radius);
background-color: #000000;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.preview-content {
color: white;
text-align: center;
font-weight: bold;
font-size: var(--text-s);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
.card-heading {
padding: 1.25rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
letter-spacing: 0.3px;
}
.card-content {
padding: 1.75rem;
}
.control-group {
margin-bottom: 1.75rem;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.label-text {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
}
.value-text {
font-size: var(--text-xs);
color: var(--text-secondary);
background-color: rgba(50, 50, 50, 0.5);
padding: 2px 8px;
border-radius: 4px;
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 4px;
background: var(--track);
border-radius: 2px;
outline: none;
margin: 0.8rem 0;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
background: var(--thumb);
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow: 0 0 8px rgba(239, 96, 19, 0.5);
}
input[type="range"]::-moz-range-thumb {
width: 18px;
height: 18px;
background: var(--thumb);
border: none;
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-moz-range-thumb:hover {
transform: scale(1.1);
box-shadow: 0 0 8px rgba(239, 96, 19, 0.5);
}
input[type="text"],
input[type="number"],
select {
width: 100%;
padding: 0.8rem 1rem;
border: 1px solid var(--border);
border-radius: var(--input-radius);
font-family: var(--font);
font-size: var(--text-xs);
color: var(--text-primary);
background-color: rgba(30, 30, 30, 0.7);
margin-bottom: 0.75rem;
outline: none;
transition: var(--transition);
}
input[type="text"]:focus,
input[type="number"]:focus,
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.color-mode {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.radio-label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
position: relative;
padding-left: 1.5rem;
font-size: var(--text-xs);
}
.radio-label input {
position: absolute;
opacity: 0;
cursor: pointer;
}
.radio-checkmark {
position: absolute;
top: 2px;
left: 0;
height: 16px;
width: 16px;
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: 50%;
}
.radio-label input:checked ~ .radio-checkmark {
background-color: var(--card-bg);
border-color: var(--accent);
}
.radio-checkmark:after {
content: "";
position: absolute;
display: none;
}
.radio-label input:checked ~ .radio-checkmark:after {
display: block;
top: 3px;
left: 3px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
}
.color-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.8rem;
margin-bottom: 1.2rem;
}
.color-input-group {
position: relative;
border: 1px solid var(--border);
border-radius: var(--input-radius);
padding: 0.4rem;
background-color: rgba(30, 30, 30, 0.7);
transition: var(--transition);
}
.color-input-group:hover {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.1);
}
.color-preview {
position: absolute;
top: 0.75rem;
left: 0.75rem;
width: 24px;
height: 24px;
border-radius: 4px;
pointer-events: none;
}
input[type="color"] {
-webkit-appearance: none;
border: none;
width: 100%;
height: 24px;
cursor: pointer;
background-color: transparent;
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 4px;
}
input[type="color"]::-moz-color-swatch {
border: none;
border-radius: 4px;
}
.color-text {
padding-left: 2.5rem;
border: none;
background-color: transparent;
color: var(--text-secondary);
font-size: 0.8rem;
letter-spacing: 0.3px;
}
.color-text:focus {
color: var(--text-primary);
box-shadow: none;
}
.switch-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.2rem;
padding: 0.5rem 0;
}
.switch-label {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
}
.switch {
position: relative;
display: inline-block;
width: 52px;
height: 28px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--track);
transition: var(--transition);
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 4px;
bottom: 4px;
background-color: #f2f2f7;
transition: var(--transition);
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
input:checked + .slider {
background-color: var(--accent);
}
input:focus + .slider {
box-shadow: 0 0 1px var(--accent);
}
input:checked + .slider:before {
transform: translateX(24px);
}
.btn {
display: block;
width: 100%;
padding: 1rem 1.5rem;
background-color: var(--accent);
color: white;
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 600;
text-align: center;
border: none;
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
letter-spacing: 0.3px;
}
.btn:hover {
background-color: var(--accent-hover);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(239, 96, 19, 0.3);
}
.btn:active {
transform: translateY(0);
box-shadow: 0 2px 8px rgba(239, 96, 19, 0.2);
}
.instructions {
background-color: var(--card-bg);
padding: 1.75rem;
border-radius: var(--card-radius);
margin-bottom: 2rem;
box-shadow: var(--shadow);
border: 1px solid var(--border);
}
.instructions h3 {
font-size: var(--text-s);
margin-bottom: 1rem;
color: var(--text-primary);
font-weight: 600;
letter-spacing: 0.3px;
}
.instructions ol {
padding-left: 1.5rem;
}
.instructions li {
margin-bottom: 0.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.6;
}
.instructions code {
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
}
.instructions strong {
font-weight: 500;
color: var(--text-primary);
}
.notice {
background-color: rgba(239, 96, 19, 0.08);
color: #ff8c51;
padding: 1rem 1.2rem;
border-radius: var(--input-radius);
font-size: var(--text-xs);
margin-top: 1.5rem;
border-left: 3px solid var(--accent);
letter-spacing: 0.2px;
line-height: 1.6;
}
.code-modal {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.code-modal.active {
visibility: visible;
opacity: 1;
}
.modal-content {
width: 90%;
max-width: 1000px;
max-height: 90vh;
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
overflow: hidden;
transform: translateY(20px);
transition: all 0.3s ease;
border: 1px solid var(--border);
}
.code-modal.active .modal-content {
transform: translateY(0);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.modal-title {
font-size: var(--text-s);
font-weight: 600;
letter-spacing: 0.3px;
}
.close-modal {
background: none;
border: none;
font-size: var(--text-s);
cursor: pointer;
color: var(--text-secondary);
transition: var(--transition);
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.close-modal:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.1);
}
.modal-body {
padding: 1.75rem;
overflow-y: auto;
max-height: calc(90vh - 130px);
}
.code-display {
background-color: #161616;
color: #e4e4e4;
border-radius: var(--card-radius);
padding: 1.5rem;
overflow-x: auto;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
line-height: 1.6;
white-space: pre;
margin-bottom: 1rem;
max-height: 400px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
border: 1px solid #252525;
}
.modal-footer {
padding: 1.25rem 1.75rem;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.copy-btn,
.download-file-btn {
padding: 0.8rem 1.2rem;
background-color: var(--accent);
color: white;
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
border: none;
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
letter-spacing: 0.2px;
}
.copy-btn:hover,
.download-file-btn:hover {
background-color: var(--accent-hover);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}
.copy-btn:active,
.download-file-btn:active {
transform: scale(0.98);
box-shadow: 0 2px 8px rgba(239, 96, 19, 0.2);
}
.premium-card {
margin-top: 2rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
background: linear-gradient(145deg, #212121, #1e1e1e);
}
.premium-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
border-color: rgba(239, 96, 19, 0.3);
}
.premium-badge {
display: inline-block;
background: linear-gradient(90deg, #ef6013, #ff8c51);
color: white;
font-size: var(--text-xs);
padding: 0.3rem 0.7rem;
border-radius: 6px;
margin-right: 0.75rem;
font-weight: 600;
vertical-align: middle;
letter-spacing: 0.3px;
box-shadow: 0 2px 8px rgba(239, 96, 19, 0.3);
}
.premium-description {
color: var(--text-secondary);
font-size: var(--text-xs);
margin-bottom: 1.5rem;
line-height: 1.7;
letter-spacing: 0.2px;
}
.feature-list {
list-style: none;
padding: 0;
margin: 0 0 1.75rem 0;
}
.feature-list li {
position: relative;
padding-left: 1.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
margin-bottom: 0.9rem;
letter-spacing: 0.2px;
line-height: 1.5;
}
.feature-list li:before {
content: "✓";
position: absolute;
left: 0;
color: var(--accent);
font-weight: bold;
}
.premium-btn {
background: linear-gradient(90deg, #ef6013, #ff8c51);
transition: transform 0.2s ease, box-shadow 0.2s ease;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
letter-spacing: 0.3px;
}
.premium-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(239, 96, 19, 0.4);
}
.btn-icon {
font-size: 1.1em;
}
.copy-attribute {
display: inline-flex;
align-items: center;
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
cursor: pointer;
transition: var(--transition);
margin: 0 0.2rem;
}
.copy-attribute:hover {
background-color: rgba(239, 96, 19, 0.2);
}
.copy-icon {
margin-left: 0.5rem;
font-size: 0.9em;
opacity: 0.7;
}
.copy-attribute:hover .copy-icon {
opacity: 1;
}
.blend-mode-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.8rem;
}
.blend-mode-option {
font-size: var(--text-xs);
padding: 0.75rem;
text-align: center;
border: 1px solid var(--border);
border-radius: var(--input-radius);
cursor: pointer;
transition: var(--transition);
background-color: rgba(30, 30, 30, 0.7);
}
.blend-mode-option.active {
background-color: var(--accent);
color: white;
border-color: var(--accent);
box-shadow: 0 2px 8px rgba(239, 96, 19, 0.4);
}
</style>
</head>
<body>
<div class="container">
<div class="instructions">
<h3>How to Use Aura Flow in Bricks Builder</h3>
<ol>
<li>Design your Aura Flow animation effect using the controls below</li>
<li>Click the <strong>Get Code</strong> button and copy the JavaScript</li>
<li>In Bricks Builder, add a <strong>Code</strong> element and paste the JavaScript</li>
<li>Add the attribute <div class="copy-attribute" data-attribute="data-aura-flow">data-aura-flow<span class="copy-icon">📋</span></div> to any section you want to apply the Aura Flow effect to</li>
</ol>
<div class="notice">
This tool creates a custom script with your settings built-in. You only need to add the data attribute to see your exact configuration!
</div>
</div>
<div class="content">
<section class="preview-section">
<div class="preview-container" id="fluid-preview" data-aura-flow="true">
<div class="preview-content"></div>
</div>
<div class="card premium-card">
<div class="card-heading">
<span class="premium-badge">BricksFusion Exclusive</span>
Get Your Custom Aura Flow Animation
</div>
<div class="card-content">
<p class="premium-description">Create stunning interactive Aura Flow animations for your Bricks Builder projects with this custom-crafted WebGL animation.</p>
<ul class="feature-list">
<li>Easy implementation with a single attribute</li>
<li>Interactive mouse-follow animations</li>
<li>Fully customizable colors and effects</li>
<li>Performance optimized for all devices</li>
</ul>
<button class="btn premium-btn" id="download-btn" data-protection-animation="true">
<span class="btn-icon">⚡</span>
Get Instant Code
</button>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">Colors & Appearance</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Color Palette</span>
</div>
<div class="color-grid">
<div class="color-input-group">
<input type="color" id="color1" value="#4444ff">
<input type="text" id="color1-text" class="color-text" value="#4444ff">
</div>
<div class="color-input-group">
<input type="color" id="color2" value="#0088ff">
<input type="text" id="color2-text" class="color-text" value="#0088ff">
</div>
<div class="color-input-group">
<input type="color" id="color3" value="#00ffff">
<input type="text" id="color3-text" class="color-text" value="#00ffff">
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Opacity</span>
<span class="value-text"><span id="opacity-value">0.9</span></span>
</div>
<input type="range" id="opacity" min="0.1" max="1" value="0.9" step="0.05">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">Effect Properties</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Intensity</span>
<span class="value-text"><span id="intensity-value">1.0</span></span>
</div>
<input type="range" id="intensity" min="0.1" max="2" value="1.0" step="0.1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Glow Effect</span>
<span class="value-text"><span id="glow-value">0.8</span></span>
</div>
<input type="range" id="glow" min="0.1" max="2" value="0.8" step="0.1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Electric Effect</span>
<span class="value-text"><span id="electric-value">1.2</span></span>
</div>
<input type="range" id="electric" min="0.1" max="2" value="1.2" step="0.1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Effect Radius</span>
<span class="value-text"><span id="radius-value">100</span></span>
</div>
<input type="range" id="radius" min="10" max="300" value="100" step="5">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">Animation Settings</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Animation Speed</span>
<span class="value-text"><span id="speed-value">1.0</span></span>
</div>
<input type="range" id="speed" min="0.1" max="2" value="1.0" step="0.1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Pulse Speed</span>
<span class="value-text"><span id="pulse-speed-value">8.0</span></span>
</div>
<input type="range" id="pulse-speed" min="1" max="15" value="8.0" step="0.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Mouse Transition</span>
<span class="value-text"><span id="transition-value">0.08</span></span>
</div>
<input type="range" id="transition" min="0.01" max="0.2" value="0.08" step="0.01">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="code-modal" id="code-modal">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">JavaScript Code</h3>
<button class="close-modal" id="close-modal">×</button>
</div>
<div class="modal-body">
<div class="code-display" id="js-code"></div>
</div>
<div class="modal-footer">
<button class="copy-btn" id="copy-btn">Copy to Clipboard</button>
<button class="download-file-btn" id="download-file-btn">Download JS File</button>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Configuration object for the fluid animation
let fluidConfig = {
colors: ['#4444ff', '#0088ff', '#00ffff'],
opacity: 0.9,
intensity: 1.0,
glow: 0.8,
electric: 1.2,
radius: 100,
speed: 1.0,
pulseSpeed: 8.0,
transition: 0.08
};
// Active fluid animation instance
let activeFluid = null;
// Function to convert hex to RGB array (0-1 range for WebGL)
function hexToRGB(hex) {
try {
if (!hex || typeof hex !== 'string') {
console.error('Invalid hex value provided:', hex);
return [0.5, 0.5, 1.0]; // Default blue color
}
// Make sure hex starts with #
if (hex.charAt(0) !== '#') {
hex = '#' + hex;
}
const shorthand = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthand, (m, r, g, b) => {
return '#' + r + r + g + g + b + b;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
if (!result) {
console.error('Could not parse hex value:', hex);
return [0.5, 0.5, 1.0]; // Default blue color
}
const r = parseInt(result[1], 16) / 255;
const g = parseInt(result[2], 16) / 255;
const b = parseInt(result[3], 16) / 255;
return [r, g, b];
} catch (error) {
console.error('Error in hexToRGB:', error, 'for hex value:', hex);
return [0.5, 0.5, 1.0]; // Default blue color
}
}
// Function to initialize or update the fluid animation
function initFluidAnimation() {
destroyExistingFluid();
// Get the preview container
const container = document.getElementById('fluid-preview');
// Set data attributes based on current configuration
container.setAttribute('data-aura-flow', 'true');
container.setAttribute('data-aura-flow-color1', fluidConfig.colors[0]);
container.setAttribute('data-aura-flow-color2', fluidConfig.colors[1]);
container.setAttribute('data-aura-flow-color3', fluidConfig.colors[2]);
container.setAttribute('data-aura-flow-opacity', fluidConfig.opacity);
container.setAttribute('data-aura-flow-intensity', fluidConfig.intensity);
container.setAttribute('data-aura-flow-glow', fluidConfig.glow);
container.setAttribute('data-aura-flow-electric', fluidConfig.electric);
container.setAttribute('data-aura-flow-radius', fluidConfig.radius);
container.setAttribute('data-aura-flow-speed', fluidConfig.speed);
container.setAttribute('data-aura-flow-pulse-speed', fluidConfig.pulseSpeed);
container.setAttribute('data-aura-flow-transition', fluidConfig.transition);
// Initialize the new FluidAnimation
activeFluid = new FluidAnimation(container);
}
// Function to destroy existing fluid animation
function destroyExistingFluid() {
if (activeFluid) {
activeFluid.destroy();
activeFluid = null;
}
}
// FluidAnimation class implementation
class FluidAnimation {
constructor(container) {
this.container = container;
this._cache = {
dimensions: { width: 0, height: 0, aspect: 0 },
mouse: { current: { x: 0.5, y: 0.5 }, last: { x: 0.5, y: 0.5 } },
time: 0,
frameCount: 0,
autonomous: true
};
this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
this._frameInterval = this.isMobile ? 1000/30 : 1000/60;
this._lastFrameTime = 0;
this.mouseX = 0.5;
this.mouseY = 0.5;
this.lastX = 0.5;
this.lastY = 0.5;
this.targetX = 0.5;
this.targetY = 0.5;
this.transitionSpeed = parseFloat(container.getAttribute('data-aura-flow-transition')) || fluidConfig.transition;
this.isMouseDown = false;
this.config = this._parseConfig(container);
this._setup(container);
this._initWebGL();
this._setupShaders();
this._setupEvents();
this._animate();
}
_parseConfig(container) {
// Extract colors from data attributes or use defaults
const colors = [];
for (let i = 1; i <= 3; i++) {
const colorAttr = 'data-aura-flow-color' + i;
if (container.hasAttribute(colorAttr)) {
const hexColor = container.getAttribute(colorAttr);
colors.push(hexToRGB(hexColor));
}
}
// If no colors specified, use default colors
if (colors.length !== 3) {
colors.length = 0; // Clear the array
colors.push(hexToRGB(fluidConfig.colors[0]));
colors.push(hexToRGB(fluidConfig.colors[1]));
colors.push(hexToRGB(fluidConfig.colors[2]));
}
return {
colors: colors,
opacity: parseFloat(container.getAttribute('data-aura-flow-opacity')) || fluidConfig.opacity,
intensity: parseFloat(container.getAttribute('data-aura-flow-intensity')) || fluidConfig.intensity,
glow: parseFloat(container.getAttribute('data-aura-flow-glow')) || fluidConfig.glow,
electric: parseFloat(container.getAttribute('data-aura-flow-electric')) || fluidConfig.electric,
radius: parseFloat(container.getAttribute('data-aura-flow-radius')) || fluidConfig.radius,
speed: parseFloat(container.getAttribute('data-aura-flow-speed')) || fluidConfig.speed,
pulseSpeed: parseFloat(container.getAttribute('data-aura-flow-pulse-speed')) || fluidConfig.pulseSpeed
};
}
_setup(container) {
if (getComputedStyle(container).position === 'static') {
container.style.position = 'relative';
}
this.wrapper = document.createElement('div');
Object.assign(this.wrapper.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
zIndex: '1',
pointerEvents: 'none',
opacity: this.config.opacity.toString()
});
this.canvas = document.createElement('canvas');
Object.assign(this.canvas.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
mixBlendMode: 'screen',
pointerEvents: 'none'
});
this.wrapper.appendChild(this.canvas);
container.insertBefore(this.wrapper, container.firstChild);
}
_initWebGL() {
this.gl = this.canvas.getContext('webgl', {
premultipliedAlpha: false,
alpha: true
});
if (!this.gl) {
console.error('WebGL not supported');
return;
}
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.startTime = Date.now();
this.vertices = new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0]);
// Initial resize to set canvas dimensions
this._resize();
}
_setupShaders() {
const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(vertexShader, `
attribute vec2 position;
varying vec2 uv;
void main() {
uv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}
`);
this.gl.compileShader(vertexShader);
const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(fragmentShader, `
precision highp float;
varying vec2 uv;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;
uniform vec2 lastMouse;
uniform float isMouseDown;
uniform vec3 color1;
uniform vec3 color2;
uniform vec3 color3;
uniform float intensity;
uniform float glow;
uniform float electric;
uniform float radius;
uniform float pulseSpeed;
float lightning(vec2 p, vec2 a, vec2 b) {
vec2 pa = p - a;
vec2 ba = b - a;
float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
float d = length(pa - ba*h);
float strength = 1.0 - smoothstep(0.0, 0.02 / electric, d);
strength *= sin(time * pulseSpeed + p.x * 10.0) * 0.5 + 0.5;
return strength;
}
void main() {
vec2 p = (uv * resolution * 2.0 - resolution) / min(resolution.x, resolution.y);
vec2 m = (mouse * resolution * 2.0 - resolution) / min(resolution.x, resolution.y);
vec2 lm = (lastMouse * resolution * 2.0 - resolution) / min(resolution.x, resolution.y);
vec3 color = vec3(0.0);
vec2 mouseVel = (m - lm) * isMouseDown;
float mouseLen = length(mouseVel);
for(float i = 0.0; i < 3.0; i++) {
vec2 pos = p - m;
float dist = length(pos);
float a = atan(pos.y, pos.x) + sin(time * 0.5) * 2.0;
float h = dist * (1.0 + mouseLen * 2.0);
vec3 col = mix(
mix(color1, color2, i / 2.0),
color3,
sin(time * 0.5 + h) * 0.5 + 0.5
);
color += col * (0.15 * intensity / abs(dist - mouseLen * 2.0));
color += col * (0.15 * intensity / abs(dist - mouseLen));
}
float glowStr = exp(-length(p - m) * (1.5 / glow));
color += mix(color1, color2, 0.5) * glowStr * 0.8;
if (mouseLen > 0.001) {
float electricEffect = lightning(p, lm, m);
electricEffect += lightning(p, lm + vec2(0.1), m + vec2(-0.1));
electricEffect += lightning(p, lm - vec2(0.1), m + vec2(0.1));
color += mix(color2, color3, 0.5) * electricEffect * electric;
}
float pulse = sin(time * pulseSpeed) * 0.5 + 0.5;
float mouseGlow = exp(-length(p - m) * (2.0 + pulse * 2.0));
color += mix(color1, color3, pulse) * mouseGlow * pulse * intensity;
color = pow(color, vec3(0.8));
float alpha = min(1.0, length(color) * 0.7);
gl_FragColor = vec4(color, alpha);
}
`);
this.gl.compileShader(fragmentShader);
// Check for shader compile errors
if (!this.gl.getShaderParameter(vertexShader, this.gl.COMPILE_STATUS)) {
console.error('Vertex shader compile error:', this.gl.getShaderInfoLog(vertexShader));
return;
}
if (!this.gl.getShaderParameter(fragmentShader, this.gl.COMPILE_STATUS)) {
console.error('Fragment shader compile error:', this.gl.getShaderInfoLog(fragmentShader));
return;
}
this.program = this.gl.createProgram();
this.gl.attachShader(this.program, vertexShader);
this.gl.attachShader(this.program, fragmentShader);
this.gl.linkProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
console.error('Program link error:', this.gl.getProgramInfoLog(this.program));
return;
}
this.gl.useProgram(this.program);
const buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.vertices, this.gl.STATIC_DRAW);
const positionLocation = this.gl.getAttribLocation(this.program, 'position');
this.gl.enableVertexAttribArray(positionLocation);
this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 0, 0);
this.uniformLocations = {};
['resolution', 'mouse', 'time', 'lastMouse', 'isMouseDown',
'color1', 'color2', 'color3', 'intensity', 'glow',
'electric', 'radius', 'pulseSpeed'].forEach(name => {
this.uniformLocations[name] = this.gl.getUniformLocation(this.program, name);
});
}
_setupEvents() {
this.resizeHandler = this._resize.bind(this);
window.addEventListener('resize', this.resizeHandler);
const updatePosition = (e, touch = false) => {
const rect = this.container.getBoundingClientRect();
const clientX = touch ? e.touches[0].clientX : e.clientX;
const clientY = touch ? e.touches[0].clientY : e.clientY;
if (clientX < rect.left || clientX > rect.right ||
clientY < rect.top || clientY > rect.bottom) {
this._cache.autonomous = true;
return;
}
this._cache.autonomous = false;
this.targetX = (clientX - rect.left) / rect.width;
this.targetY = 1.0 - (clientY - rect.top) / rect.height;
clearTimeout(this.autonomousTimeout);
this.autonomousTimeout = setTimeout(() => {
this._cache.autonomous = true;
}, 2000);
};
this.mouseMoveHandler = e => updatePosition(e);
this.mouseDownHandler = e => {
const rect = this.container.getBoundingClientRect();
if (e.clientX >= rect.left && e.clientX <= rect.right &&
e.clientY >= rect.top && e.clientY <= rect.bottom) {
this.isMouseDown = true;
}
};
this.mouseUpHandler = () => this.isMouseDown = false;
this.mouseLeaveHandler = () => this._cache.autonomous = true;
this.container.addEventListener('mousemove', this.mouseMoveHandler);
this.container.addEventListener('mousedown', this.mouseDownHandler);
window.addEventListener('mouseup', this.mouseUpHandler);
this.container.addEventListener('mouseleave', this.mouseLeaveHandler);
this.touchStartHandler = e => {
this.isMouseDown = true;
updatePosition(e, true);
};
this.touchMoveHandler = e => {
if (this.isMouseDown) {
updatePosition(e, true);
}
};
this.touchEndHandler = () => {
this.isMouseDown = false;
this._cache.autonomous = true;
};
this.container.addEventListener('touchstart', this.touchStartHandler, {passive: true});
this.container.addEventListener('touchmove', this.touchMoveHandler, {passive: true});
this.container.addEventListener('touchend', this.touchEndHandler, {passive: true});
}
_updateAutonomousPosition(time) {
if (this._cache.autonomous) {
const radius = 0.3;
const speed = 0.5 * this.config.speed;
this.targetX = 0.5 + Math.cos(time * speed) * radius;
this.targetY = 0.5 + Math.sin(time * speed * 1.5) * radius;
}
this.lastX = this.mouseX;
this.lastY = this.mouseY;
this.mouseX += (this.targetX - this.mouseX) * this.transitionSpeed;
this.mouseY += (this.targetY - this.mouseY) * this.transitionSpeed;
}
_resize() {
const width = this.canvas.clientWidth;
const height = this.canvas.clientHeight;
if (this.canvas.width !== width || this.canvas.height !== height) {
this.canvas.width = width;
this.canvas.height = height;
this.gl.viewport(0, 0, width, height);
}
}
_animate(timestamp = 0) {
if (timestamp - this._lastFrameTime < this._frameInterval) {
this.animationFrame = requestAnimationFrame(this._animate.bind(this));
return;
}
this._lastFrameTime = timestamp;
this._resize();
const time = (Date.now() - this.startTime) * 0.001 * this.config.speed;
this._updateAutonomousPosition(time);
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
this.gl.uniform2f(this.uniformLocations.resolution, this.canvas.width, this.canvas.height);
this.gl.uniform2f(this.uniformLocations.mouse, this.mouseX, this.mouseY);
this.gl.uniform2f(this.uniformLocations.lastMouse, this.lastX, this.lastY);
this.gl.uniform1f(this.uniformLocations.time, time);
this.gl.uniform1f(this.uniformLocations.isMouseDown, this.isMouseDown ? 1.0 : 0.0);
this.gl.uniform3fv(this.uniformLocations.color1, this.config.colors[0]);
this.gl.uniform3fv(this.uniformLocations.color2, this.config.colors[1]);
this.gl.uniform3fv(this.uniformLocations.color3, this.config.colors[2]);
this.gl.uniform1f(this.uniformLocations.intensity, this.config.intensity);
this.gl.uniform1f(this.uniformLocations.glow, this.config.glow);
this.gl.uniform1f(this.uniformLocations.electric, this.config.electric);
this.gl.uniform1f(this.uniformLocations.radius, this.config.radius);
this.gl.uniform1f(this.uniformLocations.pulseSpeed, this.config.pulseSpeed);
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
this.animationFrame = requestAnimationFrame(this._animate.bind(this));
}
destroy() {
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
}
if (this.autonomousTimeout) {
clearTimeout(this.autonomousTimeout);
}
window.removeEventListener('resize', this.resizeHandler);
this.container.removeEventListener('mousemove', this.mouseMoveHandler);
this.container.removeEventListener('mousedown', this.mouseDownHandler);
window.removeEventListener('mouseup', this.mouseUpHandler);
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler);
this.container.removeEventListener('touchstart', this.touchStartHandler);
this.container.removeEventListener('touchmove', this.touchMoveHandler);
this.container.removeEventListener('touchend', this.touchEndHandler);
if (this.gl) {
this.gl.deleteProgram(this.program);
const ext = this.gl.getExtension('WEBGL_lose_context');
if (ext) ext.loseContext();
}
if (this.wrapper && this.wrapper.parentNode) {
this.wrapper.parentNode.removeChild(this.wrapper);
}
}
}
// Function to generate JavaScript code
function generateJavaScriptCode() {
return `(function() {
'use strict';
// Configuration object for the Aura Flow animation (customized from your settings)
const DEFAULT_CONFIG = {
opacity: ${fluidConfig.opacity},
intensity: ${fluidConfig.intensity},
glow: ${fluidConfig.glow},
electric: ${fluidConfig.electric},
radius: ${fluidConfig.radius},
speed: ${fluidConfig.speed},
pulseSpeed: ${fluidConfig.pulseSpeed},
transition: ${fluidConfig.transition},
colors: [
'${fluidConfig.colors[0]}',
'${fluidConfig.colors[1]}',
'${fluidConfig.colors[2]}'
]
};
// Function to convert hex to RGB array (0-1 range for WebGL)
function hexToRGB(hex) {
try {
if (!hex || typeof hex !== 'string') {
console.error('Invalid hex value provided:', hex);
return [0.5, 0.5, 1.0]; // Default blue color
}
// Make sure hex starts with #
if (hex.charAt(0) !== '#') {
hex = '#' + hex;
}
const shorthand = /^#?([a-f\\d])([a-f\\d])([a-f\\d])$/i;
hex = hex.replace(shorthand, (m, r, g, b) => {
return '#' + r + r + g + g + b + b;
});
const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);
if (!result) {
console.error('Could not parse hex value:', hex);
return [0.5, 0.5, 1.0]; // Default blue color
}
const r = parseInt(result[1], 16) / 255;
const g = parseInt(result[2], 16) / 255;
const b = parseInt(result[3], 16) / 255;
return [r, g, b];
} catch (error) {
console.error('Error in hexToRGB:', error, 'for hex value:', hex);
return [0.5, 0.5, 1.0]; // Default blue color
}
}
// FluidAnimation class implementation
class FluidAnimation {
constructor(container) {
this.container = container;
this._cache = {
dimensions: { width: 0, height: 0, aspect: 0 },
mouse: { current: { x: 0.5, y: 0.5 }, last: { x: 0.5, y: 0.5 } },
time: 0,
frameCount: 0,
autonomous: true
};
this.isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
this._frameInterval = this.isMobile ? 1000/30 : 1000/60;
this._lastFrameTime = 0;
this.mouseX = 0.5;
this.mouseY = 0.5;
this.lastX = 0.5;
this.lastY = 0.5;
this.targetX = 0.5;
this.targetY = 0.5;
this.transitionSpeed = parseFloat(container.getAttribute('data-aura-flow-transition')) || DEFAULT_CONFIG.transition;
this.isMouseDown = false;
this.config = this._parseConfig(container);
this._setup(container);
this._initWebGL();
this._setupShaders();
this._setupEvents();
this._animate();
}
_parseConfig(container) {
// Extract colors from data attributes or use defaults
const colors = [];
for (let i = 1; i <= 3; i++) {
const colorAttr = 'data-aura-flow-color' + i;
if (container.hasAttribute(colorAttr)) {
const hexColor = container.getAttribute(colorAttr);
colors.push(hexToRGB(hexColor));
}
}
// If no colors specified, use default colors
if (colors.length !== 3) {
colors.length = 0; // Clear the array
colors.push(hexToRGB(DEFAULT_CONFIG.colors[0]));
colors.push(hexToRGB(DEFAULT_CONFIG.colors[1]));
colors.push(hexToRGB(DEFAULT_CONFIG.colors[2]));
}
return {
colors: colors,
opacity: parseFloat(container.getAttribute('data-aura-flow-opacity')) || DEFAULT_CONFIG.opacity,
intensity: parseFloat(container.getAttribute('data-aura-flow-intensity')) || DEFAULT_CONFIG.intensity,
glow: parseFloat(container.getAttribute('data-aura-flow-glow')) || DEFAULT_CONFIG.glow,
electric: parseFloat(container.getAttribute('data-aura-flow-electric')) || DEFAULT_CONFIG.electric,
radius: parseFloat(container.getAttribute('data-aura-flow-radius')) || DEFAULT_CONFIG.radius,
speed: parseFloat(container.getAttribute('data-aura-flow-speed')) || DEFAULT_CONFIG.speed,
pulseSpeed: parseFloat(container.getAttribute('data-aura-flow-pulse-speed')) || DEFAULT_CONFIG.pulseSpeed
};
}
_setup(container) {
if (getComputedStyle(container).position === 'static') {
container.style.position = 'relative';
}
this.wrapper = document.createElement('div');
Object.assign(this.wrapper.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
zIndex: '1',
pointerEvents: 'none',
opacity: this.config.opacity.toString()
});
this.canvas = document.createElement('canvas');
Object.assign(this.canvas.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
mixBlendMode: 'screen',
pointerEvents: 'none'
});
this.wrapper.appendChild(this.canvas);
container.insertBefore(this.wrapper, container.firstChild);
}
_initWebGL() {
this.gl = this.canvas.getContext('webgl', {
premultipliedAlpha: false,
alpha: true
});
if (!this.gl) {
console.error('WebGL not supported');
return;
}
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.startTime = Date.now();
this.vertices = new Float32Array([-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0]);
// Initial resize to set canvas dimensions
this._resize();
}
_setupShaders() {
const vertexShader = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(vertexShader, \`
attribute vec2 position;
varying vec2 uv;
void main() {
uv = position * 0.5 + 0.5;
gl_Position = vec4(position, 0.0, 1.0);
}
\`);
this.gl.compileShader(vertexShader);
const fragmentShader = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(fragmentShader, \`
precision highp float;
varying vec2 uv;
uniform vec2 resolution;
uniform vec2 mouse;
uniform float time;
uniform vec2 lastMouse;
uniform float isMouseDown;
uniform vec3 color1;
uniform vec3 color2;
uniform vec3 color3;
uniform float intensity;
uniform float glow;
uniform float electric;
uniform float radius;
uniform float pulseSpeed;
float lightning(vec2 p, vec2 a, vec2 b) {
vec2 pa = p - a;
vec2 ba = b - a;
float h = clamp(dot(pa,ba)/dot(ba,ba), 0.0, 1.0);
float d = length(pa - ba*h);
float strength = 1.0 - smoothstep(0.0, 0.02 / electric, d);
strength *= sin(time * pulseSpeed + p.x * 10.0) * 0.5 + 0.5;
return strength;
}
void main() {
vec2 p = (uv * resolution * 2.0 - resolution) / min(resolution.x, resolution.y);
vec2 m = (mouse * resolution * 2.0 - resolution) / min(resolution.x, resolution.y);
vec2 lm = (lastMouse * resolution * 2.0 - resolution) / min(resolution.x, resolution.y);
vec3 color = vec3(0.0);
vec2 mouseVel = (m - lm) * isMouseDown;
float mouseLen = length(mouseVel);
for(float i = 0.0; i < 3.0; i++) {
vec2 pos = p - m;
float dist = length(pos);
float a = atan(pos.y, pos.x) + sin(time * 0.5) * 2.0;
float h = dist * (1.0 + mouseLen * 2.0);
vec3 col = mix(
mix(color1, color2, i / 2.0),
color3,
sin(time * 0.5 + h) * 0.5 + 0.5
);
color += col * (0.15 * intensity / abs(dist - mouseLen * 2.0));
color += col * (0.15 * intensity / abs(dist - mouseLen));
}
float glowStr = exp(-length(p - m) * (1.5 / glow));
color += mix(color1, color2, 0.5) * glowStr * 0.8;
if (mouseLen > 0.001) {
float electricEffect = lightning(p, lm, m);
electricEffect += lightning(p, lm + vec2(0.1), m + vec2(-0.1));
electricEffect += lightning(p, lm - vec2(0.1), m + vec2(0.1));
color += mix(color2, color3, 0.5) * electricEffect * electric;
}
float pulse = sin(time * pulseSpeed) * 0.5 + 0.5;
float mouseGlow = exp(-length(p - m) * (2.0 + pulse * 2.0));
color += mix(color1, color3, pulse) * mouseGlow * pulse * intensity;
color = pow(color, vec3(0.8));
float alpha = min(1.0, length(color) * 0.7);
gl_FragColor = vec4(color, alpha);
}
\`);
this.gl.compileShader(fragmentShader);
// Check for shader compile errors
if (!this.gl.getShaderParameter(vertexShader, this.gl.COMPILE_STATUS)) {
console.error('Vertex shader compile error:', this.gl.getShaderInfoLog(vertexShader));
return;
}
if (!this.gl.getShaderParameter(fragmentShader, this.gl.COMPILE_STATUS)) {
console.error('Fragment shader compile error:', this.gl.getShaderInfoLog(fragmentShader));
return;
}
this.program = this.gl.createProgram();
this.gl.attachShader(this.program, vertexShader);
this.gl.attachShader(this.program, fragmentShader);
this.gl.linkProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
console.error('Program link error:', this.gl.getProgramInfoLog(this.program));
return;
}
this.gl.useProgram(this.program);
const buffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
this.gl.bufferData(this.gl.ARRAY_BUFFER, this.vertices, this.gl.STATIC_DRAW);
const positionLocation = this.gl.getAttribLocation(this.program, 'position');
this.gl.enableVertexAttribArray(positionLocation);
this.gl.vertexAttribPointer(positionLocation, 2, this.gl.FLOAT, false, 0, 0);
this.uniformLocations = {};
['resolution', 'mouse', 'time', 'lastMouse', 'isMouseDown',
'color1', 'color2', 'color3', 'intensity', 'glow',
'electric', 'radius', 'pulseSpeed'].forEach(name => {
this.uniformLocations[name] = this.gl.getUniformLocation(this.program, name);
});
}
_setupEvents() {
this.resizeHandler = this._resize.bind(this);
window.addEventListener('resize', this.resizeHandler);
const updatePosition = (e, touch = false) => {
const rect = this.container.getBoundingClientRect();
const clientX = touch ? e.touches[0].clientX : e.clientX;
const clientY = touch ? e.touches[0].clientY : e.clientY;
if (clientX < rect.left || clientX > rect.right ||
clientY < rect.top || clientY > rect.bottom) {
this._cache.autonomous = true;
return;
}
this._cache.autonomous = false;
this.targetX = (clientX - rect.left) / rect.width;
this.targetY = 1.0 - (clientY - rect.top) / rect.height;
clearTimeout(this.autonomousTimeout);
this.autonomousTimeout = setTimeout(() => {
this._cache.autonomous = true;
}, 2000);
};
this.mouseMoveHandler = e => updatePosition(e);
this.mouseDownHandler = e => {
const rect = this.container.getBoundingClientRect();
if (e.clientX >= rect.left && e.clientX <= rect.right &&
e.clientY >= rect.top && e.clientY <= rect.bottom) {
this.isMouseDown = true;
}
};
this.mouseUpHandler = () => this.isMouseDown = false;
this.mouseLeaveHandler = () => this._cache.autonomous = true;
this.container.addEventListener('mousemove', this.mouseMoveHandler);
this.container.addEventListener('mousedown', this.mouseDownHandler);
window.addEventListener('mouseup', this.mouseUpHandler);
this.container.addEventListener('mouseleave', this.mouseLeaveHandler);
this.touchStartHandler = e => {
this.isMouseDown = true;
updatePosition(e, true);
};
this.touchMoveHandler = e => {
if (this.isMouseDown) {
updatePosition(e, true);
}
};
this.touchEndHandler = () => {
this.isMouseDown = false;
this._cache.autonomous = true;
};
this.container.addEventListener('touchstart', this.touchStartHandler, {passive: true});
this.container.addEventListener('touchmove', this.touchMoveHandler, {passive: true});
this.container.addEventListener('touchend', this.touchEndHandler, {passive: true});
}
_updateAutonomousPosition(time) {
if (this._cache.autonomous) {
const radius = 0.3;
const speed = 0.5 * this.config.speed;
this.targetX = 0.5 + Math.cos(time * speed) * radius;
this.targetY = 0.5 + Math.sin(time * speed * 1.5) * radius;
}
this.lastX = this.mouseX;
this.lastY = this.mouseY;
this.mouseX += (this.targetX - this.mouseX) * this.transitionSpeed;
this.mouseY += (this.targetY - this.mouseY) * this.transitionSpeed;
}
_resize() {
const width = this.canvas.clientWidth;
const height = this.canvas.clientHeight;
if (this.canvas.width !== width || this.canvas.height !== height) {
this.canvas.width = width;
this.canvas.height = height;
this.gl.viewport(0, 0, width, height);
}
}
_animate(timestamp = 0) {
if (timestamp - this._lastFrameTime < this._frameInterval) {
this.animationFrame = requestAnimationFrame(this._animate.bind(this));
return;
}
this._lastFrameTime = timestamp;
this._resize();
const time = (Date.now() - this.startTime) * 0.001 * this.config.speed;
this._updateAutonomousPosition(time);
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
this.gl.uniform2f(this.uniformLocations.resolution, this.canvas.width, this.canvas.height);
this.gl.uniform2f(this.uniformLocations.mouse, this.mouseX, this.mouseY);
this.gl.uniform2f(this.uniformLocations.lastMouse, this.lastX, this.lastY);
this.gl.uniform1f(this.uniformLocations.time, time);
this.gl.uniform1f(this.uniformLocations.isMouseDown, this.isMouseDown ? 1.0 : 0.0);
this.gl.uniform3fv(this.uniformLocations.color1, this.config.colors[0]);
this.gl.uniform3fv(this.uniformLocations.color2, this.config.colors[1]);
this.gl.uniform3fv(this.uniformLocations.color3, this.config.colors[2]);
this.gl.uniform1f(this.uniformLocations.intensity, this.config.intensity);
this.gl.uniform1f(this.uniformLocations.glow, this.config.glow);
this.gl.uniform1f(this.uniformLocations.electric, this.config.electric);
this.gl.uniform1f(this.uniformLocations.radius, this.config.radius);
this.gl.uniform1f(this.uniformLocations.pulseSpeed, this.config.pulseSpeed);
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
this.animationFrame = requestAnimationFrame(this._animate.bind(this));
}
destroy() {
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
}
if (this.autonomousTimeout) {
clearTimeout(this.autonomousTimeout);
}
window.removeEventListener('resize', this.resizeHandler);
this.container.removeEventListener('mousemove', this.mouseMoveHandler);
this.container.removeEventListener('mousedown', this.mouseDownHandler);
window.removeEventListener('mouseup', this.mouseUpHandler);
this.container.removeEventListener('mouseleave', this.mouseLeaveHandler);
this.container.removeEventListener('touchstart', this.touchStartHandler);
this.container.removeEventListener('touchmove', this.touchMoveHandler);
this.container.removeEventListener('touchend', this.touchEndHandler);
if (this.gl) {
this.gl.deleteProgram(this.program);
const ext = this.gl.getExtension('WEBGL_lose_context');
if (ext) ext.loseContext();
}
if (this.wrapper && this.wrapper.parentNode) {
this.wrapper.parentNode.removeChild(this.wrapper);
}
}
}
// Initialize on DOM content loaded
document.addEventListener('DOMContentLoaded', () => {
const fluidElements = new Map();
document.querySelectorAll('[data-aura-flow]').forEach(element => {
const animation = new FluidAnimation(element);
fluidElements.set(element, animation);
});
// Setup mutation observer for dynamically added elements
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.matches('[data-aura-flow]:not([data-aura-flow-initialized])')) {
const animation = new FluidAnimation(node);
fluidElements.set(node, animation);
node.dataset.auraFlowInitialized = 'true';
}
});
mutation.removedNodes.forEach(node => {
if (node.nodeType === 1 && fluidElements.has(node)) {
const animation = fluidElements.get(node);
animation.destroy();
fluidElements.delete(node);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Bricks Builder specific init
document.addEventListener('bricks-lazy-load', () => {
document.querySelectorAll('[data-aura-flow]:not([data-aura-flow-initialized])').forEach(element => {
const animation = new FluidAnimation(element);
fluidElements.set(element, animation);
element.dataset.auraFlowInitialized = 'true';
});
});
});
})();
/*
To use this Aura Flow effect in Bricks Builder:
1. Add this script to a Code element
2. Add the attribute data-aura-flow="true" to any section where you want the effect to be applied
3. Optionally add data-aura-flow-color1="#ff0000", data-aura-flow-color2="#00ff00", etc. to customize colors and other parameters
*/`;
}
function downloadJsFile() {
const jsCode = generateJavaScriptCode();
const blob = new Blob([jsCode], { type: 'application/javascript' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'aura-flow.js';
a.click();
URL.revokeObjectURL(url);
}
function initializeUI() {
// Initialize the preview
initFluidAnimation();
// Setup color inputs
const colorInputs = document.querySelectorAll('input[type="color"]');
colorInputs.forEach(input => {
const textInput = document.getElementById(input.id + '-text');
// When color picker changes
input.addEventListener('input', () => {
if (textInput) {
textInput.value = input.value;
}
updateColorConfiguration();
});
// When text input changes
if (textInput) {
textInput.addEventListener('input', () => {
const isValidHex = /^#[0-9A-F]{6}$/i.test(textInput.value);
if (isValidHex) {
input.value = textInput.value;
updateColorConfiguration();
}
});
textInput.addEventListener('blur', () => {
const isValidHex = /^#[0-9A-F]{6}$/i.test(textInput.value);
if (!isValidHex) {
textInput.value = input.value;
} else {
updateColorConfiguration();
}
});
}
});
// Setup range inputs
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
const valueElement = document.getElementById(`${input.id}-value`);
// Set initial value
if (valueElement) {
valueElement.textContent = input.value;
}
input.addEventListener('input', () => {
if (valueElement) {
valueElement.textContent = input.value;
}
// Update configuration based on the slider
switch (input.id) {
case 'opacity':
fluidConfig.opacity = parseFloat(input.value);
break;
case 'intensity':
fluidConfig.intensity = parseFloat(input.value);
break;
case 'glow':
fluidConfig.glow = parseFloat(input.value);
break;
case 'electric':
fluidConfig.electric = parseFloat(input.value);
break;
case 'radius':
fluidConfig.radius = parseFloat(input.value);
break;
case 'speed':
fluidConfig.speed = parseFloat(input.value);
break;
case 'pulse-speed':
fluidConfig.pulseSpeed = parseFloat(input.value);
break;
case 'transition':
fluidConfig.transition = parseFloat(input.value);
break;
}
// Update the preview
initFluidAnimation();
});
});
// Setup modal and download functionality
const downloadBtn = document.getElementById('download-btn');
const codeModal = document.getElementById('code-modal');
const closeModal = document.getElementById('close-modal');
const copyBtn = document.getElementById('copy-btn');
const downloadFileBtn = document.getElementById('download-file-btn');
if (downloadBtn) {
downloadBtn.addEventListener('click', () => {
const jsCode = generateJavaScriptCode();
document.getElementById('js-code').textContent = jsCode;
codeModal.classList.add('active');
});
}
if (closeModal) {
closeModal.addEventListener('click', () => {
codeModal.classList.remove('active');
});
}
if (copyBtn) {
copyBtn.addEventListener('click', () => {
const jsCode = generateJavaScriptCode();
navigator.clipboard.writeText(jsCode)
.then(() => {
copyBtn.textContent = 'Copied!';
setTimeout(() => {
copyBtn.textContent = 'Copy to Clipboard';
}, 2000);
})
.catch(err => {
console.error('Could not copy text: ', err);
});
});
}
if (downloadFileBtn) {
downloadFileBtn.addEventListener('click', downloadJsFile);
}
if (codeModal) {
codeModal.addEventListener('click', (e) => {
if (e.target === codeModal) {
codeModal.classList.remove('active');
}
});
}
}
function updateColorConfiguration() {
// Update the colors in the configuration
const colors = [];
for (let i = 1; i <= 3; i++) {
colors.push(document.getElementById('color' + i).value);
}
fluidConfig.colors = colors;
// Update the preview
initFluidAnimation();
}
// Function to handle copyable attributes
function copyAttributeToClipboard(attribute, element) {
// Remover el valor "=true" del atributo si existe
const cleanAttribute = attribute.split('=')[0];
navigator.clipboard.writeText(cleanAttribute)
.then(() => {
// Añadir animación directamente sobre el elemento copiado
if (element) {
// Guardar el texto original
const originalText = element.innerHTML;
// Crear un span para el mensaje de "Copied!"
const copiedSpan = document.createElement('span');
copiedSpan.textContent = "Copied!";
copiedSpan.style.cssText = 'position:absolute; top:0; left:0; width:100%; height:100%; display:flex; align-items:center; justify-content:center; background-color:rgba(239, 96, 19, 0.9); color:white; border-radius:4px; font-weight:bold; animation: fadeInOut 1.5s ease forwards;';
// Añadir estilos de animación
const style = document.createElement('style');
style.textContent = `
@keyframes fadeInOut {
0% { opacity: 0; }
10% { opacity: 1; }
80% { opacity: 1; }
100% { opacity: 0; }
}
`;
document.head.appendChild(style);
// Asegurar que el elemento tenga posición relativa para el posicionamiento absoluto
element.style.position = 'relative';
element.style.overflow = 'hidden';
// Añadir el span al elemento
element.appendChild(copiedSpan);
// Eliminar el span después de que termine la animación
setTimeout(() => {
element.removeChild(copiedSpan);
document.head.removeChild(style);
}, 1500);
}
// Animación mejorada de feedback visual adicional (tooltip)
const tooltip = document.createElement('div');
tooltip.textContent = 'Copied to clipboard!';
tooltip.style.cssText = 'position:fixed;padding:8px 16px;background:#333;color:white;border-radius:6px;font-size:14px;z-index:9999;transition:all 0.3s ease;opacity:0;transform:translateY(10px);box-shadow:0 4px 12px rgba(0,0,0,0.2);';
document.body.appendChild(tooltip);
// Position near cursor
tooltip.style.top = (event.clientY + 20) + 'px';
tooltip.style.left = (event.clientX + 10) + 'px';
// Show with animation
setTimeout(() => {
tooltip.style.opacity = '1';
tooltip.style.transform = 'translateY(0)';
// Hide with animation
setTimeout(() => {
tooltip.style.opacity = '0';
tooltip.style.transform = 'translateY(-10px)';
// Remove from DOM
setTimeout(() => {
document.body.removeChild(tooltip);
}, 300);
}, 1500);
}, 10);
})
.catch(err => {
console.error('Error copying: ', err);
});
}
// Add event listeners for copyable elements
document.querySelectorAll('.copy-attribute').forEach(el => {
el.addEventListener('click', function(e) {
e.preventDefault();
const attribute = this.getAttribute('data-attribute');
copyAttributeToClipboard(attribute, this);
});
});
// Initialize the UI
initializeUI();
});
</script>
</body>
</html>