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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.preview-btn svg {
  width: 18px !important;
  height: 18px !important;
  stroke: currentColor !important;
  fill: none !important;
  stroke-width: 2 !important;
  stroke-linecap: round !important;
  stroke-linejoin: round !important;
  display: block !important;
  opacity: 1 !important;
  visibility: visible !important;
}

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

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

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

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

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

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

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

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

.card-content {
  padding: 1.5rem;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

.color-picker-container:hover {
  border-color: var(--accent);
  transform: scale(1.05);
  box-shadow: 0 0 12px rgba(239, 96, 19, 0.3);
}

.color-picker-container::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--selected-color, #3b82f6);
  border-radius: 6px;
  transition: var(--transition);
}

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

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

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

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

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

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

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

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

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

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

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

.add-color-btn {
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--input-radius);
  color: var(--text-secondary);
  cursor: pointer;
  transition: var(--transition);
  padding: 0.75rem 1rem;
  font-size: var(--text-xs);
  margin-top: 0.5rem;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

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

.remove-color-btn {
  background: none;
  border: 1px solid var(--border);
  color: var(--text-secondary);
  cursor: pointer;
  font-size: var(--text-xs);
  padding: 0.4rem;
  border-radius: 4px;
  transition: var(--transition);
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
}

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

.toggle-switch {
  position: relative;
  display: inline-block;
  width: 50px;
  height: 24px;
}

.toggle-input {
  opacity: 0;
  width: 0;
  height: 0;
}

.toggle-label {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: var(--track);
  transition: var(--transition);
  border-radius: 34px;
}

.toggle-label:before {
  position: absolute;
  content: "";
  height: 18px;
  width: 18px;
  left: 3px;
  bottom: 3px;
  background-color: white;
  transition: var(--transition);
  border-radius: 50%;
}

.toggle-input:checked + .toggle-label {
  background-color: var(--accent);
}

