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>Explode Text 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);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  text-align: center;
}

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

input[type="text"],
input[type="number"],
select {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--input-radius);
  font-family: var(--font);
  font-size: var(--text-xs);
  color: var(--text-primary);
  background-color: var(--card-bg);
  margin-bottom: 0.75rem;
  outline: none;
  transition: var(--transition);
}

input[type="text"]:focus,
input[type="number"]:focus,
select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}

.color-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: #ffad70;
}

.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, #ffad70);
  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 {
  background-color: rgba(50, 50, 50, 0.5);
  border: 1px dashed var(--border);
  color: var(--text-secondary);
  padding: 0.75rem;
  border-radius: var(--input-radius);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xs);
  transition: var(--transition);
  margin-top: 0.5rem;
}

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

.remove-color {
  background: none;
  border: none;
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 1.2em;
  padding: 0.5rem;
  border-radius: 50%;
  transition: var(--transition);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
}

.remove-color:hover {
  color: white;
  background-color: rgba(255, 255, 255, 0.1);
}

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

.switch {
  position: relative;
  display: inline-block;
  width: 52px;
  height: 28px;
  margin-right: 12px;
  flex-shrink: 0;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #4a4a4a;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 28px;
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
}

.slider:before {
  position: absolute;
  content: "";
  height: 22px;
  width: 22px;
  left: 3px;
  bottom: 3px;
  background-color: white;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 50%;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.1);
}

.switch input:checked + .slider {
  background-color: var(--accent);
  box-shadow: inset 0 1px 3px rgba(239, 96, 19, 0.3);
}

.switch input:focus + .slider {
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3), 0 0 0 3px rgba(239, 96, 19, 0.2);
}

.switch input:checked + .slider:before {
  transform: translateX(24px);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25), 0 1px 3px rgba(0, 0, 0, 0.15);
}

.toggle-wrapper {
  display: flex;
  align-items: center;
  gap: 0;
  margin-bottom: 1rem;
  cursor: pointer;
  transition: var(--transition);
}

.toggle-wrapper:hover {
  opacity: 0.9;
}

.toggle-label {
  font-size: var(--text-xs);
  color: var(--text-primary);
  cursor: pointer;
  flex: 1;
  user-select: none;
  line-height: 1.4;
}

.disabled-control {
  opacity: 0.5;
  pointer-events: none;
}

.mobile-notice {
  background-color: rgba(255, 193, 7, 0.1);
  color: #ffc107;
  padding: 0.75rem 1rem;
  border-radius: var(--input-radius);
  font-size: var(--text-xs);
  margin-top: 0.75rem;
  border-left: 3px solid #ffc107;
  line-height: 1.5;
}

.style-description {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  margin-top: 0.5rem;
  line-height: 1.5;
}

