NEW RELEASE
v2.2 - 40+ new elementsWhat's new
<!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 - BricksFusion</title>
  <style>
    :root {
  --background: #000;
  --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;
  --action-bar-height: 70px;
  --success: #28a745;
  --warning: #ffc107;
  --danger: #dc3545;
}

* {
  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;
  padding-bottom: var(--action-bar-height);
}

.action-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: var(--action-bar-height);
  background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
  border-top: 1px solid var(--border);
  z-index: 1000;
  display: flex;
  align-items: center;
  padding: 0 1.5rem;
  gap: 1rem;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 1;
}

.breadcrumb-item {
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-weight: 500;
  text-decoration: none;
  transition: var(--transition);
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
}

.breadcrumb-item:hover {
  color: var(--text-primary);
  background-color: rgba(255, 255, 255, 0.05);
}

.breadcrumb-item.active {
  color: var(--accent);
  background-color: rgba(239, 96, 19, 0.1);
}

.breadcrumb-separator {
  color: var(--text-secondary);
  font-size: var(--text-xs);
  opacity: 0.5;
}

.action-buttons {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.action-btn {
  padding: 0.6rem 1rem;
  background-color: var(--card-bg);
  color: var(--text-primary);
  font-family: var(--font);
  font-size: var(--text-xs);
  font-weight: 500;
  border: 1px solid var(--border);
  border-radius: var(--button-radius);
  cursor: pointer;
  transition: var(--transition);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  white-space: nowrap;
}

.action-btn:hover {
  background-color: var(--card-bg-hover);
  border-color: var(--accent);
  transform: translateY(-1px);
}

.action-btn.primary {
  background: linear-gradient(90deg, var(--accent), #ff8c51);
  border-color: var(--accent);
  color: white;
}

.action-btn.primary:hover {
  background: linear-gradient(90deg, var(--accent-hover), #e67a3f);
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}

.data-attribute-display {
  background-color: rgba(50, 50, 50, 0.8);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
  font-size: var(--text-xs);
  color: #ff8c51;
  cursor: pointer;
  transition: var(--transition);
  user-select: all;
}

.data-attribute-display:hover {
  background-color: rgba(239, 96, 19, 0.2);
  border-color: var(--accent);
}

.container {
  max-width: 100%;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}

.page-header {
  text-align: center;
  margin-bottom: 2rem;
}

.page-title {
  font-size: 2.5rem;
  font-weight: 700;
  color: var(--text-primary);
  margin-bottom: 0.5rem;
  background: linear-gradient(90deg, var(--accent), #ff8c51);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.page-subtitle {
  font-size: var(--text-s);
  color: var(--text-secondary);
  font-weight: 500;
}

.instructions-toggle {
  margin-bottom: 2rem;
}

.instructions-card {
  background-color: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: var(--card-radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  transition: var(--transition);
}

.instructions-header {
  padding: 1rem 1.5rem;
  cursor: pointer;
  transition: var(--transition);
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid transparent;
}

.instructions-header:hover {
  background-color: var(--card-bg-hover);
}

.instructions-card.expanded .instructions-header {
  border-bottom-color: var(--border);
}

.instructions-title {
  font-size: var(--text-s);
  font-weight: 600;
}

.toggle-icon {
  font-size: 1.2em;
  transition: transform 0.3s ease;
}

.toggle-icon.expanded {
  transform: rotate(180deg);
}

.instructions-content {
  padding: 0 1.5rem;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease, padding 0.3s ease;
}

.instructions-content.show {
  max-height: 500px;
  padding: 1.5rem;
}

.instructions-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.5rem;
}

.how-to-use ol {
  padding-left: 1.5rem;
}

.how-to-use li {
  margin-bottom: 0.75rem;
  font-size: var(--text-xs);
  color: var(--text-secondary);
  line-height: 1.6;
}

.how-to-use strong {
  color: var(--text-primary);
  font-weight: 600;
}

.how-to-use 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;
}

.content {
  display: grid;
  grid-template-columns: 1fr 500px;
  gap: 2rem;
  align-items: start;
}

.preview-section {
  position: sticky;
  top: 2rem;
}

.controls-section {
  max-width: 500px;
}

.card {
  background-color: var(--card-bg);
  border-radius: var(--card-radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  margin-bottom: 1.5rem;
  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: 400px;
  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;
}

.preview-controls {
  position: absolute;
  top: 1rem;
  right: 1rem;
  display: flex;
  gap: 0.5rem;
  z-index: 10;
}

.preview-btn {
  padding: 0.5rem;
  background-color: rgba(0, 0, 0, 0.7);
  color: white;
  border: 1px solid rgba(255, 255, 255, 0.2);
  border-radius: 6px;
  cursor: pointer;
  transition: var(--transition);
  font-size: var(--text-xs);
  backdrop-filter: blur(5px);
}

.preview-btn:hover {
  background-color: var(--accent);
  border-color: var(--accent);
}

.preview-btn svg {
  width: 18px;
  height: 18px;
  stroke: currentColor;
}

.background-selector-wrapper {
  position: relative;
  display: inline-block;
}

.background-selector-btn {
  position: relative;
}

.background-selector-btn:hover {
  background-color: rgba(239, 96, 19, 0.2);
  border-color: var(--accent);
  box-shadow: 0 0 8px rgba(239, 96, 19, 0.3);
}

.hidden-color-input {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
  z-index: 1;
}

.card-heading {
  padding: 1rem 1.5rem;
  font-size: var(--text-s);
  font-weight: 600;
  border-bottom: 1px solid var(--border);
  letter-spacing: 0.3px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.card-actions {
  display: flex;
  gap: 0.5rem;
}

.card-action-btn {
  padding: 0.4rem 0.8rem;
  background-color: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
  font-size: var(--text-xs);
  transition: var(--transition);
}

.card-action-btn:hover {
  color: var(--text-primary);
  border-color: var(--accent);
  background-color: rgba(239, 96, 19, 0.1);
}

.card-content {
  padding: 1.5rem;
}

.control-group {
  margin-bottom: 1.5rem;
  position: relative;
}

.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;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.help-tooltip {
  cursor: help;
  opacity: 0.7;
  transition: var(--transition);
}

.help-tooltip:hover {
  opacity: 1;
  color: var(--accent);
}

.value-display {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.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;
  min-width: 45px;
  text-align: center;
}

.reset-btn {
  padding: 0.2rem 0.4rem;
  background-color: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--border);
  border-radius: 4px;
  cursor: pointer;
  font-size: 10px;
  transition: var(--transition);
}

.reset-btn:hover {
  color: var(--danger);
  border-color: var(--danger);
  background-color: rgba(220, 53, 69, 0.1);
}

input[type="range"] {
  -webkit-appearance: none;
  width: 100%;
  height: 6px;
  background: var(--track);
  border-radius: 3px;
  outline: none;
  margin: 0.8rem 0;
  position: relative;
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  background: var(--thumb);
  border-radius: 50%;
  cursor: pointer;
  transition: var(--transition);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}

input[type="range"]::-webkit-slider-thumb:hover {
  transform: scale(1.2);
  box-shadow: 0 0 10px rgba(239, 96, 19, 0.5);
}

.color-list {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.color-row {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: 1rem 1.25rem;
  background-color: rgba(30, 30, 30, 0.7);
  border: 1px solid var(--border);
  border-radius: var(--input-radius);
  transition: var(--transition);
}

.color-row:hover {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.1);
}

.color-picker-container {
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid var(--border);
  cursor: pointer;
  transition: var(--transition);
  flex-shrink: 0;
  background: var(--card-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  --selected-color: #3b82f6;
}

.color-picker-container:hover {
  border-color: var(--accent);
  transform: scale(1.05);
  box-shadow: 0 0 12px rgba(239, 96, 19, 0.3);
}

.color-picker-container::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--selected-color, #3b82f6);
  border-radius: 6px;
  transition: var(--transition);
}

input[type="color"] {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: none;
  cursor: pointer;
  background: transparent;
  opacity: 0;
  z-index: 2;
}

.color-input-group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.color-label {
  font-size: 10px;
  font-weight: 500;
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-left: 0.25rem;
}

.color-input {
  padding: 0.5rem 0.75rem;
  background-color: rgba(0, 0, 0, 0.3);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-primary);
  font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
  font-size: 12px;
  transition: var(--transition);
  min-width: 0;
}

.color-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.2);
  outline: none;
}

.color-input.invalid {
  border-color: var(--danger);
  box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.2);
}

