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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.preview-container {
  height: 400px;
  width: 100%;
  position: relative;
  overflow: hidden;
  border-radius: var(--card-radius);
  background-color: #252525;
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  display: flex;
  align-items: center;
  justify-content: center;
}

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

.image-input-container {
  position: relative;
  margin-bottom: 1rem;
}

.image-preview {
  width: 100%;
  height: 120px;
  border-radius: var(--input-radius);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 2px dashed var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-secondary);
  font-size: var(--text-xs);
  margin-bottom: 0.75rem;
  transition: var(--transition);
  background-color: rgba(30, 30, 30, 0.5);
}

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

.image-preview.has-image {
  border-style: solid;
  border-color: var(--accent);
}

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

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

.sample-images {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
  flex-wrap: wrap;
}

.sample-image {
  width: 60px;
  height: 40px;
  border-radius: 4px;
  background-size: cover;
  background-position: center;
  cursor: pointer;
  border: 2px solid transparent;
  transition: var(--transition);
}

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

.sample-image.active {
  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;
}

@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;
  }
  
  .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">Liquid Image</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-liquid-image
      </div>
      <button class="action-btn primary" id="copy-js" 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">Liquid Image</h1>
      <p class="page-subtitle">Interactive liquid deformation effects for Bricks Builder</p>
    </div>

    <div class="instructions-toggle">
      <div class="instructions-card" id="instructions-card">
        <div class="instructions-header" id="instructions-toggle">
          <div class="instructions-title">
            How to Use & Code Information
          </div>
          <span class="toggle-icon">▼</span>
        </div>
        <div class="instructions-content" id="instructions-content">
          <div class="instructions-grid">
            <div class="how-to-use">
              <ol>
                <li>Customize your liquid image 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 the JavaScript code into the code element</li>
                <li>To add the effect to any container: go to <strong>Section → Style → Attributes</strong>, add <code>data-liquid-image</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="liquid-preview" data-liquid-image>
          <div class="preview-content">Interactive Liquid Preview</div>
        </div>
      </section>

      <section class="controls-section">
        <div class="card">
          <div class="card-heading">
            Background Image
            <div class="card-actions">
              <button class="card-action-btn" id="reset-image" title="Reset Image">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">Image URL</span>
              </div>
              <div class="image-input-container">
                <div class="image-preview" id="image-preview">
                  <span>Enter image URL below or click a sample</span>
                </div>
                <input type="url" id="image-url" placeholder="https://example.com/image.jpg" value="https://images.unsplash.com/photo-1558591710-4b4a1ae0f04d?ixlib=rb-4.0.3&auto=format&fit=crop&w=2000&q=80">
                <div class="sample-images">
                  <div class="sample-image" data-url="https://images.unsplash.com/photo-1558591710-4b4a1ae0f04d?ixlib=rb-4.0.3&auto=format&fit=crop&w=2000&q=80" style="background-image: url('https://images.unsplash.com/photo-1558591710-4b4a1ae0f04d?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80')"></div>
                  <div class="sample-image" data-url="https://images.unsplash.com/photo-1534796636912-3b95b3ab5986?ixlib=rb-4.0.3&auto=format&fit=crop&w=2000&q=80" style="background-image: url('https://images.unsplash.com/photo-1534796636912-3b95b3ab5986?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80')"></div>
                  <div class="sample-image" data-url="https://images.unsplash.com/photo-1557672172-298e090bd0f1?ixlib=rb-4.0.3&auto=format&fit=crop&w=2000&q=80" style="background-image: url('https://images.unsplash.com/photo-1557672172-298e090bd0f1?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80')"></div>
                  <div class="sample-image" data-url="https://images.unsplash.com/photo-1550745165-9bc0b252726f?ixlib=rb-4.0.3&auto=format&fit=crop&w=2000&q=80" style="background-image: url('https://images.unsplash.com/photo-1550745165-9bc0b252726f?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80')"></div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Deformation Settings
            <div class="card-actions">
              <button class="card-action-btn" id="reset-deformation" title="Reset Deformation Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Deformation Intensity
                  <span class="help-tooltip" title="Controls how strong the liquid deformation effect is">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="intensity-value">8.0</span></span>
                  <button class="reset-btn" onclick="resetParameter('deform-intensity', 8.0)">↺</button>
                </div>
              </div>
              <input type="range" id="deform-intensity" min="2.0" max="20.0" step="0.5" value="8.0">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Animation Speed
                  <span class="help-tooltip" title="Controls the speed of the automatic animation">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="speed-value">0.05</span></span>
                  <button class="reset-btn" onclick="resetParameter('animation-speed', 0.05)">↺</button>
                </div>
              </div>
              <input type="range" id="animation-speed" min="0.01" max="0.15" step="0.01" value="0.05">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Hover Sensitivity
                  <span class="help-tooltip" title="Controls how responsive the effect is to mouse movement">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="sensitivity-value">0.02</span></span>
                  <button class="reset-btn" onclick="resetParameter('hover-sensitivity', 0.02)">↺</button>
                </div>
              </div>
              <input type="range" id="hover-sensitivity" min="0.005" max="0.08" step="0.005" value="0.02">
            </div>

            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Smoothness
                  <span class="help-tooltip" title="Controls how smooth the transition between states is">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="smoothness-value">0.08</span></span>
                  <button class="reset-btn" onclick="resetParameter('smoothness', 0.08)">↺</button>
                </div>
              </div>
              <input type="range" id="smoothness" min="0.02" max="0.2" step="0.01" value="0.08">
            </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">
                  Image Scale
                  <span class="help-tooltip" title="Controls the scale of the image within the container">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="scale-value">1.2</span></span>
                  <button class="reset-btn" onclick="resetParameter('image-scale', 1.2)">↺</button>
                </div>
              </div>
              <input type="range" id="image-scale" min="1.0" max="2.0" step="0.1" value="1.2">
            </div>

            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  3D Depth
                  <span class="help-tooltip" title="Controls the depth effect in the deformation">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="depth-value">0.4</span></span>
                  <button class="reset-btn" onclick="resetParameter('depth-effect', 0.4)">↺</button>
                </div>
              </div>
              <input type="range" id="depth-effect" min="0.0" max="1.0" step="0.1" value="0.4">
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let liquidConfig = {
        imageUrl: 'https://images.unsplash.com/photo-1558591710-4b4a1ae0f04d?ixlib=rb-4.0.3&auto=format&fit=crop&w=2000&q=80',
        intensity: 8.0,
        speed: 0.05,
        sensitivity: 0.02,
        smoothness: 0.08,
        scale: 1.2,
        depth: 0.4
      };
      
      const defaultConfig = { ...liquidConfig };
      let previewEffect = null;
      let isThreeJSLoaded = false;

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

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

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

      function generateFullSectionJSON() {
        const sectionId = generateUniqueId();
        const containerId = generateUniqueId();
        const codeId = generateUniqueId();

        const jsCode = generateJavaScriptCode();

        const bricksJSON = {
          "content": [
            {
              "id": sectionId,
              "name": "section",
              "parent": 0,
              "children": [containerId, codeId],
              "settings": {
                "_justifyContent": "center",
                "_background": {
                  "color": {
                    "hex": "#000000"
                  }
                },
                "_attributes": [
                  {
                    "id": "i9wen4",
                    "name": "data-liquid-image"
                  }
                ],
                "_height": "500"
              },
              "label": "Liquid Image Section"
            },
            {
              "id": containerId,
              "name": "container",
              "parent": sectionId,
              "children": [],
              "settings": {
                "_border": {
                  "radius": {
                    "top": "15",
                    "right": "15",
                    "bottom": "15",
                    "left": "15"
                  }
                },
                "_overflow": "hidden",
                "_direction": "row",
                "_justifyContent": "center"
              }
            },
            {
              "id": codeId,
              "name": "code",
              "parent": sectionId,
              "children": [],
              "settings": {
                "javascriptCode": jsCode,
                "executeCode": true,
                "_display": "none"
              },
              "label": "Liquid Image JS"
            }
          ],
          "source": "bricksCopiedElements",
          "sourceUrl": "https://test.bricksfusion.com",
          "version": "2.0.1",
          "globalClasses": [],
          "globalElements": []
        };

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

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

  if (window.LiquidImage && window.LiquidImage.initialized) {
    return;
  }

  const initializedContainers = new WeakSet();
  
  const defaultConfig = {
    imageUrl: '${liquidConfig.imageUrl}',
    intensity: ${liquidConfig.intensity},
    speed: ${liquidConfig.speed},
    sensitivity: ${liquidConfig.sensitivity},
    smoothness: ${liquidConfig.smoothness},
    scale: ${liquidConfig.scale},
    depth: ${liquidConfig.depth}
  };

  function injectCSS() {
    if (document.getElementById('liquid-image-styles')) return;
    
    const style = document.createElement('style');
    style.id = 'liquid-image-styles';
    style.textContent = \`
      [data-liquid-image] {
        position: relative !important;
        overflow: hidden !important;
      }
      
      .liquid-image-canvas {
        position: absolute !important;
        top: 0 !important;
        left: 0 !important;
        width: 100% !important;
        height: 100% !important;
        z-index: 1 !important;
        display: block !important;
        cursor: pointer !important;
        pointer-events: auto !important;
      }

      [data-liquid-image] > *:not(.liquid-image-canvas):not(.liquid-image-loading) {
        position: relative !important;
        z-index: 100 !important;
        pointer-events: auto !important;
      }

      [data-liquid-image] .brxe-element,
      [data-liquid-image] .bricks-element,
      [data-liquid-image] h1,
      [data-liquid-image] h2,
      [data-liquid-image] h3,
      [data-liquid-image] h4,
      [data-liquid-image] h5,
      [data-liquid-image] h6,
      [data-liquid-image] p,
      [data-liquid-image] div:not(.liquid-image-canvas):not(.liquid-image-loading),
      [data-liquid-image] span,
      [data-liquid-image] a,
      [data-liquid-image] button {
        position: relative !important;
        z-index: 100 !important;
        pointer-events: auto !important;
      }

      .liquid-image-loading {
        position: absolute !important;
        top: 50% !important;
        left: 50% !important;
        transform: translate(-50%, -50%) !important;
        color: #666666 !important;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif !important;
        font-size: 14px !important;
        z-index: 10 !important;
      }
    \`;
    document.head.appendChild(style);
  }

  function createLiquidImage(container) {
    if (initializedContainers.has(container)) {
      return;
    }
    initializedContainers.add(container);

    const config = parseConfig(container);
    const imageSrc = config.imageSrc || defaultConfig.imageUrl;

    if (!imageSrc) {
      return;
    }
    
    const loadingDiv = document.createElement('div');
    loadingDiv.className = 'liquid-image-loading';
    loadingDiv.textContent = 'Loading Three.js...';
    container.appendChild(loadingDiv);

    const canvas = document.createElement('canvas');
    canvas.className = 'liquid-image-canvas';
    container.appendChild(canvas);

    function ensureChildrenAreVisible() {
      const existingElements = container.querySelectorAll('*:not(.liquid-image-canvas):not(.liquid-image-loading)');
      existingElements.forEach(element => {
        element.style.position = 'relative';
        element.style.zIndex = '100';
        element.style.pointerEvents = 'auto';
      });
    }

    ensureChildrenAreVisible();

    let scene, camera, renderer, mesh, material;
    let animationFrame = null;
    let isMounted = true;
    let texture = null;

    const mousePos = { x: 0, y: 0 };
    const lerpedMousePos = { x: 0, y: 0 };
    let isHovering = false;
    let hoverProgress = 0;
    let currentStrength = 0;
    let time = 0;

    function loadThreeJS() {
      if (window.THREE) {
        initThreeJS();
        return;
      }

      const script = document.createElement('script');
      script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
      script.onload = () => {
        if (isMounted) {
          initThreeJS();
        }
      };
      script.onerror = () => {
        loadingDiv.textContent = 'Failed to load Three.js';
      };
      document.head.appendChild(script);
    }

    function initThreeJS() {
      if (!window.THREE || !isMounted) return;

      loadingDiv.style.display = 'none';

      scene = new THREE.Scene();
      camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
      camera.position.z = 5;

      renderer = new THREE.WebGLRenderer({ 
        canvas: canvas,
        antialias: true,
        alpha: true
      });

      const loader = new THREE.TextureLoader();
      loader.load(
        imageSrc,
        (loadedTexture) => {
          texture = loadedTexture;
          texture.minFilter = THREE.LinearFilter;
          texture.magFilter = THREE.LinearFilter;
          
          createMesh();
          resizeRenderer();
          animate();
          
          ensureChildrenAreVisible();
        },
        undefined,
        (error) => {
          loadingDiv.textContent = 'Failed to load image';
          loadingDiv.style.display = 'block';
        }
      );
    }

    function createMesh() {
      if (!texture) return;

      const rect = container.getBoundingClientRect();
      const viewport = {
        width: rect.width / 100,
        height: rect.height / 100
      };

      const planeAspect = viewport.width / viewport.height;
      const imageAspect = texture.image.width / texture.image.height;

      let planeWidth, planeHeight;

      if (imageAspect > planeAspect) {
        planeHeight = viewport.height;
        planeWidth = viewport.height * imageAspect;
      } else {
        planeWidth = viewport.width;
        planeHeight = viewport.width / imageAspect;
      }

      if (planeWidth < viewport.width) {
        const scale = viewport.width / planeWidth;
        planeWidth *= scale;
        planeHeight *= scale;
      }
      if (planeHeight < viewport.height) {
        const scale = viewport.height / planeHeight;
        planeWidth *= scale;
        planeHeight *= scale;
      }

      planeWidth *= config.scale;
      planeHeight *= config.scale;

      material = new THREE.ShaderMaterial({
        uniforms: {
          uTexture: { value: texture },
          uMouse: { value: new THREE.Vector2(0, 0) },
          uStrength: { value: 0.0 },
          uHoverProgress: { value: 0.0 },
          uTime: { value: 0 },
          uIntensity: { value: config.intensity },
          uDepth: { value: config.depth }
        },
        vertexShader: \`
          varying vec2 vUv;
          uniform vec2 uMouse;
          uniform float uStrength;
          uniform float uHoverProgress;
          uniform float uIntensity;
          uniform float uDepth;

          void main() {
            vUv = uv;
            vec3 pos = position;

            vec2 mouseInverted = vec2(-uMouse.x, -uMouse.y);
            float dist = distance(uv, vec2(0.5) + mouseInverted * 0.5);

            float deformation = (1.0 - dist) * uStrength * uHoverProgress * (uIntensity / 10.0);

            pos.x += mouseInverted.x * deformation * 0.3;
            pos.y += mouseInverted.y * deformation * 0.3;
            pos.z += deformation * 0.1;

            vec2 centerOffset = uv - 0.5;
            float depthX = centerOffset.x * mouseInverted.x * uHoverProgress * uDepth;
            float depthY = centerOffset.y * mouseInverted.y * uHoverProgress * uDepth;

            pos.z += (depthX + depthY) * 0.5;
            pos.x += depthX * 0.1;
            pos.y += depthY * 0.1;

            gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
          }
        \`,
        fragmentShader: \`
          uniform sampler2D uTexture;
          varying vec2 vUv;

          void main() {
            vec4 color = texture2D(uTexture, vUv);
            gl_FragColor = color;
          }
        \`
      });

      const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight, 32, 32);
      mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);

      container.updateConfig = function(newConfig) {
        if (material) {
          material.uniforms.uIntensity.value = newConfig.intensity;
          material.uniforms.uDepth.value = newConfig.depth;
        }
        config.intensity = newConfig.intensity;
        config.speed = newConfig.speed;
        config.sensitivity = newConfig.sensitivity;
        config.smoothness = newConfig.smoothness;
        config.scale = newConfig.scale;
        config.depth = newConfig.depth;
      };
    }

    function animate() {
      if (!isMounted || !renderer || !material) return;

      animationFrame = requestAnimationFrame(animate);

      time += config.speed;

      material.uniforms.uTime.value = time;

      const targetHoverProgress = isHovering ? 1.0 : 0.0;
      hoverProgress = lerp(hoverProgress, targetHoverProgress, config.sensitivity);
      material.uniforms.uHoverProgress.value = hoverProgress;

      const targetMousePos = isHovering ? mousePos : { x: 0, y: 0 };

      lerpedMousePos.x = lerp(lerpedMousePos.x, targetMousePos.x, config.smoothness);
      lerpedMousePos.y = lerp(lerpedMousePos.y, targetMousePos.y, config.smoothness);

      material.uniforms.uMouse.value.set(lerpedMousePos.x, lerpedMousePos.y);

      const distance = Math.sqrt(lerpedMousePos.x * lerpedMousePos.x + lerpedMousePos.y * lerpedMousePos.y);
      const strength = Math.pow(distance, 2) * config.intensity;
      currentStrength = lerp(currentStrength, strength, 0.02);
      material.uniforms.uStrength.value = currentStrength;

      renderer.render(scene, camera);
    }

    function lerp(start, end, factor) {
      return start + (end - start) * factor;
    }

    function handleMouseMove(event) {
      if (!mesh) return;

      const rect = canvas.getBoundingClientRect();
      const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
      const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;

      const normalizedX = (x + 1) / 2 - 0.5;
      const normalizedY = (y + 1) / 2 - 0.5;

      mousePos.x = normalizedX;
      mousePos.y = normalizedY;
    }

    function handleMouseEnter() {
      isHovering = true;
      canvas.style.cursor = 'pointer';
    }

    function handleMouseLeave() {
      isHovering = false;
      canvas.style.cursor = 'default';
    }

    function resizeRenderer() {
      if (!renderer || !camera) return;

      const rect = container.getBoundingClientRect();
      const width = rect.width;
      const height = rect.height;

      camera.aspect = width / height;
      camera.updateProjectionMatrix();
      renderer.setSize(width, height);
      renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    }

    let resizeTimeout;
    function handleResize() {
      clearTimeout(resizeTimeout);
      resizeTimeout = setTimeout(() => {
        resizeRenderer();
        if (texture && scene) {
          if (mesh) {
            scene.remove(mesh);
            mesh.geometry.dispose();
            mesh.material.dispose();
          }
          createMesh();
        }
      }, 100);
    }

    function handleVisibilityChange() {
      if (document.visibilityState === 'hidden') {
        if (animationFrame) {
          cancelAnimationFrame(animationFrame);
          animationFrame = null;
        }
      } else if (document.visibilityState === 'visible' && isMounted) {
        if (renderer && material) {
          animate();
        }
      }
    }

    canvas.addEventListener('mousemove', handleMouseMove);
    canvas.addEventListener('mouseenter', handleMouseEnter);
    canvas.addEventListener('mouseleave', handleMouseLeave);
    window.addEventListener('resize', handleResize);
    document.addEventListener('visibilitychange', handleVisibilityChange);

    function cleanup() {
      isMounted = false;

      if (animationFrame) {
        cancelAnimationFrame(animationFrame);
      }

      if (resizeTimeout) {
        clearTimeout(resizeTimeout);
      }

      canvas.removeEventListener('mousemove', handleMouseMove);
      canvas.removeEventListener('mouseenter', handleMouseEnter);
      canvas.removeEventListener('mouseleave', handleMouseLeave);
      window.removeEventListener('resize', handleResize);
      document.removeEventListener('visibilitychange', handleVisibilityChange);

      if (renderer) {
        renderer.dispose();
      }

      if (material) {
        material.dispose();
      }

      if (mesh && mesh.geometry) {
        mesh.geometry.dispose();
      }

      if (texture) {
        texture.dispose();
      }
    }

    container._liquidImageCleanup = cleanup;

    loadThreeJS();
  }

  function parseConfig(element) {
    const config = Object.assign({}, defaultConfig);
    
    const imageSrc = element.getAttribute('data-image-src');
    if (imageSrc) {
      config.imageSrc = imageSrc;
    }

    const intensity = element.getAttribute('data-intensity');
    if (intensity) {
      config.intensity = parseFloat(intensity);
    }

    const speed = element.getAttribute('data-speed');
    if (speed) {
      config.speed = parseFloat(speed);
    }

    const sensitivity = element.getAttribute('data-sensitivity');
    if (sensitivity) {
      config.sensitivity = parseFloat(sensitivity);
    }

    const smoothness = element.getAttribute('data-smoothness');
    if (smoothness) {
      config.smoothness = parseFloat(smoothness);
    }

    const scale = element.getAttribute('data-scale');
    if (scale) {
      config.scale = parseFloat(scale);
    }

    const depth = element.getAttribute('data-depth');
    if (depth) {
      config.depth = parseFloat(depth);
    }

    return config;
  }

  function init() {
    injectCSS();
    
    const elements = document.querySelectorAll('[data-liquid-image]');
    
    elements.forEach(element => {
      createLiquidImage(element);
    });
  }

  function safeInit() {
    if (window.LiquidImage && window.LiquidImage.initialized) {
      return;
    }
    
    init();
    
    window.LiquidImage = {
      init: init,
      create: createLiquidImage,
      initialized: true
    };
  }

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

  document.addEventListener('bricks/content_updated', () => {
    const elements = document.querySelectorAll('[data-liquid-image]');
    elements.forEach(element => {
      if (!initializedContainers.has(element)) {
        createLiquidImage(element);
      }
    });
  });

  window.addEventListener('beforeunload', () => {
    document.querySelectorAll('[data-liquid-image]').forEach(container => {
      if (container._liquidImageCleanup) {
        container._liquidImageCleanup();
      }
    });
  });

})();`;
      }

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

      window.resetParameter = function(parameterId, defaultValue) {
        const element = document.getElementById(parameterId);
        if (element) {
          element.value = defaultValue;
          const valueElement = document.getElementById(`${parameterId.replace('deform-intensity', 'intensity').replace('animation-speed', 'speed').replace('hover-sensitivity', 'sensitivity').replace('image-scale', 'scale').replace('depth-effect', 'depth')}-value`);
          if (valueElement) {
            valueElement.textContent = defaultValue;
          }
          
          switch (parameterId) {
            case 'deform-intensity':
              liquidConfig.intensity = defaultValue;
              break;
            case 'animation-speed':
              liquidConfig.speed = defaultValue;
              break;
            case 'hover-sensitivity':
              liquidConfig.sensitivity = defaultValue;
              break;
            case 'smoothness':
              liquidConfig.smoothness = defaultValue;
              break;
            case 'image-scale':
              liquidConfig.scale = defaultValue;
              break;
            case 'depth-effect':
              liquidConfig.depth = defaultValue;
              break;
          }
          
          updatePreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };
      
      function initializeUI() {
        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-liquid-image');
        });

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

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

        document.getElementById('reset-image').addEventListener('click', () => {
          liquidConfig.imageUrl = defaultConfig.imageUrl;
          document.getElementById('image-url').value = defaultConfig.imageUrl;
          updateImagePreview();
          updatePreview();
          showNotification('Image settings reset');
        });

        document.getElementById('reset-deformation').addEventListener('click', () => {
          liquidConfig.intensity = defaultConfig.intensity;
          liquidConfig.speed = defaultConfig.speed;
          liquidConfig.sensitivity = defaultConfig.sensitivity;
          liquidConfig.smoothness = defaultConfig.smoothness;
          
          document.getElementById('deform-intensity').value = defaultConfig.intensity;
          document.getElementById('animation-speed').value = defaultConfig.speed;
          document.getElementById('hover-sensitivity').value = defaultConfig.sensitivity;
          document.getElementById('smoothness').value = defaultConfig.smoothness;
          
          document.getElementById('intensity-value').textContent = defaultConfig.intensity;
          document.getElementById('speed-value').textContent = defaultConfig.speed;
          document.getElementById('sensitivity-value').textContent = defaultConfig.sensitivity;
          document.getElementById('smoothness-value').textContent = defaultConfig.smoothness;
          
          updatePreview();
          showNotification('Deformation settings reset');
        });

        document.getElementById('reset-advanced').addEventListener('click', () => {
          liquidConfig.scale = defaultConfig.scale;
          liquidConfig.depth = defaultConfig.depth;
          
          document.getElementById('image-scale').value = defaultConfig.scale;
          document.getElementById('depth-effect').value = defaultConfig.depth;
          document.getElementById('scale-value').textContent = defaultConfig.scale;
          document.getElementById('depth-value').textContent = defaultConfig.depth;
          
          updatePreview();
          showNotification('Advanced settings reset');
        });

        const imageUrlInput = document.getElementById('image-url');
        imageUrlInput.addEventListener('input', function() {
          liquidConfig.imageUrl = this.value;
          updateImagePreview();
          debouncedUpdatePreview();
        });

        setupSampleImages();
        
        const rangeInputs = document.querySelectorAll('input[type="range"]');
        rangeInputs.forEach(input => {
          let valueElement;
          
          switch (input.id) {
            case 'deform-intensity':
              valueElement = document.getElementById('intensity-value');
              break;
            case 'animation-speed':
              valueElement = document.getElementById('speed-value');
              break;
            case 'hover-sensitivity':
              valueElement = document.getElementById('sensitivity-value');
              break;
            case 'smoothness':
              valueElement = document.getElementById('smoothness-value');
              break;
            case 'image-scale':
              valueElement = document.getElementById('scale-value');
              break;
            case 'depth-effect':
              valueElement = document.getElementById('depth-value');
              break;
          }
          
          if (valueElement) {
            valueElement.textContent = input.value;
          }
          
          input.addEventListener('input', () => {
            if (valueElement) {
              valueElement.textContent = input.value;
            }
            
            switch (input.id) {
              case 'deform-intensity':
                liquidConfig.intensity = parseFloat(input.value);
                break;
              case 'animation-speed':
                liquidConfig.speed = parseFloat(input.value);
                break;
              case 'hover-sensitivity':
                liquidConfig.sensitivity = parseFloat(input.value);
                break;
              case 'smoothness':
                liquidConfig.smoothness = parseFloat(input.value);
                break;
              case 'image-scale':
                liquidConfig.scale = parseFloat(input.value);
                break;
              case 'depth-effect':
                liquidConfig.depth = parseFloat(input.value);
                break;
            }
            
            debouncedUpdatePreview();
          });
        });

        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 copyBtn = document.getElementById('copy-js');
                if (copyBtn && copyBtn.hasAttribute('data-protection-animation')) {
                  copyBtn.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;
            }
          }
        });
        
        initializePreview();
        updateImagePreview();
        
        setTimeout(() => {
          showNotification('BricksFusion Liquid Image Configurator loaded!');
        }, 500);
      }
      
      function setupSampleImages() {
        const sampleImages = document.querySelectorAll('.sample-image');
        sampleImages.forEach(img => {
          img.addEventListener('click', function() {
            const url = this.getAttribute('data-url');
            document.getElementById('image-url').value = url;
            liquidConfig.imageUrl = url;
            updateImagePreview();
            debouncedUpdatePreview();
            
            sampleImages.forEach(i => i.classList.remove('active'));
            this.classList.add('active');
          });
        });
        
        sampleImages[0].classList.add('active');
      }
      
      function updateImagePreview() {
        const imagePreview = document.getElementById('image-preview');
        if (liquidConfig.imageUrl) {
          imagePreview.style.backgroundImage = `url('${liquidConfig.imageUrl}')`;
          imagePreview.classList.add('has-image');
          imagePreview.innerHTML = '';
        } else {
          imagePreview.style.backgroundImage = '';
          imagePreview.classList.remove('has-image');
          imagePreview.innerHTML = '<span>Enter image URL below or click a sample</span>';
        }
      }
      
      let updateTimeout = null;
      function debouncedUpdatePreview() {
        if (updateTimeout) clearTimeout(updateTimeout);
        updateTimeout = setTimeout(() => {
          updatePreview();
        }, 100);
      }
      
      function initializePreview() {
        loadThreeJS().then(() => {
          updatePreview();
        });
      }
      
      function updatePreview() {
        if (previewEffect && previewEffect.updateConfig) {
          previewEffect.updateConfig(liquidConfig);
        } else if (isThreeJSLoaded) {
          createPreviewEffect();
        }
      }
      
      function loadThreeJS() {
        return new Promise((resolve) => {
          if (window.THREE) {
            isThreeJSLoaded = true;
            resolve();
            return;
          }
          
          const script = document.createElement('script');
          script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
          script.onload = () => {
            isThreeJSLoaded = true;
            resolve();
          };
          script.onerror = () => {
            resolve();
          };
          document.head.appendChild(script);
        });
      }
      
      function createPreviewEffect() {
        if (!window.THREE || !liquidConfig.imageUrl) return;
        
        const container = document.getElementById('liquid-preview');
        
        if (previewEffect && previewEffect.cleanup) {
          previewEffect.cleanup();
        }
        
        const canvas = document.createElement('canvas');
        canvas.style.position = 'absolute';
        canvas.style.top = '0';
        canvas.style.left = '0';
        canvas.style.width = '100%';
        canvas.style.height = '100%';
        canvas.style.zIndex = '1';
        
        const existingCanvas = container.querySelector('canvas');
        if (existingCanvas) {
          existingCanvas.remove();
        }
        
        container.appendChild(canvas);
        
        let scene, camera, renderer, mesh, material, texture;
        let animationFrame = null;
        let isMounted = true;
        
        const mousePos = { x: 0, y: 0 };
        const lerpedMousePos = { x: 0, y: 0 };
        let isHovering = false;
        let hoverProgress = 0;
        let currentStrength = 0;
        let time = 0;
        
        scene = new THREE.Scene();
        camera = new THREE.PerspectiveCamera(45, 1, 0.1, 1000);
        camera.position.z = 5;
        
        renderer = new THREE.WebGLRenderer({ 
          canvas: canvas,
          antialias: true,
          alpha: true
        });
        
        const loader = new THREE.TextureLoader();
        loader.load(liquidConfig.imageUrl, (loadedTexture) => {
          texture = loadedTexture;
          texture.minFilter = THREE.LinearFilter;
          texture.magFilter = THREE.LinearFilter;
          
          createMesh();
          resizeRenderer();
          animate();
        });
        
        function createMesh() {
          if (!texture) return;
          
          const rect = container.getBoundingClientRect();
          const viewport = {
            width: rect.width / 100,
            height: rect.height / 100
          };
          
          const planeAspect = viewport.width / viewport.height;
          const imageAspect = texture.image.width / texture.image.height;
          
          let planeWidth, planeHeight;
          
          if (imageAspect > planeAspect) {
            planeHeight = viewport.height;
            planeWidth = viewport.height * imageAspect;
          } else {
            planeWidth = viewport.width;
            planeHeight = viewport.width / imageAspect;
          }
          
          if (planeWidth < viewport.width) {
            const scale = viewport.width / planeWidth;
            planeWidth *= scale;
            planeHeight *= scale;
          }
          if (planeHeight < viewport.height) {
            const scale = viewport.height / planeHeight;
            planeWidth *= scale;
            planeHeight *= scale;
          }
          
          planeWidth *= liquidConfig.scale;
          planeHeight *= liquidConfig.scale;
          
          material = new THREE.ShaderMaterial({
            uniforms: {
              uTexture: { value: texture },
              uMouse: { value: new THREE.Vector2(0, 0) },
              uStrength: { value: 0.0 },
              uHoverProgress: { value: 0.0 },
              uTime: { value: 0 },
              uIntensity: { value: liquidConfig.intensity },
              uDepth: { value: liquidConfig.depth }
            },
            vertexShader: `
              varying vec2 vUv;
              uniform vec2 uMouse;
              uniform float uStrength;
              uniform float uHoverProgress;
              uniform float uIntensity;
              uniform float uDepth;

              void main() {
                vUv = uv;
                vec3 pos = position;

                vec2 mouseInverted = vec2(-uMouse.x, -uMouse.y);
                float dist = distance(uv, vec2(0.5) + mouseInverted * 0.5);

                float deformation = (1.0 - dist) * uStrength * uHoverProgress * (uIntensity / 10.0);

                pos.x += mouseInverted.x * deformation * 0.3;
                pos.y += mouseInverted.y * deformation * 0.3;
                pos.z += deformation * 0.1;

                vec2 centerOffset = uv - 0.5;
                float depthX = centerOffset.x * mouseInverted.x * uHoverProgress * uDepth;
                float depthY = centerOffset.y * mouseInverted.y * uHoverProgress * uDepth;

                pos.z += (depthX + depthY) * 0.5;
                pos.x += depthX * 0.1;
                pos.y += depthY * 0.1;

                gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
              }
            `,
            fragmentShader: `
              uniform sampler2D uTexture;
              varying vec2 vUv;

              void main() {
                vec4 color = texture2D(uTexture, vUv);
                gl_FragColor = color;
              }
            `
          });
          
          const geometry = new THREE.PlaneGeometry(planeWidth, planeHeight, 32, 32);
          
          if (mesh) {
            scene.remove(mesh);
            mesh.geometry.dispose();
            mesh.material.dispose();
          }
          
          mesh = new THREE.Mesh(geometry, material);
          scene.add(mesh);
        }
        
        function animate() {
          if (!isMounted || !renderer || !material) return;
          
          animationFrame = requestAnimationFrame(animate);
          
          time += liquidConfig.speed;
          material.uniforms.uTime.value = time;
          
          const targetHoverProgress = isHovering ? 1.0 : 0.0;
          hoverProgress = lerp(hoverProgress, targetHoverProgress, liquidConfig.sensitivity);
          material.uniforms.uHoverProgress.value = hoverProgress;
          
          const targetMousePos = isHovering ? mousePos : { x: 0, y: 0 };
          lerpedMousePos.x = lerp(lerpedMousePos.x, targetMousePos.x, liquidConfig.smoothness);
          lerpedMousePos.y = lerp(lerpedMousePos.y, targetMousePos.y, liquidConfig.smoothness);
          
          material.uniforms.uMouse.value.set(lerpedMousePos.x, lerpedMousePos.y);
          material.uniforms.uIntensity.value = liquidConfig.intensity;
          material.uniforms.uDepth.value = liquidConfig.depth;
          
          const distance = Math.sqrt(lerpedMousePos.x * lerpedMousePos.x + lerpedMousePos.y * lerpedMousePos.y);
          const strength = Math.pow(distance, 2) * liquidConfig.intensity;
          currentStrength = lerp(currentStrength, strength, 0.02);
          material.uniforms.uStrength.value = currentStrength;
          
          renderer.render(scene, camera);
        }
        
        function lerp(start, end, factor) {
          return start + (end - start) * factor;
        }
        
        function handleMouseMove(event) {
          if (!mesh) return;
          
          const rect = canvas.getBoundingClientRect();
          const x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
          const y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
          
          mousePos.x = (x + 1) / 2 - 0.5;
          mousePos.y = (y + 1) / 2 - 0.5;
        }
        
        function handleMouseEnter() {
          isHovering = true;
        }
        
        function handleMouseLeave() {
          isHovering = false;
        }
        
        function resizeRenderer() {
          if (!renderer || !camera) return;
          
          const rect = container.getBoundingClientRect();
          const width = rect.width;
          const height = rect.height;
          
          camera.aspect = width / height;
          camera.updateProjectionMatrix();
          renderer.setSize(width, height);
        }
        
        function handleResize() {
          resizeRenderer();
          if (texture && scene) {
            createMesh();
          }
        }
        
        canvas.addEventListener('mousemove', handleMouseMove);
        canvas.addEventListener('mouseenter', handleMouseEnter);
        canvas.addEventListener('mouseleave', handleMouseLeave);
        window.addEventListener('resize', handleResize);
        
        function cleanup() {
          isMounted = false;
          
          if (animationFrame) {
            cancelAnimationFrame(animationFrame);
          }
          
          canvas.removeEventListener('mousemove', handleMouseMove);
          canvas.removeEventListener('mouseenter', handleMouseEnter);
          canvas.removeEventListener('mouseleave', handleMouseLeave);
          window.removeEventListener('resize', handleResize);
          
          if (renderer) renderer.dispose();
          if (material) material.dispose();
          if (mesh && mesh.geometry) mesh.geometry.dispose();
          if (texture) texture.dispose();
        }
        
        previewEffect = {
          cleanup: cleanup,
          updateConfig: function(newConfig) {
            Object.assign(liquidConfig, newConfig);
            
            if (material) {
              material.uniforms.uIntensity.value = newConfig.intensity;
              material.uniforms.uDepth.value = newConfig.depth;
            }
            
            if (newConfig.imageUrl !== texture?.image?.currentSrc) {
              const newLoader = new THREE.TextureLoader();
              newLoader.load(newConfig.imageUrl, (newTexture) => {
                newTexture.minFilter = THREE.LinearFilter;
                newTexture.magFilter = THREE.LinearFilter;
                
                if (texture) texture.dispose();
                texture = newTexture;
                if (material) material.uniforms.uTexture.value = texture;
                
                createMesh();
              });
            } else {
              if (texture && scene) {
                createMesh();
              }
            }
          }
        };
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Liquid Image - Bricksfusion
HEAVY

Liquid Image

Creates fluid deformation effect on images using WebGL shaders. Image morphs and warps based on mouse position with smooth physics simulation. Uses custom GLSL vertex shaders for 3D displacement with depth parallax. Features adjustable intensity, smoothness, and scale. Perfect for hero images, portfolio showcases, or adding interactive visual interest to any image.

Liquid Image

Hover over the image to see the liquid deformation.

Image

Image URL URL string

Source image for liquid effect. Automatically covers container maintaining aspect ratio.

Required

Scale 1.0-1.5

Image zoom level. Higher creates closer crop with more room for deformation without edge artifacts.

Default: 1.2

Deformation

Intensity 1.0-20.0

Strength of mesh deformation. Higher creates more dramatic warping and displacement.

Default: 8.0

Smoothness 0.01-0.2

Mouse movement interpolation speed. Lower is smoother but slower, higher is more responsive.

Default: 0.08

Sensitivity 0.01-0.1

Hover transition speed. Lower fades in/out slower, higher snaps more quickly.

Default: 0.02

Animation

Speed 0.01-0.2

Time increment per frame. Affects overall animation tempo and fluidity.

Default: 0.05

3D Effect

Depth 0.1-1.0

Depth parallax intensity. Creates 3D layering effect where edges move differently than center.

Default: 0.4

Performance

This element uses WebGL with Three.js r128 for GPU-accelerated rendering. Creates custom GLSL vertex shader for mesh deformation with 32x32 subdivided plane geometry. Implements smooth interpolation using lerp algorithm with distance-based displacement calculations. Features continuous requestAnimationFrame loop for 60fps animation. Uses PerspectiveCamera with texture mapping. Very resource intensive - limit to 1 instance per page. Not recommended for mobile devices or low-end hardware.