@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/animated-text/" class="breadcrumb-item">Animated text</a>
      <span class="breadcrumb-separator">›</span>
      <span class="breadcrumb-item active">Explode Text</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-explode-text
      </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">Explode Text</h1>
      <p class="page-subtitle">Interactive particle text effects 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 explode text effect 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 and paste the JavaScript</li>
                <li>To add the effect to any element: go to <strong>Section → Style → Attributes</strong>, add <code>data-explode-text</code> as attribute name with your desired text as value</li>
                <li>The effect will automatically initialize with your configured settings</li>
              </ol>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="content">
      <section class="preview-section">
        <div class="preview-container" id="explode-preview" data-explode-text="HOVER!">
          <div class="preview-content">Interactive Explode Text Preview</div>
          <div class="preview-controls">
            <button class="preview-btn" id="randomize-explode" 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">
            Text Configuration
            <div class="card-actions">
              <button class="card-action-btn" id="reset-text" title="Reset Text Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">Display Text</span>
              </div>
              <input type="text" id="display-text" placeholder="Enter your text..." value="HOVER!">
              <p class="style-description">The text that will be displayed with particle explosion effect</p>
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Text Size (Desktop)
                  <span class="help-tooltip" title="Font size in pixels for desktop (0 = auto-size)">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="text-size-value">auto</span></span>
                  <button class="reset-btn" onclick="resetParameter('text-size', 0)">↺</button>
                </div>
              </div>
              <input type="range" id="text-size" min="0" max="200" step="10" value="0">
            </div>

            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Text Size (Mobile)
                  <span class="help-tooltip" title="Font size in pixels for mobile devices (0 = auto-size)">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="mobile-text-size-value">auto</span></span>
                  <button class="reset-btn" onclick="resetParameter('mobile-text-size', 0)">↺</button>
                </div>
              </div>
              <input type="range" id="mobile-text-size" min="0" max="120" step="5" value="0">
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Color Palette
            <div class="card-actions">
              <button class="card-action-btn" id="generate-random-palette" title="Generate Random Palette">🎲</button>
              <button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div id="color-palette">
              <!-- Color inputs will be generated here -->
            </div>
            <div class="add-color-btn" id="add-color-btn">
              <span>+ Add Color</span>
            </div>
            <p class="style-description">Colors for the gradient effect applied to particles</p>
          </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">
                  Animation Force
                  <span class="help-tooltip" title="Strength of particle movement when interacting with cursor">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="animation-force-value">80</span></span>
                  <button class="reset-btn" onclick="resetParameter('animation-force', 80)">↺</button>
                </div>
              </div>
              <input type="range" id="animation-force" min="20" max="150" step="5" value="80">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Particle Density
                  <span class="help-tooltip" title="Controls the number of particles generated from the text">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="particle-density-value">4</span></span>
                  <button class="reset-btn" onclick="resetParameter('particle-density', 4)">↺</button>
                </div>
              </div>
              <input type="range" id="particle-density" min="2" max="8" step="1" value="4">
            </div>

            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Interaction Radius
                  <span class="help-tooltip" title="Distance from cursor that affects particle movement">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="interaction-radius-value">100</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('interaction-radius', 100)">↺</button>
                </div>
              </div>
              <input type="range" id="interaction-radius" min="50" max="250" step="10" value="100">
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Mobile Optimization
            <div class="card-actions">
              <button class="card-action-btn" id="reset-mobile" title="Reset Mobile Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="toggle-wrapper">
                <label class="switch">
                  <input type="checkbox" id="disable-on-mobile">
                  <span class="slider"></span>
                </label>
                <label for="disable-on-mobile" class="toggle-label">Disable animation on mobile devices</label>
              </div>
              <p class="style-description">When enabled, shows static text on mobile for better performance</p>
              
              <div class="mobile-notice">
                💡 <strong>Recommended:</strong> Disabling animation on mobile improves performance and battery life while maintaining visual appeal.
              </div>
            </div>

            <div class="control-group" id="breakpoint-control">
              <div class="control-label">
                <span class="label-text">
                  Mobile Breakpoint
                  <span class="help-tooltip" title="Screen width below which animation is disabled">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="mobile-breakpoint-value">768</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('mobile-breakpoint', 768)">↺</button>
                </div>
              </div>
              <input type="range" id="mobile-breakpoint" min="480" max="1024" step="10" value="768">
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let explodeConfig = {
        text: 'HOVER!',
        colors: ['ffad70', 'f7d297', 'edb9a1', 'e697ac', 'b38dca', '9c76db', '705cb5', '43428e', '2c2142'],
        animationForce: 80,
        particleDensity: 4,
        interactionRadius: 100,
        textSize: 0,
        mobileTextSize: 0,
        disableOnMobile: false,
        mobileBreakpoint: 768
      };

      const defaultConfig = { ...explodeConfig, colors: [...explodeConfig.colors] };
      let currentEffect = null;
      const colorPalette = document.getElementById('color-palette');
      const addColorBtn = document.getElementById('add-color-btn');

      function rand(max = 1, min = 0, dec = 0) {
        return +(min + Math.random() * (max - min)).toFixed(dec);
      }
      
      class ParticleClass {
        constructor(x, y, rgb = [rand(128), rand(128), rand(128)], animationForce, isMobile = false) {
            this.ox = x;
            this.oy = y;
            this.cx = x;
            this.cy = y;
            this.or = isMobile ? rand(2.5, 0.8) : rand(5, 1);
            this.cr = this.or;
            this.pv = 0;
            this.ov = 0;
            this.f = rand(animationForce + 15, animationForce - 15);
            this.rgb = rgb.map(c => Math.max(0, c + rand(13, -13)));
        }

        draw(ctx) {
          ctx.fillStyle = `rgb(${this.rgb.join(',')})`;
          ctx.beginPath();
          ctx.arc(this.cx, this.cy, this.cr, 0, 2 * Math.PI);
          ctx.fill();
        }

        move(interactionRadius, hasPointer, pointerX, pointerY) {
          let moved = false;

          if (hasPointer && pointerX !== undefined && pointerY !== undefined) {
            const dx = this.cx - pointerX;
            const dy = this.cy - pointerY;
            const dist = Math.hypot(dx, dy);
            if (dist < interactionRadius && dist > 0) {
              const force = Math.min(this.f, (interactionRadius - dist) / dist * 2);
              this.cx += (dx / dist) * force;
              this.cy += (dy / dist) * force;
              moved = true;
            }
          }

          const odx = this.ox - this.cx;
          const ody = this.oy - this.cy;
          const od = Math.hypot(odx, ody);

          if (od > 1) {
            const restore = Math.min(od * 0.1, 3);
            this.cx += (odx / od) * restore;
            this.cy += (ody / od) * restore;
            moved = true;
          }

          return moved;
        }
      }

      class ParticleTextEffect {
        constructor(container, options = {}) {
          this.container = container;
          this.text = options.text || 'HOVER!';
          this.colors = options.colors || explodeConfig.colors;
          this.animationForce = options.animationForce || 80;
          this.particleDensity = options.particleDensity || 4;
          this.interactionRadius = options.interactionRadius || 100;
          this.textSize = options.textSize || 0;
          this.mobileTextSize = options.mobileTextSize || 0;
          this.disableOnMobile = options.disableOnMobile || false;
          this.mobileBreakpoint = options.mobileBreakpoint || 768;

          this.isMobile = window.innerWidth <= this.mobileBreakpoint;
          this.isTablet = window.innerWidth <= 1024 && window.innerWidth > 768;
          
          this.shouldDisableAnimation = this.disableOnMobile && this.isMobile;
          
          if (this.shouldDisableAnimation) {
            this.showStaticText();
            return;
          }
          
          if (this.isMobile) {
            this.particleDensity = Math.max(2, Math.min(this.particleDensity, 3));
            this.animationForce = Math.min(this.animationForce, 60);
          }

          this.canvas = document.createElement('canvas');
          this.ctx = this.canvas.getContext('2d');
          this.particles = [];
          this.pointer = {};
          this.hasPointer = false;
          this.animationId = null;
          this.textBox = { str: this.text };
          this.lastFrameTime = 0;
          this.targetFPS = this.isMobile ? 30 : 60;

          this.setupCanvas();
          this.setupEventListeners();
          this.initialize();
        }

        updateConfig(newConfig) {
          const wasStatic = this.shouldDisableAnimation;
          
          // Categorize changes more granularly
          const needsCompleteRegeneration = (
            (newConfig.text !== undefined && newConfig.text !== this.text) ||
            (newConfig.textSize !== undefined && newConfig.textSize !== this.textSize) ||
            (newConfig.mobileTextSize !== undefined && newConfig.mobileTextSize !== this.mobileTextSize) ||
            (newConfig.particleDensity !== undefined && newConfig.particleDensity !== this.particleDensity)
          );
          
          const needsColorUpdate = (
            newConfig.colors && JSON.stringify(newConfig.colors) !== JSON.stringify(this.colors)
          );
          
          const needsPropertyUpdate = (
            (newConfig.animationForce !== undefined && newConfig.animationForce !== this.animationForce) ||
            (newConfig.interactionRadius !== undefined && newConfig.interactionRadius !== this.interactionRadius)
          );
          
          // Update all properties
          this.text = newConfig.text !== undefined ? newConfig.text : this.text;
          this.colors = newConfig.colors || this.colors;
          this.animationForce = newConfig.animationForce !== undefined ? newConfig.animationForce : this.animationForce;
          this.particleDensity = newConfig.particleDensity !== undefined ? newConfig.particleDensity : this.particleDensity;
          this.interactionRadius = newConfig.interactionRadius !== undefined ? newConfig.interactionRadius : this.interactionRadius;
          this.textSize = newConfig.textSize !== undefined ? newConfig.textSize : this.textSize;
          this.mobileTextSize = newConfig.mobileTextSize !== undefined ? newConfig.mobileTextSize : this.mobileTextSize;
          this.disableOnMobile = newConfig.disableOnMobile !== undefined ? newConfig.disableOnMobile : this.disableOnMobile;
          this.mobileBreakpoint = newConfig.mobileBreakpoint !== undefined ? newConfig.mobileBreakpoint : this.mobileBreakpoint;
          
          this.isMobile = window.innerWidth <= this.mobileBreakpoint;
          this.shouldDisableAnimation = this.disableOnMobile && this.isMobile;
          
          // Handle static/animated mode changes
          if (this.shouldDisableAnimation && !wasStatic) {
            this.destroy();
            this.showStaticText();
            return;
          } else if (!this.shouldDisableAnimation && wasStatic) {
            this.container.innerHTML = '';
            this.canvas = document.createElement('canvas');
            this.ctx = this.canvas.getContext('2d');
            this.particles = [];
            this.setupCanvas();
            this.setupEventListeners();
            this.initialize();
            return;
          }
          
          if (!this.shouldDisableAnimation) {
            // Apply mobile optimizations
            if (this.isMobile) {
              this.particleDensity = Math.max(2, Math.min(this.particleDensity, 3));
              this.animationForce = Math.min(this.animationForce, 60);
            }
            
            if (needsCompleteRegeneration) {
              // Only pause for major structural changes
              const wasAnimating = !!this.animationId;
              if (this.animationId) {
                cancelAnimationFrame(this.animationId);
                this.animationId = null;
              }
              
              this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
              this.particles = [];
              this.write();
              
              if (wasAnimating && !this.animationId) {
                this.animate(performance.now());
              }
            } else if (needsColorUpdate) {
              // FLUID color updates without pausing animation
              this.updateParticleColors();
            } else if (needsPropertyUpdate) {
              // Quick property updates
              this.particles.forEach(particle => {
                particle.f = rand(this.animationForce + 15, this.animationForce - 15);
              });
            }
          }
        }

        updateParticleColors() {
          if (!this.particles.length || !this.textBox.x || !this.textBox.y || !this.textBox.w || !this.textBox.h) return;
          
          // Get fresh color data from current gradient
          const gradient = this.ctx.createLinearGradient(
            this.textBox.x, 
            this.textBox.y, 
            this.textBox.x + this.textBox.w, 
            this.textBox.y + this.textBox.h
          );
          
          const N = this.colors.length - 1;
          this.colors.forEach((c, i) => gradient.addColorStop(i / N, `#${c}`));
          this.ctx.fillStyle = gradient;
          
          // Redraw text with new gradient to get updated color data
          this.ctx.fillText(this.textBox.str, 0.5 * this.canvas.width, 0.5 * this.canvas.height);
          
          // Get updated pixel data
          const data = this.ctx.getImageData(this.textBox.x, this.textBox.y, this.textBox.w, this.textBox.h).data;
          
          // Update existing particles with new colors
          let particleIndex = 0;
          for (let i = 0; i < data.length; i += 4) {
            const x = (i / 4) % this.textBox.w;
            const y = Math.floor((i / 4) / this.textBox.w);
            const alpha = data[i + 3];

            if (alpha && !(x % this.particleDensity) && !(y % this.particleDensity)) {
              if (this.particles[particleIndex]) {
                // Update RGB values of existing particle with slight variation
                const baseRgb = [data[i], data[i + 1], data[i + 2]];
                this.particles[particleIndex].rgb = baseRgb.map(c => Math.max(0, c + rand(13, -13)));
              }
              particleIndex++;
            }
          }
        }

        showStaticText() {
          this.container.innerHTML = '';
          
          const staticTextWrapper = document.createElement('div');
          staticTextWrapper.style.display = 'flex';
          staticTextWrapper.style.alignItems = 'center';
          staticTextWrapper.style.justifyContent = 'center';
          staticTextWrapper.style.textAlign = 'center';
          staticTextWrapper.style.fontWeight = '900';
          staticTextWrapper.style.color = 'transparent';
          staticTextWrapper.style.width = '100%';
          staticTextWrapper.style.height = '100%';
          staticTextWrapper.style.position = 'relative';
          staticTextWrapper.className = 'explode-text-static';
          
          const currentTextSize = this.isMobile ? this.mobileTextSize : this.textSize;
          let fontSize;
          
          if (currentTextSize > 0) {
            fontSize = currentTextSize + 'px';
          } else {
            const containerWidth = this.container.getBoundingClientRect().width;
            const scaleFactor = this.isMobile ? 0.8 : 1;
            fontSize = Math.floor((containerWidth / this.text.length) * scaleFactor) + 'px';
            
            if (this.isMobile && parseInt(fontSize) < 24) {
              fontSize = '24px';
            }
          }
          
          staticTextWrapper.style.fontSize = fontSize;
          
          const gradientColors = this.colors.map(color => `#${color}`).join(', ');
          staticTextWrapper.style.background = `linear-gradient(45deg, ${gradientColors})`;
          staticTextWrapper.style.webkitBackgroundClip = 'text';
          staticTextWrapper.style.backgroundClip = 'text';
          
          staticTextWrapper.textContent = this.text;
          this.container.appendChild(staticTextWrapper);
        }

        setupCanvas() {
          this.canvas.style.width = '100%';
          this.canvas.style.height = '100%';
          this.container.appendChild(this.canvas);
          this.resize();
        }

        resize() {
          const rect = this.container.getBoundingClientRect();
          this.canvas.width = rect.width;
          this.canvas.height = rect.height;
          this.write();
        }

        setupEventListeners() {
          const throttle = (func, limit) => {
            let inThrottle;
            return function() {
              const args = arguments;
              const context = this;
              if (!inThrottle) {
                func.apply(context, args);
                inThrottle = true;
                setTimeout(() => inThrottle = false, limit);
              }
            }
          };

          let isScrolling = false;
          let scrollTimeout;

          const handlePointerMove = (e) => {
            if (isScrolling && this.isMobile) return;
            
            const rect = this.canvas.getBoundingClientRect();
            const scaleX = this.canvas.width / rect.width;
            const scaleY = this.canvas.height / rect.height;

            this.pointer.x = (e.clientX - rect.left) * scaleX;
            this.pointer.y = (e.clientY - rect.top) * scaleY;
            this.hasPointer = true;

            if (!this.animationId) this.animate();
          };

          const handlePointerLeave = () => {
            this.hasPointer = false;
            this.pointer.x = undefined;
            this.pointer.y = undefined;
            if (!this.animationId) this.animate();
          };

          const handleResize = throttle(() => {
            this.resize();
          }, 250);

          const handleScroll = () => {
            if (this.isMobile) {
              isScrolling = true;
              clearTimeout(scrollTimeout);
              
              if (this.animationId) {
                cancelAnimationFrame(this.animationId);
                this.animationId = null;
              }
              
              scrollTimeout = setTimeout(() => {
                isScrolling = false;
                if (this.hasPointer) {
                  this.animate();
                }
              }, 200);
            }
          };

          this.container.addEventListener('pointermove', handlePointerMove);
          this.container.addEventListener('pointerleave', handlePointerLeave);
          window.addEventListener('resize', handleResize);
          window.addEventListener('scroll', handleScroll, { passive: true });

          this.cleanup = () => {
            this.container.removeEventListener('pointermove', handlePointerMove);
            this.container.removeEventListener('pointerleave', handlePointerLeave);
            window.removeEventListener('resize', handleResize);
            window.removeEventListener('scroll', handleScroll);
            if (scrollTimeout) clearTimeout(scrollTimeout);
          };
        }

        dottify() {
          if (!this.textBox.x || !this.textBox.y || !this.textBox.w || !this.textBox.h) return;

          const data = this.ctx.getImageData(this.textBox.x, this.textBox.y, this.textBox.w, this.textBox.h).data;
          const pixels = [];

          for (let i = 0; i < data.length; i += 4) {
            const x = (i / 4) % this.textBox.w;
            const y = Math.floor((i / 4) / this.textBox.w);
            const alpha = data[i + 3];

            if (alpha && !(x % this.particleDensity) && !(y % this.particleDensity)) {
              pixels.push({
                x: x,
                y: y,
                rgb: [data[i], data[i + 1], data[i + 2]]
              });
            }
          }

          // Clear canvas and reset particles array completely
          this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
          this.particles.length = 0; // Force clear the array

          // Create new particles
          pixels.forEach((p, i) => {
            this.particles[i] = new ParticleClass(
              this.textBox.x + p.x,
              this.textBox.y + p.y,
              p.rgb,
              this.animationForce,
              this.isMobile
            );
            this.particles[i].draw(this.ctx);
          });

          // Ensure array is exactly the right length
          this.particles.length = pixels.length;
        }

        write() {
          if (!this.canvas || !this.ctx) return;

          this.textBox.str = this.text;
          
          const currentTextSize = this.isMobile ? this.mobileTextSize : this.textSize;
          
          if (currentTextSize > 0) {
            this.textBox.h = currentTextSize;
          } else {
            const scaleFactor = this.isMobile ? 0.8 : 1;
            this.textBox.h = Math.floor((this.canvas.width / this.textBox.str.length) * scaleFactor);
            
            if (this.isMobile && this.textBox.h < 24) {
              this.textBox.h = 24;
            }
          }
          
          const computedStyle = window.getComputedStyle(this.container);
          const fontFamily = computedStyle.fontFamily || 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
          
          this.ctx.font = `900 ${this.textBox.h}px ${fontFamily}`;
          this.ctx.textAlign = 'center';
          this.ctx.textBaseline = 'middle';

          this.textBox.w = Math.round(this.ctx.measureText(this.textBox.str).width);
          this.textBox.x = 0.5 * (this.canvas.width - this.textBox.w);
          this.textBox.y = 0.5 * (this.canvas.height - this.textBox.h);

          const gradient = this.ctx.createLinearGradient(
            this.textBox.x, 
            this.textBox.y, 
            this.textBox.x + this.textBox.w, 
            this.textBox.y + this.textBox.h
          );
          
          const N = this.colors.length - 1;
          this.colors.forEach((c, i) => gradient.addColorStop(i / N, `#${c}`));
          this.ctx.fillStyle = gradient;

          this.ctx.fillText(this.textBox.str, 0.5 * this.canvas.width, 0.5 * this.canvas.height);
          this.dottify();
        }

        animate = (currentTime) => {
          if (currentTime - this.lastFrameTime < 1000 / this.targetFPS) {
            this.animationId = requestAnimationFrame(this.animate);
            return;
          }
          this.lastFrameTime = currentTime;

          let hasMovement = false;
          
          this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
          
          const step = this.isMobile ? 2 : 1;
          
          for (let i = 0; i < this.particles.length; i += step) {
            const p = this.particles[i];
            if (p) {
              const moved = p.move(this.interactionRadius, this.hasPointer, this.pointer.x, this.pointer.y);
              if (moved) hasMovement = true;
              p.draw(this.ctx);
            }
          }
          
          if (this.isMobile && step > 1) {
            for (let i = 1; i < this.particles.length; i += step) {
              const p = this.particles[i];
              if (p) {
                p.draw(this.ctx);
              }
            }
          }
          
          if (hasMovement || this.hasPointer) {
            this.animationId = requestAnimationFrame(this.animate);
          } else {
            this.animationId = null;
          }
        };

        initialize() {
          this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
          this.write();
        }

        destroy() {
          if (this.animationId) {
            cancelAnimationFrame(this.animationId);
          }
          if (this.cleanup) {
            this.cleanup();
          }
          if (this.canvas && this.canvas.parentNode) {
            this.canvas.parentNode.removeChild(this.canvas);
          }
        }
      }

      function updateExplodePreview() {
        const preview = document.getElementById('explode-preview');
        if (!preview) return;
        
        // Create a config copy with a fresh colors array to ensure change detection works
        const configCopy = {
          ...explodeConfig,
          colors: [...explodeConfig.colors] // Force new array reference
        };
        
        if (currentEffect) {
          currentEffect.updateConfig(configCopy);
        } else {
          currentEffect = new ParticleTextEffect(preview, configCopy);
        }
      }

      function renderColorPalette() {
        colorPalette.innerHTML = '';
        
        explodeConfig.colors.forEach((color, index) => {
          const colorRow = document.createElement('div');
          colorRow.className = 'color-row';
          
          const colorPickerContainer = document.createElement('div');
          colorPickerContainer.className = 'color-picker-container';
          colorPickerContainer.style.setProperty('--selected-color', `#${color}`);
          
          const colorInput = document.createElement('input');
          colorInput.type = 'color';
          colorInput.value = `#${color}`;
          
          colorInput.addEventListener('input', function() {
            const newColor = this.value.substring(1);
            explodeConfig.colors[index] = newColor;
            colorPickerContainer.style.setProperty('--selected-color', this.value);
            hexInput.value = this.value;
            hslInput.value = `hsl(${hexToHsl(this.value).h}, ${hexToHsl(this.value).s}%, ${hexToHsl(this.value).l}%)`;
            hexInput.classList.remove('invalid');
            hslInput.classList.remove('invalid');
            updateExplodePreview();
          });
          
          colorPickerContainer.appendChild(colorInput);
          
          const hexGroup = document.createElement('div');
          hexGroup.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.value = `#${color}`;
          hexInput.placeholder = '#FFFFFF';
          
          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}%)`;
              explodeConfig.colors[index] = hex.substring(1);
              e.target.classList.remove('invalid');
              hslInput.classList.remove('invalid');
              colorPickerContainer.style.setProperty('--selected-color', hex);
              updateExplodePreview();
            } 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');
            }
          });
          
          hexGroup.appendChild(hexLabel);
          hexGroup.appendChild(hexInput);
          
          const hslGroup = document.createElement('div');
          hslGroup.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.value = `hsl(${hexToHsl(`#${color}`).h}, ${hexToHsl(`#${color}`).s}%, ${hexToHsl(`#${color}`).l}%)`;
          hslInput.placeholder = 'hsl(0, 100%, 50%)';
          
          hslInput.addEventListener('input', (e) => {
            let hsl = e.target.value;
            
            if (isValidHsl(hsl)) {
              const hex = hslToHex(hsl);
              if (hex) {
                colorInput.value = hex;
                hexInput.value = hex;
                explodeConfig.colors[index] = hex.substring(1);
                e.target.classList.remove('invalid');
                hexInput.classList.remove('invalid');
                colorPickerContainer.style.setProperty('--selected-color', hex);
                updateExplodePreview();
                return;
              }
            }
            
            e.target.classList.add('invalid');
          });
          
          hslInput.addEventListener('blur', (e) => {
            let hsl = e.target.value;
            
            if (!isValidHsl(hsl) && hsl.trim()) {
              const formatted = formatHsl(hsl);
              if (isValidHsl(formatted)) {
                e.target.value = formatted;
                const hex = hslToHex(formatted);
                if (hex) {
                  colorInput.value = hex;
                  hexInput.value = hex;
                  explodeConfig.colors[index] = hex.substring(1);
                  e.target.classList.remove('invalid');
                  hexInput.classList.remove('invalid');
                  colorPickerContainer.style.setProperty('--selected-color', hex);
                  updateExplodePreview();
                  return;
                }
              }
            }
            
            if (!isValidHsl(e.target.value)) {
              e.target.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
              e.target.classList.remove('invalid');
            }
          });
          
          hslGroup.appendChild(hslLabel);
          hslGroup.appendChild(hslInput);
          
          const removeBtn = document.createElement('button');
          removeBtn.className = 'remove-color';
          removeBtn.innerHTML = '×';
          removeBtn.addEventListener('click', function() {
            if (explodeConfig.colors.length > 2) {
              explodeConfig.colors.splice(index, 1);
              renderColorPalette();
              updateExplodePreview();
            } else {
              showNotification('At least 2 colors are required for the gradient effect', 'warning');
            }
          });
          
          colorRow.appendChild(colorPickerContainer);
          colorRow.appendChild(hexGroup);
          colorRow.appendChild(hslGroup);
          colorRow.appendChild(removeBtn);
          colorPalette.appendChild(colorRow);
        });
      }

      function addColor() {
        const randomColor = Math.floor(Math.random()*16777215).toString(16).padStart(6, '0');
        explodeConfig.colors.push(randomColor);
        renderColorPalette();
        updateExplodePreview();
      }

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

      function generateRandomPalette() {
        const paletteSize = Math.floor(Math.random() * 6) + 3;
        explodeConfig.colors = Array.from({ length: paletteSize }, () => generateRandomColor());
        renderColorPalette();
        updateExplodePreview();
        showNotification('Random color palette 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 formatHex(value) {
        let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
        
        if (!hex.startsWith('#')) {
          hex = '#' + hex;
        }
        
        if (hex.length > 7) {
          hex = hex.substring(0, 7);
        }
        
        return hex.toUpperCase();
      }

      function formatHsl(value) {
        const cleanValue = value.replace(/[^\d,\s]/g, '');
        const numbers = cleanValue.match(/\d+/g);
        
        if (!numbers || numbers.length < 3) {
          const partialMatch = value.match(/(\d+)/g);
          if (partialMatch && partialMatch.length >= 1) {
            const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
            const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
            const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
            return `hsl(${h}, ${s}%, ${l}%)`;
          }
          return value;
        }
        
        let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
        let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
        let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
        
        return `hsl(${h}, ${s}%, ${l}%)`;
      }

      function 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 copyToClipboard(text) {
        navigator.clipboard.writeText(text)
          .then(() => {
            showNotification('Copied to clipboard!');
          })
          .catch(err => {
            showNotification('Failed to copy to clipboard', 'error');
          });
      }

      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 divId = 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": {
          "_justifyContent": "center",
          "_background": {
            "color": {
              "hex": "#000000"
            }
          }
        }
      },
      {
        "id": containerId,
        "name": "container",
        "parent": sectionId,
        "children": [divId],
        "settings": {
          "_direction": "row",
          "_justifyContent": "center"
        }
      },
      {
        "id": divId,
        "name": "div",
        "parent": containerId,
        "children": [],
        "settings": {
          "_display": "flex",
          "_alignItems": "center",
          "_justifyContent": "center",
          "_attributes": [
            {
              "id": attributeId,
              "name": "data-explode-text"
            }
          ],
          "_width": "100%"
        },
        "label": "Explode text Div"
      },
      {
        "id": codeId,
        "name": "code",
        "parent": sectionId,
        "children": [],
        "settings": {
          "javascriptCode": jsCode,
          "executeCode": true,
          "_display": "none"
        },
        "label": "Explode Text JS"
      }
    ],
    "source": "bricksCopiedElements",
    "sourceUrl": "https://test.bricksfusion.com",
    "version": "2.0.1",
    "globalClasses": [],
    "globalElements": []
  };

  return JSON.stringify(bricksJSON, null, 2);
}

      function generateJavaScriptCode() {
        const colorsJSON = JSON.stringify(explodeConfig.colors);
        
        return `(function() {
    'use strict';

    const DEFAULT_CONFIG = {
        text: '${explodeConfig.text}',
        colors: ${colorsJSON},
        animationForce: ${explodeConfig.animationForce},
        particleDensity: ${explodeConfig.particleDensity},
        interactionRadius: ${explodeConfig.interactionRadius},
        textSize: ${explodeConfig.textSize},
        mobileTextSize: ${explodeConfig.mobileTextSize},
        disableOnMobile: ${explodeConfig.disableOnMobile},
        mobileBreakpoint: ${explodeConfig.mobileBreakpoint}
    };

    function rand(max = 1, min = 0, dec = 0) {
        return +(min + Math.random() * (max - min)).toFixed(dec);
    }

    class ParticleClass {
        constructor(x, y, rgb = [rand(128), rand(128), rand(128)], animationForce, isMobile = false) {
            this.ox = x;
            this.oy = y;
            this.cx = x;
            this.cy = y;
            this.or = isMobile ? rand(2.5, 0.8) : rand(5, 1);
            this.cr = this.or;
            this.pv = 0;
            this.ov = 0;
            this.f = rand(animationForce + 15, animationForce - 15);
            this.rgb = rgb.map(c => Math.max(0, c + rand(13, -13)));
        }

        draw(ctx) {
            ctx.fillStyle = \`rgb(\${this.rgb.join(',')})\`;
            ctx.beginPath();
            ctx.arc(this.cx, this.cy, this.cr, 0, 2 * Math.PI);
            ctx.fill();
        }

        move(interactionRadius, hasPointer, pointerX, pointerY) {
            let moved = false;

            if (hasPointer && pointerX !== undefined && pointerY !== undefined) {
                const dx = this.cx - pointerX;
                const dy = this.cy - pointerY;
                const dist = Math.hypot(dx, dy);
                if (dist < interactionRadius && dist > 0) {
                    const force = Math.min(this.f, (interactionRadius - dist) / dist * 2);
                    this.cx += (dx / dist) * force;
                    this.cy += (dy / dist) * force;
                    moved = true;
                }
            }

            const odx = this.ox - this.cx;
            const ody = this.oy - this.cy;
            const od = Math.hypot(odx, ody);

            if (od > 1) {
                const restore = Math.min(od * 0.1, 3);
                this.cx += (odx / od) * restore;
                this.cy += (ody / od) * restore;
                moved = true;
            }

            return moved;
        }
    }

    class ParticleTextEffect {
        constructor(container, options = {}) {
            this.container = container;
            this.text = options.text || DEFAULT_CONFIG.text;
            this.colors = options.colors || DEFAULT_CONFIG.colors;
            this.animationForce = options.animationForce || DEFAULT_CONFIG.animationForce;
            this.particleDensity = options.particleDensity || DEFAULT_CONFIG.particleDensity;
            this.interactionRadius = options.interactionRadius || DEFAULT_CONFIG.interactionRadius;
            this.textSize = options.textSize || DEFAULT_CONFIG.textSize;
            this.mobileTextSize = options.mobileTextSize || DEFAULT_CONFIG.mobileTextSize;
            this.disableOnMobile = options.disableOnMobile !== undefined ? options.disableOnMobile : DEFAULT_CONFIG.disableOnMobile;
            this.mobileBreakpoint = options.mobileBreakpoint || DEFAULT_CONFIG.mobileBreakpoint;

            this.isMobile = window.innerWidth <= this.mobileBreakpoint;
            
            this.shouldDisableAnimation = this.disableOnMobile && this.isMobile;
            
            if (this.shouldDisableAnimation) {
                this.showStaticText();
                return;
            }
            
            if (this.isMobile) {
                this.particleDensity = Math.max(2, Math.min(this.particleDensity, 3));
                this.animationForce = Math.min(this.animationForce, 60);
            }

            this.canvas = document.createElement('canvas');
            this.ctx = this.canvas.getContext('2d');
            this.particles = [];
            this.pointer = {};
            this.hasPointer = false;
            this.animationId = null;
            this.textBox = { str: this.text };
            this.lastFrameTime = 0;
            this.targetFPS = this.isMobile ? 30 : 60;

            this.setupCanvas();
            this.setupEventListeners();
            this.initialize();
        }

        updateConfig(newConfig) {
            const needsCompleteRegeneration = (
                (newConfig.text !== undefined && newConfig.text !== this.text) ||
                (newConfig.textSize !== undefined && newConfig.textSize !== this.textSize) ||
                (newConfig.mobileTextSize !== undefined && newConfig.mobileTextSize !== this.mobileTextSize) ||
                (newConfig.particleDensity !== undefined && newConfig.particleDensity !== this.particleDensity)
            );
            
            const needsColorUpdate = (
                newConfig.colors && JSON.stringify(newConfig.colors) !== JSON.stringify(this.colors)
            );
            
            const needsPropertyUpdate = (
                (newConfig.animationForce !== undefined && newConfig.animationForce !== this.animationForce) ||
                (newConfig.interactionRadius !== undefined && newConfig.interactionRadius !== this.interactionRadius)
            );
            
            this.text = newConfig.text !== undefined ? newConfig.text : this.text;
            this.colors = newConfig.colors || this.colors;
            this.animationForce = newConfig.animationForce !== undefined ? newConfig.animationForce : this.animationForce;
            this.particleDensity = newConfig.particleDensity !== undefined ? newConfig.particleDensity : this.particleDensity;
            this.interactionRadius = newConfig.interactionRadius !== undefined ? newConfig.interactionRadius : this.interactionRadius;
            this.textSize = newConfig.textSize !== undefined ? newConfig.textSize : this.textSize;
            this.mobileTextSize = newConfig.mobileTextSize !== undefined ? newConfig.mobileTextSize : this.mobileTextSize;
            
            if (this.isMobile) {
                this.particleDensity = Math.max(2, Math.min(this.particleDensity, 3));
                this.animationForce = Math.min(this.animationForce, 60);
            }
            
            if (needsCompleteRegeneration) {
                const wasAnimating = !!this.animationId;
                if (this.animationId) {
                    cancelAnimationFrame(this.animationId);
                    this.animationId = null;
                }
                
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.particles = [];
                this.write();
                
                if (wasAnimating && !this.animationId) {
                    this.animate(performance.now());
                }
            } else if (needsColorUpdate) {
                this.updateParticleColors();
            } else if (needsPropertyUpdate) {
                this.particles.forEach(particle => {
                    particle.f = rand(this.animationForce + 15, this.animationForce - 15);
                });
            }
        }

        updateParticleColors() {
            if (!this.particles.length || !this.textBox.x || !this.textBox.y || !this.textBox.w || !this.textBox.h) return;
            
            const gradient = this.ctx.createLinearGradient(
                this.textBox.x, 
                this.textBox.y, 
                this.textBox.x + this.textBox.w, 
                this.textBox.y + this.textBox.h
            );
            
            const N = this.colors.length - 1;
            this.colors.forEach((c, i) => gradient.addColorStop(i / N, \`#\${c}\`));
            this.ctx.fillStyle = gradient;
            
            this.ctx.fillText(this.textBox.str, 0.5 * this.canvas.width, 0.5 * this.canvas.height);
            
            const data = this.ctx.getImageData(this.textBox.x, this.textBox.y, this.textBox.w, this.textBox.h).data;
            
            let particleIndex = 0;
            for (let i = 0; i < data.length; i += 4) {
                const x = (i / 4) % this.textBox.w;
                const y = Math.floor((i / 4) / this.textBox.w);
                const alpha = data[i + 3];

                if (alpha && !(x % this.particleDensity) && !(y % this.particleDensity)) {
                    if (this.particles[particleIndex]) {
                        const baseRgb = [data[i], data[i + 1], data[i + 2]];
                        this.particles[particleIndex].rgb = baseRgb.map(c => Math.max(0, c + rand(13, -13)));
                    }
                    particleIndex++;
                }
            }
        }

        showStaticText() {
            this.container.innerHTML = '';
            
            const staticTextWrapper = document.createElement('div');
            staticTextWrapper.style.display = 'flex';
            staticTextWrapper.style.alignItems = 'center';
            staticTextWrapper.style.justifyContent = 'center';
            staticTextWrapper.style.textAlign = 'center';
            staticTextWrapper.style.fontWeight = '900';
            staticTextWrapper.style.color = 'transparent';
            staticTextWrapper.style.width = '100%';
            staticTextWrapper.style.height = '100%';
            staticTextWrapper.style.position = 'relative';
            staticTextWrapper.className = 'explode-text-static';
            
            const currentTextSize = this.isMobile ? this.mobileTextSize : this.textSize;
            let fontSize;
            
            if (currentTextSize > 0) {
                fontSize = currentTextSize + 'px';
            } else {
                const containerWidth = this.container.getBoundingClientRect().width;
                const scaleFactor = this.isMobile ? 0.8 : 1;
                fontSize = Math.floor((containerWidth / this.text.length) * scaleFactor) + 'px';
                
                if (this.isMobile && parseInt(fontSize) < 24) {
                    fontSize = '24px';
                }
            }
            
            staticTextWrapper.style.fontSize = fontSize;
            
            const gradientColors = this.colors.map(color => \`#\${color}\`).join(', ');
            staticTextWrapper.style.background = \`linear-gradient(45deg, \${gradientColors})\`;
            staticTextWrapper.style.webkitBackgroundClip = 'text';
            staticTextWrapper.style.backgroundClip = 'text';
            
            staticTextWrapper.textContent = this.text;
            this.container.appendChild(staticTextWrapper);
        }

        setupCanvas() {
            this.canvas.style.width = '100%';
            this.canvas.style.height = '100%';
            this.container.appendChild(this.canvas);
            this.resize();
        }

        resize() {
            const rect = this.container.getBoundingClientRect();
            this.canvas.width = rect.width;
            this.canvas.height = rect.height;
            this.write();
        }

        setupEventListeners() {
            const throttle = (func, limit) => {
                let inThrottle;
                return function() {
                    const args = arguments;
                    const context = this;
                    if (!inThrottle) {
                        func.apply(context, args);
                        inThrottle = true;
                        setTimeout(() => inThrottle = false, limit);
                    }
                }
            };

            let isScrolling = false;
            let scrollTimeout;

            const handlePointerMove = (e) => {
                if (isScrolling && this.isMobile) return;
                
                const rect = this.canvas.getBoundingClientRect();
                const scaleX = this.canvas.width / rect.width;
                const scaleY = this.canvas.height / rect.height;

                this.pointer.x = (e.clientX - rect.left) * scaleX;
                this.pointer.y = (e.clientY - rect.top) * scaleY;
                this.hasPointer = true;

                if (!this.animationId) this.animate();
            };

            const handlePointerLeave = () => {
                this.hasPointer = false;
                this.pointer.x = undefined;
                this.pointer.y = undefined;
                if (!this.animationId) this.animate();
            };

            const handleResize = throttle(() => {
                const wasMobile = this.isMobile;
                this.isMobile = window.innerWidth <= this.mobileBreakpoint;
                const newShouldDisableAnimation = this.disableOnMobile && this.isMobile;
                
                if (newShouldDisableAnimation !== this.shouldDisableAnimation) {
                    this.shouldDisableAnimation = newShouldDisableAnimation;
                    
                    this.container.innerHTML = '';
                    
                    if (this.shouldDisableAnimation) {
                        if (this.animationId) {
                            cancelAnimationFrame(this.animationId);
                            this.animationId = null;
                        }
                        this.canvas = null;
                        this.ctx = null;
                        this.showStaticText();
                    } else {
                        this.canvas = document.createElement('canvas');
                        this.ctx = this.canvas.getContext('2d');
                        this.particles = [];
                        this.setupCanvas();
                        this.initialize();
                    }
                } else if (!this.shouldDisableAnimation) {
                    this.resize();
                }
            }, 250);

            const handleScroll = () => {
                if (this.isMobile && !this.shouldDisableAnimation) {
                    isScrolling = true;
                    clearTimeout(scrollTimeout);
                    
                    if (this.animationId) {
                        cancelAnimationFrame(this.animationId);
                        this.animationId = null;
                    }
                    
                    scrollTimeout = setTimeout(() => {
                        isScrolling = false;
                        if (this.hasPointer) {
                            this.animate();
                        }
                    }, 200);
                }
            };

            if (!this.shouldDisableAnimation) {
                this.container.addEventListener('pointermove', handlePointerMove);
                this.container.addEventListener('pointerleave', handlePointerLeave);
            }
            
            window.addEventListener('resize', handleResize);
            window.addEventListener('scroll', handleScroll, { passive: true });

            this.cleanup = () => {
                this.container.removeEventListener('pointermove', handlePointerMove);
                this.container.removeEventListener('pointerleave', handlePointerLeave);
                window.removeEventListener('resize', handleResize);
                window.removeEventListener('scroll', handleScroll);
                if (scrollTimeout) clearTimeout(scrollTimeout);
            };
        }

        dottify() {
            if (!this.textBox.x || !this.textBox.y || !this.textBox.w || !this.textBox.h) return;

            const data = this.ctx.getImageData(this.textBox.x, this.textBox.y, this.textBox.w, this.textBox.h).data;
            const pixels = [];

            for (let i = 0; i < data.length; i += 4) {
                const x = (i / 4) % this.textBox.w;
                const y = Math.floor((i / 4) / this.textBox.w);
                const alpha = data[i + 3];

                if (alpha && !(x % this.particleDensity) && !(y % this.particleDensity)) {
                    pixels.push({
                        x: x,
                        y: y,
                        rgb: [data[i], data[i + 1], data[i + 2]]
                    });
                }
            }

            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.particles.length = 0;

            pixels.forEach((p, i) => {
                this.particles[i] = new ParticleClass(
                    this.textBox.x + p.x,
                    this.textBox.y + p.y,
                    p.rgb,
                    this.animationForce,
                    this.isMobile
                );
                this.particles[i].draw(this.ctx);
            });

            this.particles.length = pixels.length;
        }

        write() {
            if (!this.canvas || !this.ctx) return;

            this.textBox.str = this.text;
            
            const currentTextSize = this.isMobile ? this.mobileTextSize : this.textSize;
            
            if (currentTextSize > 0) {
                this.textBox.h = currentTextSize;
            } else {
                const scaleFactor = this.isMobile ? 0.8 : 1;
                this.textBox.h = Math.floor((this.canvas.width / this.textBox.str.length) * scaleFactor);
                
                if (this.isMobile && this.textBox.h < 24) {
                    this.textBox.h = 24;
                }
            }
            
            const computedStyle = window.getComputedStyle(this.container);
            const fontFamily = computedStyle.fontFamily || 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif';
            
            this.ctx.font = \`900 \${this.textBox.h}px \${fontFamily}\`;
            this.ctx.textAlign = 'center';
            this.ctx.textBaseline = 'middle';

            this.textBox.w = Math.round(this.ctx.measureText(this.textBox.str).width);
            this.textBox.x = 0.5 * (this.canvas.width - this.textBox.w);
            this.textBox.y = 0.5 * (this.canvas.height - this.textBox.h);

            const gradient = this.ctx.createLinearGradient(
                this.textBox.x, 
                this.textBox.y, 
                this.textBox.x + this.textBox.w, 
                this.textBox.y + this.textBox.h
            );
            
            const N = this.colors.length - 1;
            this.colors.forEach((c, i) => gradient.addColorStop(i / N, \`#\${c}\`));
            this.ctx.fillStyle = gradient;

            this.ctx.fillText(this.textBox.str, 0.5 * this.canvas.width, 0.5 * this.canvas.height);
            this.dottify();
        }

        animate = (currentTime) => {
            if (currentTime - this.lastFrameTime < 1000 / this.targetFPS) {
                this.animationId = requestAnimationFrame(this.animate);
                return;
            }
            this.lastFrameTime = currentTime;

            let hasMovement = false;
            
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            
            const step = this.isMobile ? 2 : 1;
            
            for (let i = 0; i < this.particles.length; i += step) {
                const p = this.particles[i];
                if (p) {
                    const moved = p.move(this.interactionRadius, this.hasPointer, this.pointer.x, this.pointer.y);
                    if (moved) hasMovement = true;
                    p.draw(this.ctx);
                }
            }
            
            if (this.isMobile && step > 1) {
                for (let i = 1; i < this.particles.length; i += step) {
                    const p = this.particles[i];
                    if (p) {
                        p.draw(this.ctx);
                    }
                }
            }
            
            if (hasMovement || this.hasPointer) {
                this.animationId = requestAnimationFrame(this.animate);
            } else {
                this.animationId = null;
            }
        };

        initialize() {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            this.write();
        }

        destroy() {
            if (this.animationId) {
                cancelAnimationFrame(this.animationId);
            }
            if (this.cleanup) {
                this.cleanup();
            }
            if (this.canvas && this.canvas.parentNode) {
                this.canvas.parentNode.removeChild(this.canvas);
            }
        }
    }

    function initExplodeTextEffects() {
        const elements = document.querySelectorAll('[data-explode-text]');
        
        elements.forEach(element => {
            const text = element.getAttribute('data-explode-text') || DEFAULT_CONFIG.text;
            const colors = element.getAttribute('data-colors');
            const colorsArray = colors ? colors.split(',') : DEFAULT_CONFIG.colors;

            const options = {
                text: text,
                colors: colorsArray,
                animationForce: parseInt(element.getAttribute('data-force')) || DEFAULT_CONFIG.animationForce,
                particleDensity: parseInt(element.getAttribute('data-density')) || DEFAULT_CONFIG.particleDensity,
                interactionRadius: parseInt(element.getAttribute('data-radius')) || DEFAULT_CONFIG.interactionRadius,
                textSize: parseInt(element.getAttribute('data-size')) || DEFAULT_CONFIG.textSize,
                mobileTextSize: parseInt(element.getAttribute('data-mobile-size')) || DEFAULT_CONFIG.mobileTextSize,
                disableOnMobile: element.getAttribute('data-disable-mobile') === 'true' || DEFAULT_CONFIG.disableOnMobile,
                mobileBreakpoint: parseInt(element.getAttribute('data-mobile-breakpoint')) || DEFAULT_CONFIG.mobileBreakpoint
            };

            element.style.position = element.style.position || 'relative';
            element.style.overflow = 'hidden';

            new ParticleTextEffect(element, options);
        });
    }

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

    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === 1) {
                        if (node.hasAttribute && node.hasAttribute('data-explode-text')) {
                            const text = node.getAttribute('data-explode-text') || DEFAULT_CONFIG.text;
                            const colors = node.getAttribute('data-colors');
                            const colorsArray = colors ? colors.split(',') : DEFAULT_CONFIG.colors;

                            const options = {
                                text: text,
                                colors: colorsArray,
                                animationForce: parseInt(node.getAttribute('data-force')) || DEFAULT_CONFIG.animationForce,
                                particleDensity: parseInt(node.getAttribute('data-density')) || DEFAULT_CONFIG.particleDensity,
                                interactionRadius: parseInt(node.getAttribute('data-radius')) || DEFAULT_CONFIG.interactionRadius,
                                textSize: parseInt(node.getAttribute('data-size')) || DEFAULT_CONFIG.textSize,
                                mobileTextSize: parseInt(node.getAttribute('data-mobile-size')) || DEFAULT_CONFIG.mobileTextSize,
                                disableOnMobile: node.getAttribute('data-disable-mobile') === 'true' || DEFAULT_CONFIG.disableOnMobile,
                                mobileBreakpoint: parseInt(node.getAttribute('data-mobile-breakpoint')) || DEFAULT_CONFIG.mobileBreakpoint
                            };

                            node.style.position = node.style.position || 'relative';
                            node.style.overflow = 'hidden';

                            new ParticleTextEffect(node, options);
                        }
                        
                        const childElements = node.querySelectorAll && node.querySelectorAll('[data-explode-text]');
                        if (childElements && childElements.length > 0) {
                            childElements.forEach(element => {
                                const text = element.getAttribute('data-explode-text') || DEFAULT_CONFIG.text;
                                const colors = element.getAttribute('data-colors');
                                const colorsArray = colors ? colors.split(',') : DEFAULT_CONFIG.colors;

                                const options = {
                                    text: text,
                                    colors: colorsArray,
                                    animationForce: parseInt(element.getAttribute('data-force')) || DEFAULT_CONFIG.animationForce,
                                    particleDensity: parseInt(element.getAttribute('data-density')) || DEFAULT_CONFIG.particleDensity,
                                    interactionRadius: parseInt(element.getAttribute('data-radius')) || DEFAULT_CONFIG.interactionRadius,
                                    textSize: parseInt(element.getAttribute('data-size')) || DEFAULT_CONFIG.textSize,
                                    mobileTextSize: parseInt(element.getAttribute('data-mobile-size')) || DEFAULT_CONFIG.mobileTextSize,
                                    disableOnMobile: element.getAttribute('data-disable-mobile') === 'true' || DEFAULT_CONFIG.disableOnMobile,
                                    mobileBreakpoint: parseInt(element.getAttribute('data-mobile-breakpoint')) || DEFAULT_CONFIG.mobileBreakpoint
                                };

                                element.style.position = element.style.position || 'relative';
                                element.style.overflow = 'hidden';

                                new ParticleTextEffect(element, options);
                            });
                        }
                    }
                });
            }
        });
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();`;
      }

      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 generateRandomExplode() {
        explodeConfig.animationForce = Math.floor(Math.random() * 130) + 20;
        explodeConfig.particleDensity = Math.floor(Math.random() * 6) + 2;
        explodeConfig.interactionRadius = Math.floor(Math.random() * 200) + 50;
        explodeConfig.textSize = Math.floor(Math.random() * 150);
        explodeConfig.mobileTextSize = Math.floor(Math.random() * 80);
        
        generateRandomPalette();
        
        document.getElementById('animation-force').value = explodeConfig.animationForce;
        document.getElementById('particle-density').value = explodeConfig.particleDensity;
        document.getElementById('interaction-radius').value = explodeConfig.interactionRadius;
        document.getElementById('text-size').value = explodeConfig.textSize;
        document.getElementById('mobile-text-size').value = explodeConfig.mobileTextSize;
        
        document.getElementById('animation-force-value').textContent = explodeConfig.animationForce;
        document.getElementById('particle-density-value').textContent = explodeConfig.particleDensity;
        document.getElementById('interaction-radius-value').textContent = explodeConfig.interactionRadius;
        document.getElementById('text-size-value').textContent = explodeConfig.textSize === 0 ? 'auto' : explodeConfig.textSize + 'px';
        document.getElementById('mobile-text-size-value').textContent = explodeConfig.mobileTextSize === 0 ? 'auto' : explodeConfig.mobileTextSize + 'px';
        
        updateExplodePreview();
        showNotification('Random explode text effect generated!');
      }

      window.resetParameter = function(parameterId, defaultValue) {
        const element = document.getElementById(parameterId);
        if (element) {
          element.value = defaultValue;
          const valueElement = document.getElementById(`${parameterId}-value`);
          if (valueElement) {
            if (parameterId.includes('text-size')) {
              valueElement.textContent = defaultValue === 0 ? 'auto' : defaultValue + 'px';
            } else if (parameterId === 'interaction-radius' || parameterId === 'mobile-breakpoint') {
              valueElement.textContent = defaultValue + 'px';
            } else {
              valueElement.textContent = defaultValue;
            }
          }
          
          switch (parameterId) {
            case 'animation-force':
              explodeConfig.animationForce = defaultValue;
              break;
            case 'particle-density':
              explodeConfig.particleDensity = defaultValue;
              break;
            case 'interaction-radius':
              explodeConfig.interactionRadius = defaultValue;
              break;
            case 'text-size':
              explodeConfig.textSize = defaultValue;
              break;
            case 'mobile-text-size':
              explodeConfig.mobileTextSize = defaultValue;
              break;
            case 'mobile-breakpoint':
              explodeConfig.mobileBreakpoint = defaultValue;
              break;
          }
          
          updateExplodePreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function updateBreakpointControlVisibility() {
        const breakpointControl = document.getElementById('breakpoint-control');
        if (explodeConfig.disableOnMobile) {
          breakpointControl.classList.remove('disabled-control');
        } else {
          breakpointControl.classList.add('disabled-control');
        }
      }

      function initializeUI() {
        currentEffect = new ParticleTextEffect(document.getElementById('explode-preview'), explodeConfig);
        renderColorPalette();
        
        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-explode-text');
        });

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

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

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

        document.getElementById('generate-random-palette').addEventListener('click', () => {
          generateRandomPalette();
        });

        const backgroundPicker = document.getElementById('preview-background-picker');
        const previewContainer = document.getElementById('explode-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('display-text').addEventListener('input', function() {
          explodeConfig.text = this.value || 'HOVER!';
          updateExplodePreview();
        });

        document.getElementById('disable-on-mobile').addEventListener('change', function() {
          explodeConfig.disableOnMobile = this.checked;
          updateBreakpointControlVisibility();
          updateExplodePreview();
        });

        addColorBtn.addEventListener('click', addColor);

        document.getElementById('reset-text').addEventListener('click', () => {
          explodeConfig.text = defaultConfig.text;
          explodeConfig.textSize = defaultConfig.textSize;
          explodeConfig.mobileTextSize = defaultConfig.mobileTextSize;
          
          document.getElementById('display-text').value = defaultConfig.text;
          document.getElementById('text-size').value = defaultConfig.textSize;
          document.getElementById('mobile-text-size').value = defaultConfig.mobileTextSize;
          document.getElementById('text-size-value').textContent = 'auto';
          document.getElementById('mobile-text-size-value').textContent = 'auto';
          
          updateExplodePreview();
          showNotification('Text settings reset');
        });

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

        document.getElementById('reset-animation').addEventListener('click', () => {
          explodeConfig.animationForce = defaultConfig.animationForce;
          explodeConfig.particleDensity = defaultConfig.particleDensity;
          explodeConfig.interactionRadius = defaultConfig.interactionRadius;
          
          document.getElementById('animation-force').value = defaultConfig.animationForce;
          document.getElementById('particle-density').value = defaultConfig.particleDensity;
          document.getElementById('interaction-radius').value = defaultConfig.interactionRadius;
          
          document.getElementById('animation-force-value').textContent = defaultConfig.animationForce;
          document.getElementById('particle-density-value').textContent = defaultConfig.particleDensity;
          document.getElementById('interaction-radius-value').textContent = defaultConfig.interactionRadius;
          
          updateExplodePreview();
          showNotification('Animation settings reset');
        });

        document.getElementById('reset-mobile').addEventListener('click', () => {
          explodeConfig.disableOnMobile = defaultConfig.disableOnMobile;
          explodeConfig.mobileBreakpoint = defaultConfig.mobileBreakpoint;
          
          document.getElementById('disable-on-mobile').checked = defaultConfig.disableOnMobile;
          document.getElementById('mobile-breakpoint').value = defaultConfig.mobileBreakpoint;
          document.getElementById('mobile-breakpoint-value').textContent = defaultConfig.mobileBreakpoint;
          
          updateBreakpointControlVisibility();
          updateExplodePreview();
          showNotification('Mobile settings reset');
        });

        const rangeInputs = document.querySelectorAll('input[type="range"]');
        rangeInputs.forEach(input => {
          const valueElement = document.getElementById(`${input.id}-value`);
          
          if (valueElement) {
            if (input.id.includes('text-size')) {
              valueElement.textContent = input.value === '0' ? 'auto' : input.value + 'px';
            } else if (input.id === 'interaction-radius' || input.id === 'mobile-breakpoint') {
              valueElement.textContent = input.value + 'px';
            } else {
              valueElement.textContent = input.value;
            }
          }
          
          input.addEventListener('input', () => {
            if (valueElement) {
              if (input.id.includes('text-size')) {
                valueElement.textContent = input.value === '0' ? 'auto' : input.value + 'px';
              } else if (input.id === 'interaction-radius' || input.id === 'mobile-breakpoint') {
                valueElement.textContent = input.value + 'px';
              } else {
                valueElement.textContent = input.value;
              }
            }
            
            switch (input.id) {
              case 'animation-force':
                explodeConfig.animationForce = parseInt(input.value);
                break;
              case 'particle-density':
                explodeConfig.particleDensity = parseInt(input.value);
                break;
              case 'interaction-radius':
                explodeConfig.interactionRadius = parseInt(input.value);
                break;
              case 'text-size':
                explodeConfig.textSize = parseInt(input.value);
                break;
              case 'mobile-text-size':
                explodeConfig.mobileTextSize = parseInt(input.value);
                break;
              case 'mobile-breakpoint':
                explodeConfig.mobileBreakpoint = parseInt(input.value);
                break;
            }
            
            updateExplodePreview();
          });
        });

        updateBreakpointControlVisibility();

        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();
                }
                break;
              case 's':
                e.preventDefault();
                const fullSectionBtn = document.getElementById('copy-full-section');
                if (fullSectionBtn && fullSectionBtn.hasAttribute('data-protection-animation')) {
                  fullSectionBtn.click();
                }
                break;
            }
          } else {
            switch (e.key.toLowerCase()) {
              case 'r':
                generateRandomExplode();
                break;
              case 'b':
                document.getElementById('preview-background-picker').click();
                break;
            }
          }
        });

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

