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>Nebula Effect Configurator - BricksFusion</title>
  <style>
    :root {
  --background: #000;
  --card-bg: #1e1e1e;
  --card-bg-hover: #252525;
  --text-primary: #f2f2f7;
  --text-secondary: #8e8e93;
  --accent: #ef6013;
  --accent-hover: #c64c0c;
  --border: #2c2c2e;
  --shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  --track: #2c2c2e;
  --thumb: #ef6013;
  --card-radius: 16px;
  --input-radius: 8px;
  --button-radius: 12px;
  --transition: all 0.25s ease;
  --font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --action-bar-height: 70px;
  --success: #28a745;
  --warning: #ffc107;
  --danger: #dc3545;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: var(--font);
  background-color: var(--background);
  color: var(--text-primary);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  padding-bottom: var(--action-bar-height);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.how-to-use code {
  background-color: rgba(50, 50, 50, 0.5);
  padding: 0.2rem 0.5rem;
  border-radius: 4px;
  font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
  font-size: var(--text-xs);
  color: #ff8c51;
}

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

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

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

.card {
  background-color: var(--card-bg);
  border-radius: var(--card-radius);
  box-shadow: var(--shadow);
  overflow: hidden;
  margin-bottom: 1.5rem;
  border: 1px solid var(--border);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.card:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}

.preview-container {
  height: 400px;
  width: 100%;
  position: relative;
  overflow: hidden;
  border-radius: var(--card-radius);
  background-color: #000000;
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
}

.preview-content {
  color: white;
  text-align: center;
  font-weight: bold;
  font-size: var(--text-s);
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
}

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

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

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

.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;
  appearance: none;
  width: 100%;
  height: 6px;
  background: var(--track);
  border-radius: 3px;
  outline: none;
  margin: 0.8rem 0;
  position: relative;
}

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

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

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

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

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

