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>Weave Motion Configurator - BricksFusion</title>
<style>
:root {
--background: #000;
--card-bg: #1e1e1e;
--card-bg-hover: #252525;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--accent: #ef6013;
--accent-hover: #c64c0c;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
--thumb: #ef6013;
--card-radius: 16px;
--input-radius: 8px;
--button-radius: 12px;
--transition: all 0.25s ease;
--font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
--action-bar-height: 70px;
--success: #28a745;
--warning: #ffc107;
--danger: #dc3545;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
background-color: var(--background);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-bottom: var(--action-bar-height);
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--action-bar-height);
background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
border-top: 1px solid var(--border);
z-index: 1000;
display: flex;
align-items: center;
padding: 0 1.5rem;
gap: 1rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.breadcrumb-item {
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: 500;
text-decoration: none;
transition: var(--transition);
padding: 0.5rem 0.75rem;
border-radius: 6px;
}
.breadcrumb-item:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.05);
}
.breadcrumb-item.active {
color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.breadcrumb-separator {
color: var(--text-secondary);
font-size: var(--text-xs);
opacity: 0.5;
}
.action-buttons {
display: flex;
align-items: center;
gap: 0.75rem;
}
.action-btn {
padding: 0.6rem 1rem;
background-color: var(--card-bg);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
border: 1px solid var(--border);
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
}
.action-btn:hover {
background-color: var(--card-bg-hover);
border-color: var(--accent);
transform: translateY(-1px);
}
.action-btn.primary {
background: linear-gradient(90deg, var(--accent), #ff8c51);
border-color: var(--accent);
color: white;
}
.action-btn.primary:hover {
background: linear-gradient(90deg, var(--accent-hover), #e67a3f);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}
.data-attribute-display {
background-color: rgba(50, 50, 50, 0.8);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
cursor: pointer;
transition: var(--transition);
user-select: all;
}
.data-attribute-display:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.page-header {
text-align: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--accent), #ff8c51);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-subtitle {
font-size: var(--text-s);
color: var(--text-secondary);
font-weight: 500;
}
.instructions-toggle {
margin-bottom: 2rem;
}
.instructions-card {
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: var(--transition);
}
.instructions-header {
padding: 1rem 1.5rem;
cursor: pointer;
transition: var(--transition);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid transparent;
}
.instructions-header:hover {
background-color: var(--card-bg-hover);
}
.instructions-card.expanded .instructions-header {
border-bottom-color: var(--border);
}
.instructions-title {
font-size: var(--text-s);
font-weight: 600;
}
.toggle-icon {
font-size: 1.2em;
transition: transform 0.3s ease;
}
.toggle-icon.expanded {
transform: rotate(180deg);
}
.instructions-content {
padding: 0 1.5rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.instructions-content.show {
max-height: 500px;
padding: 1.5rem;
}
.instructions-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.how-to-use ol {
padding-left: 1.5rem;
}
.how-to-use li {
margin-bottom: 0.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.6;
}
.how-to-use strong {
color: var(--text-primary);
font-weight: 600;
}
.how-to-use code {
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
}
.content {
display: grid;
grid-template-columns: 1fr 500px;
gap: 2rem;
align-items: start;
}
.preview-section {
position: sticky;
top: 2rem;
}
.controls-section {
max-width: 500px;
}
.card {
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.preview-container {
height: 400px;
width: 100%;
position: relative;
overflow: hidden;
border-radius: var(--card-radius);
background-color: #252525;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.preview-content {
color: white;
text-align: center;
font-weight: bold;
font-size: var(--text-s);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
.preview-controls {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
gap: 0.5rem;
z-index: 10;
}
.preview-btn {
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
font-size: var(--text-xs);
backdrop-filter: blur(5px);
}
.preview-btn:hover {
background-color: var(--accent);
border-color: var(--accent);
}
.card-heading {
padding: 1rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
letter-spacing: 0.3px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-actions {
display: flex;
gap: 0.5rem;
}
.card-action-btn {
padding: 0.4rem 0.8rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
}
.card-action-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.card-content {
padding: 1.5rem;
}
.control-group {
margin-bottom: 1.5rem;
position: relative;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.label-text {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.help-tooltip {
cursor: help;
opacity: 0.7;
transition: var(--transition);
}
.help-tooltip:hover {
opacity: 1;
color: var(--accent);
}
.value-display {
display: flex;
align-items: center;
gap: 0.5rem;
}
.value-text {
font-size: var(--text-xs);
color: var(--text-secondary);
background-color: rgba(50, 50, 50, 0.5);
padding: 2px 8px;
border-radius: 4px;
min-width: 45px;
text-align: center;
}
.reset-btn {
padding: 0.2rem 0.4rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 10px;
transition: var(--transition);
}
.reset-btn:hover {
color: var(--danger);
border-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
background: var(--track);
border-radius: 3px;
outline: none;
margin: 0.8rem 0;
position: relative;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--thumb);
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 10px rgba(239, 96, 19, 0.5);
}
.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: linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
background-size: 8px 8px;
background-position: 0 0, 0 4px, 4px -4px, -4px 0px;
}
.color-picker-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--current-color, #7b7481);
border-radius: 6px;
z-index: 1;
}
.color-picker-container:hover {
border-color: var(--accent);
transform: scale(1.05);
}
input[type="color"] {
position: absolute;
top: -2px;
left: -2px;
width: calc(100% + 4px);
height: calc(100% + 4px);
border: none;
cursor: pointer;
background: transparent;
z-index: 2;
opacity: 0;
}
.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;
}
.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/corebackground/" class="breadcrumb-item">Core Backgrounds</a>
<span class="breadcrumb-separator">›</span>
<span class="breadcrumb-item active">Weave Motion</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-weave-motion
</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">Weave Motion</h1>
<p class="page-subtitle">Interactive texture 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 weave motion 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-weave-motion</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="weave-motion-preview" data-weave-motion="true">
<div class="preview-content">Interactive Weave Motion Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-weave-motion" title="Randomize (R)">🎲</button>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Weave 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-row">
<div class="color-picker-container">
<input type="color" id="weave-color" value="#7b7481">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="weave-color-hex" value="#7b7481" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="weave-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Motion Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-motion" title="Reset Motion Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Animation Speed
<span class="help-tooltip" title="Controls how fast the weave motion animates">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="speed-value">0.02</span></span>
<button class="reset-btn" onclick="resetParameter('speed', 0.02)">↺</button>
</div>
</div>
<input type="range" id="speed" min="0.005" max="0.08" step="0.005" value="0.02">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Wave Frequency
<span class="help-tooltip" title="Controls the density of the wave pattern">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="frequency-value">5.0</span></span>
<button class="reset-btn" onclick="resetParameter('frequency', 5.0)">↺</button>
</div>
</div>
<input type="range" id="frequency" min="2.0" max="12.0" step="0.5" value="5.0">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Wave Amplitude
<span class="help-tooltip" title="Controls the strength of the wave effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="amplitude-value">0.03</span></span>
<button class="reset-btn" onclick="resetParameter('amplitude', 0.03)">↺</button>
</div>
</div>
<input type="range" id="amplitude" min="0.01" max="0.1" step="0.005" value="0.03">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Pattern Scale
<span class="help-tooltip" title="Controls the size of the weave pattern">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="scale-value">2.0</span></span>
<button class="reset-btn" onclick="resetParameter('scale', 2.0)">↺</button>
</div>
</div>
<input type="range" id="scale" min="1.0" max="5.0" step="0.2" value="2.0">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Appearance Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-appearance" title="Reset Appearance Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Opacity
<span class="help-tooltip" title="Controls the transparency of the weave pattern">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="opacity-value">0.6</span></span>
<button class="reset-btn" onclick="resetParameter('opacity', 0.6)">↺</button>
</div>
</div>
<input type="range" id="opacity" min="0.1" max="1.0" step="0.05" value="0.6">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Noise Intensity
<span class="help-tooltip" title="Controls the amount of texture noise in the pattern">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="noise-intensity-value">0.8</span></span>
<button class="reset-btn" onclick="resetParameter('noise-intensity', 0.8)">↺</button>
</div>
</div>
<input type="range" id="noise-intensity" min="0.1" max="2.0" step="0.1" value="0.8">
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let weaveMotionConfig = {
speed: 0.02,
frequency: 5.0,
amplitude: 0.03,
scale: 2.0,
opacity: 0.6,
noiseIntensity: 0.8,
weaveColor: "#7b7481"
};
const defaultConfig = { ...weaveMotionConfig };
let activeWeaveMotion = null;
class WeaveMotion {
constructor(element) {
this.element = element;
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
this.canvas.width = 1;
this.canvas.height = 1;
this.speed = parseFloat(element.getAttribute('data-weave-motion-speed') || '0.02');
this.frequency = parseFloat(element.getAttribute('data-weave-motion-frequency') || '5.0');
this.amplitude = parseFloat(element.getAttribute('data-weave-motion-amplitude') || '0.03');
this.scale = parseFloat(element.getAttribute('data-weave-motion-scale') || '2.0');
this.opacity = parseFloat(element.getAttribute('data-weave-motion-opacity') || '0.6');
this.noiseIntensity = parseFloat(element.getAttribute('data-weave-motion-noise-intensity') || '0.8');
this.weaveColor = this.hexToRgb(element.getAttribute('data-weave-motion-weave-color') || '#7b7481');
this.time = 0;
this.animationFrame = null;
this.isMounted = true;
this.isVisible = true;
this.container = document.createElement('div');
this.container.style.position = 'absolute';
this.container.style.top = '0';
this.container.style.left = '0';
this.container.style.width = '100%';
this.container.style.height = '100%';
this.container.style.zIndex = '1';
this.container.style.pointerEvents = 'none';
this.container.className = 'weave-motion-canvas';
this.canvas.style.position = 'absolute';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.container.appendChild(this.canvas);
this.element.style.position = 'relative';
this.element.appendChild(this.container);
this.setup();
this.bindEvents();
}
updateConfig(config) {
this.speed = config.speed;
this.frequency = config.frequency;
this.amplitude = config.amplitude;
this.scale = config.scale;
this.opacity = config.opacity;
this.noiseIntensity = config.noiseIntensity;
this.weaveColor = this.hexToRgb(config.weaveColor);
}
hexToRgb(hex) {
hex = hex.replace(/^#/, '');
let r, g, b;
if(hex.length === 3) {
r = parseInt(hex.charAt(0) + hex.charAt(0), 16);
g = parseInt(hex.charAt(1) + hex.charAt(1), 16);
b = parseInt(hex.charAt(2) + hex.charAt(2), 16);
} else {
r = parseInt(hex.substring(0, 2), 16);
g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
}
return { r, g, b };
}
noise(x, y) {
const G = 2.71828;
const rx = G * Math.sin(G * x);
const ry = G * Math.sin(G * y);
return (rx * ry * (1 + x)) % 1;
}
setup() {
this.resize();
this.animate();
}
resize() {
const rect = this.element.getBoundingClientRect();
this.canvas.width = Math.max(1, rect.width);
this.canvas.height = Math.max(1, rect.height);
}
animate() {
if (!this.isMounted || !this.ctx) return;
const width = this.canvas.width;
const height = this.canvas.height;
if (this.isVisible && width > 0 && height > 0) {
this.ctx.clearRect(0, 0, width, height);
const imageData = this.ctx.createImageData(width, height);
const data = imageData.data;
for (let x = 0; x < width; x += 2) {
for (let y = 0; y < height; y += 2) {
const u = (x / width) * this.scale;
const v = (y / height) * this.scale;
const tOffset = this.speed * this.time;
let tex_x = u;
let tex_y = v + this.amplitude * Math.sin(8.0 * tex_x - tOffset);
const pattern = 0.6 + 0.4 * Math.sin(
this.frequency * (tex_x + tex_y +
Math.cos(3.0 * tex_x + 5.0 * tex_y) +
0.02 * tOffset) +
Math.sin(20.0 * (tex_x + tex_y - 0.1 * tOffset))
);
const rnd = this.noise(x, y);
const intensity = Math.max(0, pattern - rnd / 15.0 * this.noiseIntensity);
const r = Math.floor(this.weaveColor.r * intensity);
const g = Math.floor(this.weaveColor.g * intensity);
const b = Math.floor(this.weaveColor.b * intensity);
const a = Math.floor(255 * intensity * this.opacity);
const index = (y * width + x) * 4;
if (index < data.length) {
data[index] = r;
data[index + 1] = g;
data[index + 2] = b;
data[index + 3] = a;
}
}
}
this.ctx.putImageData(imageData, 0, 0);
}
this.time += 1;
this.animationFrame = requestAnimationFrame(() => this.animate());
}
bindEvents() {
const resizeHandler = () => this.resize();
window.addEventListener('resize', resizeHandler);
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.target === this.element) {
this.isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
}
});
}, { threshold: 0, rootMargin: '100px' });
observer.observe(this.element);
this.destroy = () => {
this.isMounted = false;
if (this.animationFrame) {
cancelAnimationFrame(this.animationFrame);
}
window.removeEventListener('resize', resizeHandler);
observer.disconnect();
if (this.container && this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
}
}
function initWeaveMotion() {
const weaveMotionElements = document.querySelectorAll('[data-weave-motion]:not([data-weave-motion-initialized="true"])');
weaveMotionElements.forEach(element => {
const instance = new WeaveMotion(element);
element.dataset.weaveMotionInitialized = 'true';
element._weaveMotionInstance = instance;
if (element.id === 'weave-motion-preview') {
activeWeaveMotion = instance;
}
});
}
function updateWeaveMotionPreview() {
if (activeWeaveMotion) {
activeWeaveMotion.updateConfig(weaveMotionConfig);
}
}
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 `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${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 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
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const codeId = generateUniqueId();
const attributeId = generateUniqueId();
// Obtener el código JavaScript generado
const jsCode = generateJavaScriptCode();
// Crear la estructura JSON completa
const fullSectionData = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_attributes": [
{
"id": attributeId,
"name": "data-weave-motion"
}
],
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
}
},
"label": "Weave Section"
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": {}
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Weave JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(fullSectionData, null, 2);
}
function generateJavaScriptCode() {
const rgbColor = hexToRgb(weaveMotionConfig.weaveColor);
return `(function() {
'use strict';
if (window.WeaveMotion && window.WeaveMotion.initialized) {
return;
}
const initializedContainers = new WeakSet();
function injectCSS() {
if (document.getElementById('weave-motion-styles')) return;
const style = document.createElement('style');
style.id = 'weave-motion-styles';
style.textContent = \`
[data-weave-motion] {
position: relative !important;
overflow: hidden !important;
}
.weave-motion-canvas {
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
z-index: 1 !important;
pointer-events: none !important;
}
\`;
document.head.appendChild(style);
}
function createWeaveMotion(container) {
if (initializedContainers.has(container)) {
return;
}
initializedContainers.add(container);
const config = parseConfig(container);
const canvas = document.createElement('canvas');
canvas.className = 'weave-motion-canvas';
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
if (!ctx) {
return;
}
let animationFrame = null;
let isMounted = true;
let isVisible = true;
let time = 0;
const speed = config.speed || ${weaveMotionConfig.speed};
const frequency = config.frequency || ${weaveMotionConfig.frequency};
const amplitude = config.amplitude || ${weaveMotionConfig.amplitude};
const scale = config.scale || ${weaveMotionConfig.scale};
const opacity = config.opacity || ${weaveMotionConfig.opacity};
const noiseIntensity = config.noiseIntensity || ${weaveMotionConfig.noiseIntensity};
const weaveColor = config.weaveColor || { r: ${rgbColor.r}, g: ${rgbColor.g}, b: ${rgbColor.b} };
function noise(x, y) {
const G = 2.71828;
const rx = G * Math.sin(G * x);
const ry = G * Math.sin(G * y);
return (rx * ry * (1 + x)) % 1;
}
function resizeCanvas() {
if (!container || !canvas || !ctx) return;
const rect = container.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
}
function animate() {
if (!isMounted || !ctx) return;
const width = canvas.width;
const height = canvas.height;
if (isVisible && width > 0 && height > 0) {
ctx.clearRect(0, 0, width, height);
const imageData = ctx.createImageData(width, height);
const data = imageData.data;
for (let x = 0; x < width; x += 2) {
for (let y = 0; y < height; y += 2) {
const u = (x / width) * scale;
const v = (y / height) * scale;
const tOffset = speed * time;
let tex_x = u;
let tex_y = v + amplitude * Math.sin(8.0 * tex_x - tOffset);
const pattern = 0.6 + 0.4 * Math.sin(
frequency * (tex_x + tex_y +
Math.cos(3.0 * tex_x + 5.0 * tex_y) +
0.02 * tOffset) +
Math.sin(20.0 * (tex_x + tex_y - 0.1 * tOffset))
);
const rnd = noise(x, y);
const intensity = Math.max(0, pattern - rnd / 15.0 * noiseIntensity);
const r = Math.floor(weaveColor.r * intensity);
const g = Math.floor(weaveColor.g * intensity);
const b = Math.floor(weaveColor.b * intensity);
const a = Math.floor(255 * intensity * opacity);
const index = (y * width + x) * 4;
if (index < data.length) {
data[index] = r;
data[index + 1] = g;
data[index + 2] = b;
data[index + 3] = a;
}
}
}
ctx.putImageData(imageData, 0, 0);
}
time += 1;
animationFrame = requestAnimationFrame(animate);
}
let resizeTimeout;
function handleResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
resizeCanvas();
}, 100);
}
resizeCanvas();
animate();
window.addEventListener('resize', handleResize);
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.target === container) {
isVisible = entry.isIntersecting || entry.intersectionRatio > 0;
}
});
}, { threshold: 0, rootMargin: '100px' });
observer.observe(container);
function cleanup() {
isMounted = false;
if (animationFrame) {
cancelAnimationFrame(animationFrame);
}
if (resizeTimeout) {
clearTimeout(resizeTimeout);
}
window.removeEventListener('resize', handleResize);
observer.disconnect();
}
container._weaveMotionCleanup = cleanup;
}
function parseConfig(element) {
const config = {};
const speed = element.getAttribute('data-speed');
if (speed) config.speed = parseFloat(speed) || ${weaveMotionConfig.speed};
const frequency = element.getAttribute('data-frequency');
if (frequency) config.frequency = parseFloat(frequency) || ${weaveMotionConfig.frequency};
const amplitude = element.getAttribute('data-amplitude');
if (amplitude) config.amplitude = parseFloat(amplitude) || ${weaveMotionConfig.amplitude};
const scale = element.getAttribute('data-scale');
if (scale) config.scale = parseFloat(scale) || ${weaveMotionConfig.scale};
const opacity = element.getAttribute('data-opacity');
if (opacity) config.opacity = parseFloat(opacity) || ${weaveMotionConfig.opacity};
const noiseIntensity = element.getAttribute('data-noise-intensity');
if (noiseIntensity) config.noiseIntensity = parseFloat(noiseIntensity) || ${weaveMotionConfig.noiseIntensity};
const weaveColor = element.getAttribute('data-weave-color');
if (weaveColor) {
try {
config.weaveColor = JSON.parse(weaveColor);
} catch (e) {
}
}
return config;
}
function init() {
injectCSS();
const elements = document.querySelectorAll('[data-weave-motion]');
elements.forEach(element => {
createWeaveMotion(element);
});
}
function safeInit() {
if (window.WeaveMotion && window.WeaveMotion.initialized) {
return;
}
init();
window.WeaveMotion = {
init: init,
create: createWeaveMotion,
initialized: true
};
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', safeInit);
} else {
safeInit();
}
document.addEventListener('bricks/content_updated', () => {
const elements = document.querySelectorAll('[data-weave-motion]');
elements.forEach(element => {
if (!initializedContainers.has(element)) {
createWeaveMotion(element);
}
});
});
window.addEventListener('beforeunload', () => {
document.querySelectorAll('[data-weave-motion]').forEach(container => {
if (container._weaveMotionCleanup) {
container._weaveMotionCleanup();
}
});
});
})();`;
}
function hexToRgb(hex) {
hex = hex.replace(/^#/, '');
let r, g, b;
if(hex.length === 3) {
r = parseInt(hex.charAt(0) + hex.charAt(0), 16);
g = parseInt(hex.charAt(1) + hex.charAt(1), 16);
b = parseInt(hex.charAt(2) + hex.charAt(2), 16);
} else {
r = parseInt(hex.substring(0, 2), 16);
g = parseInt(hex.substring(2, 4), 16);
b = parseInt(hex.substring(4, 6), 16);
}
return { r, g, b };
}
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 'speed':
weaveMotionConfig.speed = defaultValue;
break;
case 'frequency':
weaveMotionConfig.frequency = defaultValue;
break;
case 'amplitude':
weaveMotionConfig.amplitude = defaultValue;
break;
case 'scale':
weaveMotionConfig.scale = defaultValue;
break;
case 'opacity':
weaveMotionConfig.opacity = defaultValue;
break;
case 'noise-intensity':
weaveMotionConfig.noiseIntensity = defaultValue;
break;
}
updateWeaveMotionPreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function generateRandomWeaveMotion() {
const randomColor = generateRandomColor();
weaveMotionConfig.weaveColor = randomColor;
weaveMotionConfig.speed = Math.random() * 0.075 + 0.005;
weaveMotionConfig.frequency = Math.random() * 10 + 2;
weaveMotionConfig.amplitude = Math.random() * 0.09 + 0.01;
document.getElementById('weave-color').value = randomColor;
document.getElementById('weave-color-hex').value = randomColor;
document.getElementById('weave-color-hsl').value = hexToHsl(randomColor);
document.getElementById('speed').value = weaveMotionConfig.speed;
document.getElementById('frequency').value = weaveMotionConfig.frequency;
document.getElementById('amplitude').value = weaveMotionConfig.amplitude;
document.getElementById('speed-value').textContent = weaveMotionConfig.speed.toFixed(3);
document.getElementById('frequency-value').textContent = weaveMotionConfig.frequency.toFixed(1);
document.getElementById('amplitude-value').textContent = weaveMotionConfig.amplitude.toFixed(3);
const container = document.getElementById('weave-color').closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', randomColor);
}
updateWeaveMotionPreview();
showNotification('Random weave motion generated!');
}
function updateColorInputs() {
const color = weaveMotionConfig.weaveColor;
const colorInput = document.getElementById('weave-color');
const hexInput = document.getElementById('weave-color-hex');
const hslInput = document.getElementById('weave-color-hsl');
if (colorInput && hexInput && hslInput) {
colorInput.value = color;
hexInput.value = color;
hslInput.value = hexToHsl(color);
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', color);
}
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
}
}
function initializeUI() {
initWeaveMotion();
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-weave-motion');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-weave-motion').addEventListener('click', () => {
generateRandomWeaveMotion();
});
document.getElementById('reset-colors').addEventListener('click', () => {
weaveMotionConfig.weaveColor = defaultConfig.weaveColor;
updateColorInputs();
updateWeaveMotionPreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-motion').addEventListener('click', () => {
weaveMotionConfig.speed = defaultConfig.speed;
weaveMotionConfig.frequency = defaultConfig.frequency;
weaveMotionConfig.amplitude = defaultConfig.amplitude;
weaveMotionConfig.scale = defaultConfig.scale;
document.getElementById('speed').value = defaultConfig.speed;
document.getElementById('frequency').value = defaultConfig.frequency;
document.getElementById('amplitude').value = defaultConfig.amplitude;
document.getElementById('scale').value = defaultConfig.scale;
document.getElementById('speed-value').textContent = defaultConfig.speed;
document.getElementById('frequency-value').textContent = defaultConfig.frequency;
document.getElementById('amplitude-value').textContent = defaultConfig.amplitude;
document.getElementById('scale-value').textContent = defaultConfig.scale;
updateWeaveMotionPreview();
showNotification('Motion settings reset');
});
document.getElementById('reset-appearance').addEventListener('click', () => {
weaveMotionConfig.opacity = defaultConfig.opacity;
weaveMotionConfig.noiseIntensity = defaultConfig.noiseIntensity;
document.getElementById('opacity').value = defaultConfig.opacity;
document.getElementById('noise-intensity').value = defaultConfig.noiseIntensity;
document.getElementById('opacity-value').textContent = defaultConfig.opacity;
document.getElementById('noise-intensity-value').textContent = defaultConfig.noiseIntensity;
updateWeaveMotionPreview();
showNotification('Appearance settings reset');
});
const colorInput = document.getElementById('weave-color');
const hexInput = document.getElementById('weave-color-hex');
const hslInput = document.getElementById('weave-color-hsl');
hslInput.value = hexToHsl(colorInput.value);
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', colorInput.value);
}
colorInput.addEventListener('input', () => {
const color = colorInput.value;
hexInput.value = color;
hslInput.value = hexToHsl(color);
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', color);
}
weaveMotionConfig.weaveColor = color;
updateWeaveMotionPreview();
});
hexInput.addEventListener('input', (e) => {
let hex = e.target.value;
hex = formatHex(hex);
e.target.value = hex;
if (isValidHex(hex)) {
colorInput.value = hex;
hslInput.value = hexToHsl(hex);
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', hex);
}
weaveMotionConfig.weaveColor = hex;
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
updateWeaveMotionPreview();
} 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;
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', hex);
}
weaveMotionConfig.weaveColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateWeaveMotionPreview();
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;
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', hex);
}
weaveMotionConfig.weaveColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateWeaveMotionPreview();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
e.target.value = hexToHsl(colorInput.value);
e.target.classList.remove('invalid');
}
});
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 'speed':
weaveMotionConfig.speed = parseFloat(input.value);
break;
case 'frequency':
weaveMotionConfig.frequency = parseFloat(input.value);
break;
case 'amplitude':
weaveMotionConfig.amplitude = parseFloat(input.value);
break;
case 'scale':
weaveMotionConfig.scale = parseFloat(input.value);
break;
case 'opacity':
weaveMotionConfig.opacity = parseFloat(input.value);
break;
case 'noise-intensity':
weaveMotionConfig.noiseIntensity = parseFloat(input.value);
break;
}
updateWeaveMotionPreview();
});
});
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':
generateRandomWeaveMotion();
break;
}
}
});
updateColorInputs();
setTimeout(() => {
showNotification('BricksFusion Weave Motion Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-weave-motion-config', JSON.stringify(weaveMotionConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-weave-motion-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(weaveMotionConfig, savedConfig);
document.getElementById('weave-color').value = savedConfig.weaveColor;
document.getElementById('weave-color-hex').value = savedConfig.weaveColor;
document.getElementById('weave-color-hsl').value = hexToHsl(savedConfig.weaveColor);
document.getElementById('speed').value = savedConfig.speed;
document.getElementById('frequency').value = savedConfig.frequency;
document.getElementById('amplitude').value = savedConfig.amplitude;
document.getElementById('scale').value = savedConfig.scale;
document.getElementById('opacity').value = savedConfig.opacity;
document.getElementById('noise-intensity').value = savedConfig.noiseIntensity;
document.getElementById('speed-value').textContent = savedConfig.speed;
document.getElementById('frequency-value').textContent = savedConfig.frequency;
document.getElementById('amplitude-value').textContent = savedConfig.amplitude;
document.getElementById('scale-value').textContent = savedConfig.scale;
document.getElementById('opacity-value').textContent = savedConfig.opacity;
document.getElementById('noise-intensity-value').textContent = savedConfig.noiseIntensity;
updateWeaveMotionPreview();
}
} catch (e) {
}
}
const originalUpdateWeaveMotionPreview = updateWeaveMotionPreview;
updateWeaveMotionPreview = function() {
originalUpdateWeaveMotionPreview();
saveConfiguration();
};
loadConfiguration();
function handleResize() {
if (window.innerWidth <= 768) {
document.body.style.paddingBottom = 'calc(var(--action-bar-height) + 20px)';
} else {
document.body.style.paddingBottom = 'var(--action-bar-height)';
}
}
window.addEventListener('resize', handleResize);
handleResize();
}
initializeUI();
});
</script>
</body>
</html>
Weave Motion
Creates animated woven fabric texture using generative noise patterns. Features flowing sinusoidal motion with customizable frequency and amplitude for organic textile appearance. Uses HTML5 Canvas with procedural noise generation for authentic weave patterns. Includes IntersectionObserver to pause animation when off-screen for optimal performance. Perfect for elegant backgrounds, premium sections, or adding sophisticated texture to any design.
Weave Motion
Watch the elegant woven texture flow with subtle organic motion.
Animation
Animation speed of the weave movement. Higher creates faster flowing motion.
Default: 0.02
Pattern repetition frequency. Higher creates tighter, more intricate weave patterns.
Default: 5.0
Wave amplitude for vertical motion. Higher creates more pronounced flowing effect.
Default: 0.03
Appearance
Overall pattern scale. Lower creates zoomed-in detailed view, higher shows wider pattern area.
Default: 2.0
Pattern transparency. Lower allows more background visibility through the weave.
Default: 0.6
Base color of the woven texture. Pattern intensity creates natural color variations.
Default: #7b7481 (gray-purple)
Effects
Amount of random variation in pattern. Higher creates more organic, irregular textile appearance.
Default: 0.8
Performance
This element uses HTML5 Canvas with pixel-level manipulation to generate procedural weave patterns. Features custom noise function for organic texture variation and sinusoidal functions for flowing motion. Uses IntersectionObserver to automatically pause animation when scrolled out of view for battery savings. Renders at 2-pixel intervals for performance optimization while maintaining visual quality. Medium weight - suitable for background textures and decorative elements. Works well on most devices with one or two instances per page. Performance scales with container size and scale setting.