Explode Text

Converts text into interactive particles that react to mouse movement. Particles disperse when cursor approaches and return to form the text. Perfect for hero headings, eye-catching titles, or creative text displays.

HOVER ME

Colors

Gradient Colors comma-separated hex (no #)

Hex color codes without # symbol, separated by commas. Creates a gradient across the text particles. Use 2-5 colors for best results.

Default: ffad70,f7d297,edb9a1 (warm gradient)

Interaction

Animation Force 20-200

How strongly particles react to mouse. Lower values are subtle, higher values create explosive dispersal.

Default: 80

Interaction Radius 50-300 pixels

Distance from cursor where particles start reacting. Larger radius affects particles from further away.

Default: 100

Particles

Particle Density 2-8 pixels

Spacing between particles. Lower values create more particles and denser text, higher values are lighter and faster.

Default: 4

Layout

Min Width 0-1000 pixels

Minimum width of text container. Ensures consistent sizing. Set to 0 to disable.

Default: 300

Min Height 0-500 pixels

Minimum height of text container. Prevents layout shifts. Set to 0 to disable.

Default: 200

Mobile

Disable on Mobile on/off

Show static gradient text on mobile instead of particles. Saves battery and improves performance on phones.

Default: Off

Mobile Breakpoint 320-1024 pixels

Screen width below which device is considered mobile. Standard is 768px for tablets and phones.

Default: 768

Performance

This element uses HTML5 Canvas to render text as particles with physics simulation. Runs at 60fps on desktop, 30fps on mobile. Particle density is automatically reduced on mobile devices. Each particle has position, velocity, and color with spring-based restoration physics. Text is sampled from canvas and converted to particle coordinates. Medium weight - suitable for hero sections with one or two instances per page.