.color-picker-container {
  position: relative;
  width: 40px;
  height: 40px;
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid var(--border);
  cursor: pointer;
  transition: var(--transition);
  flex-shrink: 0;
  background: linear-gradient(45deg, #f0f0f0 25%, transparent 25%), 
              linear-gradient(-45deg, #f0f0f0 25%, transparent 25%), 
              linear-gradient(45deg, transparent 75%, #f0f0f0 75%), 
              linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
  background-size: 8px 8px;
  background-position: 0 0, 0 4px, 4px -4px, -4px 0px;
}

.color-picker-container::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--current-color, #3366ff);
  border-radius: 6px;
  z-index: 1;
}

.color-picker-container:hover {
  border-color: var(--accent);
  transform: scale(1.05);
}

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

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

.quick-actions {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
}

.quick-action-btn {
  flex: 1;
  padding: 0.6rem;
  background-color: rgba(30, 30, 30, 0.7);
  border: 1px solid var(--border);
  border-radius: var(--input-radius);
  color: var(--text-secondary);
  cursor: pointer;
  transition: var(--transition);
  font-size: var(--text-xs);
  text-align: center;
}

.quick-action-btn:hover {
  background-color: var(--accent);
  color: white;
  border-color: var(--accent);
}

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

.nebula-full-container {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

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

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

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

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

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

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

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

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

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

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

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

@keyframes spin {
  to { transform: rotate(360deg); }
}
  </style>
</head>
<body>
  <div class="action-bar">
    <nav class="breadcrumb">
      <a href="https://bricksfusion.com" class="breadcrumb-item">Home</a>
      <span class="breadcrumb-separator">›</span>
      <a href="https://bricksfusion.com/corebackground/" class="breadcrumb-item">Core Backgrounds</a>
      <span class="breadcrumb-separator">›</span>
      <span class="breadcrumb-item active">Nebula Effect</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-nebula-effect
      </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">Nebula Effect</h1>
      <p class="page-subtitle">Interactive nebula backgrounds 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 nebula 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</li>
                <li>Paste or upload the JavaScript code</li>
                <li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-nebula-effect</code> as attribute name (leave value empty)</li>
              </ol>
            </div>
          </div>
        </div>
      </div>
    </div>

    <div class="content">
      <section class="preview-section">
        <div class="preview-container" id="nebula-preview" data-nebula-effect="true">
          <div id="nebula-effect-container" class="nebula-full-container">
          </div>
          <div class="preview-content">Interactive Nebula Preview</div>
          <div class="preview-controls">
            <button class="preview-btn" id="randomize-nebula" title="Randomize (R)">🎲</button>
          </div>
        </div>
      </section>

      <section class="controls-section">
        <div class="card">
          <div class="card-heading">
            Nebula Colors
            <div class="card-actions">
              <button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="color-list">
              <div class="color-row">
                <div class="color-picker-container">
                  <input type="color" id="main-color" value="#3366ff">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HEX</span>
                  <input type="text" class="color-input hex-input" id="main-color-hex" value="#3366ff" placeholder="#FFFFFF">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HSL</span>
                  <input type="text" class="color-input hsl-input" id="main-color-hsl" placeholder="hsl(0, 100%, 50%)">
                </div>
              </div>
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Opacity
                  <span class="help-tooltip" title="Controls the overall opacity of the nebula effect">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="opacity-value">0.8</span></span>
                  <button class="reset-btn" onclick="resetParameter('opacity', 0.8)">↺</button>
                </div>
              </div>
              <input type="range" id="opacity" min="0.4" max="1" step="0.05" value="0.8">
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Animation Settings
            <div class="card-actions">
              <button class="card-action-btn" id="reset-animation" title="Reset Animation Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Animation Speed
                  <span class="help-tooltip" title="Controls how fast the nebula moves">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="animation-speed-value">1</span>x</span>
                  <button class="reset-btn" onclick="resetParameter('animation-speed', 1)">↺</button>
                </div>
              </div>
              <input type="range" id="animation-speed" min="0.5" max="2" step="0.1" value="1">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Flow Intensity
                  <span class="help-tooltip" title="Controls the intensity of the nebula flow patterns">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="flow-intensity-value">1</span></span>
                  <button class="reset-btn" onclick="resetParameter('flow-intensity', 1)">↺</button>
                </div>
              </div>
              <input type="range" id="flow-intensity" min="0.5" max="1.5" step="0.1" value="1">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Detail Level
                  <span class="help-tooltip" title="Controls the level of detail in the nebula patterns">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="detail-level-value">5</span></span>
                  <button class="reset-btn" onclick="resetParameter('detail-level', 5)">↺</button>
                </div>
              </div>
              <input type="range" id="detail-level" min="3" max="7" step="1" value="5">
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Advanced Options
            <div class="card-actions">
              <button class="card-action-btn" id="reset-advanced" title="Reset Advanced Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">Z-Index Position</span>
                <div class="value-display">
                  <span class="value-text" id="z-index-value">0</span>
                  <button class="reset-btn" onclick="resetParameter('z-index', 0)">↺</button>
                </div>
              </div>
              <input type="range" id="z-index" min="-10" max="10" value="0" step="1">
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let nebulaConfig = {
        speed: 1,
        flowIntensity: 1,
        detailLevel: 5,
        customColors: {
          colorA: '#3366ff',
          colorB: '#66a3ff',
          colorC: '#001a66',
          colorD: '#cce0ff'
        },
        opacity: 0.8,
        zIndex: 0
      };

      const defaultConfig = { ...nebulaConfig };

      function initNebulaEffect() {
        const sections = document.querySelectorAll('[data-nebula-effect]:not([data-nebula-initialized="true"])');
        
        sections.forEach((section) => {
          const speed = section.hasAttribute('data-nebula-speed') 
            ? parseFloat(section.getAttribute('data-nebula-speed')) 
            : nebulaConfig.speed;
            
          const flowIntensity = section.hasAttribute('data-nebula-flow') 
            ? parseFloat(section.getAttribute('data-nebula-flow')) 
            : nebulaConfig.flowIntensity;
            
          const detailLevel = section.hasAttribute('data-nebula-detail') 
            ? parseInt(section.getAttribute('data-nebula-detail')) 
            : nebulaConfig.detailLevel;
            
          const opacity = section.hasAttribute('data-nebula-opacity') 
            ? parseFloat(section.getAttribute('data-nebula-opacity')) 
            : nebulaConfig.opacity;
            
          const zIndex = section.hasAttribute('data-nebula-z-index') 
            ? parseInt(section.getAttribute('data-nebula-z-index')) 
            : nebulaConfig.zIndex;
            
          const mainColor = section.hasAttribute('data-nebula-color') 
            ? section.getAttribute('data-nebula-color') 
            : nebulaConfig.customColors.colorA;
            
          const colorScheme = generateColorScheme(mainColor);
          
          const options = {
            speed,
            flowIntensity,
            detailLevel,
            customColors: colorScheme,
            opacity,
            zIndex
          };
          
          setupNebulaEffect(section, options);
          section.dataset.nebulaInitialized = 'true';
        });
      }
      
      function setupNebulaEffect(element, options) {
        if (window.getComputedStyle(element).position === 'static') {
          element.style.position = 'relative';
        }

        const wrapper = document.createElement('div');
        wrapper.style.cssText = `position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;overflow:hidden;pointer-events:none;z-index:${options.zIndex};`;
        
        const containerStyle = window.getComputedStyle(element);
        wrapper.style.borderRadius = containerStyle.borderRadius;
        
        element.insertBefore(wrapper, element.firstChild);
        element.style.opacity = options.opacity.toString();

        Array.from(element.children).forEach(child => {
          if (child !== wrapper) {
            child.style.position = 'relative';
            child.style.zIndex = '1';
          }
        });

        if (window.THREE) {
          const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
          renderer.setPixelRatio(window.devicePixelRatio);
          renderer.domElement.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;';
          renderer.domElement.style.borderRadius = containerStyle.borderRadius;
          wrapper.appendChild(renderer.domElement);

          const scene = new THREE.Scene();
          const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
          const geometry = new THREE.PlaneGeometry(2, 2);

          const uniforms = {
            time: { value: 0 },
            resolution: { value: new THREE.Vector2() },
            colorA: { value: new THREE.Vector3() },
            colorB: { value: new THREE.Vector3() },
            colorC: { value: new THREE.Vector3() },
            colorD: { value: new THREE.Vector3() },
            detailLevel: { value: options.detailLevel },
            flowIntensity: { value: options.flowIntensity }
          };

          const material = new THREE.ShaderMaterial({
            uniforms: uniforms,
            fragmentShader: getFragmentShader(),
            transparent: true
          });

          const mesh = new THREE.Mesh(geometry, material);
          scene.add(mesh);

          const colorA = hexToRgbNormalized(options.customColors.colorA);
          const colorB = hexToRgbNormalized(options.customColors.colorB);
          const colorC = hexToRgbNormalized(options.customColors.colorC);
          const colorD = hexToRgbNormalized(options.customColors.colorD);
          
          uniforms.colorA.value.set(colorA.r, colorA.g, colorA.b);
          uniforms.colorB.value.set(colorB.r, colorB.g, colorB.b);
          uniforms.colorC.value.set(colorC.r, colorC.g, colorC.b);
          uniforms.colorD.value.set(colorD.r, colorD.g, colorD.b);

          const resize = () => {
            const rect = element.getBoundingClientRect();
            renderer.setSize(rect.width, rect.height, false);
            uniforms.resolution.value.set(rect.width, rect.height);
            
            const currentStyle = window.getComputedStyle(element);
            wrapper.style.borderRadius = currentStyle.borderRadius;
            renderer.domElement.style.borderRadius = currentStyle.borderRadius;
          };

          const animate = (time) => {
            uniforms.time.value = time / 1000 * options.speed;
            renderer.render(scene, camera);
          };

          resize();
          
          const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
              if (entry.isIntersecting) {
                const animateLoop = (time) => {
                  animate(time);
                  requestAnimationFrame(animateLoop);
                };
                requestAnimationFrame(animateLoop);
              }
            });
          }, { threshold: 0 });
          
          observer.observe(element);
          window.addEventListener('resize', resize);
          
          element._nebulaCleanup = () => {
            observer.disconnect();
            if (wrapper && wrapper.parentNode) {
              wrapper.parentNode.removeChild(wrapper);
            }
            if (renderer) {
              renderer.dispose();
            }
            element.dataset.nebulaInitialized = 'false';
          };
        }
      }

      function getFragmentShader() {
        return `
          #ifdef GL_ES
          precision highp float;
          #endif

          uniform float time;
          uniform vec2 resolution;
          uniform vec3 colorA;
          uniform vec3 colorB;
          uniform vec3 colorC;
          uniform vec3 colorD;
          uniform float detailLevel;
          uniform float flowIntensity;

          float random(vec2 st) {
            return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
          }

          float noise(vec2 st) {
            vec2 i = floor(st);
            vec2 f = fract(st);
            float a = random(i);
            float b = random(i + vec2(1.0, 0.0));
            float c = random(i + vec2(0.0, 1.0));
            float d = random(i + vec2(1.0, 1.0));
            vec2 u = f * f * (3.0 - 2.0 * f);
            return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
          }

          float fbm(vec2 st) {
            float value = 0.0;
            float amplitude = 0.5;
            int octaves = int(detailLevel);
            
            for (int i = 0; i < 10; i++) {
              if (i >= octaves) break;
              value += amplitude * noise(st);
              st *= 2.0;
              amplitude *= 0.5;
            }
            return value;
          }

          void main() {
            vec2 st = gl_FragCoord.xy / resolution.xy;
            st.x *= resolution.x / resolution.y;
            
            vec2 q = vec2(fbm(st + 0.1 * time * flowIntensity), fbm(st + vec2(1.0)));
            vec2 r = vec2(fbm(st + 1.0 * q + vec2(1.7, 9.2) + 0.15 * time * flowIntensity), 
                        fbm(st + 1.0 * q + vec2(8.3, 2.8) + 0.126 * time * flowIntensity));
            float f = fbm(st + r);

            vec3 color = mix(colorA, colorB, clamp((f * f) * 4.0, 0.0, 1.0));
            color = mix(color, colorC, clamp(length(q), 0.0, 1.0));
            color = mix(color, colorD, clamp(length(r.x), 0.0, 1.0));

            gl_FragColor = vec4((f * f * f + 0.6 * f * f + 0.5 * f) * color, 0.8);
          }
        `;
      }
      
      function updateNebulaPreview() {
        const previews = ['nebula-preview'];
        
        previews.forEach(previewId => {
          const preview = document.getElementById(previewId);
          if (!preview) return;
          
          if (preview._nebulaCleanup) {
            preview._nebulaCleanup();
          }
          
          preview.setAttribute('data-nebula-effect', 'true');
          preview.setAttribute('data-nebula-speed', nebulaConfig.speed);
          preview.setAttribute('data-nebula-flow', nebulaConfig.flowIntensity);
          preview.setAttribute('data-nebula-detail', nebulaConfig.detailLevel);
          preview.setAttribute('data-nebula-opacity', nebulaConfig.opacity);
          preview.setAttribute('data-nebula-z-index', nebulaConfig.zIndex);
          preview.setAttribute('data-nebula-color', nebulaConfig.customColors.colorA);
        });
        
        initNebulaEffect();
      }

      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 `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${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 generateColorScheme(baseColor) {
        const baseRgb = hexToRgb(baseColor);
        
        const colorA = baseColor;
        const colorB = lightenColor(baseColor, 0.3);
        const colorC = darkenColor(baseColor, 0.5);
        const colorD = lightenColor(baseColor, 0.6);
        
        return {
          colorA: colorA,
          colorB: colorB,
          colorC: colorC,
          colorD: colorD
        };
      }
      
      function lightenColor(hex, amount) {
        const rgb = hexToRgb(hex);
        const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
        
        hsl.l = Math.min(1, hsl.l + amount);
        
        const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
        
        return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
      }
      
      function darkenColor(hex, amount) {
        const rgb = hexToRgb(hex);
        const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
        
        hsl.l = Math.max(0, hsl.l - amount);
        
        const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
        
        return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
      }
      
      function hexToRgb(hex) {
        hex = hex.replace(/^#/, '');
        
        const bigint = parseInt(hex, 16);
        const r = (bigint >> 16) & 255;
        const g = (bigint >> 8) & 255;
        const b = bigint & 255;
        
        return { r, g, b };
      }
      
      function rgbToHex(r, g, b) {
        return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
      }
      
      function rgbToHsl(r, g, b) {
        r /= 255, g /= 255, b /= 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, s, l };
      }
      
      function hslToRgb(h, s, l) {
        let r, g, b;
        
        if (s === 0) {
          r = g = b = l;
        } else {
          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;
          };
          
          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);
        }
        
        return {
          r: Math.round(r * 255),
          g: Math.round(g * 255),
          b: Math.round(b * 255)
        };
      }

      function hexToRgbNormalized(hex) {
        const rgb = hexToRgb(hex);
        return { 
          r: rgb.r / 255, 
          g: rgb.g / 255, 
          b: rgb.b / 255 
        };
      }

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

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

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

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

      function generateJavaScriptCode() {
        const fragmentShaderCode = `
          #ifdef GL_ES
          precision highp float;
          #endif

          uniform float time;
          uniform vec2 resolution;
          uniform vec3 colorA;
          uniform vec3 colorB;
          uniform vec3 colorC;
          uniform vec3 colorD;
          uniform float detailLevel;
          uniform float flowIntensity;

          float random(vec2 st) {
            return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
          }

          float noise(vec2 st) {
            vec2 i = floor(st);
            vec2 f = fract(st);
            float a = random(i);
            float b = random(i + vec2(1.0, 0.0));
            float c = random(i + vec2(0.0, 1.0));
            float d = random(i + vec2(1.0, 1.0));
            vec2 u = f * f * (3.0 - 2.0 * f);
            return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
          }

          float fbm(vec2 st) {
            float value = 0.0;
            float amplitude = 0.5;
            int octaves = int(detailLevel);
            
            for (int i = 0; i < 10; i++) {
              if (i >= octaves) break;
              value += amplitude * noise(st);
              st *= 2.0;
              amplitude *= 0.5;
            }
            return value;
          }

          void main() {
            vec2 st = gl_FragCoord.xy / resolution.xy;
            st.x *= resolution.x / resolution.y;
            
            vec2 q = vec2(fbm(st + 0.1 * time * flowIntensity), fbm(st + vec2(1.0)));
            vec2 r = vec2(fbm(st + 1.0 * q + vec2(1.7, 9.2) + 0.15 * time * flowIntensity), 
                        fbm(st + 1.0 * q + vec2(8.3, 2.8) + 0.126 * time * flowIntensity));
            float f = fbm(st + r);

            vec3 color = mix(colorA, colorB, clamp((f * f) * 4.0, 0.0, 1.0));
            color = mix(color, colorC, clamp(length(q), 0.0, 1.0));
            color = mix(color, colorD, clamp(length(r.x), 0.0, 1.0));

            gl_FragColor = vec4((f * f * f + 0.6 * f * f + 0.5 * f) * color, 0.8);
          }
        `;

        return `(function() {
        class EnhancedNebulaEffect {
          constructor() {
            this.instances = [];
            this.three = null;
            this.isInitialized = false;
            this.initAttempts = 0;
            this.maxAttempts = 10;
            this.checkInterval = null;
            this.config = {
              speed: ${nebulaConfig.speed},
              flowIntensity: ${nebulaConfig.flowIntensity},
              detailLevel: ${nebulaConfig.detailLevel},
              customColors: {
                colorA: "${nebulaConfig.customColors.colorA}",
                colorB: "${nebulaConfig.customColors.colorB}",
                colorC: "${nebulaConfig.customColors.colorC}",
                colorD: "${nebulaConfig.customColors.colorD}"
              },
              opacity: ${nebulaConfig.opacity},
              zIndex: ${nebulaConfig.zIndex}
            };
          }

          hexToRgb(hex) {
            hex = hex.replace(/^#/, '');
            const bigint = parseInt(hex, 16);
            const r = ((bigint >> 16) & 255) / 255;
            const g = ((bigint >> 8) & 255) / 255;
            const b = (bigint & 255) / 255;
            return { r, g, b };
          }

          forceInit() {
            if (this.checkInterval) clearInterval(this.checkInterval);
            
            this.checkInterval = setInterval(() => {
              const elements = document.querySelectorAll('[data-nebula-effect]');
              if (elements.length > 0) {
                elements.forEach(element => {
                  if (!element.hasAttribute('data-nebula-initialized')) {
                    this.initAttempts = 0;
                    this.isInitialized = false;
                    this.init();
                  }
                });
              }
            }, 100);

            setTimeout(() => {
              if (this.checkInterval) clearInterval(this.checkInterval);
            }, 5000);
          }

          async init(options = {}) {
            if (this.isInitialized) return;

            const defaultOptions = {
              selector: '[data-nebula-effect]',
              colorAttr: 'data-nebula-color'
            };
            this.options = { ...defaultOptions, ...options };

            try {
              await this.loadThreeJS();
              await this.initializeElements();
            } catch (error) {
              if (this.initAttempts < this.maxAttempts) {
                this.initAttempts++;
                setTimeout(() => this.init(options), 200);
              }
            }
          }

          async loadThreeJS() {
            return new Promise((resolve, reject) => {
              if (window.THREE) {
                this.three = window.THREE;
                resolve();
              } else {
                const script = document.createElement('script');
                script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
                script.onload = () => {
                  this.three = window.THREE;
                  resolve();
                };
                script.onerror = reject;
                document.head.appendChild(script);
              }
            });
          }

          async initializeElements() {
            const elements = document.querySelectorAll(this.options.selector);
            if (elements.length === 0) {
              throw new Error('No elements found');
            }

            elements.forEach(element => {
              if (!element.hasAttribute('data-nebula-initialized')) {
                this.createInstance(element);
                element.setAttribute('data-nebula-initialized', 'true');
              }
            });

            this.isInitialized = true;
            this.animate(0);
            window.addEventListener('resize', () => this.resize());
          }

          createInstance(element) {
            if (window.getComputedStyle(element).position === 'static') {
              element.style.position = 'relative';
            }

            const wrapper = document.createElement('div');
            wrapper.style.cssText = \`position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;overflow:hidden;pointer-events:none;z-index:\${this.config.zIndex};\`;
            
            const containerStyle = window.getComputedStyle(element);
            wrapper.style.borderRadius = containerStyle.borderRadius;
            
            element.insertBefore(wrapper, element.firstChild);
            element.style.opacity = this.config.opacity.toString();

            Array.from(element.children).forEach(child => {
              if (child !== wrapper) {
                child.style.position = 'relative';
                child.style.zIndex = '1';
              }
            });

            const renderer = new this.three.WebGLRenderer({ alpha: true, antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.domElement.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;';
            renderer.domElement.style.borderRadius = containerStyle.borderRadius;
            wrapper.appendChild(renderer.domElement);

            const scene = new this.three.Scene();
            const camera = new this.three.OrthographicCamera(-1, 1, 1, -1, 0, 1);
            const geometry = new this.three.PlaneGeometry(2, 2);

            const uniforms = {
              time: { value: 0 },
              resolution: { value: new this.three.Vector2() },
              colorA: { value: new this.three.Vector3() },
              colorB: { value: new this.three.Vector3() },
              colorC: { value: new this.three.Vector3() },
              colorD: { value: new this.three.Vector3() },
              detailLevel: { value: this.config.detailLevel },
              flowIntensity: { value: this.config.flowIntensity }
            };

            const material = new this.three.ShaderMaterial({
              uniforms: uniforms,
              fragmentShader: this.getFragmentShader(),
              transparent: true
            });

            const mesh = new this.three.Mesh(geometry, material);
            scene.add(mesh);

            const instance = {
              element,
              wrapper,
              renderer,
              scene,
              camera,
              uniforms,
              animate: (time) => {
                uniforms.time.value = time / 1000 * this.config.speed;
                renderer.render(scene, camera);
              },
              resize: () => {
                const rect = element.getBoundingClientRect();
                renderer.setSize(rect.width, rect.height, false);
                uniforms.resolution.value.set(rect.width, rect.height);
                
                const currentStyle = window.getComputedStyle(element);
                wrapper.style.borderRadius = currentStyle.borderRadius;
                renderer.domElement.style.borderRadius = currentStyle.borderRadius;
                
                wrapper.style.width = '100%';
                wrapper.style.height = '100%';
                renderer.domElement.style.width = '100%';
                renderer.domElement.style.height = '100%';
              },
              setCustomColors: (colorA, colorB, colorC, colorD) => {
                uniforms.colorA.value.set(colorA.r, colorA.g, colorA.b);
                uniforms.colorB.value.set(colorB.r, colorB.g, colorB.b);
                uniforms.colorC.value.set(colorC.r, colorC.g, colorC.b);
                uniforms.colorD.value.set(colorD.r, colorD.g, colorD.b);
              }
            };

            const colorA = this.hexToRgb(this.config.customColors.colorA);
            const colorB = this.hexToRgb(this.config.customColors.colorB);
            const colorC = this.hexToRgb(this.config.customColors.colorC);
            const colorD = this.hexToRgb(this.config.customColors.colorD);
            instance.setCustomColors(colorA, colorB, colorC, colorD);

            this.instances.push(instance);
            instance.resize();
          }

          animate(time) {
            this.instances.forEach(instance => {
              if (this.isElementInViewport(instance.element)) {
                instance.animate(time);
              }
            });
            requestAnimationFrame((t) => this.animate(t));
          }

          resize() {
            this.instances.forEach(instance => instance.resize());
          }

          isElementInViewport(el) {
            const rect = el.getBoundingClientRect();
            return (
              rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
              rect.bottom >= 0 &&
              rect.left <= (window.innerWidth || document.documentElement.clientWidth) &&
              rect.right >= 0
            );
          }

          getFragmentShader() {
            return ${JSON.stringify(fragmentShaderCode)};
          }
        }

        window.enhancedNebulaEffect = new EnhancedNebulaEffect();

        const initEvents = ['DOMContentLoaded', 'load', 'readystatechange'];

        initEvents.forEach(event => {
          document.addEventListener(event, () => {
            window.enhancedNebulaEffect.forceInit();
          });
        });

        window.enhancedNebulaEffect.forceInit();

        const observer = new MutationObserver(() => {
          window.enhancedNebulaEffect.forceInit();
        });

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

        setTimeout(() => {
          window.enhancedNebulaEffect.forceInit();
        }, 500);

        let checkCount = 0;
        const initCheck = setInterval(() => {
          if (checkCount++ < 5) {
            window.enhancedNebulaEffect.forceInit();
          } else {
            clearInterval(initCheck);
          }
        }, 1000);
      })();`;
      }

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

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

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

      window.resetParameter = function(parameterId, defaultValue) {
        const element = document.getElementById(parameterId);
        if (element) {
          element.value = defaultValue;
          const valueElement = document.getElementById(`${parameterId}-value`);
          if (valueElement) {
            valueElement.textContent = defaultValue;
          }
          
          switch (parameterId) {
            case 'animation-speed':
              nebulaConfig.speed = defaultValue;
              break;
            case 'flow-intensity':
              nebulaConfig.flowIntensity = defaultValue;
              break;
            case 'detail-level':
              nebulaConfig.detailLevel = defaultValue;
              break;
            case 'opacity':
              nebulaConfig.opacity = defaultValue;
              break;
            case 'z-index':
              nebulaConfig.zIndex = defaultValue;
              break;
          }
          
          updateNebulaPreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function generateRandomNebula() {
        const randomColor = generateRandomColor();
        
        nebulaConfig.customColors = generateColorScheme(randomColor);
        
        const colorInput = document.getElementById('main-color');
        const hexInput = document.getElementById('main-color-hex');
        const hslInput = document.getElementById('main-color-hsl');
        
        colorInput.value = randomColor;
        hexInput.value = randomColor;
        hslInput.value = hexToHsl(randomColor);
        
        const container = colorInput.closest('.color-picker-container');
        if (container) {
          container.style.setProperty('--current-color', randomColor);
        }
        
        updateNebulaPreview();
        showNotification('Random nebula generated!');
      }

      function updateColorInputs() {
        const color = nebulaConfig.customColors.colorA;
        const colorInput = document.getElementById('main-color');
        const hexInput = document.getElementById('main-color-hex');
        const hslInput = document.getElementById('main-color-hsl');
        
        if (colorInput && hexInput && hslInput) {
          colorInput.value = color;
          hexInput.value = color;
          hslInput.value = hexToHsl(color);
          
          const container = colorInput.closest('.color-picker-container');
          if (container) {
            container.style.setProperty('--current-color', color);
          }
          
          hexInput.classList.remove('invalid');
          hslInput.classList.remove('invalid');
        }
      }

      function initializeUI() {
        loadScript('https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js', function() {
          initNebulaEffect();
          
          window.addEventListener('resize', function() {
            if (window.enhancedNebulaEffect) {
              window.enhancedNebulaEffect.resize();
            }
          });
        });
        
        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-nebula-effect');
        });

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

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

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

        document.getElementById('reset-colors').addEventListener('click', () => {
          nebulaConfig.customColors = generateColorScheme(defaultConfig.customColors.colorA);
          updateColorInputs();
          updateNebulaPreview();
          showNotification('Colors reset to default');
        });

        document.getElementById('reset-animation').addEventListener('click', () => {
          nebulaConfig.speed = defaultConfig.speed;
          nebulaConfig.flowIntensity = defaultConfig.flowIntensity;
          nebulaConfig.detailLevel = defaultConfig.detailLevel;
          
          document.getElementById('animation-speed').value = defaultConfig.speed;
          document.getElementById('flow-intensity').value = defaultConfig.flowIntensity;
          document.getElementById('detail-level').value = defaultConfig.detailLevel;
          
          document.getElementById('animation-speed-value').textContent = defaultConfig.speed;
          document.getElementById('flow-intensity-value').textContent = defaultConfig.flowIntensity;
          document.getElementById('detail-level-value').textContent = defaultConfig.detailLevel;
          
          updateNebulaPreview();
          showNotification('Animation settings reset');
        });

        document.getElementById('reset-advanced').addEventListener('click', () => {
          nebulaConfig.opacity = defaultConfig.opacity;
          nebulaConfig.zIndex = defaultConfig.zIndex;
          
          document.getElementById('opacity').value = defaultConfig.opacity;
          document.getElementById('z-index').value = defaultConfig.zIndex;
          document.getElementById('opacity-value').textContent = defaultConfig.opacity;
          document.getElementById('z-index-value').textContent = defaultConfig.zIndex;
          
          updateNebulaPreview();
          showNotification('Advanced settings reset');
        });

        const colorInput = document.getElementById('main-color');
        const hexInput = document.getElementById('main-color-hex');
        const hslInput = document.getElementById('main-color-hsl');
        
        hslInput.value = hexToHsl(colorInput.value);
        
        const container = colorInput.closest('.color-picker-container');
        if (container) {
          container.style.setProperty('--current-color', colorInput.value);
        }
        
        colorInput.addEventListener('input', () => {
          const color = colorInput.value;
          hexInput.value = color;
          hslInput.value = hexToHsl(color);
          hexInput.classList.remove('invalid');
          hslInput.classList.remove('invalid');
          
          const container = colorInput.closest('.color-picker-container');
          if (container) {
            container.style.setProperty('--current-color', color);
          }
          
          nebulaConfig.customColors = generateColorScheme(color);
          updateNebulaPreview();
        });
        
        hexInput.addEventListener('input', (e) => {
          let hex = e.target.value;
          
          hex = formatHex(hex);
          e.target.value = hex;
          
          if (isValidHex(hex)) {
            colorInput.value = hex;
            hslInput.value = hexToHsl(hex);
            
            const container = colorInput.closest('.color-picker-container');
            if (container) {
              container.style.setProperty('--current-color', hex);
            }
            
            nebulaConfig.customColors = generateColorScheme(hex);
            e.target.classList.remove('invalid');
            hslInput.classList.remove('invalid');
            updateNebulaPreview();
          } else {
            e.target.classList.add('invalid');
          }
        });
        
        hexInput.addEventListener('blur', (e) => {
          if (!isValidHex(e.target.value)) {
            e.target.value = colorInput.value;
            e.target.classList.remove('invalid');
          }
        });
        
        hslInput.addEventListener('input', (e) => {
          let hsl = e.target.value;
          
          if (isValidHsl(hsl)) {
            const hex = hslToHex(hsl);
            if (hex) {
              colorInput.value = hex;
              hexInput.value = hex;
              
              const container = colorInput.closest('.color-picker-container');
              if (container) {
                container.style.setProperty('--current-color', hex);
              }
              
              nebulaConfig.customColors = generateColorScheme(hex);
              e.target.classList.remove('invalid');
              hexInput.classList.remove('invalid');
              updateNebulaPreview();
              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;
                
                const container = colorInput.closest('.color-picker-container');
                if (container) {
                  container.style.setProperty('--current-color', hex);
                }
                
                nebulaConfig.customColors = generateColorScheme(hex);
                e.target.classList.remove('invalid');
                hexInput.classList.remove('invalid');
                updateNebulaPreview();
                return;
              }
            }
          }
          
          if (!isValidHsl(e.target.value)) {
            e.target.value = hexToHsl(colorInput.value);
            e.target.classList.remove('invalid');
          }
        });

        const rangeInputs = document.querySelectorAll('input[type="range"]');
        rangeInputs.forEach(input => {
          let valueElement = document.getElementById(`${input.id}-value`);
          
          if (valueElement) {
            valueElement.textContent = input.value;
          }
          
          input.addEventListener('input', () => {
            if (valueElement) {
              valueElement.textContent = input.value;
            }
            
            switch (input.id) {
              case 'animation-speed':
                nebulaConfig.speed = parseFloat(input.value);
                break;
              case 'flow-intensity':
                nebulaConfig.flowIntensity = parseFloat(input.value);
                break;
              case 'detail-level':
                nebulaConfig.detailLevel = parseInt(input.value);
                break;
              case 'opacity':
                nebulaConfig.opacity = parseFloat(input.value);
                break;
              case 'z-index':
                nebulaConfig.zIndex = parseInt(input.value);
                break;
            }
            
            updateNebulaPreview();
          });
        });

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

        updateColorInputs();
        nebulaConfig.customColors = generateColorScheme(nebulaConfig.customColors.colorA);
        
        setTimeout(() => {
          showNotification('BricksFusion Nebula Effect Configurator loaded!');
        }, 500);
      }

      function loadScript(url, callback) {
        const script = document.createElement('script');
        script.src = url;
        script.onload = callback;
        document.head.appendChild(script);
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Nebula Effect - Bricksfusion
HEAVY

Nebula Effect

Creates flowing cosmic cloud effects using animated procedural noise. Features organic swirling patterns that evolve continuously. Choose your base color and the effect automatically generates a harmonious color palette. Perfect for hero sections, backgrounds, or creating atmospheric visual depth.

Nebula Effect

Animated cosmic cloud background.

Appearance

Main Color Color picker

Choose the primary color for your nebula. The effect will automatically create lighter and darker variations to build a rich, layered atmosphere.

Default: Blue (#3366ff)

Opacity Transparent to Solid

Controls how see-through the effect is. Lower values let more background show through. Higher values create a more solid, opaque look.

Default: 80%

Motion

Animation Speed Slow to Fast

How quickly the nebula clouds move and evolve. Lower creates gentle, calming movement. Higher produces energetic, dynamic flow.

Default: Normal

Flow Intensity Subtle to Strong

Controls how dramatically the patterns swirl and flow. Lower keeps motion smooth and gentle. Higher creates bold, turbulent movement.

Default: Normal

Detail

Detail Level Simple to Complex

Amount of fine texture and small details in the nebula. Lower creates smooth, soft clouds. Higher adds intricate patterns and complexity.

Default: Medium (5)

Layout

Layer Position Behind to Front

Controls whether the nebula appears behind or in front of your content. Use negative values to push it back, or positive to bring it forward.

Default: Behind content (0)

Performance

This effect uses WebGL with custom shaders for GPU-accelerated rendering. Creates organic patterns using procedural noise with automatic color palette generation. Features viewport detection to pause when off-screen for better performance. Very resource intensive - limit to 1 instance per page. Not recommended for mobile devices or low-end hardware.