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>Circles 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: #252525;
  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;
}

.add-color-btn,
.remove-color {
  padding: 0.5rem;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-secondary);
  cursor: pointer;
  transition: var(--transition);
  font-size: var(--text-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 40px;
}

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

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

.add-color-btn {
  width: 100%;
  margin-top: 1rem;
}

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

.circles-animation-wrapper {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 5;
  transform: translateZ(0);
  backface-visibility: hidden;
}

.circle-animation {
  position: absolute;
  border-radius: 50%;
  will-change: transform;
  left: 0;
  top: 0;
  backface-visibility: hidden;
  transform: translateZ(0);
  opacity: 0;
}

@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/visual-effects/" class="breadcrumb-item">Visual effects</a>
      <span class="breadcrumb-separator">›</span>
      <span class="breadcrumb-item active">Circles Animation</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-circles-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">Circles Animation</h1>
      <p class="page-subtitle">Interactive floating circles 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 circles 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-circles-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="circles-preview" data-circles-animation="true">
          <div class="preview-content">Interactive Circles Animation Preview</div>
          <div class="preview-controls">
            <button class="preview-btn" id="randomize-circles" 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="#252525" title="Change Preview Background (B)">
            </div>
          </div>
        </div>
      </section>

      <section class="controls-section">
        <div class="card">
          <div class="card-heading">
            Color Settings
            <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" id="colors-list">
            </div>
            
            <button class="add-color-btn" id="add-color-btn">
              + Add Color
            </button>
          </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">
                  Circle Count
                  <span class="help-tooltip" title="Number of circles (desktop)">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="circle-count-value">12</span></span>
                  <button class="reset-btn" onclick="resetParameter('circle-count', 12)">↺</button>
                </div>
              </div>
              <input type="range" id="circle-count" min="5" max="30" step="1" value="12">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Mobile Count
                  <span class="help-tooltip" title="Number of circles on mobile devices">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="mobile-count-value">3</span></span>
                  <button class="reset-btn" onclick="resetParameter('mobile-count', 3)">↺</button>
                </div>
              </div>
              <input type="range" id="mobile-count" min="2" max="15" step="1" value="3">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Minimum Size
                  <span class="help-tooltip" title="Minimum circle size in pixels">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="min-size-value">150</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('min-size', 150)">↺</button>
                </div>
              </div>
              <input type="range" id="min-size" min="50" max="300" step="10" value="150">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Maximum Size
                  <span class="help-tooltip" title="Maximum circle size in pixels">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="max-size-value">400</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('max-size', 400)">↺</button>
                </div>
              </div>
              <input type="range" id="max-size" min="100" max="600" step="10" value="400">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Animation Speed
                  <span class="help-tooltip" title="Speed multiplier for circle movement">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="animation-speed-value">2</span>x</span>
                  <button class="reset-btn" onclick="resetParameter('animation-speed', 2)">↺</button>
                </div>
              </div>
              <input type="range" id="animation-speed" min="0.5" max="4" step="0.1" value="2">
            </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">Responsive Mode</span>
              </div>
              <select id="responsive-mode">
                <option value="auto">Auto (Recommended)</option>
                <option value="minimal">Minimal (Light Performance)</option>
                <option value="moderate">Moderate</option>
                <option value="none">None (Full Size)</option>
              </select>
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">Blend Mode</span>
              </div>
              <select id="blend-mode">
                <option value="screen">Screen</option>
                <option value="multiply">Multiply</option>
                <option value="overlay">Overlay</option>
                <option value="color-dodge">Color Dodge</option>
                <option value="color-burn">Color Burn</option>
                <option value="hard-light">Hard Light</option>
                <option value="soft-light">Soft Light</option>
                <option value="difference">Difference</option>
                <option value="exclusion">Exclusion</option>
                <option value="normal">Normal</option>
              </select>
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let circlesConfig = {
        count: 12,
        mobileCount: 3,
        minSize: 150,
        maxSize: 400,
        speed: 2,
        responsiveMode: 'auto',
        blendMode: 'screen',
        colors: ["rgba(75,119,190,0.6)", "rgba(120,76,192,0.6)", "rgba(164,81,149,0.6)", "rgba(38,166,154,0.6)", "rgba(241,138,98,0.6)"]
      };

      const defaultConfig = { ...circlesConfig };

      let activeCircles = null;
      
      function initCirclesAnimation() {
        const sections = document.querySelectorAll('[data-circles-animation]:not([data-circles-initialized="true"])');
        
        sections.forEach((section) => {
          const count = section.hasAttribute('data-circles-count') 
            ? parseInt(section.getAttribute('data-circles-count')) 
            : circlesConfig.count;
            
          const mobileCount = section.hasAttribute('data-circles-mobile-count') 
            ? parseInt(section.getAttribute('data-circles-mobile-count')) 
            : circlesConfig.mobileCount;
            
          const minSize = section.hasAttribute('data-circles-min-size') 
            ? parseInt(section.getAttribute('data-circles-min-size')) 
            : circlesConfig.minSize;
            
          const maxSize = section.hasAttribute('data-circles-max-size') 
            ? parseInt(section.getAttribute('data-circles-max-size')) 
            : circlesConfig.maxSize;
            
          const speed = section.hasAttribute('data-circles-speed') 
            ? parseFloat(section.getAttribute('data-circles-speed')) 
            : circlesConfig.speed;
            
          const responsiveMode = section.hasAttribute('data-circles-responsive') 
            ? section.getAttribute('data-circles-responsive') 
            : circlesConfig.responsiveMode;
            
          const blendMode = section.hasAttribute('data-circles-blend-mode') 
            ? section.getAttribute('data-circles-blend-mode') 
            : circlesConfig.blendMode;
            
          const customColorsAttr = section.getAttribute('data-circles-colors');
          const colors = customColorsAttr ? customColorsAttr.split(',') : circlesConfig.colors;
            
          const options = {
            count,
            mobileCount,
            minSize,
            maxSize,
            speed,
            responsiveMode,
            blendMode,
            colors
          };
          
          setupCirclesAnimation(section, options);
          section.dataset.circlesInitialized = 'true';
          
          if (section.id === 'circles-preview') {
            activeCircles = { element: section, options };
            circlesConfig = {
              count: options.count,
              mobileCount: options.mobileCount,
              minSize: options.minSize,
              maxSize: options.maxSize,
              speed: options.speed,
              responsiveMode: options.responsiveMode,
              blendMode: options.blendMode,
              colors: options.colors
            };
          }
        });
      }
      
      function setupCirclesAnimation(element, options) {
        if (window.getComputedStyle(element).position === 'static') {
          element.style.position = 'relative';
        }
        
        const wrapper = document.createElement('div');
        wrapper.className = 'circles-animation-wrapper';
        element.appendChild(wrapper);
        
        element.circlesWrapper = wrapper;
        element.circlesElements = [];
        element.animationFrame = null;
        element.circles = [];
        
        const isMobile = window.innerWidth <= 768;
        const responsiveConfig = getResponsiveConfig(element, options, options.responsiveMode);
        const circleCount = isMobile ? options.mobileCount : responsiveConfig.count;
        
        for (let i = 0; i < circleCount; i++) {
          const circle = createCircle(element, options, responsiveConfig, i);
          element.circles.push(circle);
        }
        
        startCirclesAnimation(element, options, responsiveConfig);
      }
      
      function getResponsiveConfig(element, baseConfig, responsiveMode) {
        const width = window.innerWidth;
        let scale = 1;
        let countScale = 1;
        
        switch(responsiveMode) {
          case 'minimal':
            scale = width <= 768 ? 0.3 : width <= 1199 ? 0.5 : 0.7;
            countScale = width <= 768 ? 0.5 : width <= 1199 ? 0.7 : 0.8;
            break;
          case 'moderate':
            scale = width <= 768 ? 0.5 : width <= 1199 ? 0.7 : 0.9;
            countScale = width <= 768 ? 0.6 : width <= 1199 ? 0.8 : 0.9;
            break;
          case 'none':
            scale = 1;
            countScale = 1;
            break;
          case 'auto':
          default:
            scale = width <= 768 ? 0.4 : width <= 1199 ? 0.6 : 0.8;
            countScale = width <= 768 ? 0.6 : width <= 1199 ? 0.75 : 0.9;
        }
        
        return {
          ...baseConfig,
          minSize: Math.round(baseConfig.minSize * scale),
          maxSize: Math.round(baseConfig.maxSize * scale),
          count: width <= 768 ? baseConfig.mobileCount : Math.round(baseConfig.count * countScale)
        };
      }
      
      function createCircle(element, options, responsiveConfig, index) {
        const size = random(responsiveConfig.minSize, responsiveConfig.maxSize);
        const x = random(0, element.offsetWidth - size);
        const y = random(0, element.offsetHeight - size);
        const vx = random(-responsiveConfig.speed, responsiveConfig.speed);
        const vy = random(-responsiveConfig.speed, responsiveConfig.speed);
        const color = options.colors[index % options.colors.length];
        
        const circleEl = document.createElement('div');
        circleEl.className = 'circle-animation';
        circleEl.style.cssText = `
          width: ${size}px;
          height: ${size}px;
          background-color: ${color};
          mix-blend-mode: ${options.blendMode};
          transform: translate3d(${x}px, ${y}px, 0);
          position: absolute;
          border-radius: 50%;
          left: 0;
          top: 0;
          will-change: transform;
          opacity: 1;
        `;
        
        element.circlesWrapper.appendChild(circleEl);
        element.circlesElements.push(circleEl);
        
        return {
          element: circleEl,
          x,
          y,
          vx,
          vy,
          size,
          color,
          index
        };
      }
      
      function startCirclesAnimation(element, options, responsiveConfig) {
        let lastTime = 0;
        const isMobile = window.innerWidth <= 768;
        const fps = isMobile ? 30 : 60;
        const fpsInterval = 1000 / fps;
        
        function animate(currentTime) {
          element.animationFrame = requestAnimationFrame(animate);
          
          const deltaTime = currentTime - lastTime;
          if (deltaTime < fpsInterval) return;
          
          lastTime = currentTime - (deltaTime % fpsInterval);
          
          element.circles.forEach(circle => {
            circle.x += circle.vx;
            circle.y += circle.vy;
            
            if (circle.x <= 0 || circle.x >= element.offsetWidth - circle.size) {
              circle.vx *= -1;
              circle.x = Math.max(0, Math.min(circle.x, element.offsetWidth - circle.size));
            }
            
            if (circle.y <= 0 || circle.y >= element.offsetHeight - circle.size) {
              circle.vy *= -1;
              circle.y = Math.max(0, Math.min(circle.y, element.offsetHeight - circle.size));
            }
            
            circle.element.style.transform = `translate3d(${circle.x}px, ${circle.y}px, 0)`;
          });
        }
        
        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._cleanupCircles = () => {
          if (element.animationFrame) {
            cancelAnimationFrame(element.animationFrame);
          }
          observer.disconnect();
          if (element.circlesWrapper && element.circlesWrapper.parentNode) {
            element.circlesWrapper.parentNode.removeChild(element.circlesWrapper);
          }
          element.dataset.circlesInitialized = 'false';
        };
        
        element.updateConfig = (newOptions) => {
          element.circles.forEach((circle, index) => {
            if (newOptions.colors) {
              const newColor = newOptions.colors[index % newOptions.colors.length];
              circle.color = newColor;
              circle.element.style.backgroundColor = newColor;
            }
            
            if (newOptions.blendMode) {
              circle.element.style.mixBlendMode = newOptions.blendMode;
            }
            
            if (newOptions.speed !== undefined) {
              const currentSpeed = Math.sqrt(circle.vx * circle.vx + circle.vy * circle.vy);
              if (currentSpeed > 0) {
                const scaleFactor = newOptions.speed / currentSpeed;
                circle.vx *= scaleFactor;
                circle.vy *= scaleFactor;
              } else {
                circle.vx = random(-newOptions.speed, newOptions.speed);
                circle.vy = random(-newOptions.speed, newOptions.speed);
              }
            }
          });
        };
      }
      
      function random(min, max) {
        return Math.random() * (max - min) + min;
      }
      
      function updateCirclesPreview(mode = 'reset') {
        const preview = document.getElementById('circles-preview');
        if (!preview) return;
        
        if (mode === 'smooth' && preview.updateConfig && preview.circles && preview.circles.length > 0) {
          preview.updateConfig({
            colors: circlesConfig.colors,
            blendMode: circlesConfig.blendMode,
            speed: circlesConfig.speed
          });
          return;
        }
        
        if (preview._cleanupCircles) {
          preview._cleanupCircles();
        }
        
        preview.setAttribute('data-circles-animation', 'true');
        preview.setAttribute('data-circles-count', circlesConfig.count);
        preview.setAttribute('data-circles-mobile-count', circlesConfig.mobileCount);
        preview.setAttribute('data-circles-min-size', circlesConfig.minSize);
        preview.setAttribute('data-circles-max-size', circlesConfig.maxSize);
        preview.setAttribute('data-circles-speed', circlesConfig.speed);
        preview.setAttribute('data-circles-responsive', circlesConfig.responsiveMode);
        preview.setAttribute('data-circles-blend-mode', circlesConfig.blendMode);
        preview.setAttribute('data-circles-colors', circlesConfig.colors.join(','));
        
        preview.removeAttribute('data-circles-initialized');
        
        const options = {
          count: circlesConfig.count,
          mobileCount: circlesConfig.mobileCount,
          minSize: circlesConfig.minSize,
          maxSize: circlesConfig.maxSize,
          speed: circlesConfig.speed,
          responsiveMode: circlesConfig.responsiveMode,
          blendMode: circlesConfig.blendMode,
          colors: circlesConfig.colors
        };
        
        setupCirclesAnimation(preview, options);
      }

      function generateRandomColor() {
        const colors = [
          '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57',
          '#ff9ff3', '#54a0ff', '#5f27cd', '#00d2d3', '#ff9f43',
          '#10ac84', '#ee5a6f', '#60a3bc', '#8395a7', '#222f3e'
        ];
        return colors[Math.floor(Math.random() * colors.length)];
      }

      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 para todos los elementos
  const sectionId = generateUniqueId();
  const containerId = generateUniqueId();
  const codeId = generateUniqueId();
  const attributeId = generateUniqueId();
  
  // Obtener el JavaScript actual con la configuración del usuario
  const jsCode = generateJavaScriptCode();
  
  // Crear el objeto JSON completo de Bricks Builder
  const bricksJSON = {
    "content": [
      {
        "id": sectionId,
        "name": "section",
        "parent": 0,
        "children": [containerId, codeId],
        "settings": {
          "_height": "500",
          "_justifyContent": "center",
          "_background": {
            "color": {
              "hex": "#000000"
            }
          },
          "_attributes": [
            {
              "id": attributeId,
              "name": "data-circles-animation"
            }
          ]
        },
        "label": "Circles Section"
      },
      {
        "id": containerId,
        "name": "container",
        "parent": sectionId,
        "children": [],
        "settings": {},
        "label": ""
      },
      {
        "id": codeId,
        "name": "code",
        "parent": sectionId,
        "children": [],
        "settings": {
          "javascriptCode": jsCode,
          "executeCode": true,
          "_display": "none"
        },
        "label": "Circles JS"
      }
    ],
    "source": "bricksCopiedElements",
    "sourceUrl": "https://bricksfusion.com",
    "version": "2.0.1",
    "globalClasses": [],
    "globalElements": []
  };
  
  return JSON.stringify(bricksJSON, null, 2);
}

      function generateJavaScriptCode() {
        return `(function() {
const defaultConfig = {
  count: ${circlesConfig.count},
  mobileCount: ${circlesConfig.mobileCount},
  minSize: ${circlesConfig.minSize},
  maxSize: ${circlesConfig.maxSize},
  speed: ${circlesConfig.speed},
  responsiveMode: "${circlesConfig.responsiveMode}",
  blendMode: "${circlesConfig.blendMode}",
  colors: [${circlesConfig.colors.map(color => `"${color}"`).join(', ')}]
};

function random(min, max) {
  return Math.random() * (max - min) + min;
}

function getResponsiveConfig(element, baseConfig, responsiveMode) {
  const width = window.innerWidth;
  let scale = 1;
  let countScale = 1;
  
  switch(responsiveMode) {
    case 'minimal':
      scale = width <= 768 ? 0.3 : width <= 1199 ? 0.5 : 0.7;
      countScale = width <= 768 ? 0.5 : width <= 1199 ? 0.7 : 0.8;
      break;
    case 'moderate':
      scale = width <= 768 ? 0.5 : width <= 1199 ? 0.7 : 0.9;
      countScale = width <= 768 ? 0.6 : width <= 1199 ? 0.8 : 0.9;
      break;
    case 'none':
      scale = 1;
      countScale = 1;
      break;
    case 'auto':
    default:
      scale = width <= 768 ? 0.4 : width <= 1199 ? 0.6 : 0.8;
      countScale = width <= 768 ? 0.6 : width <= 1199 ? 0.75 : 0.9;
  }
  
  return {
    ...baseConfig,
    minSize: Math.round(baseConfig.minSize * scale),
    maxSize: Math.round(baseConfig.maxSize * scale),
    count: width <= 768 ? baseConfig.mobileCount : Math.round(baseConfig.count * countScale)
  };
}

const styleEl = document.createElement('style');
styleEl.textContent = \`
  .circles-animation-wrapper {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 5;
    transform: translateZ(0);
    backface-visibility: hidden;
  }

  .circle-animation {
    position: absolute;
    border-radius: 50%;
    will-change: transform;
    left: 0;
    top: 0;
    backface-visibility: hidden;
    transform: translateZ(0);
  }
\`;
document.head.appendChild(styleEl);

function applyCirclesAnimation() {
  document.querySelectorAll('[data-circles-animation]').forEach(targetElement => {
    if (targetElement._circlesAnimationCleanup) {
      targetElement._circlesAnimationCleanup();
      delete targetElement._circlesAnimationCleanup;
    }

    const count = parseInt(targetElement.getAttribute('data-circles-count')) || defaultConfig.count;
    const mobileCount = parseInt(targetElement.getAttribute('data-circles-mobile-count')) || defaultConfig.mobileCount;
    const minSize = parseInt(targetElement.getAttribute('data-circles-min-size')) || defaultConfig.minSize;
    const maxSize = parseInt(targetElement.getAttribute('data-circles-max-size')) || defaultConfig.maxSize;
    const speed = parseFloat(targetElement.getAttribute('data-circles-speed')) || defaultConfig.speed;
    const responsiveMode = targetElement.getAttribute('data-circles-responsive') || defaultConfig.responsiveMode;
    const blendMode = targetElement.getAttribute('data-circles-blend-mode') || defaultConfig.blendMode;
    const customColorsAttr = targetElement.getAttribute('data-circles-colors');
    
    const customColors = customColorsAttr ? customColorsAttr.split(',') : null;
    const colors = customColors || defaultConfig.colors;

    const isMobile = window.innerWidth <= 768;
    const responsiveConfig = getResponsiveConfig(targetElement, {
      count,
      mobileCount,
      minSize,
      maxSize,
      speed
    }, responsiveMode);

    const targetPosition = window.getComputedStyle(targetElement).position;
    if (targetPosition === 'static') {
      targetElement.style.position = 'relative';
    }

    const wrapper = document.createElement('div');
    wrapper.className = 'circles-animation-wrapper';
    
    let circles = [];
    const circlesCount = isMobile ? mobileCount : responsiveConfig.count;
    
    for (let i = 0; i < circlesCount; i++) {
      const size = random(responsiveConfig.minSize, responsiveConfig.maxSize);
      const x = random(0, targetElement.offsetWidth - size);
      const y = random(0, targetElement.offsetHeight - size);
      
      circles.push({
        element: null,
        x,
        y,
        vx: random(-responsiveConfig.speed, responsiveConfig.speed),
        vy: random(-responsiveConfig.speed, responsiveConfig.speed),
        size,
        color: colors[i % colors.length]
      });
    }
    
    circles.forEach((circle, index) => {
      const circleEl = document.createElement('div');
      circleEl.className = 'circle-animation';
      circleEl.style.cssText = \`
        width: \${circle.size}px;
        height: \${circle.size}px;
        background-color: \${circle.color};
        mix-blend-mode: \${blendMode};
        transform: translate3d(\${circle.x}px, \${circle.y}px, 0);
        position: absolute;
        border-radius: 50%;
        left: 0;
        top: 0;
        will-change: transform;
      \`;

      wrapper.appendChild(circleEl);
      circles[index].element = circleEl;
    });
    
    targetElement.appendChild(wrapper);

    let lastTime = 0;
    const fps = isMobile ? 30 : 60;
    const fpsInterval = 1000 / fps;
    
    let animationFrame;
    let isRunning = true;
    let isPaused = false;

    function animate(timestamp) {
      if (!isRunning) return;
      
      if (!timestamp) timestamp = performance.now();
      const elapsed = timestamp - lastTime;
      
      if (elapsed > fpsInterval) {
        lastTime = timestamp - (elapsed % fpsInterval);
        
        if (!isPaused) {
          circles.forEach(circle => {
            circle.x += circle.vx;
            circle.y += circle.vy;
            
            if (circle.x <= 0 || circle.x >= targetElement.offsetWidth - circle.size) {
              circle.vx *= -1;
              circle.x = Math.max(0, Math.min(circle.x, targetElement.offsetWidth - circle.size));
            }
            
            if (circle.y <= 0 || circle.y >= targetElement.offsetHeight - circle.size) {
              circle.vy *= -1;
              circle.y = Math.max(0, Math.min(circle.y, targetElement.offsetHeight - circle.size));
            }
            
            circle.element.style.transform = \`translate3d(\${circle.x}px, \${circle.y}px, 0)\`;
          });
        }
      }
      
      animationFrame = requestAnimationFrame(animate);
    }

    animationFrame = requestAnimationFrame(animate);

    let scrollTimeout;
    function handleScroll() {
      isPaused = true;
      clearTimeout(scrollTimeout);
      scrollTimeout = setTimeout(() => {
        isPaused = false;
      }, 50);
    }
    
    window.addEventListener('scroll', handleScroll, { passive: true });
    window.addEventListener('touchmove', handleScroll, { passive: true });
    
    let visibilityChange;
    if (typeof document.hidden !== "undefined") {
      visibilityChange = "visibilitychange";
    } else if (typeof document.webkitHidden !== "undefined") {
      visibilityChange = "webkitvisibilitychange";
    } else if (typeof document.msHidden !== "undefined") {
      visibilityChange = "msvisibilitychange";
    }
    
    function handleVisibilityChange() {
      if (document.hidden || document.webkitHidden || document.msHidden) {
        isPaused = true;
      } else {
        isPaused = false;
      }
    }
    
    if (visibilityChange) {
      document.addEventListener(visibilityChange, handleVisibilityChange, false);
    }

    let resizeTimeout;
    const handleResize = () => {
      clearTimeout(resizeTimeout);
      resizeTimeout = setTimeout(() => {
        const newIsMobile = window.innerWidth <= 768;
        if (newIsMobile !== isMobile) {
          if (targetElement._circlesAnimationCleanup) {
            targetElement._circlesAnimationCleanup();
          }
          setTimeout(() => applyCirclesAnimation(), 100);
        }
      }, 250);
    };

    window.addEventListener('resize', handleResize);

    targetElement._circlesAnimationCleanup = () => {
      isRunning = false;
      cancelAnimationFrame(animationFrame);
      
      window.removeEventListener('scroll', handleScroll);
      window.removeEventListener('touchmove', handleScroll);
      window.removeEventListener('resize', handleResize);
      
      if (visibilityChange) {
        document.removeEventListener(visibilityChange, handleVisibilityChange);
      }
      
      if (wrapper.parentNode) {
        wrapper.parentNode.removeChild(wrapper);
      }
    };
  });
}

const initWithDelay = () => {
  setTimeout(applyCirclesAnimation, 200);
};

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

window.addEventListener('load', () => {
  setTimeout(applyCirclesAnimation, 300);
});

const observer = new MutationObserver((mutations) => {
  let shouldInit = false;

  mutations.forEach((mutation) => {
    if (mutation.type === 'childList') {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === 1) {
          if (node.hasAttribute && node.hasAttribute('data-circles-animation')) {
            shouldInit = true;
          } else if (node.querySelectorAll) {
            const animationElements = node.querySelectorAll('[data-circles-animation]');
            if (animationElements.length > 0) {
              shouldInit = true;
            }
          }
        }
      });
    } else if (mutation.type === 'attributes' && mutation.attributeName === 'data-circles-animation') {
      shouldInit = true;
    }
  });

  if (shouldInit) {
    setTimeout(applyCirclesAnimation, 200);
  }
});

observer.observe(document.body, { 
  childList: true, 
  subtree: true,
  attributes: true,
  attributeFilter: ['data-circles-animation']
});
})();`;
      }

      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 'circle-count':
              circlesConfig.count = defaultValue;
              break;
            case 'mobile-count':
              circlesConfig.mobileCount = defaultValue;
              break;
            case 'min-size':
              circlesConfig.minSize = defaultValue;
              break;
            case 'max-size':
              circlesConfig.maxSize = defaultValue;
              break;
            case 'animation-speed':
              circlesConfig.speed = defaultValue;
              break;
          }
          
          updateCirclesPreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function generateRandomCircles() {
        circlesConfig.count = Math.floor(Math.random() * 25) + 5;
        circlesConfig.mobileCount = Math.floor(Math.random() * 13) + 2;
        circlesConfig.minSize = Math.floor(Math.random() * 250) + 50;
        circlesConfig.maxSize = Math.floor(Math.random() * 400) + 200;
        circlesConfig.speed = Math.random() * 3.5 + 0.5;
        circlesConfig.responsiveMode = ['auto', 'minimal', 'moderate', 'none'][Math.floor(Math.random() * 4)];
        circlesConfig.blendMode = ['screen', 'multiply', 'overlay', 'color-dodge', 'normal'][Math.floor(Math.random() * 5)];
        
        const newColors = [];
        const colorCount = Math.floor(Math.random() * 4) + 3;
        for (let i = 0; i < colorCount; i++) {
          const r = Math.floor(Math.random() * 256);
          const g = Math.floor(Math.random() * 256);
          const b = Math.floor(Math.random() * 256);
          newColors.push(`rgba(${r},${g},${b},0.6)`);
        }
        circlesConfig.colors = newColors;
        
        document.getElementById('circle-count').value = circlesConfig.count;
        document.getElementById('mobile-count').value = circlesConfig.mobileCount;
        document.getElementById('min-size').value = circlesConfig.minSize;
        document.getElementById('max-size').value = circlesConfig.maxSize;
        document.getElementById('animation-speed').value = circlesConfig.speed;
        document.getElementById('responsive-mode').value = circlesConfig.responsiveMode;
        document.getElementById('blend-mode').value = circlesConfig.blendMode;
        
        document.getElementById('circle-count-value').textContent = circlesConfig.count;
        document.getElementById('mobile-count-value').textContent = circlesConfig.mobileCount;
        document.getElementById('min-size-value').textContent = circlesConfig.minSize;
        document.getElementById('max-size-value').textContent = circlesConfig.maxSize;
        document.getElementById('animation-speed-value').textContent = circlesConfig.speed;
        
        initializeColorInputs();
        updateCirclesPreview();
        showNotification('Random circles animation generated!');
      }

      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 convertRgbaToHex(rgba) {
        const rgbaMatch = rgba.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
        if (rgbaMatch) {
          const r = parseInt(rgbaMatch[1]);
          const g = parseInt(rgbaMatch[2]);
          const b = parseInt(rgbaMatch[3]);
          return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
        }
        return rgba;
      }

      function convertHexToRgba(hex, alpha = 0.6) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        return `rgba(${r},${g},${b},${alpha})`;
      }

      function initializeColorInputs() {
        const colorsList = document.getElementById('colors-list');
        colorsList.innerHTML = '';
        
        circlesConfig.colors.forEach((color, index) => {
          addColorInput(color, index);
        });
      }

      function addColorInput(color, index) {
        if (color === undefined || index === undefined) {
          const newColor = convertHexToRgba(generateRandomColor(), 0.6);
          circlesConfig.colors.push(newColor);
          color = newColor;
          index = circlesConfig.colors.length - 1;
        }
        
        const colorsList = document.getElementById('colors-list');
        
        const colorRow = document.createElement('div');
        colorRow.className = 'color-row';
        colorRow.setAttribute('data-index', index);
        
        const colorPickerContainer = document.createElement('div');
        colorPickerContainer.className = 'color-picker-container';
        colorPickerContainer.style.setProperty('--selected-color', convertRgbaToHex(color));
        
        const colorInput = document.createElement('input');
        colorInput.type = 'color';
        colorInput.id = `color-${index}`;
        colorInput.value = convertRgbaToHex(color);
        colorInput.addEventListener('input', function() {
          const rgbaColor = convertHexToRgba(this.value, 0.6);
          updateColorValue(index, rgbaColor);
          colorPickerContainer.style.setProperty('--selected-color', this.value);
          updateCirclesPreview('smooth');
        });
        
        colorPickerContainer.appendChild(colorInput);
        
        const hexInputGroup = document.createElement('div');
        hexInputGroup.className = 'color-input-group';
        
        const hexLabel = document.createElement('span');
        hexLabel.className = 'color-label';
        hexLabel.textContent = 'HEX';
        
        const hexInput = document.createElement('input');
        hexInput.type = 'text';
        hexInput.className = 'color-input hex-input';
        hexInput.id = `hex-${index}`;
        hexInput.value = convertRgbaToHex(color);
        hexInput.placeholder = '#FFFFFF';
        
        hexInput.addEventListener('input', function() {
          if (isValidHex(this.value)) {
            const rgbaColor = convertHexToRgba(this.value, 0.6);
            updateColorValue(index, rgbaColor);
            colorInput.value = this.value;
            colorPickerContainer.style.setProperty('--selected-color', this.value);
            const hslInput = document.getElementById(`hsl-${index}`);
            hslInput.value = `hsl(${hexToHsl(this.value).h}, ${hexToHsl(this.value).s}%, ${hexToHsl(this.value).l}%)`;
            this.classList.remove('invalid');
            hslInput.classList.remove('invalid');
            updateCirclesPreview('smooth');
          } else {
            this.classList.add('invalid');
          }
        });
        
        hexInputGroup.appendChild(hexLabel);
        hexInputGroup.appendChild(hexInput);
        
        const hslInputGroup = document.createElement('div');
        hslInputGroup.className = 'color-input-group';
        
        const hslLabel = document.createElement('span');
        hslLabel.className = 'color-label';
        hslLabel.textContent = 'HSL';
        
        const hslInput = document.createElement('input');
        hslInput.type = 'text';
        hslInput.className = 'color-input hsl-input';
        hslInput.id = `hsl-${index}`;
        const hslColor = hexToHsl(convertRgbaToHex(color));
        hslInput.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.l}%)`;
        hslInput.placeholder = 'hsl(0, 100%, 50%)';
        
        hslInput.addEventListener('input', function() {
          if (isValidHsl(this.value)) {
            const hex = hslToHex(this.value);
            if (hex) {
              const rgbaColor = convertHexToRgba(hex, 0.6);
              updateColorValue(index, rgbaColor);
              colorInput.value = hex;
              colorPickerContainer.style.setProperty('--selected-color', hex);
              hexInput.value = hex;
              this.classList.remove('invalid');
              hexInput.classList.remove('invalid');
              updateCirclesPreview('smooth');
            }
          } else {
            this.classList.add('invalid');
          }
        });
        
        hslInputGroup.appendChild(hslLabel);
        hslInputGroup.appendChild(hslInput);
        
        colorRow.appendChild(colorPickerContainer);
        colorRow.appendChild(hexInputGroup);
        colorRow.appendChild(hslInputGroup);
        
        if (circlesConfig.colors.length > 2) {
          const removeBtn = document.createElement('button');
          removeBtn.className = 'remove-color';
          removeBtn.innerHTML = '×';
          removeBtn.addEventListener('click', function() {
            removeColorInput(index);
          });
          colorRow.appendChild(removeBtn);
        }
        
        colorsList.appendChild(colorRow);
      }

      function updateColorValue(index, value) {
        circlesConfig.colors[index] = value;
      }

      function removeColorInput(index) {
        circlesConfig.colors.splice(index, 1);
        initializeColorInputs();
        updateCirclesPreview();
      }

      function initializeUI() {
        initCirclesAnimation();
        
        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-circles-animation');
        });

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

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

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

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

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

        previewContainer.style.backgroundColor = '#252525';

        document.getElementById('reset-colors').addEventListener('click', () => {
          circlesConfig.colors = [...defaultConfig.colors];
          initializeColorInputs();
          updateCirclesPreview();
          showNotification('Colors reset to default');
        });

        document.getElementById('reset-animation').addEventListener('click', () => {
          circlesConfig.count = defaultConfig.count;
          circlesConfig.mobileCount = defaultConfig.mobileCount;
          circlesConfig.minSize = defaultConfig.minSize;
          circlesConfig.maxSize = defaultConfig.maxSize;
          circlesConfig.speed = defaultConfig.speed;
          
          document.getElementById('circle-count').value = defaultConfig.count;
          document.getElementById('mobile-count').value = defaultConfig.mobileCount;
          document.getElementById('min-size').value = defaultConfig.minSize;
          document.getElementById('max-size').value = defaultConfig.maxSize;
          document.getElementById('animation-speed').value = defaultConfig.speed;
          
          document.getElementById('circle-count-value').textContent = defaultConfig.count;
          document.getElementById('mobile-count-value').textContent = defaultConfig.mobileCount;
          document.getElementById('min-size-value').textContent = defaultConfig.minSize;
          document.getElementById('max-size-value').textContent = defaultConfig.maxSize;
          document.getElementById('animation-speed-value').textContent = defaultConfig.speed;
          
          updateCirclesPreview();
          showNotification('Animation settings reset');
        });

        document.getElementById('reset-advanced').addEventListener('click', () => {
          circlesConfig.responsiveMode = defaultConfig.responsiveMode;
          circlesConfig.blendMode = defaultConfig.blendMode;
          
          document.getElementById('responsive-mode').value = defaultConfig.responsiveMode;
          document.getElementById('blend-mode').value = defaultConfig.blendMode;
          
          updateCirclesPreview();
          showNotification('Advanced settings reset');
        });

        document.getElementById('add-color-btn').addEventListener('click', () => {
          addColorInput();
          updateCirclesPreview();
        });

        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 'circle-count':
                circlesConfig.count = parseInt(input.value);
                break;
              case 'mobile-count':
                circlesConfig.mobileCount = parseInt(input.value);
                break;
              case 'min-size':
                circlesConfig.minSize = parseInt(input.value);
                break;
              case 'max-size':
                circlesConfig.maxSize = parseInt(input.value);
                break;
              case 'animation-speed':
                circlesConfig.speed = parseFloat(input.value);
                break;
            }
            
            const shouldRecreate = input.id === 'circle-count' || input.id === 'mobile-count' || 
                                 input.id === 'min-size' || input.id === 'max-size';
            updateCirclesPreview(shouldRecreate ? 'reset' : 'smooth');
          });
        });

        document.getElementById('responsive-mode').addEventListener('change', function() {
          circlesConfig.responsiveMode = this.value;
          updateCirclesPreview();
        });

        document.getElementById('blend-mode').addEventListener('change', function() {
          circlesConfig.blendMode = this.value;
          updateCirclesPreview('smooth');
        });

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

        initializeColorInputs();
        
        setTimeout(() => {
          showNotification('BricksFusion Circles Animation Configurator loaded!');
        }, 500);
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Circles Animation - Bricksfusion
MEDIUM

Circles Animation

Creates animated circles that bounce around your section. Perfect for hero sections, backgrounds, or adding dynamic energy to any container.

Circles Animation

Watch the colorful circles bounce and blend behind your content.

Circles

Circle Count 3-30

Number of circles on desktop. More circles create a busier effect. Keep under 15 for best performance.

Default: 12

Mobile Count 1-10

Number of circles on mobile devices. Fewer circles improve performance on phones and tablets.

Default: 3

Colors color picker array

Colors for the circles. Multiple colors create variety. Use semi-transparent colors for better blending effects.

Default: Blue/purple gradient tones

Size

Min Size 50-500 pixels

Minimum circle size. Smaller circles add variety and depth.

Default: 150

Max Size 100-800 pixels

Maximum circle size. Larger circles create bold focal points. Mix sizes for natural variation.

Default: 400

Animation

Speed 0.5-5.0

How fast circles move. Lower is slow and calm, higher is fast and energetic. 2.0 creates good movement.

Default: 2.0

Blend Mode screen / multiply / overlay / normal

How circles blend with each other and background. Screen brightens, multiply darkens, overlay does both.

Default: Screen

Responsive

Responsive Mode auto / minimal / moderate / none

How circles adapt to smaller screens. Auto balances size and count, minimal reduces heavily, moderate reduces moderately, none keeps same size.

Default: Auto

Performance

This element uses requestAnimationFrame for smooth animations at 60fps on desktop and 30fps on mobile. It automatically pauses during scrolling and when the tab is hidden. Keep circle count under 15 for optimal performance.