.hex-input,
.hsl-input {
  width: 100%;
}

.color-input-group:nth-child(2) {
  flex: 0.3;
}

.color-input-group:nth-child(3) {
  flex: 0.7;
}

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);
}

select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}

.notification {
  position: fixed;
  bottom: calc(var(--action-bar-height) + 1rem);
  left: 50%;
  background-color: var(--success);
  color: white;
  padding: 0.75rem 1rem;
  border-radius: var(--input-radius);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  z-index: 1001;
  transform: translate(-50%, 200px);
  opacity: 0;
  transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
  font-size: var(--text-xs);
  font-weight: 500;
  max-width: 320px;
  word-wrap: break-word;
  line-height: 1.4;
  text-align: center;
}

.notification.show {
  transform: translate(-50%, 0);
  opacity: 1;
}

@media (max-width: 1200px) {
  .content {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
  
  .preview-section {
    position: static;
  }
  
  .controls-section {
    max-width: 100%;
  }
}

@media (max-width: 768px) {
  .action-bar {
    flex-direction: column;
    height: auto;
    min-height: var(--action-bar-height);
    padding: 0.75rem;
  }
  
  .breadcrumb {
    order: 1;
    width: 100%;
  }
  
  .action-buttons {
    order: 2;
    width: 100%;
    justify-content: center;
    flex-wrap: wrap;
  }
  
  body {
    padding-bottom: calc(var(--action-bar-height) + 20px);
  }
  
  .notification {
    bottom: calc(var(--action-bar-height) + 2rem);
    max-width: 280px;
    transform: translate(-50%, 250px);
  }
  
  .notification.show {
    transform: translate(-50%, 0);
    opacity: 1;
  }
  
  .color-row {
    flex-direction: column;
    align-items: stretch;
    gap: 1rem;
    padding: 1rem;
  }
  
  .color-picker-container {
    align-self: center;
    margin-bottom: 0.5rem;
  }
  
  .color-input-group {
    align-items: stretch;
  }
  
  .hex-input,
  .hsl-input {
    width: 100%;
  }

  
  .preview-container {
    height: 300px;
  }
  
  .data-attribute-display {
    font-size: 10px;
    padding: 0.4rem 0.6rem;
  }
  
  .action-btn {
    font-size: 11px;
    padding: 0.5rem 0.8rem;
  }
  
  .page-title {
    font-size: 2rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

button:focus-visible,
input:focus-visible,
.action-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  background: var(--background);
}

::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

.loading {
  opacity: 0.6;
  pointer-events: none;
  position: relative;
}

.loading::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 20px;
  height: 20px;
  margin: -10px 0 0 -10px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}
  </style>
</head>
<body>
  <div class="action-bar">
    <nav class="breadcrumb">
      <a href="https://bricksfusion.com" class="breadcrumb-item">Home</a>
      <span class="breadcrumb-separator">›</span>
      <a href="https://bricksfusion.com/corebackground/" class="breadcrumb-item">Core Backgrounds</a>
      <span class="breadcrumb-separator">›</span>
      <span class="breadcrumb-item active">Celestial Flow</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-celestial-flow-animation
      </div>
      <button class="action-btn primary" id="download-config" title="Copy JavaScript code (Ctrl+D)" data-protection-animation="true">
        <span>📋</span>
        Copy JS
      </button>
      <button class="action-btn" id="copy-full-section" title="Copy complete section JSON for Bricks Builder (Ctrl+S)" data-protection-animation="true">
        <span>📦</span>
        Copy Full Section
      </button>
    </div>
  </div>

  <div class="container">
    <div class="page-header">
      <h1 class="page-title">Celestial Flow</h1>
      <p class="page-subtitle">Interactive particle animations for Bricks Builder</p>
    </div>

    <div class="instructions-toggle">
      <div class="instructions-card" id="instructions-card">
        <div class="instructions-header" id="instructions-toggle">
          <div class="instructions-title">
            How to Use & Code Information
          </div>
          <span class="toggle-icon">▼</span>
        </div>
        <div class="instructions-content" id="instructions-content">
          <div class="instructions-grid">
            <div class="how-to-use">
              <ol>
                <li>Customize your celestial flow animation using the controls below</li>
                <li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
                <li>In Bricks Builder, add a <strong>Code</strong> element</li>
                <li>Paste or upload the JavaScript code</li>
                <li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-celestial-flow-animation</code> as attribute name (leave value empty)</li>
              </ol>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="content">
      <section class="preview-section">
        <div class="preview-container" id="celestial-preview" data-celestial-flow-animation="true">
          <div class="preview-content">Interactive Celestial Flow Preview</div>
          <div class="preview-controls">
            <button class="preview-btn" id="randomize-celestial" title="Randomize (R)">🎲</button>
            <div class="background-selector-wrapper">
              <button class="preview-btn background-selector-btn" id="background-selector">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                  <polygon points="12,2 2,7 12,12 22,7"/>
                  <polyline points="2,17 12,22 22,17"/>
                  <polyline points="2,12 12,17 22,12"/>
                </svg>
              </button>
              <input type="color" id="preview-background-picker" class="hidden-color-input" value="#000000" title="Change Preview Background (B)">
            </div>
          </div>
        </div>
      </section>

      <section class="controls-section">
        <div class="card">
          <div class="card-heading">
            Particle Color
            <div class="card-actions">
              <button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="color-list">
              <div class="color-row">
                <div class="color-picker-container">
                  <input type="color" id="particle-color" value="#3b82f6">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HEX</span>
                  <input type="text" class="color-input hex-input" id="particle-color-hex" value="#3b82f6" placeholder="#FFFFFF">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HSL</span>
                  <input type="text" class="color-input hsl-input" id="particle-color-hsl" placeholder="hsl(0, 100%, 50%)">
                </div>
              </div>
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Particle Opacity
                  <span class="help-tooltip" title="Transparency of particles">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="opacity-value">0.7</span></span>
                  <button class="reset-btn" onclick="resetParameter('opacity', 0.7)">↺</button>
                </div>
              </div>
              <input type="range" id="opacity" min="0.2" max="1" step="0.05" value="0.7">
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Animation Settings
            <div class="card-actions">
              <button class="card-action-btn" id="reset-animation" title="Reset Animation Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Particle Count
                  <span class="help-tooltip" title="Number of particles in the animation">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="particle-count-value">50</span></span>
                  <button class="reset-btn" onclick="resetParameter('particle-count', 50)">↺</button>
                </div>
              </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 class="help-tooltip" title="Size of individual particles">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="particle-size-value">1.5</span></span>
                  <button class="reset-btn" onclick="resetParameter('particle-size', 1.5)">↺</button>
                </div>
              </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 class="help-tooltip" title="Speed multiplier for the animation">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="animation-speed-value">1</span>x</span>
                  <button class="reset-btn" onclick="resetParameter('animation-speed', 1)">↺</button>
                </div>
              </div>
              <input type="range" id="animation-speed" min="0.5" max="2" step="0.1" value="1">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Trail Length
                  <span class="help-tooltip" title="Length of particle trails">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="trail-length-value">15</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('trail-length', 15)">↺</button>
                </div>
              </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 class="card-actions">
              <button class="card-action-btn" id="reset-advanced" title="Reset Advanced Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">Flow 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 class="help-tooltip" title="Animation frame rate (lower = better performance)">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="fps-value">60</span> FPS</span>
                  <button class="reset-btn" onclick="resetParameter('fps', 60)">↺</button>
                </div>
              </div>
              <input type="range" id="fps" min="30" max="60" step="10" value="60">
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

  <div class="notification" id="notification"></div>

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let celestialConfig = {
        particleCount: 50,
        particleSize: 1.5,
        animationSpeed: 1,
        particleColor: '#3b82f6',
        opacity: 0.7,
        trailLength: 15,
        flowDirection: 'diagonal',
        fps: 60
      };

      const defaultConfig = { ...celestialConfig };

      let activeCelestial = null;
      
      function initCelestialFlow() {
        const sections = document.querySelectorAll('[data-celestial-flow-animation]:not([data-celestial-initialized="true"])');
        
        sections.forEach((section) => {
          const particleCount = section.hasAttribute('data-celestial-particle-count') 
            ? parseInt(section.getAttribute('data-celestial-particle-count')) 
            : celestialConfig.particleCount;
            
          const particleSize = section.hasAttribute('data-celestial-particle-size') 
            ? parseFloat(section.getAttribute('data-celestial-particle-size')) 
            : celestialConfig.particleSize;
            
          const animationSpeed = section.hasAttribute('data-celestial-animation-speed') 
            ? parseFloat(section.getAttribute('data-celestial-animation-speed')) 
            : celestialConfig.animationSpeed;
            
          const particleColor = section.hasAttribute('data-celestial-color') 
            ? section.getAttribute('data-celestial-color') 
            : celestialConfig.particleColor;
            
          const opacity = section.hasAttribute('data-celestial-opacity') 
            ? parseFloat(section.getAttribute('data-celestial-opacity')) 
            : celestialConfig.opacity;
            
          const trailLength = section.hasAttribute('data-celestial-trail-length') 
            ? parseInt(section.getAttribute('data-celestial-trail-length')) 
            : celestialConfig.trailLength;
            
          const flowDirection = section.hasAttribute('data-celestial-flow-direction') 
            ? section.getAttribute('data-celestial-flow-direction') 
            : celestialConfig.flowDirection;
            
          const fps = section.hasAttribute('data-celestial-fps') 
            ? parseInt(section.getAttribute('data-celestial-fps')) 
            : celestialConfig.fps;
            
          const options = {
            particleCount,
            particleSize,
            animationSpeed,
            particleColor,
            opacity,
            trailLength,
            flowDirection,
            fps
          };
          
          setupCelestialFlow(section, options);
          section.dataset.celestialInitialized = 'true';
          
          if (section.id === 'celestial-preview') {
            activeCelestial = { element: section, options };
            celestialConfig = {
              particleCount: options.particleCount,
              particleSize: options.particleSize,
              animationSpeed: options.animationSpeed,
              particleColor: options.particleColor,
              opacity: options.opacity,
              trailLength: options.trailLength,
              flowDirection: options.flowDirection,
              fps: options.fps
            };
          }
        });
      }
      
      function setupCelestialFlow(element, options) {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        
        element.appendChild(canvas);
        
        if (window.getComputedStyle(element).position === 'static') {
          element.style.position = 'relative';
        }
        
        Object.assign(canvas.style, {
          position: 'absolute',
          top: '0',
          left: '0',
          width: '100%',
          height: '100%',
          pointerEvents: 'none'
        });
        
        canvas.width = element.clientWidth;
        canvas.height = element.clientHeight;
        
        element.celestialCanvas = canvas;
        element.celestialCtx = ctx;
        element.celestialParticles = [];
        element.animationFrame = null;
        
        for (let i = 0; i < options.particleCount; i++) {
          element.celestialParticles.push(createParticle(canvas.width, canvas.height, options));
        }
        
        startCelestialAnimation(element, options);
      }
      
      function createParticle(width, height, options) {
        const color = hexToHsl(options.particleColor);
        return {
          x: Math.random() * width,
          y: height + Math.random() * 50,
          radius: Math.random() * options.particleSize + 0.5,
          speed: (Math.random() * 1.5 + 0.5) * options.animationSpeed,
          life: 0,
          maxLife: Math.random() * 150 + 200,
          hue: color.h,
          saturation: color.s + '%',
          lightness: color.l + '%'
        };
      }
      
      function hexToHsl(hex) {
        const r = parseInt(hex.slice(1, 3), 16) / 255;
        const g = parseInt(hex.slice(3, 5), 16) / 255;
        const b = parseInt(hex.slice(5, 7), 16) / 255;
        
        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;
        
        if (max === min) {
          h = s = 0;
        } else {
          const d = max - min;
          s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
          switch (max) {
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
          }
          h /= 6;
        }
        
        return {
          h: Math.round(h * 360),
          s: Math.round(s * 100),
          l: Math.round(l * 100)
        };
      }

      function hslToHex(hsl) {
        const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
        if (!match) return null;
        
        let h = parseInt(match[1]) / 360;
        let s = parseInt(match[2]) / 100;
        let l = parseInt(match[3]) / 100;
        
        const hue2rgb = (p, q, t) => {
          if (t < 0) t += 1;
          if (t > 1) t -= 1;
          if (t < 1/6) return p + (q - p) * 6 * t;
          if (t < 1/2) return q;
          if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
          return p;
        };
        
        let r, g, b;
        if (s === 0) {
          r = g = b = l;
        } else {
          const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
          const p = 2 * l - q;
          r = hue2rgb(p, q, h + 1/3);
          g = hue2rgb(p, q, h);
          b = hue2rgb(p, q, h - 1/3);
        }
        
        const toHex = (c) => {
          const hex = Math.round(c * 255).toString(16);
          return hex.length === 1 ? '0' + hex : hex;
        };
        
        return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
      }

      function isValidHex(hex) {
        return /^#[0-9A-F]{6}$/i.test(hex);
      }

      function isValidHsl(hsl) {
        return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
      }

      function formatHex(value) {
        let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
        
        if (!hex.startsWith('#')) {
          hex = '#' + hex;
        }
        
        if (hex.length > 7) {
          hex = hex.substring(0, 7);
        }
        
        return hex.toUpperCase();
      }

      function formatHsl(value) {
        const cleanValue = value.replace(/[^\d,\s]/g, '');
        const numbers = cleanValue.match(/\d+/g);
        
        if (!numbers || numbers.length < 3) {
          const partialMatch = value.match(/(\d+)/g);
          if (partialMatch && partialMatch.length >= 1) {
            const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
            const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
            const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
            return `hsl(${h}, ${s}%, ${l}%)`;
          }
          return value;
        }
        
        let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
        let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
        let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
        
        return `hsl(${h}, ${s}%, ${l}%)`;
      }
      
      function startCelestialAnimation(element, options) {
        let lastTime = 0;
        const fpsInterval = 1000 / options.fps;
        
        function animate(currentTime) {
          element.animationFrame = requestAnimationFrame(animate);
          
          const deltaTime = currentTime - lastTime;
          if (deltaTime < fpsInterval) return;
          
          lastTime = currentTime - (deltaTime % fpsInterval);
          
          element.celestialCtx.clearRect(0, 0, element.celestialCanvas.width, element.celestialCanvas.height);
          
          element.celestialParticles.forEach(particle => {
            updateParticle(particle, element.celestialCanvas.width, element.celestialCanvas.height, deltaTime, options);
            drawParticle(particle, element.celestialCtx, options);
          });
        }
        
        element.animationFrame = requestAnimationFrame(animate);
        
        const observer = new IntersectionObserver((entries) => {
          entries.forEach(entry => {
            if (entry.isIntersecting) {
              if (!element.animationFrame) {
                element.animationFrame = requestAnimationFrame(animate);
              }
            } else {
              if (element.animationFrame) {
                cancelAnimationFrame(element.animationFrame);
                element.animationFrame = null;
              }
            }
          });
        }, { threshold: 0 });
        
        observer.observe(element);
        
        element._cleanupCelestial = () => {
          if (element.animationFrame) {
            cancelAnimationFrame(element.animationFrame);
          }
          observer.disconnect();
          if (element.celestialCanvas && element.celestialCanvas.parentNode) {
            element.celestialCanvas.parentNode.removeChild(element.celestialCanvas);
          }
          element.dataset.celestialInitialized = 'false';
        };
      }
      
      function updateParticle(particle, width, height, deltaTime, options) {
        const factor = (deltaTime / 16) * options.animationSpeed;
        
        switch (options.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:
            particle.x -= particle.speed * factor;
            particle.y -= particle.speed * 1.5 * factor;
        }
        
        particle.life += factor;
        
        if (particle.life >= particle.maxLife || particle.x < -50 || particle.y < -50 || particle.x > width + 50) {
          resetParticle(particle, width, height, options);
        }
      }
      
      function resetParticle(particle, width, height, options) {
        if (options.flowDirection === 'downward') {
          particle.x = Math.random() * width;
          particle.y = -Math.random() * 50;
        } else {
          particle.x = Math.random() * width;
          particle.y = height + Math.random() * 50;
        }
        
        const color = hexToHsl(options.particleColor);
        particle.radius = Math.random() * options.particleSize + 0.5;
        particle.speed = (Math.random() * 1.5 + 0.5) * options.animationSpeed;
        particle.life = 0;
        particle.maxLife = Math.random() * 150 + 200;
        particle.hue = color.h;
        particle.saturation = color.s + '%';
        particle.lightness = color.l + '%';
      }
      
      function drawParticle(particle, ctx, options) {
        const alpha = Math.sin((particle.life / particle.maxLife) * Math.PI) * options.opacity;
        
        ctx.beginPath();
        ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
        ctx.fillStyle = "hsla(" + particle.hue + ", " + particle.saturation + ", " + particle.lightness + ", " + alpha + ")";
        ctx.fill();
        
        const trailLength = options.trailLength * particle.speed;
        
        ctx.beginPath();
        ctx.moveTo(particle.x, particle.y);
        
        let trailX, trailY;
        
        switch (options.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:
            trailX = particle.x + trailLength * 0.7;
            trailY = particle.y + trailLength;
        }
        
        ctx.lineTo(trailX, trailY);
        ctx.strokeStyle = "hsla(" + particle.hue + ", " + particle.saturation + ", " + particle.lightness + ", " + (alpha * 0.5) + ")";
        ctx.lineWidth = particle.radius * 0.5;
        ctx.stroke();
      }
      
      function updateCelestialPreview() {
        const previews = ['celestial-preview'];
        
        previews.forEach(previewId => {
          const preview = document.getElementById(previewId);
          if (!preview) return;
          
          if (preview._cleanupCelestial) {
            preview._cleanupCelestial();
          }
          
          preview.setAttribute('data-celestial-flow-animation', 'true');
          preview.setAttribute('data-celestial-particle-count', celestialConfig.particleCount);
          preview.setAttribute('data-celestial-particle-size', celestialConfig.particleSize);
          preview.setAttribute('data-celestial-animation-speed', celestialConfig.animationSpeed);
          preview.setAttribute('data-celestial-color', celestialConfig.particleColor);
          preview.setAttribute('data-celestial-opacity', celestialConfig.opacity);
          preview.setAttribute('data-celestial-trail-length', celestialConfig.trailLength);
          preview.setAttribute('data-celestial-flow-direction', celestialConfig.flowDirection);
          preview.setAttribute('data-celestial-fps', celestialConfig.fps);
        });
        
        initCelestialFlow();
      }

      function generateRandomColor() {
        return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
      }

      function showNotification(message, type = 'success') {
        const notification = document.getElementById('notification');
        notification.textContent = message;
        notification.className = `notification ${type}`;
        
        notification.offsetHeight;
        
        notification.style.visibility = 'visible';
        notification.classList.add('show');
        
        setTimeout(() => {
          notification.classList.remove('show');
          
          setTimeout(() => {
            if (!notification.classList.contains('show')) {
              notification.style.visibility = 'hidden';
            }
          }, 400);
        }, 3000);
      }

      function generateUniqueId() {
        return Math.random().toString(36).substring(2, 8);
      }

      function generateFullSectionJSON() {
  // Generar IDs únicos
  const sectionId = generateUniqueId();
  const containerId = generateUniqueId();
  const codeId = generateUniqueId();
  const attributeId = generateUniqueId();
  
  // Obtener el código JavaScript generado
  const jsCode = generateJavaScriptCode();
  
  // Crear la estructura JSON completa
  const fullSectionData = {
    "content": [
      {
        "id": sectionId,
        "name": "section",
        "parent": 0,
        "children": [containerId, codeId],
        "settings": {
          "_attributes": [
            {
              "id": attributeId,
              "name": "data-celestial-flow-animation"
            }
          ],
          "_height": "500",
          "_justifyContent": "center",
          "_background": {
            "color": {
              "hex": "#000000"
            }
          }
        },
        "label": "Celestial Flow Section"
      },
      {
        "id": containerId,
        "name": "container",
        "parent": sectionId,
        "children": [],
        "settings": {}
      },
      {
        "id": codeId,
        "name": "code",
        "parent": sectionId,
        "children": [],
        "settings": {
          "javascriptCode": jsCode,
          "executeCode": true,
          "_display": "none"
        },
        "label": "Celestial Flow JS"
      }
    ],
    "source": "bricksCopiedElements",
    "sourceUrl": "https://bricksfusion.com",
    "version": "2.0.1",
    "globalClasses": [],
    "globalElements": []
  };
  
  return JSON.stringify(fullSectionData, null, 2);
}

      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" +
        "      particleColor: \"" + celestialConfig.particleColor + "\",\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" +
        "        particleColor: element.getAttribute(config.colorAttr) || this.config.particleColor,\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" +
        "        changeParticleColor: function(color) {\n" +
        "          this.particleColor = color;\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" +
        "          if (EnhancedCelestialFlowAnimation.config.flowDirection === 'downward') {\n" +
        "            this.x = Math.random() * this.instance.width;\n" +
        "            this.y = -Math.random() * 50;\n" +
        "          } else {\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" +
        "        hexToHsl(hex) {\n" +
        "          const r = parseInt(hex.slice(1, 3), 16) / 255;\n" +
        "          const g = parseInt(hex.slice(3, 5), 16) / 255;\n" +
        "          const b = parseInt(hex.slice(5, 7), 16) / 255;\n" +
        "          const max = Math.max(r, g, b);\n" +
        "          const min = Math.min(r, g, b);\n" +
        "          let h, s, l = (max + min) / 2;\n" +
        "          if (max === min) {\n" +
        "            h = s = 0;\n" +
        "          } else {\n" +
        "            const d = max - min;\n" +
        "            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n" +
        "            switch (max) {\n" +
        "              case r: h = (g - b) / d + (g < b ? 6 : 0); break;\n" +
        "              case g: h = (b - r) / d + 2; break;\n" +
        "              case b: h = (r - g) / d + 4; break;\n" +
        "            }\n" +
        "            h /= 6;\n" +
        "          }\n" +
        "          return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };\n" +
        "        }\n" +
        "\n" +
        "        updateColor() {\n" +
        "          const hsl = this.hexToHsl(this.instance.particleColor);\n" +
        "          this.hue = hsl.h;\n" +
        "          this.saturation = hsl.s + '%';\n" +
        "          this.lightness = hsl.l + '%';\n" +
        "        }\n" +
        "\n" +
        "        update(deltaTime) {\n" +
        "          const factor = (deltaTime / 16) * EnhancedCelestialFlowAnimation.config.animationSpeed;\n" +
        "          \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:\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" +
        "          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:\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" +
        "    changeParticleColor: function(element, color) {\n" +
        "      const instance = this.instances.find(inst => inst.element === element);\n" +
        "      if (instance) {\n" +
        "        instance.changeParticleColor(color);\n" +
        "      }\n" +
        "    }\n" +
        "  };\n" +
        "\n" +
        "  window.EnhancedCelestialFlowAnimation = EnhancedCelestialFlowAnimation;\n" +
        "  EnhancedCelestialFlowAnimation.init();\n" +
        "})(window);";
      }

      function copyJsToClipboard() {
        const jsCode = generateJavaScriptCode();
        
        navigator.clipboard.writeText(jsCode)
          .then(() => {
            showNotification('JavaScript code copied to clipboard!');
          })
          .catch(err => {
            try {
              const textArea = document.createElement('textarea');
              textArea.value = jsCode;
              textArea.style.position = 'fixed';
              textArea.style.opacity = '0';
              document.body.appendChild(textArea);
              textArea.select();
              document.execCommand('copy');
              document.body.removeChild(textArea);
              showNotification('JavaScript code copied to clipboard!');
            } catch (fallbackErr) {
              showNotification('Failed to copy to clipboard. Please try again.', 'error');
            }
          });
      }

      function copyFullSectionToClipboard() {
        const sectionJSON = generateFullSectionJSON();
        
        navigator.clipboard.writeText(sectionJSON)
          .then(() => {
            showNotification('Full section JSON copied to clipboard!');
          })
          .catch(err => {
            try {
              const textArea = document.createElement('textarea');
              textArea.value = sectionJSON;
              textArea.style.position = 'fixed';
              textArea.style.opacity = '0';
              document.body.appendChild(textArea);
              textArea.select();
              document.execCommand('copy');
              document.body.removeChild(textArea);
              showNotification('Full section JSON copied to clipboard!');
            } catch (fallbackErr) {
              showNotification('Failed to copy to clipboard. Please try again.', 'error');
            }
          });
      }

      function copyToClipboard(text) {
        navigator.clipboard.writeText(text)
          .then(() => {
            showNotification('Copied to clipboard!');
          })
          .catch(err => {
            showNotification('Failed to copy to clipboard', 'error');
          });
      }

      window.resetParameter = function(parameterId, defaultValue) {
        const element = document.getElementById(parameterId);
        if (element) {
          element.value = defaultValue;
          const valueElement = document.getElementById(`${parameterId}-value`);
          if (valueElement) {
            valueElement.textContent = defaultValue;
          }
          
          switch (parameterId) {
            case 'particle-count':
              celestialConfig.particleCount = defaultValue;
              break;
            case 'particle-size':
              celestialConfig.particleSize = defaultValue;
              break;
            case 'animation-speed':
              celestialConfig.animationSpeed = defaultValue;
              break;
            case 'opacity':
              celestialConfig.opacity = defaultValue;
              break;
            case 'trail-length':
              celestialConfig.trailLength = defaultValue;
              break;
            case 'fps':
              celestialConfig.fps = defaultValue;
              break;
          }
          
          updateCelestialPreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function generateRandomCelestial() {
        celestialConfig.particleCount = Math.floor(Math.random() * 90) + 10;
        celestialConfig.particleSize = Math.random() * 2.5 + 0.5;
        celestialConfig.animationSpeed = Math.random() * 1.5 + 0.5;
        celestialConfig.particleColor = generateRandomColor();
        celestialConfig.opacity = Math.random() * 0.8 + 0.2;
        celestialConfig.trailLength = Math.floor(Math.random() * 25) + 5;
        celestialConfig.flowDirection = ['diagonal', 'upward', 'downward', 'random'][Math.floor(Math.random() * 4)];
        
        document.getElementById('particle-count').value = celestialConfig.particleCount;
        document.getElementById('particle-size').value = celestialConfig.particleSize;
        document.getElementById('animation-speed').value = celestialConfig.animationSpeed;
        document.getElementById('particle-color').value = celestialConfig.particleColor;
        document.getElementById('opacity').value = celestialConfig.opacity;
        document.getElementById('trail-length').value = celestialConfig.trailLength;
        document.getElementById('flow-direction').value = celestialConfig.flowDirection;
        
        document.getElementById('particle-count-value').textContent = celestialConfig.particleCount;
        document.getElementById('particle-size-value').textContent = celestialConfig.particleSize;
        document.getElementById('animation-speed-value').textContent = celestialConfig.animationSpeed;
        document.getElementById('opacity-value').textContent = celestialConfig.opacity;
        document.getElementById('trail-length-value').textContent = celestialConfig.trailLength;
        
        updateColorInputs();
        updateCelestialPreview();
        showNotification('Random celestial flow generated!');
      }

      function updateColorInputs() {
        const colorInput = document.getElementById('particle-color');
        const hexInput = document.getElementById('particle-color-hex');
        const hslInput = document.getElementById('particle-color-hsl');
        
        if (colorInput && hexInput && hslInput) {
          colorInput.value = celestialConfig.particleColor;
          hexInput.value = celestialConfig.particleColor;
          hslInput.value = `hsl(${hexToHsl(celestialConfig.particleColor).h}, ${hexToHsl(celestialConfig.particleColor).s}%, ${hexToHsl(celestialConfig.particleColor).l}%)`;
          
          hexInput.classList.remove('invalid');
          hslInput.classList.remove('invalid');
          
          // Actualizar la variable CSS para mostrar el color en el selector visual
          const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
          if (colorPickerContainer) {
            colorPickerContainer.style.setProperty('--selected-color', celestialConfig.particleColor);
          }
        }
      }

      function initializeUI() {
        initCelestialFlow();
        
        const instructionsToggle = document.getElementById('instructions-toggle');
        const instructionsContent = document.getElementById('instructions-content');
        const instructionsCard = document.getElementById('instructions-card');
        const toggleIcon = instructionsToggle.querySelector('.toggle-icon');
        
        instructionsToggle.addEventListener('click', () => {
          const isVisible = instructionsContent.classList.contains('show');
          
          if (isVisible) {
            instructionsContent.classList.remove('show');
            instructionsCard.classList.remove('expanded');
            toggleIcon.classList.remove('expanded');
          } else {
            instructionsContent.classList.add('show');
            instructionsCard.classList.add('expanded');
            toggleIcon.classList.add('expanded');
          }
        });

        document.getElementById('quick-attribute').addEventListener('click', () => {
          copyToClipboard('data-celestial-flow-animation');
        });

        document.getElementById('download-config').addEventListener('click', () => {
          copyJsToClipboard();
        });

        document.getElementById('copy-full-section').addEventListener('click', () => {
          copyFullSectionToClipboard();
        });

        document.getElementById('randomize-celestial').addEventListener('click', () => {
          generateRandomCelestial();
        });

        // Preview background selector
        const backgroundPicker = document.getElementById('preview-background-picker');
        const previewContainer = document.getElementById('celestial-preview');

        backgroundPicker.addEventListener('input', (e) => {
          const selectedColor = e.target.value;
          previewContainer.style.backgroundColor = selectedColor;
          
          // Show notification
          showNotification(`Preview background changed to ${selectedColor}`);
        });

        // Set initial background color
        previewContainer.style.backgroundColor = '#000000';

        document.getElementById('reset-colors').addEventListener('click', () => {
          celestialConfig.particleColor = defaultConfig.particleColor;
          celestialConfig.opacity = defaultConfig.opacity;
          
          document.getElementById('particle-color').value = defaultConfig.particleColor;
          document.getElementById('opacity').value = defaultConfig.opacity;
          document.getElementById('opacity-value').textContent = defaultConfig.opacity;
          
          updateColorInputs();
          updateCelestialPreview();
          showNotification('Colors reset to default');
        });

        document.getElementById('reset-animation').addEventListener('click', () => {
          celestialConfig.particleCount = defaultConfig.particleCount;
          celestialConfig.particleSize = defaultConfig.particleSize;
          celestialConfig.animationSpeed = defaultConfig.animationSpeed;
          celestialConfig.trailLength = defaultConfig.trailLength;
          
          document.getElementById('particle-count').value = defaultConfig.particleCount;
          document.getElementById('particle-size').value = defaultConfig.particleSize;
          document.getElementById('animation-speed').value = defaultConfig.animationSpeed;
          document.getElementById('trail-length').value = defaultConfig.trailLength;
          
          document.getElementById('particle-count-value').textContent = defaultConfig.particleCount;
          document.getElementById('particle-size-value').textContent = defaultConfig.particleSize;
          document.getElementById('animation-speed-value').textContent = defaultConfig.animationSpeed;
          document.getElementById('trail-length-value').textContent = defaultConfig.trailLength;
          
          updateCelestialPreview();
          showNotification('Animation settings reset');
        });

        document.getElementById('reset-advanced').addEventListener('click', () => {
          celestialConfig.flowDirection = defaultConfig.flowDirection;
          celestialConfig.fps = defaultConfig.fps;
          
          document.getElementById('flow-direction').value = defaultConfig.flowDirection;
          document.getElementById('fps').value = defaultConfig.fps;
          document.getElementById('fps-value').textContent = defaultConfig.fps;
          
          updateCelestialPreview();
          showNotification('Advanced settings reset');
        });

        const colorInput = document.getElementById('particle-color');
        const hexInput = document.getElementById('particle-color-hex');
        const hslInput = document.getElementById('particle-color-hsl');
        
        hslInput.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
        
        colorInput.addEventListener('input', () => {
          const color = colorInput.value;
          hexInput.value = color;
          hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
          hexInput.classList.remove('invalid');
          hslInput.classList.remove('invalid');
          celestialConfig.particleColor = color;
          
          // Actualizar la variable CSS para mostrar el color en el selector visual
          const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
          colorPickerContainer.style.setProperty('--selected-color', color);
          
          updateCelestialPreview();
        });
        
        hexInput.addEventListener('input', (e) => {
          let hex = e.target.value;
          
          hex = formatHex(hex);
          e.target.value = hex;
          
          if (isValidHex(hex)) {
            colorInput.value = hex;
            hslInput.value = `hsl(${hexToHsl(hex).h}, ${hexToHsl(hex).s}%, ${hexToHsl(hex).l}%)`;
            celestialConfig.particleColor = hex;
            e.target.classList.remove('invalid');
            hslInput.classList.remove('invalid');
            
            // Actualizar la variable CSS para mostrar el color en el selector visual
            const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
            colorPickerContainer.style.setProperty('--selected-color', hex);
            
            updateCelestialPreview();
          } else {
            e.target.classList.add('invalid');
          }
        });
        
        hexInput.addEventListener('blur', (e) => {
          if (!isValidHex(e.target.value)) {
            e.target.value = colorInput.value;
            e.target.classList.remove('invalid');
          }
        });
        
        hslInput.addEventListener('input', (e) => {
          let hsl = e.target.value;
          
          if (isValidHsl(hsl)) {
            const hex = hslToHex(hsl);
            if (hex) {
              colorInput.value = hex;
              hexInput.value = hex;
              celestialConfig.particleColor = hex;
              e.target.classList.remove('invalid');
              hexInput.classList.remove('invalid');
              
              // Actualizar la variable CSS para mostrar el color en el selector visual
              const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
              colorPickerContainer.style.setProperty('--selected-color', hex);
              
              updateCelestialPreview();
              return;
            }
          }
          
          e.target.classList.add('invalid');
        });
        
        hslInput.addEventListener('blur', (e) => {
          let hsl = e.target.value;
          
          if (!isValidHsl(hsl) && hsl.trim()) {
            const formatted = formatHsl(hsl);
            if (isValidHsl(formatted)) {
              e.target.value = formatted;
              const hex = hslToHex(formatted);
              if (hex) {
                colorInput.value = hex;
                hexInput.value = hex;
                celestialConfig.particleColor = hex;
                e.target.classList.remove('invalid');
                hexInput.classList.remove('invalid');
                updateCelestialPreview();
                return;
              }
            }
          }
          
          if (!isValidHsl(e.target.value)) {
            e.target.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
            e.target.classList.remove('invalid');
          }
        });

        const rangeInputs = document.querySelectorAll('input[type="range"]');
        rangeInputs.forEach(input => {
          const valueElement = document.getElementById(`${input.id}-value`);
          
          if (valueElement) {
            valueElement.textContent = input.value;
          }
          
          input.addEventListener('input', () => {
            if (valueElement) {
              valueElement.textContent = input.value;
            }
            
            switch (input.id) {
              case 'particle-count':
                celestialConfig.particleCount = parseInt(input.value);
                break;
              case 'particle-size':
                celestialConfig.particleSize = parseFloat(input.value);
                break;
              case 'animation-speed':
                celestialConfig.animationSpeed = parseFloat(input.value);
                break;
              case 'opacity':
                celestialConfig.opacity = parseFloat(input.value);
                break;
              case 'trail-length':
                celestialConfig.trailLength = parseInt(input.value);
                break;
              case 'fps':
                celestialConfig.fps = parseInt(input.value);
                break;
            }
            
            updateCelestialPreview();
          });
        });

        document.getElementById('flow-direction').addEventListener('change', function() {
          celestialConfig.flowDirection = this.value;
          updateCelestialPreview();
        });

        document.addEventListener('keydown', (e) => {
          if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
            return;
          }
          
          if (e.ctrlKey || e.metaKey) {
            switch (e.key.toLowerCase()) {
              case 'd':
                e.preventDefault();
                const downloadBtn = document.getElementById('download-config');
                if (downloadBtn && downloadBtn.hasAttribute('data-protection-animation')) {
                  downloadBtn.click();
                } else {
                  copyJsToClipboard();
                }
                break;
              case 's':
                e.preventDefault();
                const fullSectionBtn = document.getElementById('copy-full-section');
                if (fullSectionBtn && fullSectionBtn.hasAttribute('data-protection-animation')) {
                  fullSectionBtn.click();
                } else {
                  copyFullSectionToClipboard();
                }
                break;
            }
          } else {
            switch (e.key.toLowerCase()) {
              case 'r':
                generateRandomCelestial();
                break;
              case 'b':
                document.getElementById('preview-background-picker').click();
                break;
            }
          }
        });

        updateColorInputs();
        
        setTimeout(() => {
          showNotification('BricksFusion Celestial Flow Configurator loaded!');
        }, 500);

        function saveConfiguration() {
          try {
            localStorage.setItem('bricksfusion-celestial-config', JSON.stringify(celestialConfig));
          } catch (e) {
          }
        }

        function loadConfiguration() {
          try {
            const saved = localStorage.getItem('bricksfusion-celestial-config');
            if (saved) {
              const savedConfig = JSON.parse(saved);
              Object.assign(celestialConfig, savedConfig);
              
              document.getElementById('particle-count').value = savedConfig.particleCount;
              document.getElementById('particle-size').value = savedConfig.particleSize;
              document.getElementById('animation-speed').value = savedConfig.animationSpeed;
              document.getElementById('particle-color').value = savedConfig.particleColor;
              document.getElementById('opacity').value = savedConfig.opacity;
              document.getElementById('trail-length').value = savedConfig.trailLength;
              document.getElementById('flow-direction').value = savedConfig.flowDirection;
              document.getElementById('fps').value = savedConfig.fps;
              
              document.getElementById('particle-count-value').textContent = savedConfig.particleCount;
              document.getElementById('particle-size-value').textContent = savedConfig.particleSize;
              document.getElementById('animation-speed-value').textContent = savedConfig.animationSpeed;
              document.getElementById('opacity-value').textContent = savedConfig.opacity;
              document.getElementById('trail-length-value').textContent = savedConfig.trailLength;
              document.getElementById('fps-value').textContent = savedConfig.fps;
              
              updateColorInputs();
              updateCelestialPreview();
            }
          } catch (e) {
          }
        }

        const originalUpdateCelestialPreview = updateCelestialPreview;
        updateCelestialPreview = function() {
          originalUpdateCelestialPreview();
          saveConfiguration();
        };

        loadConfiguration();
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Celestial Flow - Bricksfusion
MEDIUM

Celestial Flow

Creates an animated particle effect with flowing trails across your section. Perfect for hero sections, testimonials, or adding magical ambiance to any container.

Celestial Flow

Watch the particles flow around this content. They create a beautiful ambient effect without interfering with your text or buttons.

Particles

Particle Count 10-200

Number of particles on screen. More particles create a denser effect but use more resources. Keep under 100 for best performance.

Default: 50

Particle Size 0.5-5.0

Size of each particle in pixels. Larger particles are more visible but can look heavy. Try 1-2 for subtle effects.

Default: 1.5

Particle Color color picker

Color of the particles and their trails. Choose colors that complement your design. Light colors work well on dark backgrounds.

Default: Blue (#3b82f6)

Opacity 0.1-1.0

Transparency of particles. Lower values create subtle, ethereal effects. Higher values make them more prominent.

Default: 0.7

Animation

Animation Speed 0.1-3.0

How fast particles move. Lower is slow and dreamy, higher is fast and energetic. 1.0 is a good balanced speed.

Default: 1.0

Trail Length 5-50

Length of the trail behind each particle. Longer trails create elegant streaks. Shorter trails look more like stars.

Default: 15

Flow Direction diagonal / upward / downward / random

Direction particles flow. Diagonal flows naturally, upward feels like floating, downward like rain, random creates organic movement.

Default: Diagonal

Performance

FPS Limit 30 / 60

Maximum frames per second. 60fps is smooth but uses more resources. 30fps saves battery on mobile and still looks good.

Default: 60

Performance

This element uses HTML5 Canvas for rendering. It automatically pauses when scrolled out of view to save resources. For best performance, keep particle count under 100 and use 30fps on mobile devices.