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 Drift 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;
height: 18px;
stroke: currentColor;
}
.background-selector-wrapper {
position: relative;
display: inline-block;
}
.background-selector-btn {
position: relative;
}
.background-selector-btn:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
box-shadow: 0 0 8px rgba(239, 96, 19, 0.3);
}
.hidden-color-input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.card-heading {
padding: 1rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
letter-spacing: 0.3px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-actions {
display: flex;
gap: 0.5rem;
}
.card-action-btn {
padding: 0.4rem 0.8rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
}
.card-action-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.card-content {
padding: 1.5rem;
}
.control-group {
margin-bottom: 1.5rem;
position: relative;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.label-text {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.help-tooltip {
cursor: help;
opacity: 0.7;
transition: var(--transition);
}
.help-tooltip:hover {
opacity: 1;
color: var(--accent);
}
.value-display {
display: flex;
align-items: center;
gap: 0.5rem;
}
.value-text {
font-size: var(--text-xs);
color: var(--text-secondary);
background-color: rgba(50, 50, 50, 0.5);
padding: 2px 8px;
border-radius: 4px;
min-width: 45px;
text-align: center;
}
.reset-btn {
padding: 0.2rem 0.4rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 10px;
transition: var(--transition);
}
.reset-btn:hover {
color: var(--danger);
border-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
background: var(--track);
border-radius: 3px;
outline: none;
margin: 0.8rem 0;
position: relative;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--thumb);
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 10px rgba(239, 96, 19, 0.5);
}
.color-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
}
.color-row {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem 1.25rem;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid var(--border);
border-radius: var(--input-radius);
transition: var(--transition);
}
.color-row:hover {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.1);
}
.color-picker-container {
position: relative;
width: 40px;
height: 40px;
border-radius: 8px;
overflow: hidden;
border: 2px solid var(--border);
cursor: pointer;
transition: var(--transition);
flex-shrink: 0;
background: var(--card-bg);
display: flex;
align-items: center;
justify-content: center;
--selected-color: #ffaa40;
}
.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, #ffaa40);
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;
}
.color-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
.color-random-btn {
padding: 0.4rem;
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);
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.color-random-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.color-remove-btn {
padding: 0.4rem;
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);
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
}
.color-remove-btn:hover {
color: var(--danger);
border-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
.add-color-btn {
padding: 0.75rem 1rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: var(--input-radius);
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
width: 100%;
}
.add-color-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.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 Drift</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-border-drift
</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 Drift</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 drift animation using the controls below</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste or upload the JavaScript code</li>
<li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-border-drift</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="drift-preview" data-border-drift="true">
<div class="preview-content">Interactive Border Drift Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-drift" title="Randomize (R)">🎲</button>
<div class="background-selector-wrapper">
<button class="preview-btn background-selector-btn" id="background-selector">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12,2 2,7 12,12 22,7"/>
<polyline points="2,17 12,22 22,17"/>
<polyline points="2,12 12,17 22,12"/>
</svg>
</button>
<input type="color" id="preview-background-picker" class="hidden-color-input" value="#000000" title="Change Preview Background (B)">
</div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Border 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="colors-list">
<!-- Color inputs will be dynamically generated -->
</div>
<button class="add-color-btn" id="add-color-btn">
<span>+</span> 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">
Border Width
<span class="help-tooltip" title="Width of the animated border">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="border-width-value">5</span>px</span>
<button class="reset-btn" onclick="resetParameter('border-width', 5)">↺</button>
</div>
</div>
<input type="range" id="border-width" min="1" max="6" step="0.5" value="5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Animation Duration
<span class="help-tooltip" title="Duration of each animation cycle">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="animation-duration-value">3</span>s</span>
<button class="reset-btn" onclick="resetParameter('animation-duration', 3)">↺</button>
</div>
</div>
<input type="range" id="animation-duration" min="1" max="10" step="0.5" value="3">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Border Opacity
<span class="help-tooltip" title="Transparency of the border animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="border-opacity-value">1</span></span>
<button class="reset-btn" onclick="resetParameter('border-opacity', 1)">↺</button>
</div>
</div>
<input type="range" id="border-opacity" min="0.1" max="1" step="0.05" value="1">
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let driftConfig = {
driftWidth: 5,
driftDuration: 3,
driftOpacity: 1,
driftColors: ["#ffaa40", "#0096FE", "#ffaa40"]
};
const defaultConfig = { ...driftConfig };
let activeDriftEffect = null;
function initBorderDriftFlow() {
const sections = document.querySelectorAll('[data-border-drift]:not([data-drift-initialized="true"])');
sections.forEach((section) => {
const driftWidth = section.hasAttribute('data-drift-width')
? parseFloat(section.getAttribute('data-drift-width'))
: driftConfig.driftWidth;
const driftDuration = section.hasAttribute('data-drift-duration')
? parseFloat(section.getAttribute('data-drift-duration').replace('s', ''))
: driftConfig.driftDuration;
const driftOpacity = section.hasAttribute('data-drift-opacity')
? parseFloat(section.getAttribute('data-drift-opacity'))
: driftConfig.driftOpacity;
const driftColors = section.hasAttribute('data-drift-colors')
? section.getAttribute('data-drift-colors').split(',').map(c => c.trim())
: driftConfig.driftColors;
const options = {
driftWidth,
driftDuration,
driftOpacity,
driftColors
};
setupBorderDriftFlow(section, options);
section.dataset.driftInitialized = 'true';
if (section.id === 'drift-preview') {
activeDriftEffect = { element: section, options };
driftConfig = {
driftWidth: options.driftWidth,
driftDuration: options.driftDuration,
driftOpacity: options.driftOpacity,
driftColors: [...options.driftColors]
};
}
});
}
function setupBorderDriftFlow(element, options) {
if (!element.id) {
element.id = `drift-border-${Math.random().toString(36).substr(2, 9)}`;
}
element.setAttribute('data-drift-width', options.driftWidth);
element.setAttribute('data-drift-duration', options.driftDuration + 's');
element.setAttribute('data-drift-opacity', options.driftOpacity);
element.setAttribute('data-drift-colors', options.driftColors.join(','));
element._driftBorderCleanup = createDriftEffect(element);
}
function createDriftEffect(element) {
const existingSvg = element.querySelector('svg.drift-effect');
if (existingSvg) existingSvg.remove();
const driftWidth = parseFloat(element.getAttribute("data-drift-width")) || 2;
const driftDuration = element.getAttribute("data-drift-duration") || "3s";
const driftOpacity = parseFloat(element.getAttribute("data-drift-opacity")) || 1;
const customColors = parseCustomColors(element.getAttribute("data-drift-colors"));
const colors = customColors || ["#ffaa40", "#0096FE", "#ffaa40"];
const styles = getComputedStyle(element);
if (styles.position === 'static') {
element.style.position = 'relative';
}
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.classList.add('drift-effect');
svg.style.cssText = `
position: absolute;
top: -1px;
left: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
pointer-events: none;
overflow: visible;
`;
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
svg.appendChild(defs);
const gradientIds = colors.map((_, i) => `drift-gradient-${element.id}-${i}`);
gradientIds.forEach((id, i) => {
const gradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
gradient.setAttribute("id", id);
gradient.setAttribute("gradientUnits", "objectBoundingBox");
const numStops = 4;
for (let j = 0; j <= numStops; j++) {
const stop = document.createElementNS("http://www.w3.org/2000/svg", "stop");
const offset = (j / numStops) * 100;
stop.setAttribute("offset", `${offset}%`);
const colorIndex = i % colors.length;
const nextColorIndex = (i + 1) % colors.length;
stop.setAttribute("stop-color", j <= numStops/2 ? colors[colorIndex] : colors[nextColorIndex]);
stop.setAttribute("stop-opacity", driftOpacity);
const baseDelay = i * (parseFloat(driftDuration) / colors.length);
stop.innerHTML = `
<animate attributeName="stop-opacity"
values="${driftOpacity};${driftOpacity * 0.7};${driftOpacity}"
dur="${driftDuration}"
begin="${baseDelay}s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.6 1; 0.4 0 0.6 1"/>
`;
gradient.appendChild(stop);
}
gradient.innerHTML += `
<animateTransform
attributeName="gradientTransform"
type="rotate"
values="0 0.5 0.5;360 0.5 0.5"
dur="${parseFloat(driftDuration) * 2}s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.6 1"/>
`;
defs.appendChild(gradient);
});
function createPath(borderRadii, inset = 0) {
const rect = element.getBoundingClientRect();
const [topLeft, topRight, bottomRight, bottomLeft] = borderRadii;
return `
M ${topLeft + inset},${inset}
H ${rect.width - topRight - inset}
A ${topRight},${topRight} 0 0 1 ${rect.width - inset},${topRight + inset}
V ${rect.height - bottomRight - inset}
A ${bottomRight},${bottomRight} 0 0 1 ${rect.width - bottomRight - inset},${rect.height - inset}
H ${bottomLeft + inset}
A ${bottomLeft},${bottomLeft} 0 0 1 ${inset},${rect.height - bottomLeft - inset}
V ${topLeft + inset}
A ${topLeft},${topLeft} 0 0 1 ${topLeft + inset},${inset}
`;
}
function updatePath() {
const borderRadiusStr = styles.borderRadius;
let borderRadii = borderRadiusStr.split('/').map(str =>
str.trim().split(' ').map(r => parseFloat(r))
).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]);
}
svg.querySelectorAll('path').forEach(p => p.remove());
const numPaths = 3;
const pathGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
for (let i = 0; i < numPaths; i++) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("fill", "none");
path.setAttribute("stroke", `url(#${gradientIds[i]})`);
path.setAttribute("stroke-width", driftWidth);
path.setAttribute("stroke-linecap", "round");
path.setAttribute("stroke-linejoin", "round");
path.setAttribute("d", createPath(borderRadii));
const baseDelay = i * (parseFloat(driftDuration) / numPaths);
const totalLength = path.getTotalLength();
path.style.strokeDasharray = `${totalLength * 0.6} ${totalLength * 0.4}`;
path.innerHTML = `
<animate
attributeName="stroke-dashoffset"
values="${totalLength};${-totalLength}"
dur="${driftDuration}"
begin="${baseDelay}s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.6 1"/>
`;
pathGroup.appendChild(path);
}
svg.appendChild(pathGroup);
}
element.appendChild(svg);
updatePath();
const throttledUpdatePath = throttle(updatePath, 16);
window.addEventListener('resize', throttledUpdatePath);
const resizeObserver = new ResizeObserver(throttledUpdatePath);
resizeObserver.observe(element);
return () => {
window.removeEventListener('resize', throttledUpdatePath);
resizeObserver.disconnect();
};
}
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 updateDriftPreview() {
const previews = ['drift-preview'];
previews.forEach(previewId => {
const preview = document.getElementById(previewId);
if (!preview) return;
if (preview._driftBorderCleanup) {
preview._driftBorderCleanup();
}
preview.setAttribute('data-border-drift', 'true');
preview.setAttribute('data-drift-width', driftConfig.driftWidth);
preview.setAttribute('data-drift-duration', driftConfig.driftDuration + 's');
preview.setAttribute('data-drift-opacity', driftConfig.driftOpacity);
preview.setAttribute('data-drift-colors', driftConfig.driftColors.join(','));
});
initBorderDriftFlow();
}
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-drift"
}
]
},
"label": "Border Drift Div"
},
{
"id": textId,
"name": "text-basic",
"parent": divId,
"children": [],
"settings": {
"text": "Border Drift",
"_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 Drift JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksJSON, null, 2);
}
function generateJavaScriptCode() {
const colorsString = driftConfig.driftColors.map(color => `"${color}"`).join(', ');
return `(function() {
const defaultConfig = {
driftWidth: ${driftConfig.driftWidth},
driftDuration: ${driftConfig.driftDuration},
driftOpacity: ${driftConfig.driftOpacity},
driftColors: [${colorsString}]
};
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 createDriftEffect(element) {
const existingSvg = element.querySelector('svg.drift-effect');
if (existingSvg) existingSvg.remove();
const driftWidth = parseFloat(element.getAttribute("data-drift-width")) || defaultConfig.driftWidth;
const driftDuration = element.getAttribute("data-drift-duration") || defaultConfig.driftDuration + "s";
const driftOpacity = parseFloat(element.getAttribute("data-drift-opacity")) || defaultConfig.driftOpacity;
const customColors = parseCustomColors(element.getAttribute("data-drift-colors"));
const colors = customColors || defaultConfig.driftColors;
const styles = getComputedStyle(element);
if (styles.position === 'static') {
element.style.position = 'relative';
}
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.classList.add('drift-effect');
svg.style.cssText = \`
position: absolute;
top: -1px;
left: -1px;
width: calc(100% + 2px);
height: calc(100% + 2px);
pointer-events: none;
overflow: visible;
\`;
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
svg.appendChild(defs);
const gradientIds = colors.map((_, i) => \`drift-gradient-\${element.id}-\${i}\`);
gradientIds.forEach((id, i) => {
const gradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient");
gradient.setAttribute("id", id);
gradient.setAttribute("gradientUnits", "objectBoundingBox");
const numStops = 4;
for (let j = 0; j <= numStops; j++) {
const stop = document.createElementNS("http://www.w3.org/2000/svg", "stop");
const offset = (j / numStops) * 100;
stop.setAttribute("offset", \`\${offset}%\`);
const colorIndex = i % colors.length;
const nextColorIndex = (i + 1) % colors.length;
stop.setAttribute("stop-color", j <= numStops/2 ? colors[colorIndex] : colors[nextColorIndex]);
stop.setAttribute("stop-opacity", driftOpacity);
const baseDelay = i * (parseFloat(driftDuration) / colors.length);
stop.innerHTML = \`
<animate attributeName="stop-opacity"
values="\${driftOpacity};\${driftOpacity * 0.7};\${driftOpacity}"
dur="\${driftDuration}"
begin="\${baseDelay}s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.6 1; 0.4 0 0.6 1"/>
\`;
gradient.appendChild(stop);
}
gradient.innerHTML += \`
<animateTransform
attributeName="gradientTransform"
type="rotate"
values="0 0.5 0.5;360 0.5 0.5"
dur="\${parseFloat(driftDuration) * 2}s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.6 1"/>
\`;
defs.appendChild(gradient);
});
function createPath(borderRadii, inset = 0) {
const rect = element.getBoundingClientRect();
const [topLeft, topRight, bottomRight, bottomLeft] = borderRadii;
return \`
M \${topLeft + inset},\${inset}
H \${rect.width - topRight - inset}
A \${topRight},\${topRight} 0 0 1 \${rect.width - inset},\${topRight + inset}
V \${rect.height - bottomRight - inset}
A \${bottomRight},\${bottomRight} 0 0 1 \${rect.width - bottomRight - inset},\${rect.height - inset}
H \${bottomLeft + inset}
A \${bottomLeft},\${bottomLeft} 0 0 1 \${inset},\${rect.height - bottomLeft - inset}
V \${topLeft + inset}
A \${topLeft},\${topLeft} 0 0 1 \${topLeft + inset},\${inset}
\`;
}
function updatePath() {
const borderRadiusStr = styles.borderRadius;
let borderRadii = borderRadiusStr.split('/').map(str =>
str.trim().split(' ').map(r => parseFloat(r))
).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]);
}
svg.querySelectorAll('path').forEach(p => p.remove());
const numPaths = 3;
const pathGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
for (let i = 0; i < numPaths; i++) {
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute("fill", "none");
path.setAttribute("stroke", \`url(#\${gradientIds[i]})\`);
path.setAttribute("stroke-width", driftWidth);
path.setAttribute("stroke-linecap", "round");
path.setAttribute("stroke-linejoin", "round");
path.setAttribute("d", createPath(borderRadii));
const baseDelay = i * (parseFloat(driftDuration) / numPaths);
const totalLength = path.getTotalLength();
path.style.strokeDasharray = \`\${totalLength * 0.6} \${totalLength * 0.4}\`;
path.innerHTML = \`
<animate
attributeName="stroke-dashoffset"
values="\${totalLength};\${-totalLength}"
dur="\${driftDuration}"
begin="\${baseDelay}s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.6 1"/>
\`;
pathGroup.appendChild(path);
}
svg.appendChild(pathGroup);
}
element.appendChild(svg);
updatePath();
const throttledUpdatePath = throttle(updatePath, 16);
window.addEventListener('resize', throttledUpdatePath);
const resizeObserver = new ResizeObserver(throttledUpdatePath);
resizeObserver.observe(element);
return () => {
window.removeEventListener('resize', throttledUpdatePath);
resizeObserver.disconnect();
};
}
function initializeDriftEffects() {
document.querySelectorAll('[data-border-drift]').forEach(element => {
if (!element.id) {
element.id = \`drift-border-\${Math.random().toString(36).substr(2, 9)}\`;
}
element._driftBorderCleanup = createDriftEffect(element);
});
}
const initWithDelay = () => {
setTimeout(initializeDriftEffects, 100);
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWithDelay);
} else {
initWithDelay();
}
window.addEventListener('load', initializeDriftEffects);
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-drift')) {
shouldInit = true;
} else if (node.querySelectorAll) {
const driftElements = node.querySelectorAll('[data-border-drift]');
if (driftElements.length > 0) {
shouldInit = true;
}
}
}
});
} else if (mutation.type === 'attributes' && mutation.attributeName === 'data-border-drift') {
shouldInit = true;
}
});
if (shouldInit) {
setTimeout(initializeDriftEffects, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['data-border-drift', 'data-drift-width', 'data-drift-duration', 'data-drift-opacity', 'data-drift-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');
});
}
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
};
}
function hslToHex(hsl) {
const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
if (!match) return null;
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let l = parseInt(match[3]) / 100;
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function isValidHex(hex) {
return /^#[0-9A-F]{6}$/i.test(hex);
}
function isValidHsl(hsl) {
return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
}
function formatHex(value) {
let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
if (!hex.startsWith('#')) {
hex = '#' + hex;
}
if (hex.length > 7) {
hex = hex.substring(0, 7);
}
return hex.toUpperCase();
}
function formatHsl(value) {
const cleanValue = value.replace(/[^\d,\s]/g, '');
const numbers = cleanValue.match(/\d+/g);
if (!numbers || numbers.length < 3) {
const partialMatch = value.match(/(\d+)/g);
if (partialMatch && partialMatch.length >= 1) {
const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
return `hsl(${h}, ${s}%, ${l}%)`;
}
return value;
}
let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
return `hsl(${h}, ${s}%, ${l}%)`;
}
window.resetParameter = function(parameterId, defaultValue) {
const element = document.getElementById(parameterId);
if (element) {
element.value = defaultValue;
const valueElement = document.getElementById(`${parameterId}-value`);
if (valueElement) {
valueElement.textContent = defaultValue;
}
switch (parameterId) {
case 'border-width':
driftConfig.driftWidth = defaultValue;
break;
case 'animation-duration':
driftConfig.driftDuration = defaultValue;
break;
case 'border-opacity':
driftConfig.driftOpacity = defaultValue;
break;
}
updateDriftPreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function addColorInput(color) {
if (color === undefined) {
color = generateRandomColor();
driftConfig.driftColors.push(color);
}
const index = driftConfig.driftColors.indexOf(color);
const colorsList = document.getElementById('colors-list');
const colorRow = document.createElement('div');
colorRow.className = 'color-row';
const colorPickerContainer = document.createElement('div');
colorPickerContainer.className = 'color-picker-container';
colorPickerContainer.style.setProperty('--selected-color', color);
const colorInput = document.createElement('input');
colorInput.type = 'color';
colorInput.id = `drift-color-${index}`;
colorInput.value = color;
const hexInputGroup = document.createElement('div');
hexInputGroup.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.id = `drift-color-hex-${index}`;
hexInput.value = color;
hexInput.placeholder = '#FFFFFF';
const hslInputGroup = document.createElement('div');
hslInputGroup.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.id = `drift-color-hsl-${index}`;
hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
hslInput.placeholder = 'hsl(0, 100%, 50%)';
const colorActions = document.createElement('div');
colorActions.className = 'color-actions';
const randomBtn = document.createElement('button');
randomBtn.className = 'color-random-btn';
randomBtn.innerHTML = '🎲';
randomBtn.title = 'Random Color';
randomBtn.addEventListener('click', () => randomizeColor(index));
colorPickerContainer.appendChild(colorInput);
hexInputGroup.appendChild(hexLabel);
hexInputGroup.appendChild(hexInput);
hslInputGroup.appendChild(hslLabel);
hslInputGroup.appendChild(hslInput);
colorActions.appendChild(randomBtn);
if (driftConfig.driftColors.length > 2) {
const removeBtn = document.createElement('button');
removeBtn.className = 'color-remove-btn';
removeBtn.innerHTML = '×';
removeBtn.title = 'Remove Color';
removeBtn.addEventListener('click', () => removeColorInput(index));
colorActions.appendChild(removeBtn);
}
colorRow.appendChild(colorPickerContainer);
colorRow.appendChild(hexInputGroup);
colorRow.appendChild(hslInputGroup);
colorRow.appendChild(colorActions);
colorInput.addEventListener('input', () => {
const newColor = colorInput.value;
driftConfig.driftColors[index] = newColor;
hexInput.value = newColor;
hslInput.value = `hsl(${hexToHsl(newColor).h}, ${hexToHsl(newColor).s}%, ${hexToHsl(newColor).l}%)`;
colorPickerContainer.style.setProperty('--selected-color', newColor);
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
updateDriftPreview();
});
hexInput.addEventListener('input', (e) => {
let hex = e.target.value;
hex = formatHex(hex);
e.target.value = hex;
if (isValidHex(hex)) {
colorInput.value = hex;
hslInput.value = `hsl(${hexToHsl(hex).h}, ${hexToHsl(hex).s}%, ${hexToHsl(hex).l}%)`;
driftConfig.driftColors[index] = hex;
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
colorPickerContainer.style.setProperty('--selected-color', hex);
updateDriftPreview();
} else {
e.target.classList.add('invalid');
}
});
hexInput.addEventListener('blur', (e) => {
if (!isValidHex(e.target.value)) {
e.target.value = colorInput.value;
e.target.classList.remove('invalid');
}
});
hslInput.addEventListener('input', (e) => {
let hsl = e.target.value;
if (isValidHsl(hsl)) {
const hex = hslToHex(hsl);
if (hex) {
colorInput.value = hex;
hexInput.value = hex;
driftConfig.driftColors[index] = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
colorPickerContainer.style.setProperty('--selected-color', hex);
updateDriftPreview();
return;
}
}
e.target.classList.add('invalid');
});
hslInput.addEventListener('blur', (e) => {
let hsl = e.target.value;
if (!isValidHsl(hsl) && hsl.trim()) {
const formatted = formatHsl(hsl);
if (isValidHsl(formatted)) {
e.target.value = formatted;
const hex = hslToHex(formatted);
if (hex) {
colorInput.value = hex;
hexInput.value = hex;
driftConfig.driftColors[index] = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
colorPickerContainer.style.setProperty('--selected-color', hex);
updateDriftPreview();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
e.target.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
e.target.classList.remove('invalid');
}
});
colorsList.appendChild(colorRow);
}
function removeColorInput(index) {
if (driftConfig.driftColors.length <= 2) return;
driftConfig.driftColors.splice(index, 1);
initializeColorInputs();
updateDriftPreview();
showNotification('Color removed');
}
function randomizeColor(index) {
const newColor = generateRandomColor();
driftConfig.driftColors[index] = newColor;
const colorInput = document.getElementById(`drift-color-${index}`);
const hexInput = document.getElementById(`drift-color-hex-${index}`);
const hslInput = document.getElementById(`drift-color-hsl-${index}`);
const colorPicker = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorInput.value = newColor;
hexInput.value = newColor;
hslInput.value = `hsl(${hexToHsl(newColor).h}, ${hexToHsl(newColor).s}%, ${hexToHsl(newColor).l}%)`;
colorPicker.style.setProperty('--selected-color', newColor);
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
updateDriftPreview();
showNotification(`Color ${index + 1} randomized!`);
}
function initializeColorInputs() {
const colorsList = document.getElementById('colors-list');
colorsList.innerHTML = '';
driftConfig.driftColors.forEach((color, index) => {
addColorInput(color);
});
}
function generateRandomDrift() {
driftConfig.driftWidth = Math.random() * 5 + 1;
driftConfig.driftDuration = Math.random() * 9 + 1;
driftConfig.driftOpacity = Math.random() * 0.9 + 0.1;
const numColors = Math.floor(Math.random() * 3) + 2;
driftConfig.driftColors = [];
for (let i = 0; i < numColors; i++) {
driftConfig.driftColors.push(generateRandomColor());
}
document.getElementById('border-width').value = driftConfig.driftWidth;
document.getElementById('animation-duration').value = driftConfig.driftDuration;
document.getElementById('border-opacity').value = driftConfig.driftOpacity;
document.getElementById('border-width-value').textContent = driftConfig.driftWidth;
document.getElementById('animation-duration-value').textContent = driftConfig.driftDuration;
document.getElementById('border-opacity-value').textContent = driftConfig.driftOpacity;
initializeColorInputs();
updateDriftPreview();
showNotification('Random border drift generated!');
}
function initializeUI() {
initBorderDriftFlow();
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-drift');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-drift').addEventListener('click', () => {
generateRandomDrift();
});
const backgroundPicker = document.getElementById('preview-background-picker');
const previewContainer = document.getElementById('drift-preview');
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', () => {
driftConfig.driftColors = [...defaultConfig.driftColors];
initializeColorInputs();
updateDriftPreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
driftConfig.driftWidth = defaultConfig.driftWidth;
driftConfig.driftDuration = defaultConfig.driftDuration;
driftConfig.driftOpacity = defaultConfig.driftOpacity;
document.getElementById('border-width').value = defaultConfig.driftWidth;
document.getElementById('animation-duration').value = defaultConfig.driftDuration;
document.getElementById('border-opacity').value = defaultConfig.driftOpacity;
document.getElementById('border-width-value').textContent = defaultConfig.driftWidth;
document.getElementById('animation-duration-value').textContent = defaultConfig.driftDuration;
document.getElementById('border-opacity-value').textContent = defaultConfig.driftOpacity;
updateDriftPreview();
showNotification('Animation settings reset');
});
document.getElementById('add-color-btn').addEventListener('click', () => {
addColorInput();
updateDriftPreview();
showNotification('Color added');
});
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 'border-width':
driftConfig.driftWidth = parseFloat(input.value);
break;
case 'animation-duration':
driftConfig.driftDuration = parseFloat(input.value);
break;
case 'border-opacity':
driftConfig.driftOpacity = parseFloat(input.value);
break;
}
updateDriftPreview();
});
});
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':
generateRandomDrift();
break;
case 'b':
document.getElementById('preview-background-picker').click();
break;
}
}
});
initializeColorInputs();
setTimeout(() => {
showNotification('BricksFusion Border Drift Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-drift-config', JSON.stringify(driftConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-drift-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(driftConfig, savedConfig);
document.getElementById('border-width').value = savedConfig.driftWidth;
document.getElementById('animation-duration').value = savedConfig.driftDuration;
document.getElementById('border-opacity').value = savedConfig.driftOpacity;
document.getElementById('border-width-value').textContent = savedConfig.driftWidth;
document.getElementById('animation-duration-value').textContent = savedConfig.driftDuration;
document.getElementById('border-opacity-value').textContent = savedConfig.driftOpacity;
initializeColorInputs();
updateDriftPreview();
}
} catch (e) {
}
}
const originalUpdateDriftPreview = updateDriftPreview;
updateDriftPreview = function() {
originalUpdateDriftPreview();
saveConfiguration();
};
loadConfiguration();
}
initializeUI();
});
</script>
</body>
</html>
Border Drift
Creates an animated flowing border that drifts around your element with smooth color transitions. Perfect for cards, buttons, and featured content.
Watch the border flow
Colors
Choose at least 2 colors for the flowing border. The colors blend smoothly as they drift around the element. Add more colors to create longer, more varied transitions.
Default: Orange, Blue, Orange
Appearance
Thickness of the animated border. Thinner borders are subtle and elegant, thicker borders make a bold statement.
Default: 5
How visible the border is. 1.0 is fully solid, lower values create a more subtle, ghostly effect.
Default: 1.0
Animation
How long it takes for the border to complete one full loop around the element. Shorter is faster and more energetic, longer is smooth and relaxing.
Default: 3 seconds
Performance
This element uses SVG animations which are highly efficient. The border automatically adapts to rounded corners and resizes smoothly. Safe to use multiple times on the same page.