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&family=Roboto+Flex:wght@100..900&display=swap" rel="stylesheet">

  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Variable Proximity Text Effect Configurator - BricksFusion</title>
  <style>
    :root {
  --background: #000;
  --card-bg: #1e1e1e;
  --card-bg-hover: #252525;
  --text-primary: #f2f2f7;
  --text-secondary: #8e8e93;
  --accent: #ef6013;
  --accent-hover: #c64c0c;
  --border: #2c2c2e;
  --shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  --track: #2c2c2e;
  --thumb: #ef6013;
  --card-radius: 16px;
  --input-radius: 8px;
  --button-radius: 12px;
  --transition: all 0.25s ease;
  --font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --action-bar-height: 70px;
  --success: #28a745;
  --warning: #ffc107;
  --danger: #dc3545;
  
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.preview-text {
  font-family: 'Roboto Flex', sans-serif;
  text-align: center;
  font-size: 3rem;
  color: var(--text-primary);
  transition: color 0.3s ease;
  user-select: none;
}

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

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

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

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

.card-content {
  padding: 1.5rem;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.axis-preview {
  font-family: 'Roboto Flex', sans-serif;
  font-size: 1.2rem;
  text-align: center;
  padding: 0.5rem;
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: var(--input-radius);
  margin-bottom: 10px;
  color: var(--text-primary);
}

.axis-input-group {
  display: flex;
  gap: 5px;
  align-items: center;
  margin-bottom: 10px;
}

.axis-label {
  flex: 0 0 40px;
  font-size: var(--text-xs);
  color: var(--text-secondary);
}

.axis-input {
  flex-grow: 1;
}

.row {
  display: flex;
  gap: 10px;
}

.col-6 {
  flex: 0 0 calc(50% - 5px);
}

.subtle-label {
  font-size: var(--text-xs);
  color: var(--text-secondary);
  margin-top: 5px;
  margin-bottom: 0;
  text-align: center;
}

.preview-char {
  display: inline-block;
  transition: font-variation-settings 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

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

@media (max-width: 768px) {
  .action-bar {
    flex-direction: column;
    height: auto;
    min-height: var(--action-bar-height);
    padding: 0.75rem;
  }
  
  .breadcrumb {
    order: 1;
    width: 100%;
  }
  
  .action-buttons {
    order: 2;
    width: 100%;
    justify-content: center;
    flex-wrap: wrap;
  }
  
  body {
    padding-bottom: calc(var(--action-bar-height) + 20px);
  }
  
  .notification {
    bottom: calc(var(--action-bar-height) + 2rem);
    max-width: 280px;
    transform: translate(-50%, 250px);
  }
  
  .notification.show {
    transform: translate(-50%, 0);
    opacity: 1;
  }
  
  .preview-container {
    min-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;
  }

  .axis-input-group {
    flex-direction: column;
    align-items: stretch;
  }

  .axis-label {
    flex: none;
    text-align: left;
  }
}

@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);
}
  </style>
</head>
<body>
  <div class="action-bar">
    <nav class="breadcrumb">
      <a href="https://bricksfusion.com" class="breadcrumb-item">Home</a>
      <span class="breadcrumb-separator">›</span>
      <a href="https://bricksfusion.com/animated-text/" class="breadcrumb-item">Animated text</a>
      <span class="breadcrumb-separator">›</span>
      <span class="breadcrumb-item active">Variable Proximity</span>
    </nav>
    
    <div class="action-buttons">
      <div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
        data-variable-proximity
      </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">Variable Proximity</h1>
      <p class="page-subtitle">Interactive text effects for Bricks Builder</p>
    </div>

    <div class="instructions-toggle">
      <div class="instructions-card" id="instructions-card">
        <div class="instructions-header" id="instructions-toggle">
          <div class="instructions-title">
            How to Use & Code Information
          </div>
          <span class="toggle-icon">▼</span>
        </div>
        <div class="instructions-content" id="instructions-content">
          <div class="instructions-grid">
            <div class="how-to-use">
              <ol>
                <li>Customize your variable proximity text effect using the controls below</li>
                <li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
                <li>In Bricks Builder, add a <strong>Code</strong> element</li>
                <li>Paste or upload the JavaScript code</li>
                <li>To add the effect to any text element: go to <strong>Section → Style → Attributes</strong>, add <code>data-variable-proximity</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="preview-container" data-variable-proximity="true">
          <div id="preview-text" class="preview-text">HOVER ME</div>
        </div>
      </section>

      <section class="controls-section">
        <div class="card">
          <div class="card-heading">
            General Settings
            <div class="card-actions">
              <button class="card-action-btn" id="reset-general" title="Reset General Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Sample Text
                  <span class="help-tooltip" title="Text to display in preview">ℹ</span>
                </span>
              </div>
              <input type="text" id="sample-text" value="HOVER ME" placeholder="Enter sample text...">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Proximity Radius
                  <span class="help-tooltip" title="Distance from mouse to affect letters">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="radius-value">100</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('radius', 100)">↺</button>
                </div>
              </div>
              <input type="range" id="radius" min="20" max="300" step="1" value="100">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Falloff Type
                  <span class="help-tooltip" title="How the effect fades with distance">ℹ</span>
                </span>
              </div>
              <select id="falloff-type">
                <option value="linear">Linear</option>
                <option value="exponential">Exponential</option>
                <option value="gaussian">Gaussian</option>
              </select>
            </div>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Font Variation Settings
            <div class="card-actions">
              <button class="card-action-btn" id="reset-variations" title="Reset Font Variations">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="row">
                <div class="col-6">
                  <div class="axis-preview" id="default-preview">Default</div>
                  <p class="subtle-label">Default State</p>
                </div>
                <div class="col-6">
                  <div class="axis-preview" id="hover-preview">Hover</div>
                  <p class="subtle-label">Hover State</p>
                </div>
              </div>
              
              <div class="control-label" style="margin-top: 1rem;">
                <span class="label-text">Weight (wght)</span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">From</span>
                <input type="range" class="axis-input" id="weight-from" min="100" max="900" step="1" value="400">
                <span class="value-text"><span id="weight-from-value">400</span></span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">To</span>
                <input type="range" class="axis-input" id="weight-to" min="100" max="900" step="1" value="700">
                <span class="value-text"><span id="weight-to-value">700</span></span>
              </div>
              
              <div class="control-label" style="margin-top: 1rem;">
                <span class="label-text">Width (wdth)</span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">From</span>
                <input type="range" class="axis-input" id="width-from" min="25" max="150" step="1" value="100">
                <span class="value-text"><span id="width-from-value">100</span></span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">To</span>
                <input type="range" class="axis-input" id="width-to" min="25" max="150" step="1" value="100">
                <span class="value-text"><span id="width-to-value">100</span></span>
              </div>
              
              <div class="control-label" style="margin-top: 1rem;">
                <span class="label-text">Optical Size (opsz)</span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">From</span>
                <input type="range" class="axis-input" id="opsz-from" min="8" max="144" step="1" value="14">
                <span class="value-text"><span id="opsz-from-value">14</span></span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">To</span>
                <input type="range" class="axis-input" id="opsz-to" min="8" max="144" step="1" value="72">
                <span class="value-text"><span id="opsz-to-value">72</span></span>
              </div>
              
              <div class="control-label" style="margin-top: 1rem;">
                <span class="label-text">Grade (GRAD)</span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">From</span>
                <input type="range" class="axis-input" id="grade-from" min="-200" max="150" step="1" value="0">
                <span class="value-text"><span id="grade-from-value">0</span></span>
              </div>
              <div class="axis-input-group">
                <span class="axis-label">To</span>
                <input type="range" class="axis-input" id="grade-to" min="-200" max="150" step="1" value="150">
                <span class="value-text"><span id="grade-to-value">150</span></span>
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let proximityConfig = {
        sampleText: 'HOVER ME',
        radius: 100,
        falloff: 'linear',
        fontVariations: {
          weight: { from: 400, to: 700 },
          width: { from: 100, to: 100 },
          opsz: { from: 14, to: 72 },
          grade: { from: 0, to: 150 }
        }
      };

      const defaultConfig = { 
        sampleText: 'HOVER ME',
        radius: 100,
        falloff: 'linear',
        fontVariations: {
          weight: { from: 400, to: 700 },
          width: { from: 100, to: 100 },
          opsz: { from: 14, to: 72 },
          grade: { from: 0, to: 150 }
        }
      };

      let previewText = null;
      let previewContainer = null;
      let letterRefs = [];
      let isMouseInPreview = false;
      let mousePosition = { x: 0, y: 0 };
      let animationFrameId = null;

      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 divId = generateUniqueId();
  const textId = 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": {
          "_justifyContent": "center",
          "_background": {
            "color": {
              "hex": "#ffffff"
            }
          },
          "_height": "500"
        }
      },
      {
        "id": containerId,
        "name": "container",
        "parent": sectionId,
        "children": [divId],
        "settings": {
          "_direction": "row",
          "_alignSelf": "center",
          "_justifyContent": "center",
          "_alignItems": "center"
        }
      },
      {
        "id": divId,
        "name": "div",
        "parent": containerId,
        "children": [textId],
        "settings": {}
      },
      {
        "id": textId,
        "name": "text-basic",
        "parent": divId,
        "children": [],
        "settings": {
          "text": "Proximity",
          "tag": "p",
          "_typography": {
            "font-size": "50",
            "color": {
              "hex": "#000000"
            }
          },
          "_attributes": [
            {
              "id": "wpnjqm",
              "name": "data-variable-proximity"
            }
          ]
        },
        "label": "Proximity Text"
      },
      {
        "id": codeId,
        "name": "code",
        "parent": sectionId,
        "children": [],
        "settings": {
          "javascriptCode": jsCode,
          "executeCode": true,
          "_display": "none"
        },
        "label": "Proximity 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 VariableProximityTextEffect = {
    instances: [],
    config: {
      radius: ${proximityConfig.radius},
      falloff: "${proximityConfig.falloff}",
      fontVariations: {
        weight: { from: ${proximityConfig.fontVariations.weight.from}, to: ${proximityConfig.fontVariations.weight.to} },
        width: { from: ${proximityConfig.fontVariations.width.from}, to: ${proximityConfig.fontVariations.width.to} },
        opsz: { from: ${proximityConfig.fontVariations.opsz.from}, to: ${proximityConfig.fontVariations.opsz.to} },
        grade: { from: ${proximityConfig.fontVariations.grade.from}, to: ${proximityConfig.fontVariations.grade.to} }
      }
    },
    init: function(options = {}) {
      const defaultOptions = {
        selector: '[data-variable-proximity]',
        radiusAttr: 'data-radius'
      };
      const config = { ...defaultOptions, ...options };

      const initInstances = () => {
        document.querySelectorAll(config.selector).forEach(element => {
          if (!element.hasAttribute('data-proximity-initialized')) {
            this.createInstance(element, config);
            element.setAttribute('data-proximity-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 elConfig = {
        radius: parseInt(element.getAttribute(config.radiusAttr) || this.config.radius, 10),
        falloff: element.getAttribute('data-falloff') || this.config.falloff,
        fontVariations: {
          weight: {
            from: parseInt(element.getAttribute('data-weight-from') || this.config.fontVariations.weight.from, 10),
            to: parseInt(element.getAttribute('data-weight-to') || this.config.fontVariations.weight.to, 10)
          },
          width: {
            from: parseInt(element.getAttribute('data-width-from') || this.config.fontVariations.width.from, 10),
            to: parseInt(element.getAttribute('data-width-to') || this.config.fontVariations.width.to, 10)
          },
          opsz: {
            from: parseInt(element.getAttribute('data-opsz-from') || this.config.fontVariations.opsz.from, 10),
            to: parseInt(element.getAttribute('data-opsz-to') || this.config.fontVariations.opsz.to, 10)
          },
          grade: {
            from: parseInt(element.getAttribute('data-grade-from') || this.config.fontVariations.grade.from, 10),
            to: parseInt(element.getAttribute('data-grade-to') || this.config.fontVariations.grade.to, 10)
          }
        }
      };

      const originalText = element.textContent.trim();
      element.setAttribute('data-original-text', originalText);
      
      element.innerHTML = '';
      
      const letterSpans = [];
      const words = originalText.split(' ');
      
      words.forEach((word, wordIndex) => {
        const wordSpan = document.createElement('span');
        wordSpan.style.display = 'inline-block';
        wordSpan.style.whiteSpace = 'nowrap';
        
        word.split('').forEach((letter) => {
          const letterSpan = document.createElement('span');
          letterSpan.className = 'variable-proximity-char';
          letterSpan.style.display = 'inline-block';
          letterSpan.textContent = letter;
          letterSpan.style.fontVariationSettings = this.createVariationSettingsString(
            elConfig.fontVariations.weight.from,
            elConfig.fontVariations.width.from,
            elConfig.fontVariations.opsz.from,
            elConfig.fontVariations.grade.from
          );
          letterSpan.style.transition = 'font-variation-settings 0.3s cubic-bezier(0.22, 1, 0.36, 1)';
          
          wordSpan.appendChild(letterSpan);
          letterSpans.push(letterSpan);
        });
        
        element.appendChild(wordSpan);
        
        if (wordIndex < words.length - 1) {
          const spaceSpan = document.createElement('span');
          spaceSpan.style.display = 'inline-block';
          spaceSpan.innerHTML = ' ';
          element.appendChild(spaceSpan);
        }
      });
      
      const srSpan = document.createElement('span');
      srSpan.className = 'sr-only';
      srSpan.style.position = 'absolute';
      srSpan.style.width = '1px';
      srSpan.style.height = '1px';
      srSpan.style.padding = '0';
      srSpan.style.margin = '-1px';
      srSpan.style.overflow = 'hidden';
      srSpan.style.clip = 'rect(0, 0, 0, 0)';
      srSpan.style.whiteSpace = 'nowrap';
      srSpan.style.borderWidth = '0';
      srSpan.textContent = originalText;
      element.appendChild(srSpan);

      const instance = {
        element,
        letterSpans: letterSpans,
        isMouseOver: false,
        mousePosition: { x: 0, y: 0 },
        animationFrame: null,
        config: elConfig,
        
        updateConfig: function(newConfig) {
          Object.assign(this.config, newConfig);
          this.resetToDefault();
        },

        resetToDefault: function() {
          this.letterSpans.forEach(letterSpan => {
            letterSpan.style.fontVariationSettings = VariableProximityTextEffect.createVariationSettingsString(
              this.config.fontVariations.weight.from,
              this.config.fontVariations.width.from,
              this.config.fontVariations.opsz.from,
              this.config.fontVariations.grade.from
            );
          });
        }
      };

      function handleMouseMove(e) {
        if (!instance.isMouseOver) return;
        
        const rect = element.getBoundingClientRect();
        instance.mousePosition.x = e.clientX - rect.left;
        instance.mousePosition.y = e.clientY - rect.top;
      }

      function handleTouchMove(e) {
        if (!instance.isMouseOver) return;
        
        const touch = e.touches[0];
        const rect = element.getBoundingClientRect();
        instance.mousePosition.x = touch.clientX - rect.left;
        instance.mousePosition.y = touch.clientY - rect.top;
        
        e.preventDefault();
      }

      function handleMouseEnter() {
        instance.isMouseOver = true;
        
        if (!instance.animationFrame) {
          instance.animationFrame = requestAnimationFrame(animationLoop);
        }
      }

      function handleMouseLeave() {
        instance.isMouseOver = false;
        
        instance.resetToDefault();
        
        if (instance.animationFrame) {
          cancelAnimationFrame(instance.animationFrame);
          instance.animationFrame = null;
        }
      }

      function animationLoop() {
        if (instance.isMouseOver) {
          const containerRect = element.getBoundingClientRect();
          
          instance.letterSpans.forEach(letterSpan => {
            const letterRect = letterSpan.getBoundingClientRect();
            const letterCenterX = letterRect.left + letterRect.width / 2 - containerRect.left;
            const letterCenterY = letterRect.top + letterRect.height / 2 - containerRect.top;
            
            const distance = VariableProximityTextEffect.calculateDistance(
              instance.mousePosition.x,
              instance.mousePosition.y,
              letterCenterX,
              letterCenterY
            );
            
            if (distance >= instance.config.radius) {
              letterSpan.style.fontVariationSettings = VariableProximityTextEffect.createVariationSettingsString(
                instance.config.fontVariations.weight.from,
                instance.config.fontVariations.width.from,
                instance.config.fontVariations.opsz.from,
                instance.config.fontVariations.grade.from
              );
              return;
            }
            
            const falloffValue = VariableProximityTextEffect.calculateFalloff(distance, instance.config.radius, instance.config.falloff);
            
            const weight = Math.round(instance.config.fontVariations.weight.from + 
              (instance.config.fontVariations.weight.to - instance.config.fontVariations.weight.from) * falloffValue);
            
            const width = Math.round(instance.config.fontVariations.width.from + 
              (instance.config.fontVariations.width.to - instance.config.fontVariations.width.from) * falloffValue);
            
            const opsz = Math.round(instance.config.fontVariations.opsz.from + 
              (instance.config.fontVariations.opsz.to - instance.config.fontVariations.opsz.from) * falloffValue);
            
            const grade = Math.round(instance.config.fontVariations.grade.from + 
              (instance.config.fontVariations.grade.to - instance.config.fontVariations.grade.from) * falloffValue);
            
            letterSpan.style.fontVariationSettings = VariableProximityTextEffect.createVariationSettingsString(
              weight,
              width,
              opsz,
              grade
            );
          });
          
          instance.animationFrame = requestAnimationFrame(animationLoop);
        }
      }

      element.addEventListener('mousemove', handleMouseMove);
      element.addEventListener('mouseenter', handleMouseEnter);
      element.addEventListener('mouseleave', handleMouseLeave);
      element.addEventListener('touchmove', handleTouchMove);
      element.addEventListener('touchstart', handleMouseEnter);
      element.addEventListener('touchend', handleMouseLeave);

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

    calculateDistance: function(x1, y1, x2, y2) {
      return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
    },

    calculateFalloff: function(distance, radius, falloffType) {
      const norm = Math.min(Math.max(1 - distance / radius, 0), 1);
      
      switch (falloffType) {
        case 'exponential': return norm ** 2;
        case 'gaussian': return Math.exp(-((distance / (radius / 2)) ** 2) / 2);
        case 'linear':
        default: return norm;
      }
    },

    createVariationSettingsString: function(weight, width, opsz, grade) {
      return \`'wght' \${weight}, 'wdth' \${width}, 'opsz' \${opsz}, 'GRAD' \${grade}\`;
    },

    updateInstance: function(element, newConfig) {
      const instance = this.instances.find(inst => inst.element === element);
      if (instance) {
        instance.updateConfig(newConfig);
      }
    }
  };

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

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

      function copyJsToClipboard() {
        const jsCode = generateJavaScriptCode();
        copyToClipboard(jsCode);
      }

      function copyFullSectionToClipboard() {
        const sectionJSON = generateFullSectionJSON();
        copyToClipboard(sectionJSON || '{}');
      }

      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 'radius':
              proximityConfig.radius = defaultValue;
              break;
          }
          
          updatePreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function createVariationSettingsString(weight, width, opsz, grade) {
        return `'wght' ${weight}, 'wdth' ${width}, 'opsz' ${opsz}, 'GRAD' ${grade}`;
      }

      function updateAxisPreviews() {
        const defaultPreview = document.getElementById('default-preview');
        const hoverPreview = document.getElementById('hover-preview');
        
        const fromVariationSettings = createVariationSettingsString(
          proximityConfig.fontVariations.weight.from,
          proximityConfig.fontVariations.width.from,
          proximityConfig.fontVariations.opsz.from,
          proximityConfig.fontVariations.grade.from
        );
        
        const toVariationSettings = createVariationSettingsString(
          proximityConfig.fontVariations.weight.to,
          proximityConfig.fontVariations.width.to,
          proximityConfig.fontVariations.opsz.to,
          proximityConfig.fontVariations.grade.to
        );
        
        defaultPreview.style.fontVariationSettings = fromVariationSettings;
        hoverPreview.style.fontVariationSettings = toVariationSettings;
      }

      function updatePreview() {
        if (!previewText) return;
        
        previewText.innerHTML = '';
        letterRefs = [];
        
        const words = proximityConfig.sampleText.split(' ');
        
        words.forEach((word, wordIndex) => {
          const wordSpan = document.createElement('span');
          wordSpan.style.display = 'inline-block';
          wordSpan.style.whiteSpace = 'nowrap';
          
          word.split('').forEach((letter) => {
            const letterSpan = document.createElement('span');
            letterSpan.className = 'preview-char';
            letterSpan.textContent = letter;
            letterSpan.style.fontVariationSettings = createVariationSettingsString(
              proximityConfig.fontVariations.weight.from,
              proximityConfig.fontVariations.width.from,
              proximityConfig.fontVariations.opsz.from,
              proximityConfig.fontVariations.grade.from
            );
            
            wordSpan.appendChild(letterSpan);
            letterRefs.push(letterSpan);
          });
          
          previewText.appendChild(wordSpan);
          
          if (wordIndex < words.length - 1) {
            const spaceSpan = document.createElement('span');
            spaceSpan.style.display = 'inline-block';
            spaceSpan.innerHTML = ' ';
            previewText.appendChild(spaceSpan);
          }
        });
        
        const srSpan = document.createElement('span');
        srSpan.className = 'sr-only';
        srSpan.textContent = proximityConfig.sampleText;
        previewText.appendChild(srSpan);
      }

      function calculateDistance(x1, y1, x2, y2) {
        return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
      }

      function calculateFalloff(distance) {
        const radius = proximityConfig.radius;
        const norm = Math.min(Math.max(1 - distance / radius, 0), 1);
        
        switch (proximityConfig.falloff) {
          case 'exponential': return norm ** 2;
          case 'gaussian': return Math.exp(-((distance / (radius / 2)) ** 2) / 2);
          case 'linear':
          default: return norm;
        }
      }

      function handleMouseMove(e) {
        if (!isMouseInPreview) return;
        
        const rect = previewContainer.getBoundingClientRect();
        mousePosition.x = e.clientX - rect.left;
        mousePosition.y = e.clientY - rect.top;
      }

      function handleTouchMove(e) {
        if (!isMouseInPreview) return;
        
        const touch = e.touches[0];
        const rect = previewContainer.getBoundingClientRect();
        mousePosition.x = touch.clientX - rect.left;
        mousePosition.y = touch.clientY - rect.top;
        
        e.preventDefault();
      }

      function handleTouchStart(e) {
        isMouseInPreview = true;
        const touch = e.touches[0];
        const rect = previewContainer.getBoundingClientRect();
        mousePosition.x = touch.clientX - rect.left;
        mousePosition.y = touch.clientY - rect.top;
      }

      function handleMouseEnter() {
        isMouseInPreview = true;
      }

      function handleMouseLeave() {
        isMouseInPreview = false;
        
        letterRefs.forEach(letterSpan => {
          letterSpan.style.fontVariationSettings = createVariationSettingsString(
            proximityConfig.fontVariations.weight.from,
            proximityConfig.fontVariations.width.from,
            proximityConfig.fontVariations.opsz.from,
            proximityConfig.fontVariations.grade.from
          );
        });
      }

      function startAnimation() {
        animationFrameId = requestAnimationFrame(animationLoop);
      }

      function animationLoop() {
        if (isMouseInPreview && letterRefs.length > 0) {
          const containerRect = previewContainer.getBoundingClientRect();
          
          letterRefs.forEach(letterSpan => {
            const letterRect = letterSpan.getBoundingClientRect();
            const letterCenterX = letterRect.left + letterRect.width / 2 - containerRect.left;
            const letterCenterY = letterRect.top + letterRect.height / 2 - containerRect.top;
            
            const distance = calculateDistance(
              mousePosition.x,
              mousePosition.y,
              letterCenterX,
              letterCenterY
            );
            
            if (distance >= proximityConfig.radius) {
              letterSpan.style.fontVariationSettings = createVariationSettingsString(
                proximityConfig.fontVariations.weight.from,
                proximityConfig.fontVariations.width.from,
                proximityConfig.fontVariations.opsz.from,
                proximityConfig.fontVariations.grade.from
              );
              return;
            }
            
            const falloffValue = calculateFalloff(distance);
            
            const weight = proximityConfig.fontVariations.weight.from + 
              (proximityConfig.fontVariations.weight.to - proximityConfig.fontVariations.weight.from) * falloffValue;
            
            const width = proximityConfig.fontVariations.width.from + 
              (proximityConfig.fontVariations.width.to - proximityConfig.fontVariations.width.from) * falloffValue;
            
            const opsz = proximityConfig.fontVariations.opsz.from + 
              (proximityConfig.fontVariations.opsz.to - proximityConfig.fontVariations.opsz.from) * falloffValue;
            
            const grade = proximityConfig.fontVariations.grade.from + 
              (proximityConfig.fontVariations.grade.to - proximityConfig.fontVariations.grade.from) * falloffValue;
            
            letterSpan.style.fontVariationSettings = createVariationSettingsString(
              Math.round(weight),
              Math.round(width),
              Math.round(opsz),
              Math.round(grade)
            );
          });
        }
        
        animationFrameId = requestAnimationFrame(animationLoop);
      }

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

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

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

        document.getElementById('reset-general').addEventListener('click', () => {
          proximityConfig.sampleText = defaultConfig.sampleText;
          proximityConfig.radius = defaultConfig.radius;
          proximityConfig.falloff = defaultConfig.falloff;
          
          document.getElementById('sample-text').value = defaultConfig.sampleText;
          document.getElementById('radius').value = defaultConfig.radius;
          document.getElementById('radius-value').textContent = defaultConfig.radius;
          document.getElementById('falloff-type').value = defaultConfig.falloff;
          
          updatePreview();
          updateAxisPreviews();
          showNotification('General settings reset');
        });

        document.getElementById('reset-variations').addEventListener('click', () => {
          proximityConfig.fontVariations = { ...defaultConfig.fontVariations };
          
          document.getElementById('weight-from').value = defaultConfig.fontVariations.weight.from;
          document.getElementById('weight-from-value').textContent = defaultConfig.fontVariations.weight.from;
          document.getElementById('weight-to').value = defaultConfig.fontVariations.weight.to;
          document.getElementById('weight-to-value').textContent = defaultConfig.fontVariations.weight.to;
          
          document.getElementById('width-from').value = defaultConfig.fontVariations.width.from;
          document.getElementById('width-from-value').textContent = defaultConfig.fontVariations.width.from;
          document.getElementById('width-to').value = defaultConfig.fontVariations.width.to;
          document.getElementById('width-to-value').textContent = defaultConfig.fontVariations.width.to;
          
          document.getElementById('opsz-from').value = defaultConfig.fontVariations.opsz.from;
          document.getElementById('opsz-from-value').textContent = defaultConfig.fontVariations.opsz.from;
          document.getElementById('opsz-to').value = defaultConfig.fontVariations.opsz.to;
          document.getElementById('opsz-to-value').textContent = defaultConfig.fontVariations.opsz.to;
          
          document.getElementById('grade-from').value = defaultConfig.fontVariations.grade.from;
          document.getElementById('grade-from-value').textContent = defaultConfig.fontVariations.grade.from;
          document.getElementById('grade-to').value = defaultConfig.fontVariations.grade.to;
          document.getElementById('grade-to-value').textContent = defaultConfig.fontVariations.grade.to;
          
          updatePreview();
          updateAxisPreviews();
          showNotification('Font variations reset');
        });

        document.getElementById('sample-text').addEventListener('input', function() {
          proximityConfig.sampleText = this.value;
          updatePreview();
          saveConfiguration();
        });
        
        document.getElementById('radius').addEventListener('input', function() {
          proximityConfig.radius = parseInt(this.value, 10);
          document.getElementById('radius-value').textContent = this.value;
          saveConfiguration();
        });
        
        document.getElementById('falloff-type').addEventListener('change', function() {
          proximityConfig.falloff = this.value;
          saveConfiguration();
        });

        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;
            }
            
            const value = input.id.includes('from') || input.id.includes('to') ? parseInt(input.value) : parseFloat(input.value);
            
            if (input.id.includes('weight')) {
              if (input.id.includes('from')) {
                proximityConfig.fontVariations.weight.from = value;
              } else if (input.id.includes('to')) {
                proximityConfig.fontVariations.weight.to = value;
              }
            } else if (input.id.includes('width')) {
              if (input.id.includes('from')) {
                proximityConfig.fontVariations.width.from = value;
              } else if (input.id.includes('to')) {
                proximityConfig.fontVariations.width.to = value;
              }
            } else if (input.id.includes('opsz')) {
              if (input.id.includes('from')) {
                proximityConfig.fontVariations.opsz.from = value;
              } else if (input.id.includes('to')) {
                proximityConfig.fontVariations.opsz.to = value;
              }
            } else if (input.id.includes('grade')) {
              if (input.id.includes('from')) {
                proximityConfig.fontVariations.grade.from = value;
              } else if (input.id.includes('to')) {
                proximityConfig.fontVariations.grade.to = value;
              }
            }
            
            updateAxisPreviews();
            saveConfiguration();
          });
        });

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

        previewContainer = document.getElementById('preview-container');
        previewText = document.getElementById('preview-text');
        
        previewContainer.addEventListener('mousemove', handleMouseMove);
        previewContainer.addEventListener('mouseleave', handleMouseLeave);
        previewContainer.addEventListener('mouseenter', handleMouseEnter);
        
        previewContainer.addEventListener('touchmove', handleTouchMove);
        previewContainer.addEventListener('touchstart', handleTouchStart);
        previewContainer.addEventListener('touchend', handleMouseLeave);

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

        function loadConfiguration() {
          try {
            const saved = localStorage.getItem('bricksfusion-proximity-config');
            if (saved) {
              const savedConfig = JSON.parse(saved);
              Object.assign(proximityConfig, savedConfig);
              
              document.getElementById('sample-text').value = savedConfig.sampleText;
              document.getElementById('radius').value = savedConfig.radius;
              document.getElementById('radius-value').textContent = savedConfig.radius;
              document.getElementById('falloff-type').value = savedConfig.falloff;
              
              document.getElementById('weight-from').value = savedConfig.fontVariations.weight.from;
              document.getElementById('weight-from-value').textContent = savedConfig.fontVariations.weight.from;
              document.getElementById('weight-to').value = savedConfig.fontVariations.weight.to;
              document.getElementById('weight-to-value').textContent = savedConfig.fontVariations.weight.to;
              
              document.getElementById('width-from').value = savedConfig.fontVariations.width.from;
              document.getElementById('width-from-value').textContent = savedConfig.fontVariations.width.from;
              document.getElementById('width-to').value = savedConfig.fontVariations.width.to;
              document.getElementById('width-to-value').textContent = savedConfig.fontVariations.width.to;
              
              document.getElementById('opsz-from').value = savedConfig.fontVariations.opsz.from;
              document.getElementById('opsz-from-value').textContent = savedConfig.fontVariations.opsz.from;
              document.getElementById('opsz-to').value = savedConfig.fontVariations.opsz.to;
              document.getElementById('opsz-to-value').textContent = savedConfig.fontVariations.opsz.to;
              
              document.getElementById('grade-from').value = savedConfig.fontVariations.grade.from;
              document.getElementById('grade-from-value').textContent = savedConfig.fontVariations.grade.from;
              document.getElementById('grade-to').value = savedConfig.fontVariations.grade.to;
              document.getElementById('grade-to-value').textContent = savedConfig.fontVariations.grade.to;
              
              updatePreview();
              updateAxisPreviews();
            }
          } catch (e) {
          }
        }

        loadConfiguration();
        updatePreview();
        updateAxisPreviews();
        startAnimation();
        
        setTimeout(() => {
          showNotification('BricksFusion Variable Proximity Configurator loaded!');
        }, 500);
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Variable Proximity Text - Bricksfusion
LIGHT

Variable Proximity Text

Creates interactive text that responds to cursor proximity by changing font properties. Each letter independently adjusts weight, width, optical size, and grade based on distance from mouse. Features smooth transitions with customizable falloff curves. Requires variable fonts with OpenType axes support. Perfect for hero headlines, interactive typography, or creating engaging text experiences.

Variable Proximity Text

Move your mouse over the text to see it react.

Hover Me

Distance

Effect Range Close to Far

How far from the cursor letters will be affected. Lower creates a tight, focused effect around the cursor. Higher makes letters react from further away.

Default: Medium (100)

Fade Style Transition curve

How the effect fades with distance. Linear creates even transitions. Exponential produces dramatic, accelerating changes. Gaussian gives smooth, natural bell-curve falloff.

Default: Linear

Font Weight

Starting Weight Thin to Bold

Weight of letters when cursor is far away. This is the default resting state of your text.

Default: Regular (400)

Maximum Weight Thin to Bold

Weight of letters when cursor is directly over them. Creates the peak effect at closest proximity.

Default: Bold (700)

Font Width

Starting Width Condensed to Expanded

Width of letters when cursor is far away. Lower creates narrow, compressed letters. Higher makes wide, stretched letters.

Default: Normal (100)

Maximum Width Condensed to Expanded

Width of letters when cursor is directly over them. Creates expansion or contraction effect on hover.

Default: Normal (100)

Optical Size

Starting Size Display to Text

Optical size when cursor is far away. Lower optimizes for small text sizes. Higher optimizes for large display sizes.

Default: Text (14)

Maximum Size Display to Text

Optical size when cursor is directly over letters. Changes letter proportions and details for optimal readability.

Default: Display (72)

Font Grade

Starting Grade Light to Heavy

Grade when cursor is far away. Affects stroke thickness independently from weight. Lower creates lighter strokes, higher creates heavier strokes.

Default: Normal (0)

Maximum Grade Light to Heavy

Grade when cursor is directly over letters. Fine-tunes the visual weight without changing letter width.

Default: Heavy (150)

Performance

This element uses CSS font-variation-settings with smooth transitions. Splits text into individual letter spans for independent animation. Features mouse and touch tracking with requestAnimationFrame for smooth updates. Includes screen reader support with hidden text. Very lightweight - suitable for all devices with multiple instances per page. Requires variable fonts with OpenType variation axes support.