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>Vortex Animation 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: #3f51b5;
}

.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, #3f51b5);
  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">Vortex Animation</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-vortex
      </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">Vortex Animation</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 vortex 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-vortex</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="vortex-preview" data-vortex="true">
          <div class="preview-content">Interactive Vortex Preview</div>
          <div class="preview-controls">
            <button class="preview-btn" id="randomize-vortex" 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="base-color" value="#3f51b5">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HEX</span>
                  <input type="text" class="color-input hex-input" id="base-color-hex" value="#3f51b5" placeholder="#FFFFFF">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HSL</span>
                  <input type="text" class="color-input hsl-input" id="base-color-hsl" placeholder="hsl(0, 100%, 50%)">
                </div>
              </div>
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Color Variation
                  <span class="help-tooltip" title="Hue variation range in degrees">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="range-hue-value">30</span>°</span>
                  <button class="reset-btn" onclick="resetParameter('range-hue', 30)">↺</button>
                </div>
              </div>
              <input type="range" id="range-hue" min="0" max="60" step="1" value="30">
            </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">700</span></span>
                  <button class="reset-btn" onclick="resetParameter('particle-count', 700)">↺</button>
                </div>
              </div>
              <input type="range" id="particle-count" min="100" max="700" step="50" value="700">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Base Radius
                  <span class="help-tooltip" title="Base size of particles">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="base-radius-value">1</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('base-radius', 1)">↺</button>
                </div>
              </div>
              <input type="range" id="base-radius" min="0.5" max="5" step="0.5" value="1">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Radius Range
                  <span class="help-tooltip" title="Size variation of particles">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="range-radius-value">2</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('range-radius', 2)">↺</button>
                </div>
              </div>
              <input type="range" id="range-radius" min="0" max="5" step="0.5" value="2">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Range Y
                  <span class="help-tooltip" title="Vertical spread of particle spawn area">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="range-y-value">100</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('range-y', 100)">↺</button>
                </div>
              </div>
              <input type="range" id="range-y" min="50" max="300" step="10" value="100">
            </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">
                  Base Speed
                  <span class="help-tooltip" title="Minimum speed of particles">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="base-speed-value">0.0</span></span>
                  <button class="reset-btn" onclick="resetParameter('base-speed', 0.0)">↺</button>
                </div>
              </div>
              <input type="range" id="base-speed" min="0" max="1" step="0.1" value="0.0">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Speed Range
                  <span class="help-tooltip" title="Speed variation of particles">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="range-speed-value">1.5</span></span>
                  <button class="reset-btn" onclick="resetParameter('range-speed', 1.5)">↺</button>
                </div>
              </div>
              <input type="range" id="range-speed" min="0.5" max="3" step="0.1" value="1.5">
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let vortexConfig = {
        particleCount: 700,
        rangeY: 100,
        baseSpeed: 0.0,
        rangeSpeed: 1.5,
        baseRadius: 1,
        rangeRadius: 2,
        baseColor: "#3f51b5",
        rangeHue: 30
      };

      const defaultConfig = { ...vortexConfig };

      function initVortex() {
        const vortexElements = document.querySelectorAll('[data-vortex]:not([data-vortex-initialized="true"])');
        
        vortexElements.forEach((element) => {
          const options = {
            particleCount: vortexConfig.particleCount,
            rangeY: vortexConfig.rangeY,
            baseSpeed: vortexConfig.baseSpeed,
            rangeSpeed: vortexConfig.rangeSpeed,
            baseRadius: vortexConfig.baseRadius,
            rangeRadius: vortexConfig.rangeRadius,
            baseColor: vortexConfig.baseColor,
            rangeHue: vortexConfig.rangeHue
          };
          
          setupVortex(element, options);
          element.dataset.vortexInitialized = 'true';
        });
      }

      function setupVortex(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 || 400;
        canvas.height = element.clientHeight || 400;
        
        element.vortexCanvas = canvas;
        element.vortexCtx = ctx;
        element.vortexInstance = new VortexPreview(element, canvas, ctx, options);
        
        element._cleanupVortex = () => {
          if (element.vortexInstance) {
            element.vortexInstance.destroy();
          }
        };
      }

      class VortexPreview {
        constructor(element, canvas, ctx, options) {
          this.element = element;
          this.canvas = canvas;
          this.ctx = ctx;
          this.options = options;
          
          this.particleCount = options.particleCount;
          this.rangeY = options.rangeY;
          this.baseSpeed = options.baseSpeed;
          this.rangeSpeed = options.rangeSpeed;
          this.baseRadius = options.baseRadius;
          this.rangeRadius = options.rangeRadius;
          this.baseColor = options.baseColor;
          this.rangeHue = options.rangeHue;
          
          this.baseHue = this.hexToHSL(this.baseColor).h;
          
          this.particlePropCount = 9;
          this.particlePropsLength = this.particleCount * this.particlePropCount;
          this.baseTTL = 50;
          this.rangeTTL = 150;
          this.noiseSteps = 3;
          this.xOff = 0.00125;
          this.yOff = 0.00125;
          this.zOff = 0.0005;
          this.TAU = 2 * Math.PI;
          
          this.tick = 0;
          this.noise3D = this.createNoise3D();
          this.particleProps = new Float32Array(this.particlePropsLength);
          this.center = [0, 0];
          
          this.setup();
          this.animate();
        }
        
        createNoise3D() {
          const F3 = 1.0 / 3.0;
          const G3 = 1.0 / 6.0;
          const gradients3 = new Float32Array([
            1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0,
            1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, -1,
            0, 1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1
          ]);
          
          const perm = new Uint8Array(512);
          for (let i = 0; i < 256; i++) {
            perm[i] = i;
          }
          for (let i = 0; i < 256; i++) {
            const j = Math.floor(Math.random() * (i + 1));
            perm[i] = perm[j];
            perm[j] = i;
          }
          for (let i = 0; i < 256; i++) {
            perm[i + 256] = perm[i];
          }
          
          return (x, y, z) => {
            const s = (x + y + z) * F3;
            const i = Math.floor(x + s);
            const j = Math.floor(y + s);
            const k = Math.floor(z + s);
            
            const t = (i + j + k) * G3;
            const x0 = x - (i - t);
            const y0 = y - (j - t);
            const z0 = z - (k - t);
            
            let i1, j1, k1, i2, j2, k2;
            if (x0 >= y0) {
              if (y0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; }
              else if (x0 >= z0) { i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; }
              else { i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; }
            } else {
              if (y0 < z0) { i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1; }
              else if (x0 < z0) { i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1; }
              else { i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0; }
            }
            
            const x1 = x0 - i1 + G3;
            const y1 = y0 - j1 + G3;
            const z1 = z0 - k1 + G3;
            const x2 = x0 - i2 + 2.0 * G3;
            const y2 = y0 - j2 + 2.0 * G3;
            const z2 = z0 - k2 + 2.0 * G3;
            const x3 = x0 - 1.0 + 3.0 * G3;
            const y3 = y0 - 1.0 + 3.0 * G3;
            const z3 = z0 - 1.0 + 3.0 * G3;
            
            const ii = i & 255;
            const jj = j & 255;
            const kk = k & 255;
            
            const gi0 = (perm[ii + perm[jj + perm[kk]]] % 12) * 3;
            const gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12) * 3;
            const gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12) * 3;
            const gi3 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12) * 3;
            
            let n0 = 0, n1 = 0, n2 = 0, n3 = 0;
            
            let t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
            if (t0 >= 0) {
              t0 *= t0;
              n0 = t0 * t0 * (gradients3[gi0] * x0 + gradients3[gi0 + 1] * y0 + gradients3[gi0 + 2] * z0);
            }
            
            let t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
            if (t1 >= 0) {
              t1 *= t1;
              n1 = t1 * t1 * (gradients3[gi1] * x1 + gradients3[gi1 + 1] * y1 + gradients3[gi1 + 2] * z1);
            }
            
            let t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
            if (t2 >= 0) {
              t2 *= t2;
              n2 = t2 * t2 * (gradients3[gi2] * x2 + gradients3[gi2 + 1] * y2 + gradients3[gi2 + 2] * z2);
            }
            
            let t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
            if (t3 >= 0) {
              t3 *= t3;
              n3 = t3 * t3 * (gradients3[gi3] * x3 + gradients3[gi3 + 1] * y3 + gradients3[gi3 + 2] * z3);
            }
            
            return 32.0 * (n0 + n1 + n2 + n3);
          };
        }
        
        hexToHSL(hex) {
          hex = hex.replace(/^#/, '');
          
          let r, g, b;
          if(hex.length === 3) {
            r = parseInt(hex.charAt(0) + hex.charAt(0), 16) / 255;
            g = parseInt(hex.charAt(1) + hex.charAt(1), 16) / 255;
            b = parseInt(hex.charAt(2) + hex.charAt(2), 16) / 255;
          } else {
            r = parseInt(hex.substring(0, 2), 16) / 255;
            g = parseInt(hex.substring(2, 4), 16) / 255;
            b = parseInt(hex.substring(4, 6), 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 = Math.round(h * 60);
          }
          
          return { h, s, l };
        }
        
        rand(n) {
          return n * Math.random();
        }
        
        randRange(n) {
          return n - this.rand(2 * n);
        }
        
        fadeInOut(t, m) {
          let hm = 0.5 * m;
          return Math.abs(((t + hm) % m) - hm) / hm;
        }
        
        lerp(n1, n2, speed) {
          return (1 - speed) * n1 + speed * n2;
        }
        
        setup() {
          this.resize();
          this.initParticles();
        }
        
        resize() {
          const rect = this.element.getBoundingClientRect();
          this.canvas.width = Math.max(1, rect.width);
          this.canvas.height = Math.max(1, rect.height);
          this.center[0] = 0.5 * this.canvas.width;
          this.center[1] = 0.5 * this.canvas.height;
        }
        
        initParticles() {
          this.tick = 0;
          this.particleProps = new Float32Array(this.particlePropsLength);
          
          for (let i = 0; i < this.particlePropsLength; i += this.particlePropCount) {
            this.initParticle(i);
          }
        }
        
        initParticle(i) {
          const x = this.rand(this.canvas.width);
          const y = this.center[1] + this.randRange(this.rangeY);
          const vx = 0;
          const vy = 0;
          const life = 0;
          const ttl = this.baseTTL + this.rand(this.rangeTTL);
          const speed = this.baseSpeed + this.rand(this.rangeSpeed);
          const radius = this.baseRadius + this.rand(this.rangeRadius);
          const hue = this.baseHue + this.randRange(this.rangeHue);
          
          this.particleProps[i] = x;
          this.particleProps[i+1] = y;
          this.particleProps[i+2] = vx;
          this.particleProps[i+3] = vy;
          this.particleProps[i+4] = life;
          this.particleProps[i+5] = ttl;
          this.particleProps[i+6] = speed;
          this.particleProps[i+7] = radius;
          this.particleProps[i+8] = hue;
        }
        
        animate() {
          this.animationId = requestAnimationFrame(() => this.animate());
          this.tick++;
          
          this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
          
          this.drawParticles();
          this.renderGlow();
          this.renderToScreen();
        }
        
        drawParticles() {
          for (let i = 0; i < this.particlePropsLength; i += this.particlePropCount) {
            this.updateParticle(i);
          }
        }
        
        updateParticle(i) {
          let i2 = 1 + i, i3 = 2 + i, i4 = 3 + i, i5 = 4 + i, i6 = 5 + i, i7 = 6 + i, i8 = 7 + i, i9 = 8 + i;
          
          const x = this.particleProps[i];
          const y = this.particleProps[i2];
          const n = this.noise3D(x * this.xOff, y * this.yOff, this.tick * this.zOff) * this.noiseSteps * this.TAU;
          const vx = this.lerp(this.particleProps[i3], Math.cos(n), 0.5);
          const vy = this.lerp(this.particleProps[i4], Math.sin(n), 0.5);
          let life = this.particleProps[i5];
          const ttl = this.particleProps[i6];
          const speed = this.particleProps[i7];
          const x2 = x + vx * speed;
          const y2 = y + vy * speed;
          const radius = this.particleProps[i8];
          const hue = this.particleProps[i9];
          
          this.drawParticle(x, y, x2, y2, life, ttl, radius, hue);
          
          life++;
          
          this.particleProps[i] = x2;
          this.particleProps[i2] = y2;
          this.particleProps[i3] = vx;
          this.particleProps[i4] = vy;
          this.particleProps[i5] = life;
          
          if (this.checkBounds(x, y) || life > ttl) {
            this.initParticle(i);
          }
        }
        
        drawParticle(x, y, x2, y2, life, ttl, radius, hue) {
          this.ctx.save();
          this.ctx.lineCap = 'round';
          this.ctx.lineWidth = radius;
          this.ctx.strokeStyle = `hsla(${hue},100%,60%,${this.fadeInOut(life, ttl)})`;
          this.ctx.beginPath();
          this.ctx.moveTo(x, y);
          this.ctx.lineTo(x2, y2);
          this.ctx.stroke();
          this.ctx.closePath();
          this.ctx.restore();
        }
        
        checkBounds(x, y) {
          return x > this.canvas.width || x < 0 || y > this.canvas.height || y < 0;
        }
        
        renderGlow() {
          if (this.canvas.width > 0 && this.canvas.height > 0) {
            this.ctx.save();
            this.ctx.filter = 'blur(8px) brightness(200%)';
            this.ctx.globalCompositeOperation = 'lighter';
            this.ctx.drawImage(this.canvas, 0, 0);
            this.ctx.restore();
            
            this.ctx.save();
            this.ctx.filter = 'blur(4px) brightness(200%)';
            this.ctx.globalCompositeOperation = 'lighter';
            this.ctx.drawImage(this.canvas, 0, 0);
            this.ctx.restore();
          }
        }
        
        renderToScreen() {
          if (this.canvas.width > 0 && this.canvas.height > 0) {
            this.ctx.save();
            this.ctx.globalCompositeOperation = 'lighter';
            this.ctx.drawImage(this.canvas, 0, 0);
            this.ctx.restore();
          }
        }
        
        destroy() {
          if (this.animationId) {
            cancelAnimationFrame(this.animationId);
          }
        }
        
        updateOptions(newOptions) {
          this.particleCount = newOptions.particleCount;
          this.rangeY = newOptions.rangeY;
          this.baseSpeed = newOptions.baseSpeed;
          this.rangeSpeed = newOptions.rangeSpeed;
          this.baseRadius = newOptions.baseRadius;
          this.rangeRadius = newOptions.rangeRadius;
          this.baseColor = newOptions.baseColor;
          this.rangeHue = newOptions.rangeHue;
          this.baseHue = this.hexToHSL(this.baseColor).h;
          
          this.particlePropsLength = this.particleCount * this.particlePropCount;
          this.particleProps = new Float32Array(this.particlePropsLength);
          this.initParticles();
        }
      }

      function updateVortexPreview() {
        const preview = document.getElementById('vortex-preview');
        if (!preview) return;
        
        if (preview.vortexInstance) {
          preview.vortexInstance.updateOptions(vortexConfig);
        } else {
          if (preview._cleanupVortex) {
            preview._cleanupVortex();
          }
          
          preview.setAttribute('data-vortex', 'true');
          preview.dataset.vortexInitialized = 'false';
          
          const options = {
            particleCount: vortexConfig.particleCount,
            rangeY: vortexConfig.rangeY,
            baseSpeed: vortexConfig.baseSpeed,
            rangeSpeed: vortexConfig.rangeSpeed,
            baseRadius: vortexConfig.baseRadius,
            rangeRadius: vortexConfig.rangeRadius,
            baseColor: vortexConfig.baseColor,
            rangeHue: vortexConfig.rangeHue
          };
          
          setupVortex(preview, options);
        }
      }

      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 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-vortex"
            }
          ],
          "_height": "500",
          "_justifyContent": "center",
          "_background": {
            "color": {
              "hex": "#000000"
            }
          }
        },
        "label": "VortexSection"
      },
      {
        "id": containerId,
        "name": "container",
        "parent": sectionId,
        "children": [],
        "settings": {}
      },
      {
        "id": codeId,
        "name": "code",
        "parent": sectionId,
        "children": [],
        "settings": {
          "javascriptCode": jsCode,
          "executeCode": true,
          "_display": "none"
        },
        "label": "Vortex JS"
      }
    ],
    "source": "bricksCopiedElements",
    "sourceUrl": "https://bricksfusion.com",
    "version": "2.0.1",
    "globalClasses": [],
    "globalElements": []
  };
  
  return JSON.stringify(fullSectionData, null, 2);
}

      function generateJavaScriptCode() {
        return `(function() {
'use strict';

const F3 = 1.0 / 3.0;
const G3 = 1.0 / 6.0;

const gradients3 = new Float32Array([
1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0,
1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, -1,
0, 1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1
]);

function createRandom(seed = Math.random()) {
  let state = seed;
  return function() {
    state = (state * 1664525 + 1013904223) % 4294967296;
    return state / 4294967296;
  };
}

function createNoise3D(random = Math.random) {
  const perm = new Uint8Array(512);
  const permGrad3 = new Float32Array(512 * 3);
  
  for (let i = 0; i < 256; i++) {
    perm[i] = i;
  }
  
  for (let i = 0; i < 256; i++) {
    const j = Math.floor(random() * (i + 1));
    perm[i] = perm[j];
    perm[j] = i;
  }
  
  for (let i = 0; i < 256; i++) {
    const grad3Index = (perm[i] % 12) * 3;
    permGrad3[i * 3] = gradients3[grad3Index];
    permGrad3[i * 3 + 1] = gradients3[grad3Index + 1];
    permGrad3[i * 3 + 2] = gradients3[grad3Index + 2];
    
    perm[i + 256] = perm[i];
    permGrad3[(i + 256) * 3] = permGrad3[i * 3];
    permGrad3[(i + 256) * 3 + 1] = permGrad3[i * 3 + 1];
    permGrad3[(i + 256) * 3 + 2] = permGrad3[i * 3 + 2];
  }
  
  return function(x, y, z) {
    let n0 = 0, n1 = 0, n2 = 0, n3 = 0;
    
    const s = (x + y + z) * F3;
    const i = Math.floor(x + s);
    const j = Math.floor(y + s);
    const k = Math.floor(z + s);
    
    const t = (i + j + k) * G3;
    const x0 = x - (i - t);
    const y0 = y - (j - t);
    const z0 = z - (k - t);
    
    let i1, j1, k1;
    let i2, j2, k2;
    
    if (x0 >= y0) {
      if (y0 >= z0) {
        i1 = 1; j1 = 0; k1 = 0;
        i2 = 1; j2 = 1; k2 = 0;
      } else if (x0 >= z0) {
        i1 = 1; j1 = 0; k1 = 0;
        i2 = 1; j2 = 0; k2 = 1;
      } else {
        i1 = 0; j1 = 0; k1 = 1;
        i2 = 1; j2 = 0; k2 = 1;
      }
    } else {
      if (y0 < z0) {
        i1 = 0; j1 = 0; k1 = 1;
        i2 = 0; j2 = 1; k2 = 1;
      } else if (x0 < z0) {
        i1 = 0; j1 = 1; k1 = 0;
        i2 = 0; j2 = 1; k2 = 1;
      } else {
        i1 = 0; j1 = 1; k1 = 0;
        i2 = 1; j2 = 1; k2 = 0;
      }
    }
    
    const x1 = x0 - i1 + G3;
    const y1 = y0 - j1 + G3;
    const z1 = z0 - k1 + G3;
    const x2 = x0 - i2 + 2.0 * G3;
    const y2 = y0 - j2 + 2.0 * G3;
    const z2 = z0 - k2 + 2.0 * G3;
    const x3 = x0 - 1.0 + 3.0 * G3;
    const y3 = y0 - 1.0 + 3.0 * G3;
    const z3 = z0 - 1.0 + 3.0 * G3;
    
    const ii = i & 255;
    const jj = j & 255;
    const kk = k & 255;
    
    const gi0 = (perm[ii + perm[jj + perm[kk]]] % 12) * 3;
    const gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1]]] % 12) * 3;
    const gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2]]] % 12) * 3;
    const gi3 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1]]] % 12) * 3;
    
    let t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
    if (t0 < 0) {
      n0 = 0.0;
    } else {
      t0 *= t0;
      n0 = t0 * t0 * (gradients3[gi0] * x0 + gradients3[gi0 + 1] * y0 + gradients3[gi0 + 2] * z0);
    }
    
    let t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
    if (t1 < 0) {
      n1 = 0.0;
    } else {
      t1 *= t1;
      n1 = t1 * t1 * (gradients3[gi1] * x1 + gradients3[gi1 + 1] * y1 + gradients3[gi1 + 2] * z1);
    }
    
    let t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
    if (t2 < 0) {
      n2 = 0.0;
    } else {
      t2 *= t2;
      n2 = t2 * t2 * (gradients3[gi2] * x2 + gradients3[gi2 + 1] * y2 + gradients3[gi2 + 2] * z2);
    }
    
    let t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
    if (t3 < 0) {
      n3 = 0.0;
    } else {
      t3 *= t3;
      n3 = t3 * t3 * (gradients3[gi3] * x3 + gradients3[gi3 + 1] * y3 + gradients3[gi3 + 2] * z3);
    }
    
    return 32.0 * (n0 + n1 + n2 + n3);
  };
}

class Vortex {
  constructor(element) {
    this.element = element;
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
    
    this.canvas.width = 1;
    this.canvas.height = 1;
    
    this.particleCount = ${vortexConfig.particleCount};
    this.rangeY = ${vortexConfig.rangeY};
    this.baseSpeed = ${vortexConfig.baseSpeed};
    this.rangeSpeed = ${vortexConfig.rangeSpeed};
    this.baseRadius = ${vortexConfig.baseRadius};
    this.rangeRadius = ${vortexConfig.rangeRadius};
    this.baseColor = "${vortexConfig.baseColor}";
    this.rangeHue = ${vortexConfig.rangeHue};
    
    this.baseHue = this.hexToHSL(this.baseColor).h;
    
    this.particlePropCount = 9;
    this.particlePropsLength = this.particleCount * this.particlePropCount;
    this.baseTTL = 50;
    this.rangeTTL = 150;
    this.noiseSteps = 3;
    this.xOff = 0.00125;
    this.yOff = 0.00125;
    this.zOff = 0.0005;
    this.HALF_PI = 0.5 * Math.PI;
    this.TAU = 2 * Math.PI;
    this.TO_RAD = Math.PI / 180;
    
    this.tick = 0;
    this.noise3D = createNoise3D();
    this.particleProps = new Float32Array(this.particlePropsLength);
    this.center = [0, 0];
    
    this.container = document.createElement('div');
    this.container.style.position = 'absolute';
    this.container.style.top = '0';
    this.container.style.left = '0';
    this.container.style.width = '100%';
    this.container.style.height = '100%';
    this.container.style.zIndex = '0';
    this.container.style.pointerEvents = 'none';
    
    this.canvas.style.position = 'absolute';
    this.canvas.style.top = '0';
    this.canvas.style.left = '0';
    this.canvas.style.width = '100%';
    this.canvas.style.height = '100%';
    
    this.container.appendChild(this.canvas);
    
    this.element.style.position = 'relative';
    this.element.appendChild(this.container);
    
    this.setup();
    this.bindEvents();
  }
  
  hexToHSL(hex) {
    hex = hex.replace(/^#/, '');
    
    let r, g, b;
    if(hex.length === 3) {
      r = parseInt(hex.charAt(0) + hex.charAt(0), 16) / 255;
      g = parseInt(hex.charAt(1) + hex.charAt(1), 16) / 255;
      b = parseInt(hex.charAt(2) + hex.charAt(2), 16) / 255;
    } else {
      r = parseInt(hex.substring(0, 2), 16) / 255;
      g = parseInt(hex.substring(2, 4), 16) / 255;
      b = parseInt(hex.substring(4, 6), 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 = Math.round(h * 60);
    }
    
    s = Math.round(s * 100);
    l = Math.round(l * 100);
    
    return { h, s, l };
  }
  
  rand(n) {
    return n * Math.random();
  }
  
  randRange(n) {
    return n - this.rand(2 * n);
  }
  
  fadeInOut(t, m) {
    let hm = 0.5 * m;
    return Math.abs(((t + hm) % m) - hm) / hm;
  }
  
  lerp(n1, n2, speed) {
    return (1 - speed) * n1 + speed * n2;
  }
  
  setup() {
    this.resize();
    this.initParticles();
    this.draw();
  }
  
  initParticles() {
    this.tick = 0;
    this.particleProps = new Float32Array(this.particlePropsLength);
    
    for (let i = 0; i < this.particlePropsLength; i += this.particlePropCount) {
      this.initParticle(i);
    }
  }
  
  initParticle(i) {
    let x, y, vx, vy, life, ttl, speed, radius, hue;
    
    x = this.rand(this.canvas.width);
    y = this.center[1] + this.randRange(this.rangeY);
    vx = 0;
    vy = 0;
    life = 0;
    ttl = this.baseTTL + this.rand(this.rangeTTL);
    speed = this.baseSpeed + this.rand(this.rangeSpeed);
    radius = this.baseRadius + this.rand(this.rangeRadius);
    hue = this.baseHue + this.randRange(this.rangeHue);
    
    this.particleProps[i] = x;
    this.particleProps[i+1] = y;
    this.particleProps[i+2] = vx;
    this.particleProps[i+3] = vy;
    this.particleProps[i+4] = life;
    this.particleProps[i+5] = ttl;
    this.particleProps[i+6] = speed;
    this.particleProps[i+7] = radius;
    this.particleProps[i+8] = hue;
  }
  
  draw() {
    this.tick++;
    
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
    this.drawParticles();
    this.renderGlow();
    this.renderToScreen();
    
    window.requestAnimationFrame(() => this.draw());
  }
  
  drawParticles() {
    for (let i = 0; i < this.particlePropsLength; i += this.particlePropCount) {
      this.updateParticle(i);
    }
  }
  
  updateParticle(i) {
    let i2 = 1 + i,
        i3 = 2 + i,
        i4 = 3 + i,
        i5 = 4 + i,
        i6 = 5 + i,
        i7 = 6 + i,
        i8 = 7 + i,
        i9 = 8 + i;
    let n, x, y, vx, vy, life, ttl, speed, x2, y2, radius, hue;
    
    x = this.particleProps[i];
    y = this.particleProps[i2];
    n = this.noise3D(x * this.xOff, y * this.yOff, this.tick * this.zOff) * this.noiseSteps * this.TAU;
    vx = this.lerp(this.particleProps[i3], Math.cos(n), 0.5);
    vy = this.lerp(this.particleProps[i4], Math.sin(n), 0.5);
    life = this.particleProps[i5];
    ttl = this.particleProps[i6];
    speed = this.particleProps[i7];
    x2 = x + vx * speed;
    y2 = y + vy * speed;
    radius = this.particleProps[i8];
    hue = this.particleProps[i9];
    
    this.drawParticle(x, y, x2, y2, life, ttl, radius, hue);
    
    life++;
    
    this.particleProps[i] = x2;
    this.particleProps[i2] = y2;
    this.particleProps[i3] = vx;
    this.particleProps[i4] = vy;
    this.particleProps[i5] = life;
    
    if (this.checkBounds(x, y) || life > ttl) {
      this.initParticle(i);
    }
  }
  
  drawParticle(x, y, x2, y2, life, ttl, radius, hue) {
    this.ctx.save();
    this.ctx.lineCap = 'round';
    this.ctx.lineWidth = radius;
    this.ctx.strokeStyle = \`hsla(\${hue},100%,60%,\${this.fadeInOut(life, ttl)})\`;
    this.ctx.beginPath();
    this.ctx.moveTo(x, y);
    this.ctx.lineTo(x2, y2);
    this.ctx.stroke();
    this.ctx.closePath();
    this.ctx.restore();
  }
  
  checkBounds(x, y) {
    return x > this.canvas.width || x < 0 || y > this.canvas.height || y < 0;
  }
  
  resize() {
    const rect = this.element.getBoundingClientRect();
    
    this.canvas.width = Math.max(1, rect.width);
    this.canvas.height = Math.max(1, rect.height);
    
    this.center[0] = 0.5 * this.canvas.width;
    this.center[1] = 0.5 * this.canvas.height;
  }
  
  renderGlow() {
    if (this.canvas.width > 0 && this.canvas.height > 0) {
      this.ctx.save();
      this.ctx.filter = 'blur(8px) brightness(200%)';
      this.ctx.globalCompositeOperation = 'lighter';
      this.ctx.drawImage(this.canvas, 0, 0);
      this.ctx.restore();
      
      this.ctx.save();
      this.ctx.filter = 'blur(4px) brightness(200%)';
      this.ctx.globalCompositeOperation = 'lighter';
      this.ctx.drawImage(this.canvas, 0, 0);
      this.ctx.restore();
    }
  }
  
  renderToScreen() {
    if (this.canvas.width > 0 && this.canvas.height > 0) {
      this.ctx.save();
      this.ctx.globalCompositeOperation = 'lighter';
      this.ctx.drawImage(this.canvas, 0, 0);
      this.ctx.restore();
    }
  }
  
  bindEvents() {
    const resizeHandler = () => this.resize();
    window.addEventListener('resize', resizeHandler);
    
    this.destroy = () => {
      window.removeEventListener('resize', resizeHandler);
      if (this.container && this.container.parentNode) {
        this.container.parentNode.removeChild(this.container);
      }
    };
  }
}

function initVortex() {
  const vortexElements = document.querySelectorAll('[data-vortex]');
  
  const vortexInstances = [];
  
  vortexElements.forEach(element => {
    vortexInstances.push(new Vortex(element));
  });
  
  window.vortexInstances = vortexInstances;
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initVortex);
} else {
  initVortex();
}

document.addEventListener('bricks/content_updated', initVortex);
})();`;
      }

      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 => {
            showNotification('Full section feature coming soon!', 'warning');
          });
      }

      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':
              vortexConfig.particleCount = defaultValue;
              break;
            case 'range-y':
              vortexConfig.rangeY = defaultValue;
              break;
            case 'base-speed':
              vortexConfig.baseSpeed = defaultValue;
              break;
            case 'range-speed':
              vortexConfig.rangeSpeed = defaultValue;
              break;
            case 'base-radius':
              vortexConfig.baseRadius = defaultValue;
              break;
            case 'range-radius':
              vortexConfig.rangeRadius = defaultValue;
              break;
            case 'range-hue':
              vortexConfig.rangeHue = defaultValue;
              break;
          }
          
          updateVortexPreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function generateRandomVortex() {
        vortexConfig.particleCount = Math.floor(Math.random() * 600) + 100;
        vortexConfig.rangeY = Math.floor(Math.random() * 250) + 50;
        vortexConfig.baseSpeed = Math.random() * 1;
        vortexConfig.rangeSpeed = Math.random() * 2.5 + 0.5;
        vortexConfig.baseRadius = Math.random() * 4.5 + 0.5;
        vortexConfig.rangeRadius = Math.random() * 5;
        vortexConfig.baseColor = generateRandomColor();
        vortexConfig.rangeHue = Math.floor(Math.random() * 60);
        
        document.getElementById('particle-count').value = vortexConfig.particleCount;
        document.getElementById('range-y').value = vortexConfig.rangeY;
        document.getElementById('base-speed').value = vortexConfig.baseSpeed;
        document.getElementById('range-speed').value = vortexConfig.rangeSpeed;
        document.getElementById('base-radius').value = vortexConfig.baseRadius;
        document.getElementById('range-radius').value = vortexConfig.rangeRadius;
        document.getElementById('base-color').value = vortexConfig.baseColor;
        document.getElementById('range-hue').value = vortexConfig.rangeHue;
        
        document.getElementById('particle-count-value').textContent = vortexConfig.particleCount;
        document.getElementById('range-y-value').textContent = vortexConfig.rangeY;
        document.getElementById('base-speed-value').textContent = vortexConfig.baseSpeed;
        document.getElementById('range-speed-value').textContent = vortexConfig.rangeSpeed;
        document.getElementById('base-radius-value').textContent = vortexConfig.baseRadius;
        document.getElementById('range-radius-value').textContent = vortexConfig.rangeRadius;
        document.getElementById('range-hue-value').textContent = vortexConfig.rangeHue;
        
        updateColorInputs();
        updateVortexPreview();
        showNotification('Random vortex animation generated!');
      }

      function updateColorInputs() {
        const colorInput = document.getElementById('base-color');
        const hexInput = document.getElementById('base-color-hex');
        const hslInput = document.getElementById('base-color-hsl');
        
        if (colorInput && hexInput && hslInput) {
          colorInput.value = vortexConfig.baseColor;
          hexInput.value = vortexConfig.baseColor;
          hslInput.value = `hsl(${hexToHsl(vortexConfig.baseColor).h}, ${hexToHsl(vortexConfig.baseColor).s}%, ${hexToHsl(vortexConfig.baseColor).l}%)`;
          
          hexInput.classList.remove('invalid');
          hslInput.classList.remove('invalid');
          
          const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
          if (colorPickerContainer) {
            colorPickerContainer.style.setProperty('--selected-color', vortexConfig.baseColor);
          }
        }
      }

      function initializeUI() {
        initVortex();
        
        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-vortex');
        });

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

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

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

        const backgroundPicker = document.getElementById('preview-background-picker');
        const previewContainer = document.getElementById('vortex-preview');

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

        previewContainer.style.backgroundColor = '#000000';

        document.getElementById('reset-colors').addEventListener('click', () => {
          vortexConfig.baseColor = defaultConfig.baseColor;
          vortexConfig.rangeHue = defaultConfig.rangeHue;
          
          document.getElementById('base-color').value = defaultConfig.baseColor;
          document.getElementById('range-hue').value = defaultConfig.rangeHue;
          document.getElementById('range-hue-value').textContent = defaultConfig.rangeHue;
          
          updateColorInputs();
          updateVortexPreview();
          showNotification('Colors reset to default');
        });

        document.getElementById('reset-animation').addEventListener('click', () => {
          vortexConfig.particleCount = defaultConfig.particleCount;
          vortexConfig.rangeY = defaultConfig.rangeY;
          vortexConfig.baseRadius = defaultConfig.baseRadius;
          vortexConfig.rangeRadius = defaultConfig.rangeRadius;
          
          document.getElementById('particle-count').value = defaultConfig.particleCount;
          document.getElementById('range-y').value = defaultConfig.rangeY;
          document.getElementById('base-radius').value = defaultConfig.baseRadius;
          document.getElementById('range-radius').value = defaultConfig.rangeRadius;
          
          document.getElementById('particle-count-value').textContent = defaultConfig.particleCount;
          document.getElementById('range-y-value').textContent = defaultConfig.rangeY;
          document.getElementById('base-radius-value').textContent = defaultConfig.baseRadius;
          document.getElementById('range-radius-value').textContent = defaultConfig.rangeRadius;
          
          updateVortexPreview();
          showNotification('Animation settings reset');
        });

        document.getElementById('reset-advanced').addEventListener('click', () => {
          vortexConfig.baseSpeed = defaultConfig.baseSpeed;
          vortexConfig.rangeSpeed = defaultConfig.rangeSpeed;
          
          document.getElementById('base-speed').value = defaultConfig.baseSpeed;
          document.getElementById('range-speed').value = defaultConfig.rangeSpeed;
          
          document.getElementById('base-speed-value').textContent = defaultConfig.baseSpeed;
          document.getElementById('range-speed-value').textContent = defaultConfig.rangeSpeed;
          
          updateVortexPreview();
          showNotification('Advanced settings reset');
        });

        const colorInput = document.getElementById('base-color');
        const hexInput = document.getElementById('base-color-hex');
        const hslInput = document.getElementById('base-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');
          vortexConfig.baseColor = color;
          
          const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
          colorPickerContainer.style.setProperty('--selected-color', color);
          
          updateVortexPreview();
        });
        
        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}%)`;
            vortexConfig.baseColor = hex;
            e.target.classList.remove('invalid');
            hslInput.classList.remove('invalid');
            
            const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
            colorPickerContainer.style.setProperty('--selected-color', hex);
            
            updateVortexPreview();
          } 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;
              vortexConfig.baseColor = hex;
              e.target.classList.remove('invalid');
              hexInput.classList.remove('invalid');
              
              const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
              colorPickerContainer.style.setProperty('--selected-color', hex);
              
              updateVortexPreview();
              return;
            }
          }
          
          e.target.classList.add('invalid');
        });
        
        hslInput.addEventListener('blur', (e) => {
          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':
                vortexConfig.particleCount = parseInt(input.value);
                break;
              case 'range-y':
                vortexConfig.rangeY = parseInt(input.value);
                break;
              case 'base-speed':
                vortexConfig.baseSpeed = parseFloat(input.value);
                break;
              case 'range-speed':
                vortexConfig.rangeSpeed = parseFloat(input.value);
                break;
              case 'base-radius':
                vortexConfig.baseRadius = parseFloat(input.value);
                break;
              case 'range-radius':
                vortexConfig.rangeRadius = parseFloat(input.value);
                break;
              case 'range-hue':
                vortexConfig.rangeHue = parseInt(input.value);
                break;
            }
            
            updateVortexPreview();
          });
        });

        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':
                generateRandomVortex();
                break;
              case 'b':
                document.getElementById('preview-background-picker').click();
                break;
            }
          }
        });

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

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

        function loadConfiguration() {
          try {
            const saved = localStorage.getItem('bricksfusion-vortex-config');
            if (saved) {
              const savedConfig = JSON.parse(saved);
              Object.assign(vortexConfig, savedConfig);
              
              document.getElementById('particle-count').value = savedConfig.particleCount;
              document.getElementById('range-y').value = savedConfig.rangeY;
              document.getElementById('base-speed').value = savedConfig.baseSpeed;
              document.getElementById('range-speed').value = savedConfig.rangeSpeed;
              document.getElementById('base-radius').value = savedConfig.baseRadius;
              document.getElementById('range-radius').value = savedConfig.rangeRadius;
              document.getElementById('base-color').value = savedConfig.baseColor;
              document.getElementById('range-hue').value = savedConfig.rangeHue;
              
              document.getElementById('particle-count-value').textContent = savedConfig.particleCount;
              document.getElementById('range-y-value').textContent = savedConfig.rangeY;
              document.getElementById('base-speed-value').textContent = savedConfig.baseSpeed;
              document.getElementById('range-speed-value').textContent = savedConfig.rangeSpeed;
              document.getElementById('base-radius-value').textContent = savedConfig.baseRadius;
              document.getElementById('range-radius-value').textContent = savedConfig.rangeRadius;
              document.getElementById('range-hue-value').textContent = savedConfig.rangeHue;
              
              updateColorInputs();
              updateVortexPreview();
            }
          } catch (e) {
          }
        }

        const originalUpdateVortexPreview = updateVortexPreview;
        updateVortexPreview = function() {
          originalUpdateVortexPreview();
          saveConfiguration();
        };

        loadConfiguration();
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Vortex - Bricksfusion
HEAVY

Vortex

Creates mesmerizing particle flow effect using Simplex Noise 3D algorithm for organic fluid motion. Hundreds of particles move in swirling patterns with customizable colors and glow effects. Features intelligent fade-in/fade-out lifecycle, automatic respawning, and multi-layer blur rendering for depth. Uses HTML5 Canvas with hardware acceleration and ResizeObserver for responsive scaling. Perfect for hero backgrounds, feature sections, or adding dynamic energy to any design.

Vortex Effect

Watch the particles flow in organic, mesmerizing patterns.

Particles

Particle Count 100-2000

Total number of particles in the effect. More particles create denser, more dramatic visuals but require more processing power.

Default: 500

Range Y 0-500 pixels

Vertical spawn range from center. Higher values spread particles across more height, lower creates concentrated flow.

Default: 100

Motion

Base Speed 0.0-5.0

Minimum movement speed for all particles. Creates consistent baseline motion across the effect.

Default: 0

Range Speed 0.0-5.0

Additional random speed variation added to base speed. Creates natural speed differences between particles.

Default: 1.5

Appearance

Base Radius 0.5-10 pixels

Minimum size of particle trails. Smaller creates delicate wisps, larger creates bold streaks.

Default: 1

Range Radius 0.0-10 pixels

Additional random size variation added to base radius. Creates depth through varied particle sizes.

Default: 2

Base Color color picker

Primary color for particles. All particles use variations of this color based on hue range.

Default: #3f51b5 (blue)

Range Hue 0-180 degrees

Color variation range in hue degrees. Higher creates rainbow effects, lower keeps colors similar to base.

Default: 30

Performance Warning

This element uses custom Simplex Noise 3D algorithm for organic particle motion combined with HTML5 Canvas rendering. Each frame calculates noise values for all particles and renders with multiple blur passes for glow effects. Uses requestAnimationFrame for smooth 60fps animation with hardware acceleration. ResizeObserver ensures responsive scaling. Heavy computational load - recommended particle count is 300-700 for most devices. Use sparingly, ideally one instance per page. Not recommended for mobile devices or as multiple instances. Consider reducing particle count on smaller screens for better performance.