v2.2
MENU ANIMATIONS
UI SURECART
BUTTONS
<!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
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
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
Length of the traveling light stream. Shorter streams are quick and subtle, longer streams are more prominent.
Default: 50
Thickness of the streaming border. Thinner is elegant, thicker is bold.
Default: 2
Animation
How long it takes for the stream to travel around the border once. Faster creates urgency, slower is relaxing.
Default: 10 seconds
Speed of the animation when hovering. How quickly the border fills up when you hover over it.
Default: 0.5 seconds
Hover Effects
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.