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>Fluid Ring 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: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
      border: 1px solid var(--border);
      box-shadow: var(--shadow);
      will-change: contents;
    }

    .preview-container canvas {
      will-change: contents;
      transform: translateZ(0);
      backface-visibility: hidden;
    }

    .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;
      pointer-events: none;
    }

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

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

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

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

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

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

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

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

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

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

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

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

    .card-content {
      padding: 1.5rem;
    }

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

    .control-group:last-child {
      margin-bottom: 0;
    }

    .control-label {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 0.75rem;
    }

    .label-text {
      font-size: var(--text-xs);
      font-weight: 500;
      letter-spacing: 0.2px;
      display: flex;
      align-items: center;
      gap: 0.5rem;
    }

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

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

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

    .value-text {
      font-size: var(--text-xs);
      color: var(--text-secondary);
      background-color: rgba(50, 50, 50, 0.5);
      padding: 2px 8px;
      border-radius: 4px;
      min-width: 45px;
      text-align: center;
    }

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

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

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

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

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

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

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

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

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

    .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, #6633cc);
      border-radius: 6px;
      transition: var(--transition);
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    @keyframes spin {
      to { transform: rotate(360deg); }
    }
  </style>
</head>
<body>
  <div class="action-bar">
    <nav class="breadcrumb">
      <a href="https://bricksfusion.com" class="breadcrumb-item">Home</a>
      <span class="breadcrumb-separator">›</span>
      <a href="https://bricksfusion.com/visual-effects/" class="breadcrumb-item">Visual effects</a>
      <span class="breadcrumb-separator">›</span>
      <span class="breadcrumb-item active">Fluid Ring</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-fluid-ring
      </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">Fluid Ring</h1>
      <p class="page-subtitle">Interactive fluid animations for Bricks Builder</p>
    </div>

    <div class="instructions-toggle">
      <div class="instructions-card" id="instructions-card">
        <div class="instructions-header" id="instructions-toggle">
          <div class="instructions-title">
            How to Use & Code Information
          </div>
          <span class="toggle-icon">▼</span>
        </div>
        <div class="instructions-content" id="instructions-content">
          <div class="instructions-grid">
            <div class="how-to-use">
              <ol>
                <li>Customize your fluid ring animation using the controls below</li>
                <li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
                <li>In Bricks Builder, add a <strong>Code</strong> element</li>
                <li>Paste or upload the JavaScript code</li>
                <li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-fluid-ring</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="fluid-ring-preview" data-fluid-ring="true">
          <div class="preview-content">Interactive Fluid Ring Preview</div>
          <div class="preview-controls">
            <button class="preview-btn" id="randomize-fluid-ring" 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="#1a1a1a" title="Change Preview Background (B)">
            </div>
          </div>
        </div>
      </section>

      <section class="controls-section">
        <div class="card">
          <div class="card-heading">
            Primary Color
            <div class="card-actions">
              <button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="color-list">
              <div class="color-row">
                <div class="color-picker-container">
                  <input type="color" id="primary-color" value="#6633cc">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HEX</span>
                  <input type="text" class="color-input hex-input" id="primary-color-hex" value="#6633cc" placeholder="#FFFFFF">
                </div>
                <div class="color-input-group">
                  <span class="color-label">HSL</span>
                  <input type="text" class="color-input hsl-input" id="primary-color-hsl" placeholder="hsl(0, 100%, 50%)">
                </div>
              </div>
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Variation Strength
                  <span class="help-tooltip" title="Color variation intensity">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="variation-strength-value">5.0</span></span>
                  <button class="reset-btn" onclick="resetParameter('variation-strength', 5.0)">↺</button>
                </div>
              </div>
              <input type="range" id="variation-strength" min="1.0" max="10.0" step="0.5" value="5.0">
            </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="Speed multiplier for the animation">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="animation-speed-value">1.0</span>x</span>
                  <button class="reset-btn" onclick="resetParameter('animation-speed', 1.0)">↺</button>
                </div>
              </div>
              <input type="range" id="animation-speed" min="0.1" max="3.0" step="0.1" value="1.0">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Ring Radius
                  <span class="help-tooltip" title="Radius of the fluid ring">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="ring-radius-value">0.35</span></span>
                  <button class="reset-btn" onclick="resetParameter('ring-radius', 0.35)">↺</button>
                </div>
              </div>
              <input type="range" id="ring-radius" min="0.15" max="0.5" step="0.01" value="0.35">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Ring Width
                  <span class="help-tooltip" title="Width of the fluid ring">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="ring-width-value">0.035</span></span>
                  <button class="reset-btn" onclick="resetParameter('ring-width', 0.035)">↺</button>
                </div>
              </div>
              <input type="range" id="ring-width" min="0.01" max="0.08" step="0.005" value="0.035">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Intensity
                  <span class="help-tooltip" title="Visual intensity of the effect">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="intensity-value">1.0</span></span>
                  <button class="reset-btn" onclick="resetParameter('intensity', 1.0)">↺</button>
                </div>
              </div>
              <input type="range" id="intensity" min="0.3" max="2.0" step="0.1" value="1.0">
            </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">Performance Mode</span>
              </div>
              <select id="performance-mode">
                <option value="high">High Quality</option>
                <option value="balanced" selected>Balanced</option>
                <option value="performance">Performance</option>
              </select>
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let fluidRingConfig = {
        speed: 1.0,
        radius: 0.35,
        width: 0.035,
        intensity: 1.0,
        primaryColor: "#6633cc",
        variationStrength: 5.0,
        performanceMode: "balanced"
      };

      const defaultConfig = { ...fluidRingConfig };

      let activeFluidRing = null;
      
      function initFluidRing() {
        const sections = document.querySelectorAll('[data-fluid-ring]:not([data-fluid-initialized="true"])');
        
        sections.forEach((section) => {
          const speed = section.hasAttribute('data-fluid-speed') 
            ? parseFloat(section.getAttribute('data-fluid-speed')) 
            : fluidRingConfig.speed;
            
          const radius = section.hasAttribute('data-fluid-radius') 
            ? parseFloat(section.getAttribute('data-fluid-radius')) 
            : fluidRingConfig.radius;
            
          const width = section.hasAttribute('data-fluid-width') 
            ? parseFloat(section.getAttribute('data-fluid-width')) 
            : fluidRingConfig.width;
            
          const intensity = section.hasAttribute('data-fluid-intensity') 
            ? parseFloat(section.getAttribute('data-fluid-intensity')) 
            : fluidRingConfig.intensity;
            
          const primaryColor = section.hasAttribute('data-fluid-color') 
            ? section.getAttribute('data-fluid-color') 
            : fluidRingConfig.primaryColor;
            
          const variationStrength = section.hasAttribute('data-fluid-variation') 
            ? parseFloat(section.getAttribute('data-fluid-variation')) 
            : fluidRingConfig.variationStrength;
            
          const performanceMode = section.hasAttribute('data-fluid-performance') 
            ? section.getAttribute('data-fluid-performance') 
            : fluidRingConfig.performanceMode;
            
          const options = {
            speed,
            radius,
            width,
            intensity,
            primaryColor,
            variationStrength,
            performanceMode
          };
          
          setupFluidRing(section, options);
          section.dataset.fluidInitialized = 'true';
          
          if (section.id === 'fluid-ring-preview') {
            activeFluidRing = { element: section, options };
            fluidRingConfig = {
              speed: options.speed,
              radius: options.radius,
              width: options.width,
              intensity: options.intensity,
              primaryColor: options.primaryColor,
              variationStrength: options.variationStrength,
              performanceMode: options.performanceMode
            };
          }
        });
      }
      
      function setupFluidRing(element, options) {
        const canvas = document.createElement('canvas');
        const gl = canvas.getContext('webgl');
        
        if (!gl) {
          console.warn('WebGL not supported, fluid ring effect disabled');
          return;
        }
        
        element.appendChild(canvas);
        
        if (window.getComputedStyle(element).position === 'static') {
          element.style.position = 'relative';
        }
        
        Object.assign(canvas.style, {
          position: 'absolute',
          top: '0',
          left: '0',
          width: '100%',
          height: '100%',
          pointerEvents: 'none',
          zIndex: '1'
        });
        
        canvas.width = element.clientWidth;
        canvas.height = element.clientHeight;
        
        gl.enable(gl.BLEND);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        
        const vertexShaderSource = `
          attribute vec2 aPosition;
          void main() {
            gl_Position = vec4(aPosition, 0.0, 1.0);
          }
        `;
        
        const fragmentShaderSource = `
          precision highp float;
          uniform float iTime;
          uniform vec2 iResolution;
          uniform vec3 uBackgroundColor;
          uniform float uSpeed;
          uniform float uRadius;
          uniform float uWidth;
          uniform float uIntensity;
          uniform vec3 uPrimaryColor;
          uniform float uVariationStrength;
          
          mat2 rotate2d(float angle) {
            float c = cos(angle), s = sin(angle);
            return mat2(c, -s, s, c);
          }
          
          float variation(vec2 v1, vec2 v2, float strength, float speed) {
            return sin(dot(normalize(v1), normalize(v2)) * strength + iTime * speed * uSpeed) / 100.0;
          }
          
          vec3 paintCircle(vec2 uv, vec2 center, float rad, float width) {
            vec2 diff = center - uv;
            float len = length(diff);
            len += variation(diff, vec2(0., 1.), uVariationStrength, 2.);
            len -= variation(diff, vec2(1., 0.), uVariationStrength, 2.);
            float circle = smoothstep(rad - width, rad, len) - smoothstep(rad, rad + width, len);
            return vec3(circle);
          }
          
          void main() {
            vec2 uv = gl_FragCoord.xy / iResolution.xy;
            uv.x *= 1.5;
            uv.x -= 0.25;
            
            float mask = 0.0;
            vec2 center = vec2(0.5);
            
            mask += paintCircle(uv, center, uRadius, uWidth).r;
            mask += paintCircle(uv, center, uRadius - uWidth * 0.5, uWidth * 0.3).r;
            mask += paintCircle(uv, center, uRadius + uWidth * 0.5, uWidth * 0.15).r;
            
            vec2 v = rotate2d(iTime * uSpeed) * uv;
            vec3 baseColor = vec3(v.x, v.y, 0.7 - v.y * v.x);
            vec3 finalColor = mix(baseColor, uPrimaryColor, 0.7);
            vec3 color = mix(uBackgroundColor, finalColor * uIntensity, mask);
            color = mix(color, vec3(1.), paintCircle(uv, center, uRadius, uWidth * 0.08).r);
            
            float alpha = mask + paintCircle(uv, center, uRadius, uWidth * 0.08).r;
            alpha = clamp(alpha * uIntensity, 0.0, 1.0);
            
            gl_FragColor = vec4(color, alpha);
          }
        `;
        
        const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
        const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
        
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
          console.error('Program linking failed:', gl.getProgramInfoLog(program));
          return;
        }
        
        gl.useProgram(program);
        
        const buffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
          -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1
        ]), gl.STATIC_DRAW);
        
        const aPosition = gl.getAttribLocation(program, 'aPosition');
        gl.enableVertexAttribArray(aPosition);
        gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
        
        const uniformLocations = {
          iTime: gl.getUniformLocation(program, 'iTime'),
          iResolution: gl.getUniformLocation(program, 'iResolution'),
          uBackgroundColor: gl.getUniformLocation(program, 'uBackgroundColor'),
          uSpeed: gl.getUniformLocation(program, 'uSpeed'),
          uRadius: gl.getUniformLocation(program, 'uRadius'),
          uWidth: gl.getUniformLocation(program, 'uWidth'),
          uIntensity: gl.getUniformLocation(program, 'uIntensity'),
          uPrimaryColor: gl.getUniformLocation(program, 'uPrimaryColor'),
          uVariationStrength: gl.getUniformLocation(program, 'uVariationStrength')
        };
        
        element.fluidRingCanvas = canvas;
        element.fluidRingGL = gl;
        element.fluidRingProgram = program;
        element.fluidRingUniforms = uniformLocations;
        element.fluidRingConfig = { ...options };
        element.animationFrame = null;
        element.startTime = Date.now();
        
        const backgroundColor = getBackgroundColor(element);
        
        function updateConfig(newConfig) {
          Object.assign(element.fluidRingConfig, newConfig);
        }
        
        element.updateConfig = updateConfig;
        
        startFluidRingAnimation(element);
      }
      
      function compileShader(gl, type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
          const error = gl.getShaderInfoLog(shader);
          gl.deleteShader(shader);
          throw new Error('Shader compilation failed: ' + error);
        }
        
        return shader;
      }
      
      function getBackgroundColor(element) {
        const computedStyle = getComputedStyle(element);
        let bgColor = computedStyle.backgroundColor;
        
        if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
          return cssColorToRGB(bgColor);
        }
        
        const root = document.documentElement;
        const body = document.body;
        const isDark = root.classList.contains('dark') || 
                      body.classList.contains('dark') ||
                      root.getAttribute('data-theme') === 'dark' ||
                      body.getAttribute('data-theme') === 'dark';
        
        return isDark ? [0.07, 0.07, 0.07] : [0.95, 0.95, 0.95];
      }
      
      function cssColorToRGB(cssColor) {
        if (cssColor.startsWith('#')) {
          return hexToRGB(cssColor);
        } else if (cssColor.startsWith('rgb')) {
          const match = cssColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/);
          if (match) {
            return [
              parseInt(match[1]) / 255.0,
              parseInt(match[2]) / 255.0,
              parseInt(match[3]) / 255.0
            ];
          }
        }
        return [0.07, 0.07, 0.07];
      }
      
      function hexToRGB(hex) {
        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
        return result ? [
          parseInt(result[1], 16) / 255.0,
          parseInt(result[2], 16) / 255.0,
          parseInt(result[3], 16) / 255.0
        ] : [0.07, 0.07, 0.07];
      }
      
      function startFluidRingAnimation(element) {
        if (!element.fluidRingGL || !element.fluidRingProgram) return;
        
        const gl = element.fluidRingGL;
        const program = element.fluidRingProgram;
        const uniforms = element.fluidRingUniforms;
        
        function animate(time) {
          element.animationFrame = requestAnimationFrame(animate);
          
          const currentTime = (time - element.startTime) * 0.001;
          
          gl.viewport(0, 0, element.fluidRingCanvas.width, element.fluidRingCanvas.height);
          gl.clearColor(0, 0, 0, 0);
          gl.clear(gl.COLOR_BUFFER_BIT);
          
          gl.useProgram(program);
          
          const primaryColorRgb = hexToRGB(element.fluidRingConfig.primaryColor);
          const backgroundColor = getBackgroundColor(element);
          
          gl.uniform1f(uniforms.iTime, currentTime);
          gl.uniform2f(uniforms.iResolution, element.fluidRingCanvas.width, element.fluidRingCanvas.height);
          gl.uniform3fv(uniforms.uBackgroundColor, new Float32Array(backgroundColor));
          gl.uniform1f(uniforms.uSpeed, element.fluidRingConfig.speed);
          gl.uniform1f(uniforms.uRadius, element.fluidRingConfig.radius);
          gl.uniform1f(uniforms.uWidth, element.fluidRingConfig.width);
          gl.uniform1f(uniforms.uIntensity, element.fluidRingConfig.intensity);
          gl.uniform3fv(uniforms.uPrimaryColor, new Float32Array(primaryColorRgb));
          gl.uniform1f(uniforms.uVariationStrength, element.fluidRingConfig.variationStrength);
          
          gl.drawArrays(gl.TRIANGLES, 0, 6);
        }
        
        element.startTime = performance.now();
        element.animationFrame = requestAnimationFrame(animate);
        
        const resizeObserver = new ResizeObserver(() => {
          updateCanvasSize(element);
        });
        resizeObserver.observe(element);
      }
      
      function updateCanvasSize(element) {
        if (!element.fluidRingCanvas || !element.fluidRingGL) return;
        
        const rect = element.getBoundingClientRect();
        const dpr = window.devicePixelRatio || 1;
        
        element.fluidRingCanvas.width = rect.width * dpr;
        element.fluidRingCanvas.height = rect.height * dpr;
        element.fluidRingCanvas.style.width = rect.width + 'px';
        element.fluidRingCanvas.style.height = rect.height + 'px';
      }
      
      function updateFluidRingPreview() {
        const preview = document.getElementById('fluid-ring-preview');
        if (!preview || !preview.updateConfig) return;
        
        preview.updateConfig(fluidRingConfig);
        
        preview.setAttribute('data-fluid-speed', fluidRingConfig.speed);
        preview.setAttribute('data-fluid-radius', fluidRingConfig.radius);
        preview.setAttribute('data-fluid-width', fluidRingConfig.width);
        preview.setAttribute('data-fluid-intensity', fluidRingConfig.intensity);
        preview.setAttribute('data-fluid-color', fluidRingConfig.primaryColor);
        preview.setAttribute('data-fluid-variation', fluidRingConfig.variationStrength);
        preview.setAttribute('data-fluid-performance', fluidRingConfig.performanceMode);
      }

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

      function generateJavaScriptCode() {
        return `(function(window) {
  const FluidRingAnimation = {
    instances: [],
    config: {
      speed: ${fluidRingConfig.speed},
      radius: ${fluidRingConfig.radius},
      width: ${fluidRingConfig.width},
      intensity: ${fluidRingConfig.intensity},
      primaryColor: "${fluidRingConfig.primaryColor}",
      variationStrength: ${fluidRingConfig.variationStrength},
      performanceMode: "${fluidRingConfig.performanceMode}"
    },
    init: function(options = {}) {
      const defaultOptions = {
        selector: '[data-fluid-ring]',
        colorAttr: 'data-fluid-color'
      };
      const config = { ...defaultOptions, ...options };

      const initInstances = () => {
        document.querySelectorAll(config.selector).forEach(element => {
          if (!element.hasAttribute('data-fluid-initialized')) {
            this.createInstance(element, config);
            element.setAttribute('data-fluid-initialized', 'true');
          }
        });
      };

      initInstances();
      setTimeout(initInstances, 100);
      window.addEventListener('load', initInstances);

      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          if (mutation.type === 'childList') {
            initInstances();
          }
        });
      });

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

    createInstance: function(element, config) {
      const canvas = document.createElement('canvas');
      const gl = canvas.getContext('webgl');
      
      if (!gl) {
        return;
      }
      
      element.appendChild(canvas);

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

      Object.assign(canvas.style, {
        position: 'absolute',
        top: '0',
        left: '0',
        width: '100%',
        height: '100%',
        pointerEvents: 'none',
        zIndex: '1'
      });

      gl.enable(gl.BLEND);
      gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

      const instance = {
        element,
        canvas,
        gl,
        width: 0,
        height: 0,
        program: null,
        uniforms: {},
        animationFrame: null,
        startTime: Date.now(),
        config: {
          speed: element.getAttribute('data-fluid-speed') || this.config.speed,
          radius: element.getAttribute('data-fluid-radius') || this.config.radius,
          width: element.getAttribute('data-fluid-width') || this.config.width,
          intensity: element.getAttribute('data-fluid-intensity') || this.config.intensity,
          primaryColor: element.getAttribute(config.colorAttr) || this.config.primaryColor,
          variationStrength: element.getAttribute('data-fluid-variation') || this.config.variationStrength,
          performanceMode: element.getAttribute('data-fluid-performance') || this.config.performanceMode
        },

        resizeCanvas: function() {
          const rect = this.element.getBoundingClientRect();
          const dpr = window.devicePixelRatio || 1;
          
          this.width = rect.width * dpr;
          this.height = rect.height * dpr;
          this.canvas.width = this.width;
          this.canvas.height = this.height;
          this.canvas.style.width = rect.width + 'px';
          this.canvas.style.height = rect.height + 'px';
        },

        compileShader: function(type, source) {
          const shader = this.gl.createShader(type);
          this.gl.shaderSource(shader, source);
          this.gl.compileShader(shader);
          
          if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
            this.gl.deleteShader(shader);
            return null;
          }
          
          return shader;
        },

        initShaders: function() {
          const vertexShaderSource = \`
            attribute vec2 aPosition;
            void main() {
              gl_Position = vec4(aPosition, 0.0, 1.0);
            }
          \`;

          const fragmentShaderSource = \`
            precision highp float;
            uniform float iTime;
            uniform vec2 iResolution;
            uniform vec3 uBackgroundColor;
            uniform float uSpeed;
            uniform float uRadius;
            uniform float uWidth;
            uniform float uIntensity;
            uniform vec3 uPrimaryColor;
            uniform float uVariationStrength;
            
            mat2 rotate2d(float angle) {
              float c = cos(angle), s = sin(angle);
              return mat2(c, -s, s, c);
            }
            
            float variation(vec2 v1, vec2 v2, float strength, float speed) {
              return sin(dot(normalize(v1), normalize(v2)) * strength + iTime * speed * uSpeed) / 100.0;
            }
            
            vec3 paintCircle(vec2 uv, vec2 center, float rad, float width) {
              vec2 diff = center - uv;
              float len = length(diff);
              len += variation(diff, vec2(0., 1.), uVariationStrength, 2.);
              len -= variation(diff, vec2(1., 0.), uVariationStrength, 2.);
              float circle = smoothstep(rad - width, rad, len) - smoothstep(rad, rad + width, len);
              return vec3(circle);
            }
            
            void main() {
              vec2 uv = gl_FragCoord.xy / iResolution.xy;
              uv.x *= 1.5;
              uv.x -= 0.25;
              
              float mask = 0.0;
              vec2 center = vec2(0.5);
              
              mask += paintCircle(uv, center, uRadius, uWidth).r;
              mask += paintCircle(uv, center, uRadius - uWidth * 0.5, uWidth * 0.3).r;
              mask += paintCircle(uv, center, uRadius + uWidth * 0.5, uWidth * 0.15).r;
              
              vec2 v = rotate2d(iTime * uSpeed) * uv;
              vec3 baseColor = vec3(v.x, v.y, 0.7 - v.y * v.x);
              vec3 finalColor = mix(baseColor, uPrimaryColor, 0.7);
              vec3 color = mix(uBackgroundColor, finalColor * uIntensity, mask);
              color = mix(color, vec3(1.), paintCircle(uv, center, uRadius, uWidth * 0.08).r);
              
              float alpha = mask + paintCircle(uv, center, uRadius, uWidth * 0.08).r;
              alpha = clamp(alpha * uIntensity, 0.0, 1.0);
              
              gl_FragColor = vec4(color, alpha);
            }
          \`;

          const vertexShader = this.compileShader(this.gl.VERTEX_SHADER, vertexShaderSource);
          const fragmentShader = this.compileShader(this.gl.FRAGMENT_SHADER, fragmentShaderSource);
          
          if (!vertexShader || !fragmentShader) return false;

          this.program = this.gl.createProgram();
          this.gl.attachShader(this.program, vertexShader);
          this.gl.attachShader(this.program, fragmentShader);
          this.gl.linkProgram(this.program);

          if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
            return false;
          }

          this.gl.useProgram(this.program);
          
          this.uniforms = {
            iTime: this.gl.getUniformLocation(this.program, 'iTime'),
            iResolution: this.gl.getUniformLocation(this.program, 'iResolution'),
            uBackgroundColor: this.gl.getUniformLocation(this.program, 'uBackgroundColor'),
            uSpeed: this.gl.getUniformLocation(this.program, 'uSpeed'),
            uRadius: this.gl.getUniformLocation(this.program, 'uRadius'),
            uWidth: this.gl.getUniformLocation(this.program, 'uWidth'),
            uIntensity: this.gl.getUniformLocation(this.program, 'uIntensity'),
            uPrimaryColor: this.gl.getUniformLocation(this.program, 'uPrimaryColor'),
            uVariationStrength: this.gl.getUniformLocation(this.program, 'uVariationStrength')
          };

          const buffer = this.gl.createBuffer();
          this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
          this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array([
            -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1
          ]), this.gl.STATIC_DRAW);

          const aPosition = this.gl.getAttribLocation(this.program, 'aPosition');
          this.gl.enableVertexAttribArray(aPosition);
          this.gl.vertexAttribPointer(aPosition, 2, this.gl.FLOAT, false, 0, 0);

          return true;
        },

        getBackgroundColor: function() {
          const computedStyle = getComputedStyle(this.element);
          let bgColor = computedStyle.backgroundColor;
          
          if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
            return this.cssColorToRGB(bgColor);
          }
          
          const root = document.documentElement;
          const body = document.body;
          const isDark = root.classList.contains('dark') || 
                        body.classList.contains('dark') ||
                        root.getAttribute('data-theme') === 'dark' ||
                        body.getAttribute('data-theme') === 'dark';
          
          return isDark ? [0.07, 0.07, 0.07] : [0.95, 0.95, 0.95];
        },

        cssColorToRGB: function(cssColor) {
          if (cssColor.startsWith('#')) {
            return this.hexToRGB(cssColor);
          } else if (cssColor.startsWith('rgb')) {
            const match = cssColor.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)/);
            if (match) {
              return [
                parseInt(match[1]) / 255.0,
                parseInt(match[2]) / 255.0,
                parseInt(match[3]) / 255.0
              ];
            }
          }
          return [0.07, 0.07, 0.07];
        },

        hexToRGB: function(hex) {
          const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);
          return result ? [
            parseInt(result[1], 16) / 255.0,
            parseInt(result[2], 16) / 255.0,
            parseInt(result[3], 16) / 255.0
          ] : [0.07, 0.07, 0.07];
        },

        animate: function(time) {
          this.animationFrame = requestAnimationFrame(this.animate.bind(this));
          
          const currentTime = (time - this.startTime) * 0.001;
          
          this.gl.viewport(0, 0, this.width, this.height);
          this.gl.clearColor(0, 0, 0, 0);
          this.gl.clear(this.gl.COLOR_BUFFER_BIT);
          
          this.gl.useProgram(this.program);
          
          const primaryColorRgb = this.hexToRGB(this.config.primaryColor);
          const backgroundColor = this.getBackgroundColor();
          
          this.gl.uniform1f(this.uniforms.iTime, currentTime);
          this.gl.uniform2f(this.uniforms.iResolution, this.width, this.height);
          this.gl.uniform3fv(this.uniforms.uBackgroundColor, new Float32Array(backgroundColor));
          this.gl.uniform1f(this.uniforms.uSpeed, parseFloat(this.config.speed));
          this.gl.uniform1f(this.uniforms.uRadius, parseFloat(this.config.radius));
          this.gl.uniform1f(this.uniforms.uWidth, parseFloat(this.config.width));
          this.gl.uniform1f(this.uniforms.uIntensity, parseFloat(this.config.intensity));
          this.gl.uniform3fv(this.uniforms.uPrimaryColor, new Float32Array(primaryColorRgb));
          this.gl.uniform1f(this.uniforms.uVariationStrength, parseFloat(this.config.variationStrength));
          
          this.gl.drawArrays(this.gl.TRIANGLES, 0, 6);
        },

        resize: function() {
          this.resizeCanvas();
        }
      };

      instance.resizeCanvas();
      
      if (instance.initShaders()) {
        instance.startTime = performance.now();
        instance.animate(0);

        let resizeTimeout;
        window.addEventListener('resize', () => {
          clearTimeout(resizeTimeout);
          resizeTimeout = setTimeout(() => instance.resize(), 250);
        });

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

  window.FluidRingAnimation = FluidRingAnimation;
  FluidRingAnimation.init();
})(window);`;
      }

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

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

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

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

      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':
              fluidRingConfig.speed = defaultValue;
              break;
            case 'ring-radius':
              fluidRingConfig.radius = defaultValue;
              break;
            case 'ring-width':
              fluidRingConfig.width = defaultValue;
              break;
            case 'intensity':
              fluidRingConfig.intensity = defaultValue;
              break;
            case 'variation-strength':
              fluidRingConfig.variationStrength = defaultValue;
              break;
          }
          
          updateFluidRingPreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function generateRandomFluidRing() {
        fluidRingConfig.speed = Math.random() * 2.9 + 0.1;
        fluidRingConfig.radius = Math.random() * 0.35 + 0.15;
        fluidRingConfig.width = Math.random() * 0.07 + 0.01;
        fluidRingConfig.intensity = Math.random() * 1.7 + 0.3;
        fluidRingConfig.primaryColor = generateRandomColor();
        fluidRingConfig.variationStrength = Math.random() * 9 + 1;
        
        document.getElementById('animation-speed').value = fluidRingConfig.speed;
        document.getElementById('ring-radius').value = fluidRingConfig.radius;
        document.getElementById('ring-width').value = fluidRingConfig.width;
        document.getElementById('intensity').value = fluidRingConfig.intensity;
        document.getElementById('primary-color').value = fluidRingConfig.primaryColor;
        document.getElementById('variation-strength').value = fluidRingConfig.variationStrength;
        
        document.getElementById('animation-speed-value').textContent = fluidRingConfig.speed.toFixed(1);
        document.getElementById('ring-radius-value').textContent = fluidRingConfig.radius.toFixed(2);
        document.getElementById('ring-width-value').textContent = fluidRingConfig.width.toFixed(3);
        document.getElementById('intensity-value').textContent = fluidRingConfig.intensity.toFixed(1);
        document.getElementById('variation-strength-value').textContent = fluidRingConfig.variationStrength.toFixed(1);
        
        updateColorInputs();
        updateFluidRingPreview();
        showNotification('Random fluid ring generated!');
      }

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

      function initializeUI() {
        initFluidRing();
        
        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-fluid-ring');
        });

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

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

        document.getElementById('randomize-fluid-ring').addEventListener('click', () => {
          generateRandomFluidRing();
        });

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

        backgroundPicker.addEventListener('input', (e) => {
          const selectedColor = e.target.value;
          previewContainer.style.background = `linear-gradient(135deg, ${selectedColor} 0%, ${selectedColor} 100%)`;
          
          showNotification(`Preview background changed to ${selectedColor}`);
        });

        previewContainer.style.background = 'linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)';

        document.getElementById('reset-colors').addEventListener('click', () => {
          fluidRingConfig.primaryColor = defaultConfig.primaryColor;
          fluidRingConfig.variationStrength = defaultConfig.variationStrength;
          
          document.getElementById('primary-color').value = defaultConfig.primaryColor;
          document.getElementById('variation-strength').value = defaultConfig.variationStrength;
          document.getElementById('variation-strength-value').textContent = defaultConfig.variationStrength;
          
          updateColorInputs();
          updateFluidRingPreview();
          showNotification('Colors reset to default');
        });

        document.getElementById('reset-animation').addEventListener('click', () => {
          fluidRingConfig.speed = defaultConfig.speed;
          fluidRingConfig.radius = defaultConfig.radius;
          fluidRingConfig.width = defaultConfig.width;
          fluidRingConfig.intensity = defaultConfig.intensity;
          
          document.getElementById('animation-speed').value = defaultConfig.speed;
          document.getElementById('ring-radius').value = defaultConfig.radius;
          document.getElementById('ring-width').value = defaultConfig.width;
          document.getElementById('intensity').value = defaultConfig.intensity;
          
          document.getElementById('animation-speed-value').textContent = defaultConfig.speed;
          document.getElementById('ring-radius-value').textContent = defaultConfig.radius;
          document.getElementById('ring-width-value').textContent = defaultConfig.width;
          document.getElementById('intensity-value').textContent = defaultConfig.intensity;
          
          updateFluidRingPreview();
          showNotification('Animation settings reset');
        });

        document.getElementById('reset-advanced').addEventListener('click', () => {
          fluidRingConfig.performanceMode = defaultConfig.performanceMode;
          
          document.getElementById('performance-mode').value = defaultConfig.performanceMode;
          
          updateFluidRingPreview();
          showNotification('Advanced settings reset');
        });

        const colorInput = document.getElementById('primary-color');
        const hexInput = document.getElementById('primary-color-hex');
        const hslInput = document.getElementById('primary-color-hsl');
        
        hslInput.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
        
        colorInput.addEventListener('input', () => {
          const color = colorInput.value;
          hexInput.value = color;
          hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
          hexInput.classList.remove('invalid');
          hslInput.classList.remove('invalid');
          fluidRingConfig.primaryColor = color;
          
          const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
          colorPickerContainer.style.setProperty('--selected-color', color);
          
          updateFluidRingPreview();
        });
        
        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}%)`;
            fluidRingConfig.primaryColor = hex;
            e.target.classList.remove('invalid');
            hslInput.classList.remove('invalid');
            
            const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
            colorPickerContainer.style.setProperty('--selected-color', hex);
            
            updateFluidRingPreview();
          } 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;
              fluidRingConfig.primaryColor = hex;
              e.target.classList.remove('invalid');
              hexInput.classList.remove('invalid');
              
              const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
              colorPickerContainer.style.setProperty('--selected-color', hex);
              
              updateFluidRingPreview();
              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;
                fluidRingConfig.primaryColor = hex;
                e.target.classList.remove('invalid');
                hexInput.classList.remove('invalid');
                updateFluidRingPreview();
                return;
              }
            }
          }
          
          if (!isValidHsl(e.target.value)) {
            e.target.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
            e.target.classList.remove('invalid');
          }
        });

        const rangeInputs = document.querySelectorAll('input[type="range"]');
        rangeInputs.forEach(input => {
          const valueElement = document.getElementById(`${input.id}-value`);
          
          if (valueElement) {
            valueElement.textContent = input.value;
          }
          
          input.addEventListener('input', () => {
            if (valueElement) {
              valueElement.textContent = input.value;
            }
            
            switch (input.id) {
              case 'animation-speed':
                fluidRingConfig.speed = parseFloat(input.value);
                break;
              case 'ring-radius':
                fluidRingConfig.radius = parseFloat(input.value);
                break;
              case 'ring-width':
                fluidRingConfig.width = parseFloat(input.value);
                break;
              case 'intensity':
                fluidRingConfig.intensity = parseFloat(input.value);
                break;
              case 'variation-strength':
                fluidRingConfig.variationStrength = parseFloat(input.value);
                break;
            }
            
            updateFluidRingPreview();
          });
        });

        document.getElementById('performance-mode').addEventListener('change', function() {
          fluidRingConfig.performanceMode = this.value;
          updateFluidRingPreview();
        });

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

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

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

        function loadConfiguration() {
          try {
            const saved = localStorage.getItem('bricksfusion-fluid-ring-config');
            if (saved) {
              const savedConfig = JSON.parse(saved);
              Object.assign(fluidRingConfig, savedConfig);
              
              document.getElementById('animation-speed').value = savedConfig.speed;
              document.getElementById('ring-radius').value = savedConfig.radius;
              document.getElementById('ring-width').value = savedConfig.width;
              document.getElementById('intensity').value = savedConfig.intensity;
              document.getElementById('primary-color').value = savedConfig.primaryColor;
              document.getElementById('variation-strength').value = savedConfig.variationStrength;
              document.getElementById('performance-mode').value = savedConfig.performanceMode;
              
              document.getElementById('animation-speed-value').textContent = savedConfig.speed;
              document.getElementById('ring-radius-value').textContent = savedConfig.radius;
              document.getElementById('ring-width-value').textContent = savedConfig.width;
              document.getElementById('intensity-value').textContent = savedConfig.intensity;
              document.getElementById('variation-strength-value').textContent = savedConfig.variationStrength;
              
              updateColorInputs();
              updateFluidRingPreview();
            }
          } catch (e) {
          }
        }

        const originalUpdateFluidRingPreview = updateFluidRingPreview;
        updateFluidRingPreview = function() {
          originalUpdateFluidRingPreview();
          saveConfiguration();
        };

        loadConfiguration();
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Fluid Ring - Bricksfusion
MEDIUM

Fluid Ring

Creates an animated fluid ring with organic variations using WebGL shaders. Features rotating gradients, smooth distortions, and customizable appearance. Perfect for hero sections, loading states, or adding dynamic circular motion to designs.

Fluid Ring

Watch the animated fluid ring with organic variations.

Animation

Speed 0.1-5.0

Animation speed multiplier. Lower creates slow, meditative motion. Higher makes fast, energetic rotations.

Default: 1.0

Appearance

Radius 0.1-0.8

Size of the ring relative to container. Smaller creates a compact circle, larger fills more space.

Default: 0.35

Width 0.01-0.1

Thickness of the ring stroke. Thin creates delicate lines, thick makes bold rings.

Default: 0.035

Color hex color

Primary color of the ring. Mixed with animated gradient for dynamic color shifts.

Default: #6633cc (purple)

Effects

Intensity 0.1-2.0

Overall brightness and visibility. Lower for subtle effects, higher for prominent rings.

Default: 1.0

Variation 0.0-15.0

Strength of organic distortions. Zero creates perfect circle, higher adds fluid wobbles and waves.

Default: 5.0

Performance

This element uses WebGL with custom fragment shaders for rendering. Calculates ring geometry with smooth antialiasing and applies time-based distortions using sine wave variations. Automatically detects background color and blends seamlessly. Uses ResizeObserver for efficient updates and pauses animation when tab is hidden. GPU-accelerated but lightweight - suitable for most devices.