.toggle-input:checked + .toggle-label:before {
  transform: translateX(26px);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    <div class="instructions-toggle">
      <div class="instructions-card" id="instructions-card">
        <div class="instructions-header" id="instructions-toggle">
          <div class="instructions-title">
            How to Use & Code Information
          </div>
          <span class="toggle-icon">▼</span>
        </div>
        <div class="instructions-content" id="instructions-content">
          <div class="instructions-grid">
            <div class="how-to-use">
              <ol>
                <li>Customize your border stream effect using the controls below</li>
                <li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
                <li>In Bricks Builder, add a <strong>Code</strong> element</li>
                <li>Paste or upload the JavaScript code</li>
                <li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-border-stream</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="border-stream-preview" data-border-stream="true">
          <div class="preview-content">Interactive Border Stream Preview</div>
          <div class="preview-controls">
            <button class="preview-btn" id="randomize-border-stream" title="Randomize (R)">🎲</button>
            <div class="background-selector-wrapper">
              <button class="preview-btn background-selector-btn" id="background-selector">
                <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                  <polygon points="12,2 22,8.5 12,15 2,8.5"></polygon>
                  <polyline points="2,17 12,23 22,17"></polyline>
                  <polyline points="2,12 12,18 22,12"></polyline>
                </svg>
              </button>
              <input type="color" id="preview-background-picker" class="hidden-color-input" value="#000000" title="Change Preview Background (B)">
            </div>
          </div>
        </div>
      </section>

      <section class="controls-section">
        <div class="card">
          <div class="card-heading">
            Stream Colors
            <div class="card-actions">
              <button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="color-list" id="color-list">
            </div>
            <button class="add-color-btn" id="add-color-btn">
              🎲 Add Color
            </button>
          </div>
        </div>

        <div class="card">
          <div class="card-heading">
            Animation Settings
            <div class="card-actions">
              <button class="card-action-btn" id="reset-animation" title="Reset Animation Settings">↺</button>
            </div>
          </div>
          <div class="card-content">
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Stream Size
                  <span class="help-tooltip" title="Length of the streaming particle effect">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="stream-size-value">50</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('stream-size', 50)">↺</button>
                </div>
              </div>
              <input type="range" id="stream-size" min="10" max="400" step="5" value="50">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Border Width
                  <span class="help-tooltip" title="Thickness of the border stream">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="stream-width-value">2</span>px</span>
                  <button class="reset-btn" onclick="resetParameter('stream-width', 2)">↺</button>
                </div>
              </div>
              <input type="range" id="stream-width" min="1" max="10" step="0.5" value="2">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">
                  Animation Duration
                  <span class="help-tooltip" title="Speed of the streaming animation">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="stream-duration-value">10</span>s</span>
                  <button class="reset-btn" onclick="resetParameter('stream-duration', 10)">↺</button>
                </div>
              </div>
              <input type="range" id="stream-duration" min="3" max="20" step="0.5" value="10">
            </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">
                  Hover Transition
                  <span class="help-tooltip" title="Duration of hover transition effects">ℹ</span>
                </span>
                <div class="value-display">
                  <span class="value-text"><span id="hover-transition-value">0.5</span>s</span>
                  <button class="reset-btn" onclick="resetParameter('hover-transition', 0.5)">↺</button>
                </div>
              </div>
              <input type="range" id="hover-transition" min="0.1" max="2" step="0.1" value="0.5">
            </div>
            
            <div class="control-group">
              <div class="control-label">
                <span class="label-text">Glow on Hover</span>
                <label class="toggle-switch">
                  <input type="checkbox" id="glow-on-hover" class="toggle-input" checked>
                  <span class="toggle-label"></span>
                </label>
              </div>
            </div>
          </div>
        </div>
      </section>
    </div>
  </div>

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

  <script>
    document.addEventListener('DOMContentLoaded', function() {
      let borderStreamConfig = {
        streamSize: 50,
        streamWidth: 2,
        streamDuration: 10,
        hoverTransition: 0.5,
        glowOnHover: true,
        streamColors: ["#0096FE", "#ffaa40"]
      };

      const defaultConfig = { ...borderStreamConfig };

      let activeBorderStream = null;
      
      function initBorderStreamEffect() {
        const sections = document.querySelectorAll('[data-border-stream]:not([data-border-stream-initialized="true"])');
        
        sections.forEach((section) => {
          const streamSize = section.hasAttribute('data-stream-size') 
            ? parseInt(section.getAttribute('data-stream-size')) 
            : borderStreamConfig.streamSize;
            
          const streamWidth = section.hasAttribute('data-stream-width') 
            ? parseFloat(section.getAttribute('data-stream-width')) 
            : borderStreamConfig.streamWidth;
            
          const streamDuration = section.hasAttribute('data-stream-duration') 
            ? parseFloat(section.getAttribute('data-stream-duration').replace('s', '')) 
            : borderStreamConfig.streamDuration;
            
          const hoverTransition = section.hasAttribute('data-hover-transition') 
            ? parseFloat(section.getAttribute('data-hover-transition').replace('s', '')) 
            : borderStreamConfig.hoverTransition;
            
          const glowOnHover = section.hasAttribute('data-glow-on-hover') 
            ? section.getAttribute('data-glow-on-hover') !== 'false' 
            : borderStreamConfig.glowOnHover;
            
          const customColorsAttr = section.getAttribute('data-stream-colors');
          const streamColors = parseCustomColors(customColorsAttr) || borderStreamConfig.streamColors;
            
          const options = {
            streamSize,
            streamWidth,
            streamDuration,
            hoverTransition,
            glowOnHover,
            streamColors
          };
          
          setupBorderStreamEffect(section, options);
          section.dataset.borderStreamInitialized = 'true';
          
          if (section.id === 'border-stream-preview') {
            activeBorderStream = { element: section, options };
            borderStreamConfig = {
              streamSize: options.streamSize,
              streamWidth: options.streamWidth,
              streamDuration: options.streamDuration,
              hoverTransition: options.hoverTransition,
              glowOnHover: options.glowOnHover,
              streamColors: [...options.streamColors]
            };
          }
        });
      }
      
      function parseCustomColors(colorsAttr) {
        if (!colorsAttr) return null;
        try {
          const colors = colorsAttr.split(',').map(color => color.trim());
          return colors.length < 2 ? [...colors, ...colors] : colors;
        } catch (e) {
          return null;
        }
      }
      
      function throttle(func, limit) {
        let inThrottle;
        return function(...args) {
          const context = this;
          if (!inThrottle) {
            func.apply(context, args);
            inThrottle = true;
            setTimeout(() => inThrottle = false, limit);
          }
        }
      }
      
      function setupBorderStreamEffect(element, options) {
        const existingSvg = element.querySelector('svg');
        if (existingSvg) existingSvg.remove();

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

        element.style.transition = `box-shadow ${options.hoverTransition}s ease-in-out`;
        element.style.boxShadow = "none";

        const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        Object.assign(svg.style, {
          position: "absolute",
          top: "0",
          left: "0",
          width: "100%",
          height: "100%",
          pointerEvents: "none",
          overflow: "visible",
          zIndex: "1"
        });

        const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
        path.setAttribute("fill", "none");
        path.setAttribute("stroke-width", options.streamWidth);
        path.setAttribute("stroke-linecap", "round");

        const gradientId = `gradient-${Math.random().toString(36).substr(2, 9)}`;
        const gradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
        gradient.setAttribute("id", gradientId);
        
        options.streamColors.forEach((color, index) => {
          const stop = document.createElementNS("http://www.w3.org/2000/svg", "stop");
          stop.setAttribute("offset", `${index * 100 / (options.streamColors.length - 1)}%`);
          stop.setAttribute("stop-color", color);
          gradient.appendChild(stop);
        });

        const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
        defs.appendChild(gradient);
        svg.appendChild(defs);
        svg.appendChild(path);
        element.appendChild(svg);

        element.borderStreamSvg = svg;
        element.borderStreamPath = path;
        element.borderStreamGradient = gradient;
        element.borderStreamOptions = options;

        let lastRect = null;

        function updatePath() {
          const rect = element.getBoundingClientRect();
          
          if (lastRect && 
              lastRect.width === rect.width && 
              lastRect.height === rect.height) {
            return;
          }
          
          lastRect = { width: rect.width, height: rect.height };

          const borderRadiusStr = styles.borderRadius;
          let borderRadii = [0, 0, 0, 0];
          
          if (borderRadiusStr && borderRadiusStr !== "0px") {
            borderRadii = borderRadiusStr.split('/').map(str => 
              str.trim().split(' ').map(r => parseFloat(r) || 0)
            ).flat();

            if (borderRadii.length === 1) {
              borderRadii = Array(4).fill(borderRadii[0]);
            } else if (borderRadii.length === 2) {
              borderRadii = [borderRadii[0], borderRadii[1], borderRadii[0], borderRadii[1]];
            } else if (borderRadii.length === 3) {
              borderRadii.push(borderRadii[1]);
            }
          }

          const [topLeft, topRight, bottomRight, bottomLeft] = borderRadii;
          
          const width = svg.clientWidth;
          const height = svg.clientHeight;

          const d = `
            M ${topLeft},0
            H ${width - topRight}
            A ${topRight},${topRight} 0 0 1 ${width},${topRight}
            V ${height - bottomRight}
            A ${bottomRight},${bottomRight} 0 0 1 ${width - bottomRight},${height}
            H ${bottomLeft}
            A ${bottomLeft},${bottomLeft} 0 0 1 0,${height - bottomLeft}
            V ${topLeft}
            A ${topLeft},${topLeft} 0 0 1 ${topLeft},0
            Z
          `;

          path.setAttribute("d", d);
          path.setAttribute("stroke", `url(#${gradientId})`);

          const pathLength = path.getTotalLength();
          path.style.transition = 'none';
          path.style.strokeDasharray = `${options.streamSize} ${pathLength - options.streamSize}`;
          path.style.strokeDashoffset = pathLength;
          path.style.animation = `moveStream ${options.streamDuration}s linear infinite`;
        }

        if (!document.querySelector('style#stream-keyframes')) {
          const style = document.createElement('style');
          style.id = 'stream-keyframes';
          style.textContent = `
            @keyframes moveStream {
              to {
                stroke-dashoffset: 0;
              }
            }
          `;
          document.head.appendChild(style);
        }

        setTimeout(updatePath, 0);
        
        const throttledUpdatePath = throttle(updatePath, 100);
        window.addEventListener('resize', throttledUpdatePath);
        
        const resizeObserver = new ResizeObserver(throttledUpdatePath);
        resizeObserver.observe(element);

        element.addEventListener('mouseenter', () => {
          const pathLength = path.getTotalLength();
          
          const currentHoverTransition = element.id === 'border-stream-preview' ? 
            borderStreamConfig.hoverTransition + 's' : 
            options.hoverTransition + 's';
          
          path.style.animation = 'none';
          path.style.transition = `stroke-dasharray ${currentHoverTransition} ease-in-out, stroke-dashoffset ${currentHoverTransition} ease-in-out`;
          
          const currentGlowState = element.id === 'border-stream-preview' ? 
            borderStreamConfig.glowOnHover : 
            options.glowOnHover;
          
          if (currentGlowState) {
            const currentColors = element.id === 'border-stream-preview' ? 
              borderStreamConfig.streamColors : 
              options.streamColors;
            const shadowValue = `0 0 15px ${currentColors[0]}, 0 0 25px ${currentColors[Math.floor(currentColors.length / 2) || 0]}, 0 0 35px ${currentColors[currentColors.length - 1]}`;
            requestAnimationFrame(() => {
              element.style.boxShadow = shadowValue;
            });
          } else {
            requestAnimationFrame(() => {
              element.style.boxShadow = "none";
            });
          }
          
          requestAnimationFrame(() => {
            path.style.strokeDasharray = `${pathLength} 0`;
            path.style.strokeDashoffset = '0';
          });
        });

        element.addEventListener('mouseleave', () => {
          const pathLength = path.getTotalLength();
          
          const currentStreamSize = element.id === 'border-stream-preview' ? 
            borderStreamConfig.streamSize : 
            options.streamSize;
          const currentStreamDuration = element.id === 'border-stream-preview' ? 
            borderStreamConfig.streamDuration + 's' : 
            options.streamDuration + 's';
          const currentHoverTransition = element.id === 'border-stream-preview' ? 
            borderStreamConfig.hoverTransition + 's' : 
            options.hoverTransition + 's';
          
          path.style.animation = 'none';
          path.style.transition = `stroke-dasharray ${currentHoverTransition} ease-in-out, stroke-dashoffset ${currentHoverTransition} ease-in-out`;
          
          requestAnimationFrame(() => {
            element.style.boxShadow = "none";
          });
          
          requestAnimationFrame(() => {
            path.style.strokeDasharray = `${currentStreamSize} ${pathLength - currentStreamSize}`;
            path.style.strokeDashoffset = pathLength;
            
            setTimeout(() => {
              path.style.transition = 'none';
              path.style.animation = `moveStream ${currentStreamDuration} linear infinite`;
            }, parseFloat(currentHoverTransition) * 1000);
          });
        });

        element._cleanupBorderStream = () => {
          window.removeEventListener('resize', throttledUpdatePath);
          resizeObserver.disconnect();
          if (element.borderStreamSvg && element.borderStreamSvg.parentNode) {
            element.borderStreamSvg.parentNode.removeChild(element.borderStreamSvg);
          }
          element.dataset.borderStreamInitialized = 'false';
        };
      }
      
      function updateBorderStreamPreview() {
        const preview = document.getElementById('border-stream-preview');
        if (!preview) return;
        
        preview.setAttribute('data-stream-size', borderStreamConfig.streamSize);
        preview.setAttribute('data-stream-width', borderStreamConfig.streamWidth);
        preview.setAttribute('data-stream-duration', borderStreamConfig.streamDuration + 's');
        preview.setAttribute('data-hover-transition', borderStreamConfig.hoverTransition + 's');
        preview.setAttribute('data-glow-on-hover', borderStreamConfig.glowOnHover);
        preview.setAttribute('data-stream-colors', borderStreamConfig.streamColors.join(','));
        
        updateConfig(preview);
      }

      function updateConfig(element) {
        if (!element.borderStreamPath || !element.borderStreamGradient) {
          return;
        }
        
        const path = element.borderStreamPath;
        const gradient = element.borderStreamGradient;
        
        const stops = gradient.querySelectorAll('stop');
        stops.forEach(stop => stop.remove());
        
        borderStreamConfig.streamColors.forEach((color, index) => {
          const stop = document.createElementNS("http://www.w3.org/2000/svg", "stop");
          stop.setAttribute("offset", `${index * 100 / (borderStreamConfig.streamColors.length - 1)}%`);
          stop.setAttribute("stop-color", color);
          gradient.appendChild(stop);
        });
        
        path.setAttribute("stroke-width", borderStreamConfig.streamWidth);
        
        const pathLength = path.getTotalLength();
        if (pathLength > 0) {
          path.style.strokeDasharray = `${borderStreamConfig.streamSize} ${pathLength - borderStreamConfig.streamSize}`;
          path.style.animationDuration = `${borderStreamConfig.streamDuration}s`;
        }
        
        element.style.transition = `box-shadow ${borderStreamConfig.hoverTransition}s ease-in-out`;
        
        if (element.borderStreamOptions) {
          element.borderStreamOptions.streamSize = borderStreamConfig.streamSize;
          element.borderStreamOptions.streamWidth = borderStreamConfig.streamWidth;
          element.borderStreamOptions.streamDuration = borderStreamConfig.streamDuration;
          element.borderStreamOptions.hoverTransition = borderStreamConfig.hoverTransition;
          element.borderStreamOptions.glowOnHover = borderStreamConfig.glowOnHover;
          element.borderStreamOptions.streamColors = [...borderStreamConfig.streamColors];
        }
      }

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

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

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

      function generateFullSectionJSON() {
  // Generar IDs únicos para todos los elementos
  const sectionId = generateUniqueId();
  const containerId = generateUniqueId();
  const 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": "#000000"
            }
          }
        }
      },
      {
        "id": containerId,
        "name": "container",
        "parent": sectionId,
        "children": [divId],
        "settings": {
          "_alignItems": "center"
        },
        "label": "Container"
      },
      {
        "id": divId,
        "name": "div",
        "parent": containerId,
        "children": [textId],
        "settings": {
          "_width": "300",
          "_height": "300",
          "_border": {
            "radius": {
              "top": "15",
              "right": "15",
              "bottom": "15",
              "left": "15"
            }
          },
          "_display": "flex",
          "_alignItems": "center",
          "_justifyContent": "center",
          "_attributes": [
            {
              "id": "terask",
              "name": "data-border-stream"
            }
          ]
        },
        "label": "Border Stream Div"
      },
      {
        "id": textId,
        "name": "text-basic",
        "parent": divId,
        "children": [],
        "settings": {
          "text": "Border Stream",
          "_typography": {
            "color": {
              "hex": "#ffffff"
            },
            "font-weight": "400",
            "font-size": "16"
          }
        }
      },
      {
        "id": codeId,
        "name": "code",
        "parent": sectionId,
        "children": [],
        "settings": {
          "javascriptCode": jsCode,
          "executeCode": true,
          "_display": "none"
        },
        "label": "Border Stream JS"
      }
    ],
    "source": "bricksCopiedElements",
    "sourceUrl": "https://test.bricksfusion.com",
    "version": "2.0.1",
    "globalClasses": [],
    "globalElements": []
  };
  
  return JSON.stringify(bricksJSON, null, 2);
}

      function generateJavaScriptCode() {
        return `(function() {
const defaultConfig = {
  streamSize: ${borderStreamConfig.streamSize},
  streamWidth: ${borderStreamConfig.streamWidth},
  streamDuration: ${borderStreamConfig.streamDuration},
  hoverTransition: ${borderStreamConfig.hoverTransition},
  glowOnHover: ${borderStreamConfig.glowOnHover},
  streamColors: [${borderStreamConfig.streamColors.map(color => `"${color}"`).join(', ')}]
};

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

function parseCustomColors(colorsAttr) {
  if (!colorsAttr) return null;
  try {
    const colors = colorsAttr.split(',').map(color => color.trim());
    return colors.length < 2 ? [...colors, ...colors] : colors;
  } catch (e) {
    return null;
  }
}

function applyBorderStreamEffect() {
  document.querySelectorAll('[data-border-stream]').forEach(element => {
    const existingSvg = element.querySelector('svg');
    if (existingSvg) existingSvg.remove();

    const streamSize = parseInt(element.getAttribute("data-stream-size")) || defaultConfig.streamSize;
    const streamWidth = parseInt(element.getAttribute("data-stream-width")) || defaultConfig.streamWidth;
    const streamDuration = element.getAttribute("data-stream-duration") || defaultConfig.streamDuration + "s";
    const hoverTransitionDuration = element.getAttribute("data-hover-transition") || defaultConfig.hoverTransition + "s";
    const customColorsAttr = element.getAttribute("data-stream-colors");
    const glowOnHoverAttr = element.getAttribute("data-glow-on-hover");
    const glowOnHover = glowOnHoverAttr === null ? defaultConfig.glowOnHover : glowOnHoverAttr !== "false";
    
    const gradientColors = parseCustomColors(customColorsAttr) || defaultConfig.streamColors;

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

    element.style.transition = \`box-shadow \${hoverTransitionDuration} ease-in-out\`;
    element.style.boxShadow = "none";

    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    Object.assign(svg.style, {
      position: "absolute",
      top: "0",
      left: "0",
      width: "100%",
      height: "100%",
      pointerEvents: "none",
      overflow: "visible",
      zIndex: "1"
    });

    const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
    path.setAttribute("fill", "none");
    path.setAttribute("stroke-width", streamWidth);
    path.setAttribute("stroke-linecap", "round");

    const gradientId = \`gradient-\${Math.random().toString(36).substr(2, 9)}\`;
    const gradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
    gradient.setAttribute("id", gradientId);
    
    gradientColors.forEach((color, index) => {
      const stop = document.createElementNS("http://www.w3.org/2000/svg", "stop");
      stop.setAttribute("offset", \`\${index * 100 / (gradientColors.length - 1)}%\`);
      stop.setAttribute("stop-color", color);
      gradient.appendChild(stop);
    });

    const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
    defs.appendChild(gradient);
    svg.appendChild(defs);
    svg.appendChild(path);
    element.appendChild(svg);

    let lastRect = null;

    function updatePath() {
      const rect = element.getBoundingClientRect();
      
      if (lastRect && 
          lastRect.width === rect.width && 
          lastRect.height === rect.height) {
          return;
      }
      
      lastRect = { width: rect.width, height: rect.height };

      const borderRadiusStr = styles.borderRadius;
      let borderRadii = [0, 0, 0, 0];
      
      if (borderRadiusStr && borderRadiusStr !== "0px") {
        borderRadii = borderRadiusStr.split('/').map(str => 
          str.trim().split(' ').map(r => parseFloat(r) || 0)
        ).flat();

        if (borderRadii.length === 1) {
          borderRadii = Array(4).fill(borderRadii[0]);
        } else if (borderRadii.length === 2) {
          borderRadii = [borderRadii[0], borderRadii[1], borderRadii[0], borderRadii[1]];
        } else if (borderRadii.length === 3) {
          borderRadii.push(borderRadii[1]);
        }
      }

      const [topLeft, topRight, bottomRight, bottomLeft] = borderRadii;
      
      const width = svg.clientWidth;
      const height = svg.clientHeight;

      const d = \`
        M \${topLeft},0
        H \${width - topRight}
        A \${topRight},\${topRight} 0 0 1 \${width},\${topRight}
        V \${height - bottomRight}
        A \${bottomRight},\${bottomRight} 0 0 1 \${width - bottomRight},\${height}
        H \${bottomLeft}
        A \${bottomLeft},\${bottomLeft} 0 0 1 0,\${height - bottomLeft}
        V \${topLeft}
        A \${topLeft},\${topLeft} 0 0 1 \${topLeft},0
        Z
      \`;

      path.setAttribute("d", d);
      path.setAttribute("stroke", \`url(#\${gradientId})\`);

      const pathLength = path.getTotalLength();
      path.style.transition = 'none';
      path.style.strokeDasharray = \`\${streamSize} \${pathLength - streamSize}\`;
      path.style.strokeDashoffset = pathLength;
      path.style.animation = \`moveStream \${streamDuration} linear infinite\`;
    }

    setTimeout(updatePath, 0);
    
    const throttledUpdatePath = throttle(updatePath, 100);
    window.addEventListener('resize', throttledUpdatePath);
    
    const resizeObserver = new ResizeObserver(throttledUpdatePath);
    resizeObserver.observe(element);

    element.addEventListener('mouseenter', () => {
      const pathLength = path.getTotalLength();
      path.style.animation = 'none';
      path.style.transition = \`stroke-dasharray \${hoverTransitionDuration} ease-in-out, stroke-dashoffset \${hoverTransitionDuration} ease-in-out\`;
      
      if (glowOnHover) {
        const shadowValue = \`0 0 15px \${gradientColors[0]}, 0 0 25px \${gradientColors[Math.floor(gradientColors.length / 2) || 0]}, 0 0 35px \${gradientColors[gradientColors.length - 1]}\`;
        requestAnimationFrame(() => {
          element.style.boxShadow = shadowValue;
        });
      } else {
        requestAnimationFrame(() => {
          element.style.boxShadow = "none";
        });
      }
      
      requestAnimationFrame(() => {
        path.style.strokeDasharray = \`\${pathLength} 0\`;
        path.style.strokeDashoffset = '0';
      });
    });

    element.addEventListener('mouseleave', () => {
      const pathLength = path.getTotalLength();
      path.style.animation = 'none';
      path.style.transition = \`stroke-dasharray \${hoverTransitionDuration} ease-in-out, stroke-dashoffset \${hoverTransitionDuration} ease-in-out\`;
      
      requestAnimationFrame(() => {
        element.style.boxShadow = "none";
      });
      
      requestAnimationFrame(() => {
        path.style.strokeDasharray = \`\${streamSize} \${pathLength - streamSize}\`;
        path.style.strokeDashoffset = pathLength;
        
        setTimeout(() => {
          path.style.transition = 'none';
          path.style.animation = \`moveStream \${streamDuration} linear infinite\`;
        }, parseFloat(hoverTransitionDuration) * 1000);
      });
    });

    const cleanup = () => {
      window.removeEventListener('resize', throttledUpdatePath);
      resizeObserver.disconnect();
    };

    element._borderStreamCleanup = cleanup;
  });
}

if (!document.querySelector('style#stream-keyframes')) {
  const style = document.createElement('style');
  style.id = 'stream-keyframes';
  style.textContent = \`
    @keyframes moveStream {
      to {
        stroke-dashoffset: 0;
      }
    }
  \`;
  document.head.appendChild(style);
}

const initWithDelay = () => {
  setTimeout(applyBorderStreamEffect, 100);
};

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

window.addEventListener('load', applyBorderStreamEffect);

const observer = new MutationObserver((mutations) => {
  let shouldInit = false;

  mutations.forEach((mutation) => {
    if (mutation.type === 'childList') {
      mutation.addedNodes.forEach(node => {
        if (node.nodeType === 1) {
          if (node.hasAttribute && node.hasAttribute('data-border-stream')) {
            shouldInit = true;
          } else if (node.querySelectorAll) {
            const streamElements = node.querySelectorAll('[data-border-stream]');
            if (streamElements.length > 0) {
              shouldInit = true;
            }
          }
        }
      });
    } else if (mutation.type === 'attributes' && mutation.attributeName === 'data-border-stream') {
      shouldInit = true;
    }
  });

  if (shouldInit) {
    setTimeout(applyBorderStreamEffect, 100);
  }
});

observer.observe(document.body, { 
  childList: true, 
  subtree: true,
  attributes: true,
  attributeFilter: ['data-border-stream', 'data-stream-size', 'data-stream-width', 'data-stream-duration', 'data-hover-transition', 'data-glow-on-hover', 'data-stream-colors']
});
})();`;
      }

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

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

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

      window.resetParameter = function(parameterId, defaultValue) {
        const element = document.getElementById(parameterId);
        if (element) {
          element.value = defaultValue;
          const valueElement = document.getElementById(`${parameterId}-value`);
          if (valueElement) {
            valueElement.textContent = defaultValue;
          }
          
          switch (parameterId) {
            case 'stream-size':
              borderStreamConfig.streamSize = defaultValue;
              break;
            case 'stream-width':
              borderStreamConfig.streamWidth = defaultValue;
              break;
            case 'stream-duration':
              borderStreamConfig.streamDuration = defaultValue;
              break;
            case 'hover-transition':
              borderStreamConfig.hoverTransition = defaultValue;
              break;
          }
          
          updateBorderStreamPreview();
          showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
        }
      };

      function generateRandomBorderStream() {
        borderStreamConfig.streamSize = Math.floor(Math.random() * 390) + 10;
        borderStreamConfig.streamWidth = Math.random() * 9 + 1;
        borderStreamConfig.streamDuration = Math.random() * 17 + 3;
        borderStreamConfig.hoverTransition = Math.random() * 1.9 + 0.1;
        borderStreamConfig.glowOnHover = Math.random() > 0.3;
        
        const colorCount = Math.floor(Math.random() * 3) + 2;
        borderStreamConfig.streamColors = [];
        for (let i = 0; i < colorCount; i++) {
          borderStreamConfig.streamColors.push(generateRandomColor());
        }
        
        document.getElementById('stream-size').value = borderStreamConfig.streamSize;
        document.getElementById('stream-width').value = borderStreamConfig.streamWidth;
        document.getElementById('stream-duration').value = borderStreamConfig.streamDuration;
        document.getElementById('hover-transition').value = borderStreamConfig.hoverTransition;
        document.getElementById('glow-on-hover').checked = borderStreamConfig.glowOnHover;
        
        document.getElementById('stream-size-value').textContent = borderStreamConfig.streamSize;
        document.getElementById('stream-width-value').textContent = borderStreamConfig.streamWidth;
        document.getElementById('stream-duration-value').textContent = borderStreamConfig.streamDuration;
        document.getElementById('hover-transition-value').textContent = borderStreamConfig.hoverTransition;
        
        updateColorInputs();
        updateBorderStreamPreview();
        showNotification('Random border stream generated!');
      }

      function updateColorInputs() {
        const colorList = document.getElementById('color-list');
        colorList.innerHTML = '';
        
        borderStreamConfig.streamColors.forEach((color, index) => {
          addColorRow(color, index);
        });
      }

      function hexToHsl(hex) {
        const r = parseInt(hex.slice(1, 3), 16) / 255;
        const g = parseInt(hex.slice(3, 5), 16) / 255;
        const b = parseInt(hex.slice(5, 7), 16) / 255;
        
        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;
        
        if (max === min) {
          h = s = 0;
        } else {
          const d = max - min;
          s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
          switch (max) {
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
          }
          h /= 6;
        }
        
        return {
          h: Math.round(h * 360),
          s: Math.round(s * 100),
          l: Math.round(l * 100)
        };
      }

      function hslToHex(hsl) {
        const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
        if (!match) return null;
        
        let h = parseInt(match[1]) / 360;
        let s = parseInt(match[2]) / 100;
        let l = parseInt(match[3]) / 100;
        
        const hue2rgb = (p, q, t) => {
          if (t < 0) t += 1;
          if (t > 1) t -= 1;
          if (t < 1/6) return p + (q - p) * 6 * t;
          if (t < 1/2) return q;
          if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
          return p;
        };
        
        let r, g, b;
        if (s === 0) {
          r = g = b = l;
        } else {
          const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
          const p = 2 * l - q;
          r = hue2rgb(p, q, h + 1/3);
          g = hue2rgb(p, q, h);
          b = hue2rgb(p, q, h - 1/3);
        }
        
        const toHex = (c) => {
          const hex = Math.round(c * 255).toString(16);
          return hex.length === 1 ? '0' + hex : hex;
        };
        
        return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
      }

      function isValidHex(hex) {
        return /^#[0-9A-F]{6}$/i.test(hex);
      }

      function isValidHsl(hsl) {
        return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
      }

      function formatHex(value) {
        let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
        
        if (!hex.startsWith('#')) {
          hex = '#' + hex;
        }
        
        if (hex.length > 7) {
          hex = hex.substring(0, 7);
        }
        
        return hex.toUpperCase();
      }

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

      function addColorRow(color, index) {
        const colorList = document.getElementById('color-list');
        
        const colorRow = document.createElement('div');
        colorRow.className = 'color-row';
        colorRow.dataset.index = index;
        
        const colorPickerContainer = document.createElement('div');
        colorPickerContainer.className = 'color-picker-container';
        colorPickerContainer.style.setProperty('--selected-color', color);
        
        const colorPicker = document.createElement('input');
        colorPicker.type = 'color';
        colorPicker.value = color;
        colorPicker.addEventListener('input', (e) => {
          updateColor(index, e.target.value);
        });
        
        colorPickerContainer.appendChild(colorPicker);
        
        const hexGroup = document.createElement('div');
        hexGroup.className = 'color-input-group';
        
        const hexLabel = document.createElement('span');
        hexLabel.className = 'color-label';
        hexLabel.textContent = 'HEX';
        
        const hexInput = document.createElement('input');
        hexInput.type = 'text';
        hexInput.className = 'color-input hex-input';
        hexInput.value = color;
        hexInput.placeholder = '#FFFFFF';
        
        hexInput.addEventListener('input', (e) => {
          let hex = e.target.value;
          hex = formatHex(hex);
          e.target.value = hex;
          
          if (isValidHex(hex)) {
            updateColor(index, hex);
            e.target.classList.remove('invalid');
          } else {
            e.target.classList.add('invalid');
          }
        });
        
        hexInput.addEventListener('blur', (e) => {
          if (!isValidHex(e.target.value)) {
            e.target.value = borderStreamConfig.streamColors[index];
            e.target.classList.remove('invalid');
          }
        });
        
        hexGroup.appendChild(hexLabel);
        hexGroup.appendChild(hexInput);
        
        const hslGroup = document.createElement('div');
        hslGroup.className = 'color-input-group';
        
        const hslLabel = document.createElement('span');
        hslLabel.className = 'color-label';
        hslLabel.textContent = 'HSL';
        
        const hslInput = document.createElement('input');
        hslInput.type = 'text';
        hslInput.className = 'color-input hsl-input';
        hslInput.placeholder = 'hsl(0, 100%, 50%)';
        
        const hslColor = hexToHsl(color);
        hslInput.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.l}%)`;
        
        hslInput.addEventListener('input', (e) => {
          let hsl = e.target.value;
          
          if (isValidHsl(hsl)) {
            const hex = hslToHex(hsl);
            if (hex) {
              updateColor(index, hex);
              e.target.classList.remove('invalid');
              return;
            }
          }
          
          e.target.classList.add('invalid');
        });
        
        hslInput.addEventListener('blur', (e) => {
          let hsl = e.target.value;
          
          if (!isValidHsl(hsl) && hsl.trim()) {
            const formatted = formatHsl(hsl);
            if (isValidHsl(formatted)) {
              e.target.value = formatted;
              const hex = hslToHex(formatted);
              if (hex) {
                updateColor(index, hex);
                e.target.classList.remove('invalid');
                return;
              }
            }
          }
          
          if (!isValidHsl(e.target.value)) {
            const currentHsl = hexToHsl(borderStreamConfig.streamColors[index]);
            e.target.value = `hsl(${currentHsl.h}, ${currentHsl.s}%, ${currentHsl.l}%)`;
            e.target.classList.remove('invalid');
          }
        });
        
        hslGroup.appendChild(hslLabel);
        hslGroup.appendChild(hslInput);
        
        colorRow.appendChild(colorPickerContainer);
        colorRow.appendChild(hexGroup);
        colorRow.appendChild(hslGroup);
        
        if (borderStreamConfig.streamColors.length > 2) {
          const removeBtn = document.createElement('button');
          removeBtn.className = 'remove-color-btn';
          removeBtn.innerHTML = '×';
          removeBtn.addEventListener('click', () => {
            removeColor(index);
          });
          colorRow.appendChild(removeBtn);
        }
        
        colorList.appendChild(colorRow);
      }

      function updateColor(index, color) {
        borderStreamConfig.streamColors[index] = color;
        
        const colorRow = document.querySelector(`[data-index="${index}"]`);
        if (colorRow) {
          const colorPicker = colorRow.querySelector('input[type="color"]');
          const hexInput = colorRow.querySelector('.hex-input');
          const hslInput = colorRow.querySelector('.hsl-input');
          const colorPickerContainer = colorRow.querySelector('.color-picker-container');
          
          colorPicker.value = color;
          hexInput.value = color;
          
          const hslColor = hexToHsl(color);
          hslInput.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.l}%)`;
          
          colorPickerContainer.style.setProperty('--selected-color', color);
          
          hexInput.classList.remove('invalid');
          hslInput.classList.remove('invalid');
        }
        
        const preview = document.getElementById('border-stream-preview');
        if (preview) {
          updateConfig(preview);
        }
      }

      function removeColor(index) {
        borderStreamConfig.streamColors.splice(index, 1);
        updateColorInputs();
        const preview = document.getElementById('border-stream-preview');
        if (preview) {
          updateConfig(preview);
        }
      }

      function addNewColor() {
        const newColor = generateRandomColor();
        borderStreamConfig.streamColors.push(newColor);
        updateColorInputs();
        const preview = document.getElementById('border-stream-preview');
        if (preview) {
          updateConfig(preview);
        }
        showNotification('New color added!');
      }

      function initializeUI() {
        initBorderStreamEffect();
        
        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-border-stream');
        });

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

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

        document.getElementById('randomize-border-stream').addEventListener('click', () => {
          generateRandomBorderStream();
        });

        const backgroundPicker = document.getElementById('preview-background-picker');
        const previewContainer = document.getElementById('border-stream-preview');
        const backgroundSelector = document.getElementById('background-selector');

        backgroundSelector.addEventListener('click', () => {
          backgroundPicker.click();
        });

        backgroundPicker.addEventListener('input', (e) => {
          const selectedColor = e.target.value;
          previewContainer.style.backgroundColor = selectedColor;
          showNotification(`Preview background changed to ${selectedColor}`);
        });

        previewContainer.style.backgroundColor = '#000000';

        document.getElementById('reset-colors').addEventListener('click', () => {
          borderStreamConfig.streamColors = [...defaultConfig.streamColors];
          updateColorInputs();
          const preview = document.getElementById('border-stream-preview');
          if (preview) {
            updateConfig(preview);
          }
          showNotification('Colors reset to default');
        });

        document.getElementById('reset-animation').addEventListener('click', () => {
          borderStreamConfig.streamSize = defaultConfig.streamSize;
          borderStreamConfig.streamWidth = defaultConfig.streamWidth;
          borderStreamConfig.streamDuration = defaultConfig.streamDuration;
          
          document.getElementById('stream-size').value = defaultConfig.streamSize;
          document.getElementById('stream-width').value = defaultConfig.streamWidth;
          document.getElementById('stream-duration').value = defaultConfig.streamDuration;
          
          document.getElementById('stream-size-value').textContent = defaultConfig.streamSize;
          document.getElementById('stream-width-value').textContent = defaultConfig.streamWidth;
          document.getElementById('stream-duration-value').textContent = defaultConfig.streamDuration;
          
          const preview = document.getElementById('border-stream-preview');
          if (preview) {
            updateConfig(preview);
          }
          showNotification('Animation settings reset');
        });

        document.getElementById('reset-advanced').addEventListener('click', () => {
          borderStreamConfig.hoverTransition = defaultConfig.hoverTransition;
          borderStreamConfig.glowOnHover = defaultConfig.glowOnHover;
          
          document.getElementById('hover-transition').value = defaultConfig.hoverTransition;
          document.getElementById('glow-on-hover').checked = defaultConfig.glowOnHover;
          document.getElementById('hover-transition-value').textContent = defaultConfig.hoverTransition;
          
          const preview = document.getElementById('border-stream-preview');
          if (preview) {
            updateConfig(preview);
          }
          showNotification('Advanced settings reset');
        });

        document.getElementById('add-color-btn').addEventListener('click', () => {
          addNewColor();
        });

        const rangeInputs = document.querySelectorAll('input[type="range"]');
        rangeInputs.forEach(input => {
          const valueElement = document.getElementById(`${input.id}-value`);
          
          if (valueElement) {
            valueElement.textContent = input.value;
          }
          
          input.addEventListener('input', () => {
            if (valueElement) {
              valueElement.textContent = input.value;
            }
            
            switch (input.id) {
              case 'stream-size':
                borderStreamConfig.streamSize = parseInt(input.value);
                break;
              case 'stream-width':
                borderStreamConfig.streamWidth = parseFloat(input.value);
                break;
              case 'stream-duration':
                borderStreamConfig.streamDuration = parseFloat(input.value);
                break;
              case 'hover-transition':
                borderStreamConfig.hoverTransition = parseFloat(input.value);
                break;
            }
            
            const preview = document.getElementById('border-stream-preview');
            if (preview) {
              updateConfig(preview);
            }
          });
        });

        document.getElementById('glow-on-hover').addEventListener('change', function() {
          borderStreamConfig.glowOnHover = this.checked;
          const preview = document.getElementById('border-stream-preview');
          if (preview) {
            updateConfig(preview);
          }
        });

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

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

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

        function loadConfiguration() {
          try {
            const saved = localStorage.getItem('bricksfusion-border-stream-config');
            if (saved) {
              const savedConfig = JSON.parse(saved);
              Object.assign(borderStreamConfig, savedConfig);
              
              document.getElementById('stream-size').value = savedConfig.streamSize;
              document.getElementById('stream-width').value = savedConfig.streamWidth;
              document.getElementById('stream-duration').value = savedConfig.streamDuration;
              document.getElementById('hover-transition').value = savedConfig.hoverTransition;
              document.getElementById('glow-on-hover').checked = savedConfig.glowOnHover;
              
              document.getElementById('stream-size-value').textContent = savedConfig.streamSize;
              document.getElementById('stream-width-value').textContent = savedConfig.streamWidth;
              document.getElementById('stream-duration-value').textContent = savedConfig.streamDuration;
              document.getElementById('hover-transition-value').textContent = savedConfig.hoverTransition;
              
              updateColorInputs();
              const preview = document.getElementById('border-stream-preview');
              if (preview) {
                updateConfig(preview);
              }
            }
          } catch (e) {
          }
        }

        const originalUpdateConfig = updateConfig;
        updateConfig = function(element) {
          originalUpdateConfig(element);
          saveConfiguration();
        };

        loadConfiguration();
      }
      
      initializeUI();
    });
  </script>
</body>
</html>
Border Stream - Bricksfusion
LIGHTWEIGHT

Border Stream

Creates a small animated light that travels around the border of your element. On hover, the border fills completely with an optional glow effect.

Hover over me

Colors

Stream Colors color picker (minimum 2)

Choose at least 2 colors for the streaming light. The colors blend smoothly as the light travels. More colors create richer gradients.

Default: Blue, Orange

Appearance

Stream Size 20-200 pixels

Length of the traveling light stream. Shorter streams are quick and subtle, longer streams are more prominent.

Default: 50

Border Width 1-10 pixels

Thickness of the streaming border. Thinner is elegant, thicker is bold.

Default: 2

Animation

Duration 2-20 seconds

How long it takes for the stream to travel around the border once. Faster creates urgency, slower is relaxing.

Default: 10 seconds

Hover Transition 0.1-2.0 seconds

Speed of the animation when hovering. How quickly the border fills up when you hover over it.

Default: 0.5 seconds

Hover Effects

Glow on Hover on/off

When on, the element gets a colored glow shadow when you hover over it. Creates a more dramatic hover effect.

Default: On

Performance

This element uses SVG with CSS animations, making it very efficient. The border automatically adapts to rounded corners. Safe to use multiple times on a page.