Documentation
v1.9
GETTING STARTED
DYNAMIC ISLAND
Premium element
Core Background
Celestial Flow
A premium particle animation system that brings elegant, dynamic motion to your designs. Built exclusively for Bricks Builder.
Preview
Open previewQuick Setup
data-celestial-flow-animation
Required
Add this attribute to enable the animation effect
Available Presets
data-celestial-color="preset-name"
Required
red
blue
green
gold
purple
orange
pink
teal
multicolor
silver
cosmic
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>Celestial 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;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #121212;
--card-bg: #1e1e1e;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
}
}
* {
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: 2rem 1rem;
}
header {
text-align: center;
margin-bottom: 2rem;
}
h1 {
font-size: var(--text-m);
font-weight: 600;
margin-bottom: 0.5rem;
background: linear-gradient(90deg, #ef6013, #ff8c51);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
h2 {
font-size: var(--text-s);
font-weight: 400;
color: var(--text-secondary);
margin-bottom: 1.5rem;
}
.content {
display: flex;
flex-wrap: wrap;
gap: 2rem;
}
.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: 300px;
width: 100%;
position: relative;
overflow: hidden;
border-radius: var(--card-radius);
background-color: #252525;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.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.3);
}
.card-heading {
padding: 1.25rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
}
.card-content {
padding: 1.5rem;
}
.control-group {
margin-bottom: 1.5rem;
}
.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;
}
.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.5rem 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);
}
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);
}
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.75rem;
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: var(--card-bg);
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-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);
margin-bottom: 1rem;
}
.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;
}
.btn {
display: block;
width: 100%;
padding: 0.9rem 1.5rem;
background-color: var(--accent);
color: white;
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
text-align: center;
border: none;
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
}
.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.5rem;
border-radius: var(--card-radius);
margin-bottom: 1.5rem;
box-shadow: var(--shadow);
}
.instructions h3 {
font-size: var(--text-s);
margin-bottom: 0.75rem;
color: var(--text-primary);
font-weight: 600;
}
.instructions ol {
padding-left: 1.25rem;
}
.instructions li {
margin-bottom: 0.5rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.5;
}
.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;
}
@media (prefers-color-scheme: dark) {
.notice {
background-color: rgba(239, 96, 19, 0.08);
color: #ff8c51;
}
}
.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;
}
.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.5rem;
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: 1rem 1.5rem;
border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
gap: 1rem;
}
.copy-btn,
.download-file-btn {
padding: 0.7rem 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);
}
.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);
}
@media (max-width: 768px) {
.content {
flex-direction: column;
}
.preview-container {
height: 250px;
}
h1 {
font-size: var(--text-s);
}
}
.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;
}
.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);
}
</style>
</head>
<body>
<div class="instructions">
<h3>How to Use in Bricks Builder</h3>
<ol>
<li>Customize your celestial flow animation 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-celestial-flow-animation">data-celestial-flow-animation<span class="copy-icon">📋</span></div> to any container element</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 configuration!</div>
</div>
<div class="content">
<section class="preview-section">
<div class="preview-container" id="celestial-preview">
<div id="celestial-flow-container" data-celestial-flow-animation="true">
</div>
</div>
<div class="card premium-card">
<div class="card-heading">
<span class="premium-badge">BricksFusion Exclusive</span>
Get Your Custom Celestial Flow Animation
</div>
<div class="card-content">
<p class="premium-description">Add elegant and mesmerizing celestial particle animations to your website with this custom-crafted JavaScript snippet.</p>
<ul class="feature-list">
<li>Easy implementation with simple data attributes</li>
<li>Smooth animated particles with natural flow</li>
<li>Customizable colors and animation speed</li>
<li>Multiple color themes to match your brand</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">Animation Settings</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Particle Count</span>
<span class="value-text"><span id="particle-count-value">50</span></span>
</div>
<input type="range" id="particle-count" min="10" max="100" value="50">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Particle Size</span>
<span class="value-text"><span id="particle-size-value">1.5</span></span>
</div>
<input type="range" id="particle-size" min="0.5" max="3" step="0.1" value="1.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Animation Speed</span>
<span class="value-text"><span id="animation-speed-value">1</span>x</span>
</div>
<input type="range" id="animation-speed" min="0.5" max="2" step="0.1" value="1">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">Visual Settings</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Color Preset</span>
</div>
<select id="color-preset">
<option value="blue">Blue</option>
<option value="red">Red</option>
<option value="green">Green</option>
<option value="purple">Purple</option>
<option value="orange">Orange</option>
<option value="teal">Teal</option>
<option value="pink">Pink</option>
<option value="gold">Gold</option>
<option value="silver">Silver</option>
<option value="cosmic">Cosmic</option>
<option value="multicolor">Multicolor</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Particle Opacity</span>
<span class="value-text"><span id="opacity-value">0.7</span></span>
</div>
<input type="range" id="opacity" min="0.2" max="1" step="0.05" value="0.7">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Trail Length</span>
<span class="value-text"><span id="trail-length-value">15</span>px</span>
</div>
<input type="range" id="trail-length" min="5" max="30" value="15">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">Advanced Options</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Direction</span>
</div>
<select id="flow-direction">
<option value="diagonal">Diagonal (Default)</option>
<option value="upward">Upward</option>
<option value="downward">Downward</option>
<option value="random">Random</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Frame Rate</span>
<span class="value-text"><span id="fps-value">60</span> FPS</span>
</div>
<input type="range" id="fps" min="30" max="60" step="10" value="60">
</div>
</div>
</div>
</section>
</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() {
let celestialConfig = {
particleCount: 50,
particleSize: 1.5,
animationSpeed: 1,
colorPreset: 'blue',
opacity: 0.7,
trailLength: 15,
flowDirection: 'diagonal',
fps: 60
};
// Initialize the UI with default values
function initializeUI() {
// Setup range sliders
setupRangeSlider('particle-count', updatePreview);
setupRangeSlider('particle-size', updatePreview);
setupRangeSlider('animation-speed', updatePreview);
setupRangeSlider('opacity', updatePreview);
setupRangeSlider('trail-length', updatePreview);
setupRangeSlider('fps', updatePreview);
// Setup selects
document.getElementById('color-preset').addEventListener('change', updatePreview);
document.getElementById('flow-direction').addEventListener('change', updatePreview);
// Setup download button and modal
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');
downloadBtn.addEventListener('click', function() {
const jsCode = generateJavaScriptCode();
document.getElementById('js-code').textContent = jsCode;
codeModal.classList.add('active');
});
closeModal.addEventListener('click', function() {
codeModal.classList.remove('active');
});
copyBtn.addEventListener('click', function() {
const jsCode = document.getElementById('js-code').textContent;
copyToClipboard(jsCode);
showCopiedTooltip(this);
});
downloadFileBtn.addEventListener('click', downloadJsFile);
codeModal.addEventListener('click', function(e) {
if (e.target === codeModal) {
codeModal.classList.remove('active');
}
});
// Setup copy attribute functionality
setupCopyAttributes();
// Initialize preview
initializePreview();
updatePreview();
}
function setupRangeSlider(id, callback) {
const slider = document.getElementById(id);
const valueDisplay = document.getElementById(`${id}-value`);
slider.addEventListener('input', function() {
valueDisplay.textContent = this.value;
if (callback) callback();
});
}
function setupCopyAttributes() {
document.querySelectorAll('.copy-attribute').forEach(el => {
el.addEventListener('click', function(e) {
e.preventDefault();
const attribute = this.getAttribute('data-attribute');
copyToClipboard(attribute);
showCopiedTooltip(this);
});
});
}
function initializePreview() {
const container = document.getElementById('celestial-flow-container');
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
container.style.position = 'relative';
container.style.width = '100%';
container.style.height = '100%';
container.appendChild(canvas);
Object.assign(canvas.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
});
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
container.celestialCanvas = canvas;
container.celestialCtx = ctx;
container.celestialParticles = [];
container.animationFrame = null;
}
function updatePreview() {
// Get current values from UI
celestialConfig.particleCount = parseInt(document.getElementById('particle-count').value);
celestialConfig.particleSize = parseFloat(document.getElementById('particle-size').value);
celestialConfig.animationSpeed = parseFloat(document.getElementById('animation-speed').value);
celestialConfig.colorPreset = document.getElementById('color-preset').value;
celestialConfig.opacity = parseFloat(document.getElementById('opacity').value);
celestialConfig.trailLength = parseInt(document.getElementById('trail-length').value);
celestialConfig.flowDirection = document.getElementById('flow-direction').value;
celestialConfig.fps = parseInt(document.getElementById('fps').value);
// Update the preview
renderCelestialPreview();
}
function renderCelestialPreview() {
const container = document.getElementById('celestial-flow-container');
const canvas = container.celestialCanvas;
const ctx = container.celestialCtx;
// Clear any existing animation
if (container.animationFrame) {
cancelAnimationFrame(container.animationFrame);
}
// Reset or create particles
container.celestialParticles = [];
for (let i = 0; i < celestialConfig.particleCount; i++) {
container.celestialParticles.push(createParticle(canvas.width, canvas.height));
}
// Start the animation
let lastTime = 0;
const fpsInterval = 1000 / celestialConfig.fps;
function animate(currentTime) {
container.animationFrame = requestAnimationFrame(animate);
const deltaTime = currentTime - lastTime;
if (deltaTime < fpsInterval) return;
lastTime = currentTime - (deltaTime % fpsInterval);
ctx.clearRect(0, 0, canvas.width, canvas.height);
container.celestialParticles.forEach(particle => {
updateParticle(particle, canvas.width, canvas.height, deltaTime);
drawParticle(particle, ctx);
});
}
container.animationFrame = requestAnimationFrame(animate);
}
function createParticle(width, height) {
return {
x: Math.random() * width,
y: height + Math.random() * 50,
radius: Math.random() * celestialConfig.particleSize + 0.5,
speed: (Math.random() * 1.5 + 0.5) * celestialConfig.animationSpeed,
life: 0,
maxLife: Math.random() * 150 + 200,
hue: getColorFromPreset(),
saturation: '100%',
lightness: '60%'
};
}
function getColorFromPreset() {
switch (celestialConfig.colorPreset) {
case 'blue': return 220;
case 'red': return 0;
case 'green': return 120;
case 'purple': return 270;
case 'orange': return 30;
case 'teal': return 180;
case 'pink': return 330;
case 'gold': return 45;
case 'silver': return 0; // With 0% saturation
case 'cosmic': return Math.random() * 60 + 210;
case 'multicolor': return Math.random() * 360;
default: return 220;
}
}
function updateParticle(particle, width, height, deltaTime) {
// Convert deltaTime to a factor (normalized against 16ms frame)
const factor = (deltaTime / 16) * celestialConfig.animationSpeed;
// Update based on direction
switch (celestialConfig.flowDirection) {
case 'upward':
particle.y -= particle.speed * 2 * factor;
break;
case 'downward':
particle.x -= particle.speed * 0.5 * factor;
particle.y += particle.speed * 2 * factor;
break;
case 'random':
particle.x += Math.cos(particle.life * 0.05) * particle.speed * factor;
particle.y -= particle.speed * 1.2 * factor;
break;
default: // diagonal
particle.x -= particle.speed * factor;
particle.y -= particle.speed * 1.5 * factor;
}
particle.life += factor;
// Reset particle if it's off-screen or has lived its life
if (particle.life >= particle.maxLife || particle.x < -50 || particle.y < -50 || particle.x > width + 50) {
resetParticle(particle, width, height);
}
}
function resetParticle(particle, width, height) {
// Para la dirección 'downward', coloca las partículas en la parte superior
if (celestialConfig.flowDirection === 'downward') {
particle.x = Math.random() * width;
particle.y = -Math.random() * 50; // Empieza arriba de la pantalla
} else {
// Para otras direcciones, mantén el comportamiento original
particle.x = Math.random() * width;
particle.y = height + Math.random() * 50;
}
particle.radius = Math.random() * celestialConfig.particleSize + 0.5;
particle.speed = (Math.random() * 1.5 + 0.5) * celestialConfig.animationSpeed;
particle.life = 0;
particle.maxLife = Math.random() * 150 + 200;
particle.hue = getColorFromPreset();
}
function drawParticle(particle, ctx) {
const alpha = Math.sin((particle.life / particle.maxLife) * Math.PI) * celestialConfig.opacity;
// Draw particle
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
const saturation = celestialConfig.colorPreset === 'silver' ? '0%' : '100%';
ctx.fillStyle = "hsla(" + particle.hue + ", " + saturation + ", " + particle.lightness + ", " + alpha + ")";
ctx.fill();
// Draw trail
const trailLength = celestialConfig.trailLength * particle.speed;
ctx.beginPath();
ctx.moveTo(particle.x, particle.y);
// Calculate trail end point based on direction
let trailX, trailY;
switch (celestialConfig.flowDirection) {
case 'upward':
trailX = particle.x;
trailY = particle.y + trailLength;
break;
case 'downward':
trailX = particle.x + trailLength * 0.3;
trailY = particle.y - trailLength * 1.2;
break;
case 'random':
trailX = particle.x - Math.cos(particle.life * 0.05) * trailLength;
trailY = particle.y + trailLength;
break;
default: // diagonal
trailX = particle.x + trailLength * 0.7;
trailY = particle.y + trailLength;
}
ctx.lineTo(trailX, trailY);
ctx.strokeStyle = "hsla(" + particle.hue + ", " + saturation + ", " + particle.lightness + ", " + (alpha * 0.5) + ")";
ctx.lineWidth = particle.radius * 0.5;
ctx.stroke();
}
function generateJavaScriptCode() {
return "(function(window) {\n" +
" const EnhancedCelestialFlowAnimation = {\n" +
" instances: [],\n" +
" config: {\n" +
" particleCount: " + celestialConfig.particleCount + ",\n" +
" particleSize: " + celestialConfig.particleSize + ",\n" +
" animationSpeed: " + celestialConfig.animationSpeed + ",\n" +
" colorPreset: \"" + celestialConfig.colorPreset + "\",\n" +
" opacity: " + celestialConfig.opacity + ",\n" +
" trailLength: " + celestialConfig.trailLength + ",\n" +
" flowDirection: \"" + celestialConfig.flowDirection + "\",\n" +
" fps: " + celestialConfig.fps + "\n" +
" },\n" +
" init: function(options = {}) {\n" +
" const defaultOptions = {\n" +
" selector: '[data-celestial-flow-animation]',\n" +
" colorAttr: 'data-celestial-color'\n" +
" };\n" +
" const config = { ...defaultOptions, ...options };\n" +
"\n" +
" const initInstances = () => {\n" +
" document.querySelectorAll(config.selector).forEach(element => {\n" +
" if (!element.hasAttribute('data-celestial-initialized')) {\n" +
" this.createInstance(element, config);\n" +
" element.setAttribute('data-celestial-initialized', 'true');\n" +
" }\n" +
" });\n" +
" };\n" +
"\n" +
" initInstances();\n" +
" setTimeout(initInstances, 100);\n" +
" window.addEventListener('load', initInstances);\n" +
"\n" +
" const observer = new MutationObserver((mutations) => {\n" +
" mutations.forEach((mutation) => {\n" +
" if (mutation.type === 'childList') {\n" +
" initInstances();\n" +
" }\n" +
" });\n" +
" });\n" +
"\n" +
" observer.observe(document.body, { childList: true, subtree: true });\n" +
" },\n" +
"\n" +
" createInstance: function(element, config) {\n" +
" const canvas = document.createElement('canvas');\n" +
" const ctx = canvas.getContext('2d');\n" +
" element.appendChild(canvas);\n" +
"\n" +
" if (window.getComputedStyle(element).position === 'static') {\n" +
" element.style.position = 'relative';\n" +
" }\n" +
"\n" +
" Object.assign(canvas.style, {\n" +
" position: 'absolute',\n" +
" top: '0',\n" +
" left: '0',\n" +
" width: '100%',\n" +
" height: '100%',\n" +
" pointerEvents: 'none'\n" +
" });\n" +
"\n" +
" const instance = {\n" +
" element,\n" +
" canvas,\n" +
" ctx,\n" +
" width: 0,\n" +
" height: 0,\n" +
" celestialFlows: [],\n" +
" celestialFlowCount: this.config.particleCount,\n" +
" colorPreset: element.getAttribute(config.colorAttr) || this.config.colorPreset,\n" +
" lastTime: 0,\n" +
" fpsInterval: 1000 / this.config.fps,\n" +
"\n" +
" resizeCanvas: function() {\n" +
" this.width = this.element.clientWidth;\n" +
" this.height = this.element.clientHeight;\n" +
" this.canvas.width = this.width;\n" +
" this.canvas.height = this.height;\n" +
" },\n" +
"\n" +
" createCelestialFlows: function() {\n" +
" this.celestialFlows = [];\n" +
" for (let i = 0; i < this.celestialFlowCount; i++) {\n" +
" this.celestialFlows.push(new CelestialFlow(this));\n" +
" }\n" +
" },\n" +
"\n" +
" animate: function(currentTime) {\n" +
" requestAnimationFrame(this.animate.bind(this));\n" +
" const deltaTime = currentTime - this.lastTime;\n" +
" if (deltaTime < this.fpsInterval) return;\n" +
" this.lastTime = currentTime - (deltaTime % this.fpsInterval);\n" +
"\n" +
" this.ctx.clearRect(0, 0, this.width, this.height);\n" +
" this.celestialFlows.forEach(celestialFlow => {\n" +
" celestialFlow.update(deltaTime);\n" +
" celestialFlow.draw();\n" +
" });\n" +
" },\n" +
"\n" +
" resize: function() {\n" +
" this.resizeCanvas();\n" +
" this.createCelestialFlows();\n" +
" },\n" +
"\n" +
" changeColorPreset: function(preset) {\n" +
" this.colorPreset = preset;\n" +
" this.celestialFlows.forEach(celestialFlow => celestialFlow.updateColor());\n" +
" }\n" +
" };\n" +
"\n" +
" class CelestialFlow {\n" +
" constructor(instance) {\n" +
" this.instance = instance;\n" +
" this.reset();\n" +
" this.updateColor();\n" +
" }\n" +
"\n" +
" reset() {\n" +
" // Para la dirección 'downward', coloca las partículas en la parte superior\n" +
" if (EnhancedCelestialFlowAnimation.config.flowDirection === 'downward') {\n" +
" this.x = Math.random() * this.instance.width;\n" +
" this.y = -Math.random() * 50; // Empieza arriba de la pantalla\n" +
" } else {\n" +
" // Para otras direcciones, mantén el comportamiento original\n" +
" this.x = Math.random() * this.instance.width;\n" +
" this.y = this.instance.height + Math.random() * 50;\n" +
" }\n" +
" this.speed = (Math.random() * 1.5 + 0.5) * EnhancedCelestialFlowAnimation.config.animationSpeed;\n" +
" this.radius = Math.random() * EnhancedCelestialFlowAnimation.config.particleSize + 0.5;\n" +
" this.life = 0;\n" +
" this.maxLife = Math.random() * 150 + 200;\n" +
" }\n" +
"\n" +
" updateColor() {\n" +
" const colorPresets = {\n" +
" blue: { hue: 220, saturation: '100%', lightness: '60%' },\n" +
" red: { hue: 0, saturation: '100%', lightness: '60%' },\n" +
" green: { hue: 120, saturation: '100%', lightness: '50%' },\n" +
" purple: { hue: 270, saturation: '100%', lightness: '70%' },\n" +
" orange: { hue: 30, saturation: '100%', lightness: '50%' },\n" +
" multicolor: { hue: () => Math.random() * 360, saturation: '100%', lightness: '50%' },\n" +
" teal: { hue: 180, saturation: '100%', lightness: '45%' },\n" +
" pink: { hue: 330, saturation: '100%', lightness: '70%' },\n" +
" gold: { hue: 45, saturation: '100%', lightness: '55%' },\n" +
" silver: { hue: 0, saturation: '0%', lightness: '75%' },\n" +
" cosmic: { hue: () => Math.random() * 60 + 210, saturation: '100%', lightness: '60%' }\n" +
" };\n" +
"\n" +
" const preset = colorPresets[this.instance.colorPreset] || colorPresets.blue;\n" +
" this.hue = typeof preset.hue === 'function' ? preset.hue() : preset.hue;\n" +
" this.saturation = preset.saturation;\n" +
" this.lightness = preset.lightness;\n" +
" }\n" +
"\n" +
" update(deltaTime) {\n" +
" const factor = (deltaTime / 16) * EnhancedCelestialFlowAnimation.config.animationSpeed;\n" +
" \n" +
" // Update based on direction\n" +
" switch (EnhancedCelestialFlowAnimation.config.flowDirection) {\n" +
" case 'upward':\n" +
" this.y -= this.speed * 2 * factor;\n" +
" break;\n" +
" case 'downward':\n" +
" this.x -= this.speed * 0.5 * factor;\n" +
" this.y += this.speed * 2 * factor;\n" +
" break;\n" +
" case 'random':\n" +
" this.x += Math.cos(this.life * 0.05) * this.speed * factor;\n" +
" this.y -= this.speed * 1.2 * factor;\n" +
" break;\n" +
" default: // diagonal\n" +
" this.x -= this.speed * factor;\n" +
" this.y -= this.speed * 1.5 * factor;\n" +
" }\n" +
" \n" +
" this.life += factor;\n" +
" \n" +
" if (this.life >= this.maxLife || this.x < -50 || this.y < -50 || this.x > this.instance.width + 50) {\n" +
" this.reset();\n" +
" this.updateColor();\n" +
" }\n" +
" }\n" +
"\n" +
" draw() {\n" +
" const alpha = Math.sin((this.life / this.maxLife) * Math.PI) * EnhancedCelestialFlowAnimation.config.opacity;\n" +
" const { ctx } = this.instance;\n" +
"\n" +
" ctx.beginPath();\n" +
" ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);\n" +
" ctx.fillStyle = \"hsla(\" + this.hue + \", \" + this.saturation + \", \" + this.lightness + \", \" + alpha + \")\";\n" +
" ctx.fill();\n" +
"\n" +
" // Draw trail\n" +
" const trailLength = EnhancedCelestialFlowAnimation.config.trailLength * this.speed;\n" +
" let trailX, trailY;\n" +
" \n" +
" ctx.beginPath();\n" +
" ctx.moveTo(this.x, this.y);\n" +
" \n" +
" switch (EnhancedCelestialFlowAnimation.config.flowDirection) {\n" +
" case 'upward':\n" +
" trailX = this.x;\n" +
" trailY = this.y + trailLength;\n" +
" break;\n" +
" case 'downward':\n" +
" trailX = this.x + trailLength * 0.3;\n" +
" trailY = this.y - trailLength * 1.2;\n" +
" break;\n" +
" case 'random':\n" +
" trailX = this.x - Math.cos(this.life * 0.05) * trailLength;\n" +
" trailY = this.y + trailLength;\n" +
" break;\n" +
" default: // diagonal\n" +
" trailX = this.x + trailLength * 0.7;\n" +
" trailY = this.y + trailLength;\n" +
" }\n" +
" \n" +
" ctx.lineTo(trailX, trailY);\n" +
" ctx.strokeStyle = \"hsla(\" + this.hue + \", \" + this.saturation + \", \" + this.lightness + \", \" + (alpha * 0.5) + \")\";\n" +
" ctx.lineWidth = this.radius * 0.5;\n" +
" ctx.stroke();\n" +
" }\n" +
" }\n" +
"\n" +
" instance.resizeCanvas();\n" +
" instance.createCelestialFlows();\n" +
" instance.animate(0);\n" +
"\n" +
" let resizeTimeout;\n" +
" window.addEventListener('resize', () => {\n" +
" clearTimeout(resizeTimeout);\n" +
" resizeTimeout = setTimeout(() => instance.resize(), 250);\n" +
" });\n" +
"\n" +
" this.instances.push(instance);\n" +
" },\n" +
"\n" +
" changeColorPreset: function(element, preset) {\n" +
" const instance = this.instances.find(inst => inst.element === element);\n" +
" if (instance) {\n" +
" instance.changeColorPreset(preset);\n" +
" }\n" +
" }\n" +
" };\n" +
"\n" +
" window.EnhancedCelestialFlowAnimation = EnhancedCelestialFlowAnimation;\n" +
" EnhancedCelestialFlowAnimation.init();\n" +
"})(window);";
}
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 = 'celestial-flow-animation.js';
a.click();
URL.revokeObjectURL(url);
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.catch(err => {
console.error('Error copying to clipboard:', err);
});
}
function showCopiedTooltip(element) {
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 element
const rect = element.getBoundingClientRect();
tooltip.style.top = (rect.top - 40) + 'px';
tooltip.style.left = (rect.left + rect.width/2 - 80) + '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);
}
initializeUI();
});
</script>
</body>
</html>