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>Wave Background 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: #4682B4;
}
.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, #4682B4);
border-radius: 6px;
transition: var(--transition);
}
input[type="color"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
cursor: pointer;
background: transparent;
opacity: 0;
z-index: 2;
}
.color-input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.color-label {
font-size: 10px;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-left: 0.25rem;
}
.color-input {
padding: 0.5rem 0.75rem;
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 12px;
transition: var(--transition);
min-width: 0;
}
.color-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.2);
outline: none;
}
.color-input.invalid {
border-color: var(--danger);
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.2);
}
.hex-input,
.hsl-input {
width: 100%;
}
.color-input-group:nth-child(2) {
flex: 0.3;
}
.color-input-group:nth-child(3) {
flex: 0.7;
}
select {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--input-radius);
font-family: var(--font);
font-size: var(--text-xs);
color: var(--text-primary);
background-color: var(--card-bg);
margin-bottom: 0.75rem;
outline: none;
transition: var(--transition);
}
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.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;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 52px;
height: 28px;
}
.toggle-input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-label {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #4d4d4d;
-webkit-transition: .3s;
transition: .3s;
border-radius: 34px;
}
.toggle-label:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 4px;
bottom: 4px;
background-color: white;
-webkit-transition: .3s;
transition: .3s;
border-radius: 50%;
}
.toggle-input:checked + .toggle-label {
background-color: var(--accent);
}
.toggle-input:focus + .toggle-label {
box-shadow: 0 0 1px var(--accent);
}
.toggle-input:checked + .toggle-label:before {
-webkit-transform: translateX(24px);
-ms-transform: translateX(24px);
transform: translateX(24px);
}
.sub-settings {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
@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">Wave Background</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-wave-background
</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">Wave Background</h1>
<p class="page-subtitle">Interactive wave 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 wave background 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-wave-background</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="wave-preview" data-wave-background="true">
<div class="preview-content">Interactive Wave Background Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-wave" 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">
Wave Color
<div class="card-actions">
<button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Color Mode</span>
</div>
<select id="wave-color-mode">
<option value="single" selected>Single Color</option>
<option value="gradient">Gradient</option>
<option value="rainbow">Rainbow Effect</option>
</select>
</div>
<div class="color-list" id="single-color-group">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="wave-color" value="#4682B4">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="wave-color-hex" value="#4682B4" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="wave-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
<div id="gradient-color-group" style="display: none;">
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="wave-gradient-start" value="#4682B4">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="wave-gradient-start-hex" value="#4682B4" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="wave-gradient-start-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="wave-gradient-end" value="#FF6347">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="wave-gradient-end-hex" value="#FF6347" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="wave-gradient-end-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Wave Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-wave-settings" title="Reset Wave Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Direction</span>
</div>
<select id="wave-direction">
<option value="left-to-right" selected>Left to Right</option>
<option value="right-to-left">Right to Left</option>
<option value="alternate">Alternate</option>
<option value="radial">Radial (Expanding)</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Orientation</span>
</div>
<select id="wave-orientation">
<option value="horizontal" selected>Horizontal</option>
<option value="vertical">Vertical</option>
<option value="diagonal">Diagonal</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Position</span>
</div>
<select id="wave-position">
<option value="front" selected>In Front (overlay)</option>
<option value="back">In Back (background)</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Speed
<span class="help-tooltip" title="Animation speed multiplier">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="wave-speed-value">1</span></span>
<button class="reset-btn" onclick="resetParameter('wave-speed', 1)">↺</button>
</div>
</div>
<input type="range" id="wave-speed" min="0.2" max="2" step="0.1" value="1">
</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">
Amplitude
<span class="help-tooltip" title="Wave height">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="wave-amplitude-value">1</span></span>
<button class="reset-btn" onclick="resetParameter('wave-amplitude', 1)">↺</button>
</div>
</div>
<input type="range" id="wave-amplitude" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Frequency
<span class="help-tooltip" title="Number of waves">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="wave-frequency-value">1</span></span>
<button class="reset-btn" onclick="resetParameter('wave-frequency', 1)">↺</button>
</div>
</div>
<input type="range" id="wave-frequency" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Turbulence
<span class="help-tooltip" title="Wave distortion">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="wave-turbulence-value">1</span></span>
<button class="reset-btn" onclick="resetParameter('wave-turbulence', 1)">↺</button>
</div>
</div>
<input type="range" id="wave-turbulence" min="0" max="2" step="0.1" value="1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Opacity
<span class="help-tooltip" title="Wave transparency">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="wave-opacity-value">0.7</span></span>
<button class="reset-btn" onclick="resetParameter('wave-opacity', 0.7)">↺</button>
</div>
</div>
<input type="range" id="wave-opacity" min="0.1" max="1" step="0.05" value="0.7">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Glow Effect
<span class="help-tooltip" title="Wave glow intensity">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="wave-glow-value">0</span></span>
<button class="reset-btn" onclick="resetParameter('wave-glow', 0)">↺</button>
</div>
</div>
<input type="range" id="wave-glow" min="0" max="20" step="1" value="0">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Advanced Options
<div class="card-actions">
<button class="card-action-btn" id="reset-advanced" title="Reset Advanced Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Particles Effect</span>
</div>
<div class="toggle-switch">
<input type="checkbox" id="enable-particles" class="toggle-input">
<label for="enable-particles" class="toggle-label"></label>
</div>
</div>
<div class="control-group sub-settings" id="particles-settings" style="display: none;">
<div class="control-label">
<span class="label-text">
Particle Density
<span class="help-tooltip" title="Number of particles">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="particle-density-value">50</span></span>
<button class="reset-btn" onclick="resetParameter('particle-density', 50)">↺</button>
</div>
</div>
<input type="range" id="particle-density" min="10" max="200" step="10" value="50">
<div class="control-label">
<span class="label-text">
Particle Size
<span class="help-tooltip" title="Size of particles">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="particle-size-value">3</span></span>
<button class="reset-btn" onclick="resetParameter('particle-size', 3)">↺</button>
</div>
</div>
<input type="range" id="particle-size" min="1" max="8" step="0.5" value="3">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Breathing Effect</span>
</div>
<div class="toggle-switch">
<input type="checkbox" id="enable-breathing" class="toggle-input">
<label for="enable-breathing" class="toggle-label"></label>
</div>
</div>
<div class="control-group sub-settings" id="breathing-settings" style="display: none;">
<div class="control-label">
<span class="label-text">
Breathing Speed
<span class="help-tooltip" title="Speed of breathing effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="breathing-speed-value">1</span></span>
<button class="reset-btn" onclick="resetParameter('breathing-speed', 1)">↺</button>
</div>
</div>
<input type="range" id="breathing-speed" min="0.5" max="3" step="0.1" value="1">
<div class="control-label">
<span class="label-text">
Breathing Intensity
<span class="help-tooltip" title="Strength of breathing effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="breathing-intensity-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('breathing-intensity', 0.5)">↺</button>
</div>
</div>
<input type="range" id="breathing-intensity" min="0.1" max="1" step="0.1" value="0.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Mouse Interaction</span>
</div>
<div class="toggle-switch">
<input type="checkbox" id="enable-mouse-interaction" class="toggle-input">
<label for="enable-mouse-interaction" class="toggle-label"></label>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Easing Type</span>
</div>
<select id="easing-type">
<option value="linear" selected>Linear</option>
<option value="elastic">Elastic</option>
<option value="bounce">Bounce</option>
<option value="sine">Sine</option>
<option value="quad">Quadratic</option>
</select>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let waveConfig = {
waveColor: '#4682B4',
wavePosition: 'front',
waveSpeed: 1,
waveAmplitude: 1,
waveFrequency: 1,
waveTurbulence: 1,
waveOpacity: 0.7,
waveDirection: 'left-to-right',
waveOrientation: 'horizontal',
waveColorMode: 'single',
waveGradientStart: '#4682B4',
waveGradientEnd: '#FF6347',
waveGlow: 0,
enableParticles: false,
particleDensity: 50,
particleSize: 3,
enableBreathing: false,
breathingSpeed: 1,
breathingIntensity: 0.5,
enableMouseInteraction: false,
easingType: 'linear'
};
const defaultConfig = { ...waveConfig };
let activeWave = null;
class WaveBackground {
constructor(element, config) {
this.element = element;
this.config = config;
this.canvas = null;
this.ctx = null;
this.offset = 0;
this.baseScale = 1;
this.animationFrameId = null;
this.particles = [];
this.mouseX = 0;
this.mouseY = 0;
this.interacting = false;
this.breathingPhase = 0;
this.rainbowPhase = 0;
this.hueRotation = 0;
this.currentAmplitude = config.waveAmplitude;
this.init();
}
init() {
if (getComputedStyle(this.element).position === 'static') {
this.element.style.position = 'relative';
}
this.element.style.overflow = 'hidden';
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
Object.assign(this.canvas.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
pointerEvents: 'none'
});
this.resize();
this.updateZIndex();
this.element.appendChild(this.canvas);
if (this.config.enableParticles) {
this.initParticles();
}
if (this.config.enableMouseInteraction) {
this.setupMouseInteraction();
}
this.animate(0);
window.addEventListener('resize', this.resize.bind(this));
}
initParticles() {
this.particles = [];
const count = this.config.particleDensity;
for (let i = 0; i < count; i++) {
this.particles.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
size: Math.random() * this.config.particleSize + 1,
speedX: Math.random() * 2 - 1,
speedY: Math.random() * 2 - 1,
opacity: Math.random() * 0.5 + 0.3
});
}
}
setupMouseInteraction() {
this.canvas.style.pointerEvents = 'auto';
const handleMove = (e) => {
let x, y;
if (e.type.includes('touch')) {
x = e.touches[0].clientX;
y = e.touches[0].clientY;
} else {
x = e.clientX;
y = e.clientY;
}
const rect = this.canvas.getBoundingClientRect();
this.mouseX = x - rect.left;
this.mouseY = y - rect.top;
this.interacting = true;
clearTimeout(this.interactionTimeout);
this.interactionTimeout = setTimeout(() => {
this.interacting = false;
}, 2000);
};
this.element.addEventListener('mousemove', handleMove);
this.element.addEventListener('touchmove', handleMove);
this.element.addEventListener('mouseleave', () => {
this.interacting = false;
});
}
resize() {
this.canvas.width = this.element.clientWidth;
this.canvas.height = this.element.clientHeight;
this.baseScale = Math.min(this.canvas.width, this.canvas.height) / 500;
if (this.config.enableParticles) {
this.initParticles();
}
}
updateZIndex() {
const sectionZIndex = parseInt(getComputedStyle(this.element).zIndex) || 0;
const canvasZIndex = this.config.wavePosition === 'front' ? sectionZIndex + 1 : sectionZIndex - 1;
this.canvas.style.zIndex = canvasZIndex.toString();
}
animate(time) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
const directionMultiplier = this.config.waveDirection === 'right-to-left' ? -1 : 1;
if (this.config.waveDirection === 'alternate') {
this.offset += 0.5 * this.config.waveSpeed * Math.sin(time / 2000);
} else if (this.config.waveDirection === 'radial') {
this.offset += 0.5 * this.config.waveSpeed;
} else {
this.offset += 0.5 * this.config.waveSpeed * directionMultiplier;
}
const t = time / 1000;
if (this.config.enableBreathing) {
this.breathingPhase += 0.01 * this.config.breathingSpeed;
const breathingFactor = 1 + Math.sin(this.breathingPhase) * this.config.breathingIntensity;
this.currentAmplitude = this.config.waveAmplitude * breathingFactor;
} else {
this.currentAmplitude = this.config.waveAmplitude;
}
if (this.config.waveColorMode === 'rainbow') {
this.rainbowPhase += 0.005;
this.hueRotation = (this.rainbowPhase * 360) % 360;
}
const amplitudeFactor = this.currentAmplitude;
const frequencyFactor = this.config.waveFrequency;
const turbulenceFactor = this.config.waveTurbulence;
const waves = [
{ y: 0.2, amplitude: 25 * amplitudeFactor, length: 200 / frequencyFactor, speed: 1 * this.config.waveSpeed, turbulence: 5 * turbulenceFactor },
{ y: 0.3, amplitude: 20 * amplitudeFactor, length: 180 / frequencyFactor, speed: 0.8 * this.config.waveSpeed, turbulence: 4 * turbulenceFactor },
{ y: 0.4, amplitude: 15 * amplitudeFactor, length: 160 / frequencyFactor, speed: 0.6 * this.config.waveSpeed, turbulence: 3 * turbulenceFactor },
{ y: 0.5, amplitude: 10 * amplitudeFactor, length: 140 / frequencyFactor, speed: 0.7 * this.config.waveSpeed, turbulence: 2 * turbulenceFactor },
{ y: 0.6, amplitude: 8 * amplitudeFactor, length: 120 / frequencyFactor, speed: 0.5 * this.config.waveSpeed, turbulence: 1 * turbulenceFactor },
{ y: 0.7, amplitude: 5 * amplitudeFactor, length: 100 / frequencyFactor, speed: 0.4 * this.config.waveSpeed, turbulence: 0.5 * turbulenceFactor }
];
if (this.config.waveGlow > 0) {
this.ctx.shadowBlur = this.config.waveGlow;
this.ctx.shadowColor = this.getWaveColor(0.8);
}
waves.forEach((wave, index) => {
const gradient = this.drawWave(wave.y, wave.amplitude, wave.length, wave.speed, t, wave.turbulence);
const fillStyle = this.getGradientFill(wave.y, gradient);
this.ctx.fillStyle = fillStyle;
this.ctx.fill();
});
if (this.config.enableParticles) {
this.updateAndDrawParticles(t);
}
this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
}
getWaveColor(opacity = 1) {
if (this.config.waveColorMode === 'single') {
const baseColor = hexToRgb(this.config.waveColor);
return 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + opacity + ')';
} else if (this.config.waveColorMode === 'rainbow') {
return 'hsl(' + this.hueRotation + ', 80%, 60%, ' + opacity + ')';
} else {
const baseColor = hexToRgb(this.config.waveColor);
return 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + opacity + ')';
}
}
getGradientFill(yPosition, gradient) {
const opacity = this.config.waveOpacity;
if (this.config.waveColorMode === 'single') {
const baseColor = hexToRgb(this.config.waveColor);
gradient.addColorStop(0, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.1) + ')');
gradient.addColorStop(0.2, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.2) + ')');
gradient.addColorStop(0.4, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.3) + ')');
gradient.addColorStop(0.6, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.4) + ')');
gradient.addColorStop(1, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', 0)');
} else if (this.config.waveColorMode === 'gradient') {
const startColor = hexToRgb(this.config.waveGradientStart);
const endColor = hexToRgb(this.config.waveGradientEnd);
gradient.addColorStop(0, 'rgba(' + startColor.r + ', ' + startColor.g + ', ' + startColor.b + ', ' + (opacity * 0.1) + ')');
gradient.addColorStop(0.4, 'rgba(' + startColor.r + ', ' + startColor.g + ', ' + startColor.b + ', ' + (opacity * 0.3) + ')');
gradient.addColorStop(0.6, 'rgba(' + endColor.r + ', ' + endColor.g + ', ' + endColor.b + ', ' + (opacity * 0.3) + ')');
gradient.addColorStop(1, 'rgba(' + endColor.r + ', ' + endColor.g + ', ' + endColor.b + ', 0)');
} else if (this.config.waveColorMode === 'rainbow') {
const hue1 = (this.hueRotation) % 360;
const hue2 = (this.hueRotation + 60) % 360;
const hue3 = (this.hueRotation + 180) % 360;
gradient.addColorStop(0, 'hsla(' + hue1 + ', 80%, 60%, ' + (opacity * 0.1) + ')');
gradient.addColorStop(0.3, 'hsla(' + hue2 + ', 80%, 60%, ' + (opacity * 0.3) + ')');
gradient.addColorStop(0.6, 'hsla(' + hue3 + ', 80%, 60%, ' + (opacity * 0.2) + ')');
gradient.addColorStop(1, 'hsla(' + hue1 + ', 80%, 60%, 0)');
}
return gradient;
}
drawWave(y, amplitude, length, speed, time, turbulence) {
const scale = this.baseScale * (this.canvas.width / this.canvas.height);
this.ctx.beginPath();
if (this.config.waveDirection === 'radial') {
this.drawRadialWave(y, amplitude, scale, speed, time, turbulence);
} else if (this.config.waveOrientation === 'vertical') {
this.drawVerticalWave(y, amplitude, length, scale, speed, time, turbulence);
} else if (this.config.waveOrientation === 'diagonal') {
this.drawDiagonalWave(y, amplitude, length, scale, speed, time, turbulence);
} else {
this.drawHorizontalWave(y, amplitude, length, scale, speed, time, turbulence);
}
if (this.config.waveOrientation === 'vertical') {
return this.ctx.createLinearGradient(this.canvas.width * y, 0, this.canvas.width, 0);
} else if (this.config.waveOrientation === 'diagonal') {
return this.ctx.createLinearGradient(0, 0, this.canvas.width, this.canvas.height);
} else if (this.config.waveDirection === 'radial') {
return this.ctx.createRadialGradient(
this.canvas.width / 2,
this.canvas.height / 2,
0,
this.canvas.width / 2,
this.canvas.height / 2,
this.canvas.width / 2
);
} else {
return this.ctx.createLinearGradient(0, this.canvas.height * y, 0, this.canvas.height);
}
}
drawHorizontalWave(y, amplitude, length, scale, speed, time, turbulence) {
let mouseInfluence = 0;
if (this.config.enableMouseInteraction && this.interacting) {
const distanceY = Math.abs(this.mouseY - this.canvas.height * y);
const maxDistance = this.canvas.height * 0.2;
if (distanceY < maxDistance) {
mouseInfluence = (1 - distanceY / maxDistance) * 50;
}
}
this.ctx.moveTo(0, this.canvas.height * y);
for (let x = 0; x <= this.canvas.width; x++) {
const dx = (x + this.offset * speed) / (length * scale);
const turbulenceY = turbulence * Math.sin((x + time) / 30) * scale;
let easedSin = this.applyEasing(dx * Math.PI * 2);
let easedSin2 = this.applyEasing(dx * Math.PI * 3 + time);
let easedSin3 = this.applyEasing(dx * Math.PI * 1.5 - time * 0.5);
let mouseEffect = 0;
if (mouseInfluence > 0) {
const distanceX = Math.abs(this.mouseX - x);
const maxDistanceX = 100;
if (distanceX < maxDistanceX) {
mouseEffect = (1 - distanceX / maxDistanceX) * mouseInfluence *
Math.sin(time + x / 50) * scale;
}
}
const yPos = easedSin * amplitude * scale;
const yPos2 = easedSin2 * amplitude * scale * 0.5;
const yPos3 = easedSin3 * amplitude * scale * 0.3;
const finalY = this.canvas.height * y + yPos + yPos2 + yPos3 + turbulenceY + mouseEffect;
this.ctx.lineTo(x, finalY);
}
this.ctx.lineTo(this.canvas.width, this.canvas.height);
this.ctx.lineTo(0, this.canvas.height);
this.ctx.closePath();
}
drawVerticalWave(y, amplitude, length, scale, speed, time, turbulence) {
const verticalY = this.canvas.width * y;
this.ctx.moveTo(verticalY, 0);
for (let y = 0; y <= this.canvas.height; y++) {
const dy = (y + this.offset * speed) / (length * scale);
const turbulenceX = turbulence * Math.sin((y + time) / 30) * scale;
let easedSin = this.applyEasing(dy * Math.PI * 2);
let easedSin2 = this.applyEasing(dy * Math.PI * 3 + time);
let easedSin3 = this.applyEasing(dy * Math.PI * 1.5 - time * 0.5);
const xPos = easedSin * amplitude * scale;
const xPos2 = easedSin2 * amplitude * scale * 0.5;
const xPos3 = easedSin3 * amplitude * scale * 0.3;
this.ctx.lineTo(verticalY + xPos + xPos2 + xPos3 + turbulenceX, y);
}
this.ctx.lineTo(this.canvas.width, this.canvas.height);
this.ctx.lineTo(this.canvas.width, 0);
this.ctx.closePath();
}
drawDiagonalWave(y, amplitude, length, scale, speed, time, turbulence) {
const pointCount = Math.max(this.canvas.width, this.canvas.height);
const startX = 0;
const startY = 0;
const endX = this.canvas.width;
const endY = this.canvas.height;
this.ctx.moveTo(startX, startY);
for (let i = 0; i <= pointCount; i++) {
const t = i / pointCount;
const baseX = startX + (endX - startX) * t;
const baseY = startY + (endY - startY) * t;
const dx = (baseX + baseY + this.offset * speed) / (length * scale);
const turbulenceFactor = turbulence * Math.sin((baseX + baseY + time) / 30) * scale;
let easedSin = this.applyEasing(dx * Math.PI * 2);
let easedSin2 = this.applyEasing(dx * Math.PI * 3 + time);
const perpX = easedSin * amplitude * scale;
const perpY = -perpX;
const additionalX = easedSin2 * amplitude * scale * 0.3;
const additionalY = -additionalX;
this.ctx.lineTo(
baseX + perpX + additionalX + turbulenceFactor,
baseY + perpY + additionalY + turbulenceFactor
);
}
this.ctx.lineTo(this.canvas.width, this.canvas.height);
this.ctx.lineTo(0, this.canvas.height);
this.ctx.lineTo(0, 0);
this.ctx.closePath();
}
drawRadialWave(y, amplitude, scale, speed, time, turbulence) {
const centerX = this.canvas.width / 2;
const centerY = this.canvas.height / 2;
const maxRadius = Math.max(this.canvas.width, this.canvas.height) * y;
this.ctx.moveTo(centerX, centerY);
const segments = 100;
const angleStep = (Math.PI * 2) / segments;
for (let i = 0; i <= segments; i++) {
const angle = i * angleStep;
const baseRadius = maxRadius + (this.offset * speed);
const dr = (baseRadius / 50) + time;
const waveOffset = Math.sin(dr + i * 0.2) * amplitude * 15 * scale;
const turbulenceOffset = Math.sin(angle * 5 + time) * turbulence * 10 * scale;
const radius = baseRadius + waveOffset + turbulenceOffset;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
}
this.ctx.closePath();
}
applyEasing(angle) {
const sin = Math.sin(angle);
switch (this.config.easingType) {
case 'elastic':
return Math.pow(sin, 3) + sin;
case 'bounce':
return sin > 0 ? Math.pow(sin, 2) : sin;
case 'sine':
return sin;
case 'quad':
return sin > 0 ? sin * sin : -sin * sin;
default:
return sin;
}
}
updateAndDrawParticles(time) {
this.ctx.save();
for (let i = 0; i < this.particles.length; i++) {
const p = this.particles[i];
const waveInfluence = 0.5;
const dx = (p.x + this.offset) / 100;
const waveY = Math.sin(dx * Math.PI * 2) * 5 * this.config.waveAmplitude;
p.x += p.speedX + Math.sin(time + i) * 0.2;
p.y += p.speedY + waveY * waveInfluence;
if (p.x < 0) p.x = this.canvas.width;
if (p.x > this.canvas.width) p.x = 0;
if (p.y < 0) p.y = this.canvas.height;
if (p.y > this.canvas.height) p.y = 0;
let particleColor;
if (this.config.waveColorMode === 'single') {
const color = hexToRgb(this.config.waveColor);
particleColor = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + p.opacity + ')';
} else if (this.config.waveColorMode === 'gradient') {
const startColor = hexToRgb(this.config.waveGradientStart);
const endColor = hexToRgb(this.config.waveGradientEnd);
const mixFactor = p.y / this.canvas.height;
const r = Math.floor(startColor.r * (1 - mixFactor) + endColor.r * mixFactor);
const g = Math.floor(startColor.g * (1 - mixFactor) + endColor.g * mixFactor);
const b = Math.floor(startColor.b * (1 - mixFactor) + endColor.b * mixFactor);
particleColor = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + p.opacity + ')';
} else {
const hue = (this.hueRotation + (p.x / this.canvas.width) * 100) % 360;
particleColor = 'hsla(' + hue + ', 80%, 60%, ' + p.opacity + ')';
}
this.ctx.beginPath();
this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
this.ctx.fillStyle = particleColor;
this.ctx.fill();
}
this.ctx.restore();
}
// AQUÍ ESTÁ EL MÉTODO MEJORADO updateConfig - la clave del arreglo
updateConfig(newConfig) {
const oldConfig = { ...this.config };
this.config = { ...this.config, ...newConfig };
// Solo reinicializar partículas si cambió la configuración de partículas
if (oldConfig.enableParticles !== this.config.enableParticles) {
if (this.config.enableParticles) {
this.initParticles();
} else {
this.particles = [];
}
} else if (this.config.enableParticles &&
(oldConfig.particleDensity !== this.config.particleDensity ||
oldConfig.particleSize !== this.config.particleSize)) {
this.initParticles();
}
// Solo actualizar interacción de mouse si cambió
if (oldConfig.enableMouseInteraction !== this.config.enableMouseInteraction) {
if (this.config.enableMouseInteraction) {
this.setupMouseInteraction();
} else {
this.interacting = false;
this.canvas.style.pointerEvents = 'none';
}
}
// Actualizar z-index si cambió la posición
if (oldConfig.wavePosition !== this.config.wavePosition) {
this.updateZIndex();
}
// No necesitamos hacer nada más - la animación continuará usando los nuevos valores
// automáticamente en el próximo frame sin interrupciones
}
destroy() {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
if (this.canvas && this.canvas.parentNode) {
this.canvas.parentNode.removeChild(this.canvas);
}
}
}
function initWaveBackground() {
const elements = document.querySelectorAll('[data-wave-background]:not([data-wave-initialized="true"])');
elements.forEach((element) => {
const customWaveColor = element.getAttribute('data-wave-color');
const customWavePosition = element.getAttribute('data-wave-position');
const customConfig = { ...waveConfig };
if (customWaveColor) customConfig.waveColor = customWaveColor;
if (customWavePosition) customConfig.wavePosition = customWavePosition;
element.waveBackground = new WaveBackground(element, customConfig);
element.dataset.waveInitialized = 'true';
if (element.id === 'wave-preview') {
activeWave = element.waveBackground;
}
});
}
// FUNCIÓN MEJORADA - ahora usa updateConfig en lugar de destruir/recrear
function updateWavePreview() {
const preview = document.getElementById('wave-preview');
if (!preview || !preview.waveBackground) {
// Si no existe la instancia, crear una nueva
if (preview) {
preview.waveBackground = new WaveBackground(preview, waveConfig);
activeWave = preview.waveBackground;
}
return;
}
// Si existe la instancia, solo actualizar la configuración
// Esto mantiene el bucle de animación corriendo sin interrupciones
preview.waveBackground.updateConfig(waveConfig);
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 };
}
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
};
}
function hslToHex(hsl) {
const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
if (!match) return null;
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let l = parseInt(match[3]) / 100;
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function isValidHex(hex) {
return /^#[0-9A-F]{6}$/i.test(hex);
}
function isValidHsl(hsl) {
return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
}
function formatHex(value) {
let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
if (!hex.startsWith('#')) {
hex = '#' + hex;
}
if (hex.length > 7) {
hex = hex.substring(0, 7);
}
return hex.toUpperCase();
}
function formatHsl(value) {
const cleanValue = value.replace(/[^\d,\s]/g, '');
const numbers = cleanValue.match(/\d+/g);
if (!numbers || numbers.length < 3) {
const partialMatch = value.match(/(\d+)/g);
if (partialMatch && partialMatch.length >= 1) {
const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
return `hsl(${h}, ${s}%, ${l}%)`;
}
return value;
}
let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
return `hsl(${h}, ${s}%, ${l}%)`;
}
function 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() {
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const codeId = generateUniqueId();
const attributeId = generateUniqueId();
const jsCode = generateJavaScriptCode();
const fullSectionData = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_attributes": [
{
"id": attributeId,
"name": "data-wave-background"
}
],
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
}
},
"label": "Wave Background Section"
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": {}
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Wave Background JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(fullSectionData, null, 2);
}
function generateJavaScriptCode() {
return `(function() {
const waveConfig = {
waveColor: "${waveConfig.waveColor}",
wavePosition: "${waveConfig.wavePosition}",
waveSpeed: ${waveConfig.waveSpeed},
waveAmplitude: ${waveConfig.waveAmplitude},
waveFrequency: ${waveConfig.waveFrequency},
waveTurbulence: ${waveConfig.waveTurbulence},
waveOpacity: ${waveConfig.waveOpacity},
waveDirection: "${waveConfig.waveDirection}",
waveOrientation: "${waveConfig.waveOrientation}",
waveColorMode: "${waveConfig.waveColorMode}",
waveGradientStart: "${waveConfig.waveGradientStart}",
waveGradientEnd: "${waveConfig.waveGradientEnd}",
waveGlow: ${waveConfig.waveGlow},
enableParticles: ${waveConfig.enableParticles},
particleDensity: ${waveConfig.particleDensity},
particleSize: ${waveConfig.particleSize},
enableBreathing: ${waveConfig.enableBreathing},
breathingSpeed: ${waveConfig.breathingSpeed},
breathingIntensity: ${waveConfig.breathingIntensity},
enableMouseInteraction: ${waveConfig.enableMouseInteraction},
easingType: "${waveConfig.easingType}"
};
class WaveBackground {
constructor(element, config) {
this.element = element;
this.config = config;
this.canvas = null;
this.ctx = null;
this.offset = 0;
this.baseScale = 1;
this.animationFrameId = null;
this.particles = [];
this.mouseX = 0;
this.mouseY = 0;
this.interacting = false;
this.breathingPhase = 0;
this.rainbowPhase = 0;
this.hueRotation = 0;
this.currentAmplitude = config.waveAmplitude;
this.init();
}
init() {
if (getComputedStyle(this.element).position === 'static') {
this.element.style.position = 'relative';
}
this.element.style.overflow = 'hidden';
this.canvas = document.createElement('canvas');
this.ctx = this.canvas.getContext('2d');
Object.assign(this.canvas.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
pointerEvents: 'none'
});
this.resize();
this.updateZIndex();
this.element.appendChild(this.canvas);
if (this.config.enableParticles) {
this.initParticles();
}
if (this.config.enableMouseInteraction) {
this.setupMouseInteraction();
}
this.animate(0);
window.addEventListener('resize', this.resize.bind(this));
}
initParticles() {
this.particles = [];
const count = this.config.particleDensity;
for (let i = 0; i < count; i++) {
this.particles.push({
x: Math.random() * this.canvas.width,
y: Math.random() * this.canvas.height,
size: Math.random() * this.config.particleSize + 1,
speedX: Math.random() * 2 - 1,
speedY: Math.random() * 2 - 1,
opacity: Math.random() * 0.5 + 0.3
});
}
}
setupMouseInteraction() {
this.canvas.style.pointerEvents = 'auto';
const handleMove = (e) => {
let x, y;
if (e.type.includes('touch')) {
x = e.touches[0].clientX;
y = e.touches[0].clientY;
} else {
x = e.clientX;
y = e.clientY;
}
const rect = this.canvas.getBoundingClientRect();
this.mouseX = x - rect.left;
this.mouseY = y - rect.top;
this.interacting = true;
clearTimeout(this.interactionTimeout);
this.interactionTimeout = setTimeout(() => {
this.interacting = false;
}, 2000);
};
this.element.addEventListener('mousemove', handleMove);
this.element.addEventListener('touchmove', handleMove);
this.element.addEventListener('mouseleave', () => {
this.interacting = false;
});
}
resize() {
this.canvas.width = this.element.clientWidth;
this.canvas.height = this.element.clientHeight;
this.baseScale = Math.min(this.canvas.width, this.canvas.height) / 500;
if (this.config.enableParticles) {
this.initParticles();
}
}
updateZIndex() {
const sectionZIndex = parseInt(getComputedStyle(this.element).zIndex) || 0;
const canvasZIndex = this.config.wavePosition === 'front' ? sectionZIndex + 1 : sectionZIndex - 1;
this.canvas.style.zIndex = canvasZIndex.toString();
}
animate(time) {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
const directionMultiplier = this.config.waveDirection === 'right-to-left' ? -1 : 1;
if (this.config.waveDirection === 'alternate') {
this.offset += 0.5 * this.config.waveSpeed * Math.sin(time / 2000);
} else if (this.config.waveDirection === 'radial') {
this.offset += 0.5 * this.config.waveSpeed;
} else {
this.offset += 0.5 * this.config.waveSpeed * directionMultiplier;
}
const t = time / 1000;
if (this.config.enableBreathing) {
this.breathingPhase += 0.01 * this.config.breathingSpeed;
const breathingFactor = 1 + Math.sin(this.breathingPhase) * this.config.breathingIntensity;
this.currentAmplitude = this.config.waveAmplitude * breathingFactor;
} else {
this.currentAmplitude = this.config.waveAmplitude;
}
if (this.config.waveColorMode === 'rainbow') {
this.rainbowPhase += 0.005;
this.hueRotation = (this.rainbowPhase * 360) % 360;
}
const amplitudeFactor = this.currentAmplitude;
const frequencyFactor = this.config.waveFrequency;
const turbulenceFactor = this.config.waveTurbulence;
const waves = [
{ y: 0.2, amplitude: 25 * amplitudeFactor, length: 200 / frequencyFactor, speed: 1 * this.config.waveSpeed, turbulence: 5 * turbulenceFactor },
{ y: 0.3, amplitude: 20 * amplitudeFactor, length: 180 / frequencyFactor, speed: 0.8 * this.config.waveSpeed, turbulence: 4 * turbulenceFactor },
{ y: 0.4, amplitude: 15 * amplitudeFactor, length: 160 / frequencyFactor, speed: 0.6 * this.config.waveSpeed, turbulence: 3 * turbulenceFactor },
{ y: 0.5, amplitude: 10 * amplitudeFactor, length: 140 / frequencyFactor, speed: 0.7 * this.config.waveSpeed, turbulence: 2 * turbulenceFactor },
{ y: 0.6, amplitude: 8 * amplitudeFactor, length: 120 / frequencyFactor, speed: 0.5 * this.config.waveSpeed, turbulence: 1 * turbulenceFactor },
{ y: 0.7, amplitude: 5 * amplitudeFactor, length: 100 / frequencyFactor, speed: 0.4 * this.config.waveSpeed, turbulence: 0.5 * turbulenceFactor }
];
if (this.config.waveGlow > 0) {
this.ctx.shadowBlur = this.config.waveGlow;
this.ctx.shadowColor = this.getWaveColor(0.8);
}
waves.forEach((wave, index) => {
const gradient = this.drawWave(wave.y, wave.amplitude, wave.length, wave.speed, t, wave.turbulence);
const fillStyle = this.getGradientFill(wave.y, gradient);
this.ctx.fillStyle = fillStyle;
this.ctx.fill();
});
if (this.config.enableParticles) {
this.updateAndDrawParticles(t);
}
this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
}
getWaveColor(opacity = 1) {
if (this.config.waveColorMode === 'single') {
const baseColor = hexToRgb(this.config.waveColor);
return 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + opacity + ')';
} else if (this.config.waveColorMode === 'rainbow') {
return 'hsl(' + this.hueRotation + ', 80%, 60%, ' + opacity + ')';
} else {
const baseColor = hexToRgb(this.config.waveColor);
return 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + opacity + ')';
}
}
getGradientFill(yPosition, gradient) {
const opacity = this.config.waveOpacity;
if (this.config.waveColorMode === 'single') {
const baseColor = hexToRgb(this.config.waveColor);
gradient.addColorStop(0, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.1) + ')');
gradient.addColorStop(0.2, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.2) + ')');
gradient.addColorStop(0.4, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.3) + ')');
gradient.addColorStop(0.6, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', ' + (opacity * 0.4) + ')');
gradient.addColorStop(1, 'rgba(' + baseColor.r + ', ' + baseColor.g + ', ' + baseColor.b + ', 0)');
} else if (this.config.waveColorMode === 'gradient') {
const startColor = hexToRgb(this.config.waveGradientStart);
const endColor = hexToRgb(this.config.waveGradientEnd);
gradient.addColorStop(0, 'rgba(' + startColor.r + ', ' + startColor.g + ', ' + startColor.b + ', ' + (opacity * 0.1) + ')');
gradient.addColorStop(0.4, 'rgba(' + startColor.r + ', ' + startColor.g + ', ' + startColor.b + ', ' + (opacity * 0.3) + ')');
gradient.addColorStop(0.6, 'rgba(' + endColor.r + ', ' + endColor.g + ', ' + endColor.b + ', ' + (opacity * 0.3) + ')');
gradient.addColorStop(1, 'rgba(' + endColor.r + ', ' + endColor.g + ', ' + endColor.b + ', 0)');
} else if (this.config.waveColorMode === 'rainbow') {
const hue1 = (this.hueRotation) % 360;
const hue2 = (this.hueRotation + 60) % 360;
const hue3 = (this.hueRotation + 180) % 360;
gradient.addColorStop(0, 'hsla(' + hue1 + ', 80%, 60%, ' + (opacity * 0.1) + ')');
gradient.addColorStop(0.3, 'hsla(' + hue2 + ', 80%, 60%, ' + (opacity * 0.3) + ')');
gradient.addColorStop(0.6, 'hsla(' + hue3 + ', 80%, 60%, ' + (opacity * 0.2) + ')');
gradient.addColorStop(1, 'hsla(' + hue1 + ', 80%, 60%, 0)');
}
return gradient;
}
drawWave(y, amplitude, length, speed, time, turbulence) {
const scale = this.baseScale * (this.canvas.width / this.canvas.height);
this.ctx.beginPath();
if (this.config.waveDirection === 'radial') {
this.drawRadialWave(y, amplitude, scale, speed, time, turbulence);
} else if (this.config.waveOrientation === 'vertical') {
this.drawVerticalWave(y, amplitude, length, scale, speed, time, turbulence);
} else if (this.config.waveOrientation === 'diagonal') {
this.drawDiagonalWave(y, amplitude, length, scale, speed, time, turbulence);
} else {
this.drawHorizontalWave(y, amplitude, length, scale, speed, time, turbulence);
}
if (this.config.waveOrientation === 'vertical') {
return this.ctx.createLinearGradient(this.canvas.width * y, 0, this.canvas.width, 0);
} else if (this.config.waveOrientation === 'diagonal') {
return this.ctx.createLinearGradient(0, 0, this.canvas.width, this.canvas.height);
} else if (this.config.waveDirection === 'radial') {
return this.ctx.createRadialGradient(
this.canvas.width / 2,
this.canvas.height / 2,
0,
this.canvas.width / 2,
this.canvas.height / 2,
this.canvas.width / 2
);
} else {
return this.ctx.createLinearGradient(0, this.canvas.height * y, 0, this.canvas.height);
}
}
drawHorizontalWave(y, amplitude, length, scale, speed, time, turbulence) {
let mouseInfluence = 0;
if (this.config.enableMouseInteraction && this.interacting) {
const distanceY = Math.abs(this.mouseY - this.canvas.height * y);
const maxDistance = this.canvas.height * 0.2;
if (distanceY < maxDistance) {
mouseInfluence = (1 - distanceY / maxDistance) * 50;
}
}
this.ctx.moveTo(0, this.canvas.height * y);
for (let x = 0; x <= this.canvas.width; x++) {
const dx = (x + this.offset * speed) / (length * scale);
const turbulenceY = turbulence * Math.sin((x + time) / 30) * scale;
let easedSin = this.applyEasing(dx * Math.PI * 2);
let easedSin2 = this.applyEasing(dx * Math.PI * 3 + time);
let easedSin3 = this.applyEasing(dx * Math.PI * 1.5 - time * 0.5);
let mouseEffect = 0;
if (mouseInfluence > 0) {
const distanceX = Math.abs(this.mouseX - x);
const maxDistanceX = 100;
if (distanceX < maxDistanceX) {
mouseEffect = (1 - distanceX / maxDistanceX) * mouseInfluence *
Math.sin(time + x / 50) * scale;
}
}
const yPos = easedSin * amplitude * scale;
const yPos2 = easedSin2 * amplitude * scale * 0.5;
const yPos3 = easedSin3 * amplitude * scale * 0.3;
const finalY = this.canvas.height * y + yPos + yPos2 + yPos3 + turbulenceY + mouseEffect;
this.ctx.lineTo(x, finalY);
}
this.ctx.lineTo(this.canvas.width, this.canvas.height);
this.ctx.lineTo(0, this.canvas.height);
this.ctx.closePath();
}
drawVerticalWave(y, amplitude, length, scale, speed, time, turbulence) {
const verticalY = this.canvas.width * y;
this.ctx.moveTo(verticalY, 0);
for (let y = 0; y <= this.canvas.height; y++) {
const dy = (y + this.offset * speed) / (length * scale);
const turbulenceX = turbulence * Math.sin((y + time) / 30) * scale;
let easedSin = this.applyEasing(dy * Math.PI * 2);
let easedSin2 = this.applyEasing(dy * Math.PI * 3 + time);
let easedSin3 = this.applyEasing(dy * Math.PI * 1.5 - time * 0.5);
const xPos = easedSin * amplitude * scale;
const xPos2 = easedSin2 * amplitude * scale * 0.5;
const xPos3 = easedSin3 * amplitude * scale * 0.3;
this.ctx.lineTo(verticalY + xPos + xPos2 + xPos3 + turbulenceX, y);
}
this.ctx.lineTo(this.canvas.width, this.canvas.height);
this.ctx.lineTo(this.canvas.width, 0);
this.ctx.closePath();
}
drawDiagonalWave(y, amplitude, length, scale, speed, time, turbulence) {
const pointCount = Math.max(this.canvas.width, this.canvas.height);
const startX = 0;
const startY = 0;
const endX = this.canvas.width;
const endY = this.canvas.height;
this.ctx.moveTo(startX, startY);
for (let i = 0; i <= pointCount; i++) {
const t = i / pointCount;
const baseX = startX + (endX - startX) * t;
const baseY = startY + (endY - startY) * t;
const dx = (baseX + baseY + this.offset * speed) / (length * scale);
const turbulenceFactor = turbulence * Math.sin((baseX + baseY + time) / 30) * scale;
let easedSin = this.applyEasing(dx * Math.PI * 2);
let easedSin2 = this.applyEasing(dx * Math.PI * 3 + time);
const perpX = easedSin * amplitude * scale;
const perpY = -perpX;
const additionalX = easedSin2 * amplitude * scale * 0.3;
const additionalY = -additionalX;
this.ctx.lineTo(
baseX + perpX + additionalX + turbulenceFactor,
baseY + perpY + additionalY + turbulenceFactor
);
}
this.ctx.lineTo(this.canvas.width, this.canvas.height);
this.ctx.lineTo(0, this.canvas.height);
this.ctx.lineTo(0, 0);
this.ctx.closePath();
}
drawRadialWave(y, amplitude, scale, speed, time, turbulence) {
const centerX = this.canvas.width / 2;
const centerY = this.canvas.height / 2;
const maxRadius = Math.max(this.canvas.width, this.canvas.height) * y;
this.ctx.moveTo(centerX, centerY);
const segments = 100;
const angleStep = (Math.PI * 2) / segments;
for (let i = 0; i <= segments; i++) {
const angle = i * angleStep;
const baseRadius = maxRadius + (this.offset * speed);
const dr = (baseRadius / 50) + time;
const waveOffset = Math.sin(dr + i * 0.2) * amplitude * 15 * scale;
const turbulenceOffset = Math.sin(angle * 5 + time) * turbulence * 10 * scale;
const radius = baseRadius + waveOffset + turbulenceOffset;
const x = centerX + Math.cos(angle) * radius;
const y = centerY + Math.sin(angle) * radius;
if (i === 0) {
this.ctx.moveTo(x, y);
} else {
this.ctx.lineTo(x, y);
}
}
this.ctx.closePath();
}
applyEasing(angle) {
const sin = Math.sin(angle);
switch (this.config.easingType) {
case 'elastic':
return Math.pow(sin, 3) + sin;
case 'bounce':
return sin > 0 ? Math.pow(sin, 2) : sin;
case 'sine':
return sin;
case 'quad':
return sin > 0 ? sin * sin : -sin * sin;
default:
return sin;
}
}
updateAndDrawParticles(time) {
this.ctx.save();
for (let i = 0; i < this.particles.length; i++) {
const p = this.particles[i];
const waveInfluence = 0.5;
const dx = (p.x + this.offset) / 100;
const waveY = Math.sin(dx * Math.PI * 2) * 5 * this.config.waveAmplitude;
p.x += p.speedX + Math.sin(time + i) * 0.2;
p.y += p.speedY + waveY * waveInfluence;
if (p.x < 0) p.x = this.canvas.width;
if (p.x > this.canvas.width) p.x = 0;
if (p.y < 0) p.y = this.canvas.height;
if (p.y > this.canvas.height) p.y = 0;
let particleColor;
if (this.config.waveColorMode === 'single') {
const color = hexToRgb(this.config.waveColor);
particleColor = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + p.opacity + ')';
} else if (this.config.waveColorMode === 'gradient') {
const startColor = hexToRgb(this.config.waveGradientStart);
const endColor = hexToRgb(this.config.waveGradientEnd);
const mixFactor = p.y / this.canvas.height;
const r = Math.floor(startColor.r * (1 - mixFactor) + endColor.r * mixFactor);
const g = Math.floor(startColor.g * (1 - mixFactor) + endColor.g * mixFactor);
const b = Math.floor(startColor.b * (1 - mixFactor) + endColor.b * mixFactor);
particleColor = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + p.opacity + ')';
} else {
const hue = (this.hueRotation + (p.x / this.canvas.width) * 100) % 360;
particleColor = 'hsla(' + hue + ', 80%, 60%, ' + p.opacity + ')';
}
this.ctx.beginPath();
this.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
this.ctx.fillStyle = particleColor;
this.ctx.fill();
}
this.ctx.restore();
}
updateConfig(newConfig) {
const oldConfig = { ...this.config };
this.config = { ...this.config, ...newConfig };
if (oldConfig.enableParticles !== this.config.enableParticles) {
if (this.config.enableParticles) {
this.initParticles();
} else {
this.particles = [];
}
} else if (this.config.enableParticles &&
(oldConfig.particleDensity !== this.config.particleDensity ||
oldConfig.particleSize !== this.config.particleSize)) {
this.initParticles();
}
if (oldConfig.enableMouseInteraction !== this.config.enableMouseInteraction) {
if (this.config.enableMouseInteraction) {
this.setupMouseInteraction();
} else {
this.interacting = false;
this.canvas.style.pointerEvents = 'none';
}
}
if (oldConfig.wavePosition !== this.config.wavePosition) {
this.updateZIndex();
}
}
destroy() {
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
}
if (this.canvas && this.canvas.parentNode) {
this.canvas.parentNode.removeChild(this.canvas);
}
}
}
function hexToRgb(hex) {
const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 0, g: 0, b: 0 };
}
function initWaveBackgrounds() {
document.querySelectorAll('[data-wave-background]').forEach(element => {
if (!element.waveBackground) {
const customWaveColor = element.getAttribute('data-wave-color');
const customWavePosition = element.getAttribute('data-wave-position');
const customConfig = { ...waveConfig };
if (customWaveColor) customConfig.waveColor = customWaveColor;
if (customWavePosition) customConfig.wavePosition = customWavePosition;
element.waveBackground = new WaveBackground(element, customConfig);
}
});
}
function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.bottom >= 0 &&
rect.left <= (window.innerWidth || document.documentElement.clientWidth) &&
rect.right >= 0
);
}
const initWithDelay = () => {
setTimeout(initWaveBackgrounds, 100);
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initWithDelay);
} else {
initWithDelay();
}
window.addEventListener('load', initWaveBackgrounds);
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-wave-background')) {
shouldInit = true;
} else if (node.querySelectorAll) {
const waveElements = node.querySelectorAll('[data-wave-background]');
if (waveElements.length > 0) {
shouldInit = true;
}
}
}
});
} else if (mutation.type === 'attributes' && mutation.attributeName === 'data-wave-background') {
shouldInit = true;
}
});
if (shouldInit) {
setTimeout(initWaveBackgrounds, 100);
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['data-wave-background', 'data-wave-color', 'data-wave-position']
});
})();`;
}
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 'wave-speed':
waveConfig.waveSpeed = defaultValue;
break;
case 'wave-amplitude':
waveConfig.waveAmplitude = defaultValue;
break;
case 'wave-frequency':
waveConfig.waveFrequency = defaultValue;
break;
case 'wave-turbulence':
waveConfig.waveTurbulence = defaultValue;
break;
case 'wave-opacity':
waveConfig.waveOpacity = defaultValue;
break;
case 'wave-glow':
waveConfig.waveGlow = defaultValue;
break;
case 'particle-density':
waveConfig.particleDensity = defaultValue;
break;
case 'particle-size':
waveConfig.particleSize = defaultValue;
break;
case 'breathing-speed':
waveConfig.breathingSpeed = defaultValue;
break;
case 'breathing-intensity':
waveConfig.breathingIntensity = defaultValue;
break;
}
updateWavePreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function generateRandomWave() {
waveConfig.waveColor = generateRandomColor();
waveConfig.waveDirection = ['left-to-right', 'right-to-left', 'alternate', 'radial'][Math.floor(Math.random() * 4)];
waveConfig.waveOrientation = ['horizontal', 'vertical', 'diagonal'][Math.floor(Math.random() * 3)];
waveConfig.waveSpeed = Math.random() * 1.8 + 0.2;
waveConfig.waveAmplitude = Math.random() * 1.5 + 0.5;
waveConfig.waveFrequency = Math.random() * 1.5 + 0.5;
waveConfig.waveTurbulence = Math.random() * 2;
waveConfig.waveOpacity = Math.random() * 0.9 + 0.1;
waveConfig.waveGlow = Math.floor(Math.random() * 20);
waveConfig.easingType = ['linear', 'elastic', 'bounce', 'sine', 'quad'][Math.floor(Math.random() * 5)];
if (Math.random() > 0.5) {
waveConfig.waveColorMode = 'gradient';
waveConfig.waveGradientStart = generateRandomColor();
waveConfig.waveGradientEnd = generateRandomColor();
} else if (Math.random() > 0.5) {
waveConfig.waveColorMode = 'rainbow';
} else {
waveConfig.waveColorMode = 'single';
}
document.getElementById('wave-color').value = waveConfig.waveColor;
document.getElementById('wave-direction').value = waveConfig.waveDirection;
document.getElementById('wave-orientation').value = waveConfig.waveOrientation;
document.getElementById('wave-color-mode').value = waveConfig.waveColorMode;
document.getElementById('wave-speed').value = waveConfig.waveSpeed;
document.getElementById('wave-amplitude').value = waveConfig.waveAmplitude;
document.getElementById('wave-frequency').value = waveConfig.waveFrequency;
document.getElementById('wave-turbulence').value = waveConfig.waveTurbulence;
document.getElementById('wave-opacity').value = waveConfig.waveOpacity;
document.getElementById('wave-glow').value = waveConfig.waveGlow;
document.getElementById('easing-type').value = waveConfig.easingType;
document.getElementById('wave-speed-value').textContent = waveConfig.waveSpeed;
document.getElementById('wave-amplitude-value').textContent = waveConfig.waveAmplitude;
document.getElementById('wave-frequency-value').textContent = waveConfig.waveFrequency;
document.getElementById('wave-turbulence-value').textContent = waveConfig.waveTurbulence;
document.getElementById('wave-opacity-value').textContent = waveConfig.waveOpacity;
document.getElementById('wave-glow-value').textContent = waveConfig.waveGlow;
const colorMode = waveConfig.waveColorMode;
const singleColorGroup = document.getElementById('single-color-group');
const gradientColorGroup = document.getElementById('gradient-color-group');
if (colorMode === 'single') {
singleColorGroup.style.display = 'block';
gradientColorGroup.style.display = 'none';
} else if (colorMode === 'gradient') {
singleColorGroup.style.display = 'none';
gradientColorGroup.style.display = 'block';
document.getElementById('wave-gradient-start').value = waveConfig.waveGradientStart;
document.getElementById('wave-gradient-end').value = waveConfig.waveGradientEnd;
updateGradientColorInputs();
} else {
singleColorGroup.style.display = 'none';
gradientColorGroup.style.display = 'none';
}
updateColorInputs();
updateWavePreview();
showNotification('Random wave configuration generated!');
}
function updateColorInputs() {
const colorInput = document.getElementById('wave-color');
const hexInput = document.getElementById('wave-color-hex');
const hslInput = document.getElementById('wave-color-hsl');
if (colorInput && hexInput && hslInput) {
colorInput.value = waveConfig.waveColor;
hexInput.value = waveConfig.waveColor;
hslInput.value = `hsl(${hexToHsl(waveConfig.waveColor).h}, ${hexToHsl(waveConfig.waveColor).s}%, ${hexToHsl(waveConfig.waveColor).l}%)`;
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
if (colorPickerContainer) {
colorPickerContainer.style.setProperty('--selected-color', waveConfig.waveColor);
}
}
}
function updateGradientColorInputs() {
const startColorInput = document.getElementById('wave-gradient-start');
const startHexInput = document.getElementById('wave-gradient-start-hex');
const startHslInput = document.getElementById('wave-gradient-start-hsl');
if (startColorInput && startHexInput && startHslInput) {
startColorInput.value = waveConfig.waveGradientStart;
startHexInput.value = waveConfig.waveGradientStart;
startHslInput.value = `hsl(${hexToHsl(waveConfig.waveGradientStart).h}, ${hexToHsl(waveConfig.waveGradientStart).s}%, ${hexToHsl(waveConfig.waveGradientStart).l}%)`;
const startColorPickerContainer = startColorInput.closest('.color-row').querySelector('.color-picker-container');
if (startColorPickerContainer) {
startColorPickerContainer.style.setProperty('--selected-color', waveConfig.waveGradientStart);
}
}
const endColorInput = document.getElementById('wave-gradient-end');
const endHexInput = document.getElementById('wave-gradient-end-hex');
const endHslInput = document.getElementById('wave-gradient-end-hsl');
if (endColorInput && endHexInput && endHslInput) {
endColorInput.value = waveConfig.waveGradientEnd;
endHexInput.value = waveConfig.waveGradientEnd;
endHslInput.value = `hsl(${hexToHsl(waveConfig.waveGradientEnd).h}, ${hexToHsl(waveConfig.waveGradientEnd).s}%, ${hexToHsl(waveConfig.waveGradientEnd).l}%)`;
const endColorPickerContainer = endColorInput.closest('.color-row').querySelector('.color-picker-container');
if (endColorPickerContainer) {
endColorPickerContainer.style.setProperty('--selected-color', waveConfig.waveGradientEnd);
}
}
}
function setupColorInput(colorId, hexId, hslId, configKey) {
const colorInput = document.getElementById(colorId);
const hexInput = document.getElementById(hexId);
const hslInput = document.getElementById(hslId);
hslInput.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
colorInput.addEventListener('input', () => {
const color = colorInput.value;
hexInput.value = color;
hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
waveConfig[configKey] = color;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', color);
updateWavePreview();
});
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}%)`;
waveConfig[configKey] = hex;
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', hex);
updateWavePreview();
} 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;
waveConfig[configKey] = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', hex);
updateWavePreview();
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;
waveConfig[configKey] = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateWavePreview();
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');
}
});
}
function initializeUI() {
initWaveBackground();
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-wave-background');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-wave').addEventListener('click', () => {
generateRandomWave();
});
const backgroundPicker = document.getElementById('preview-background-picker');
const previewContainer = document.getElementById('wave-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', () => {
waveConfig.waveColor = defaultConfig.waveColor;
waveConfig.waveColorMode = defaultConfig.waveColorMode;
waveConfig.waveGradientStart = defaultConfig.waveGradientStart;
waveConfig.waveGradientEnd = defaultConfig.waveGradientEnd;
document.getElementById('wave-color').value = defaultConfig.waveColor;
document.getElementById('wave-color-mode').value = defaultConfig.waveColorMode;
document.getElementById('wave-gradient-start').value = defaultConfig.waveGradientStart;
document.getElementById('wave-gradient-end').value = defaultConfig.waveGradientEnd;
const singleColorGroup = document.getElementById('single-color-group');
const gradientColorGroup = document.getElementById('gradient-color-group');
singleColorGroup.style.display = 'block';
gradientColorGroup.style.display = 'none';
updateColorInputs();
updateGradientColorInputs();
updateWavePreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-wave-settings').addEventListener('click', () => {
waveConfig.waveDirection = defaultConfig.waveDirection;
waveConfig.waveOrientation = defaultConfig.waveOrientation;
waveConfig.wavePosition = defaultConfig.wavePosition;
waveConfig.waveSpeed = defaultConfig.waveSpeed;
document.getElementById('wave-direction').value = defaultConfig.waveDirection;
document.getElementById('wave-orientation').value = defaultConfig.waveOrientation;
document.getElementById('wave-position').value = defaultConfig.wavePosition;
document.getElementById('wave-speed').value = defaultConfig.waveSpeed;
document.getElementById('wave-speed-value').textContent = defaultConfig.waveSpeed;
updateWavePreview();
showNotification('Wave settings reset');
});
document.getElementById('reset-appearance').addEventListener('click', () => {
waveConfig.waveAmplitude = defaultConfig.waveAmplitude;
waveConfig.waveFrequency = defaultConfig.waveFrequency;
waveConfig.waveTurbulence = defaultConfig.waveTurbulence;
waveConfig.waveOpacity = defaultConfig.waveOpacity;
waveConfig.waveGlow = defaultConfig.waveGlow;
document.getElementById('wave-amplitude').value = defaultConfig.waveAmplitude;
document.getElementById('wave-frequency').value = defaultConfig.waveFrequency;
document.getElementById('wave-turbulence').value = defaultConfig.waveTurbulence;
document.getElementById('wave-opacity').value = defaultConfig.waveOpacity;
document.getElementById('wave-glow').value = defaultConfig.waveGlow;
document.getElementById('wave-amplitude-value').textContent = defaultConfig.waveAmplitude;
document.getElementById('wave-frequency-value').textContent = defaultConfig.waveFrequency;
document.getElementById('wave-turbulence-value').textContent = defaultConfig.waveTurbulence;
document.getElementById('wave-opacity-value').textContent = defaultConfig.waveOpacity;
document.getElementById('wave-glow-value').textContent = defaultConfig.waveGlow;
updateWavePreview();
showNotification('Appearance settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
waveConfig.enableParticles = defaultConfig.enableParticles;
waveConfig.particleDensity = defaultConfig.particleDensity;
waveConfig.particleSize = defaultConfig.particleSize;
waveConfig.enableBreathing = defaultConfig.enableBreathing;
waveConfig.breathingSpeed = defaultConfig.breathingSpeed;
waveConfig.breathingIntensity = defaultConfig.breathingIntensity;
waveConfig.enableMouseInteraction = defaultConfig.enableMouseInteraction;
waveConfig.easingType = defaultConfig.easingType;
document.getElementById('enable-particles').checked = defaultConfig.enableParticles;
document.getElementById('particle-density').value = defaultConfig.particleDensity;
document.getElementById('particle-size').value = defaultConfig.particleSize;
document.getElementById('enable-breathing').checked = defaultConfig.enableBreathing;
document.getElementById('breathing-speed').value = defaultConfig.breathingSpeed;
document.getElementById('breathing-intensity').value = defaultConfig.breathingIntensity;
document.getElementById('enable-mouse-interaction').checked = defaultConfig.enableMouseInteraction;
document.getElementById('easing-type').value = defaultConfig.easingType;
document.getElementById('particle-density-value').textContent = defaultConfig.particleDensity;
document.getElementById('particle-size-value').textContent = defaultConfig.particleSize;
document.getElementById('breathing-speed-value').textContent = defaultConfig.breathingSpeed;
document.getElementById('breathing-intensity-value').textContent = defaultConfig.breathingIntensity;
document.getElementById('particles-settings').style.display = 'none';
document.getElementById('breathing-settings').style.display = 'none';
updateWavePreview();
showNotification('Advanced settings reset');
});
document.getElementById('wave-color-mode').addEventListener('change', function() {
const colorMode = this.value;
const singleColorGroup = document.getElementById('single-color-group');
const gradientColorGroup = document.getElementById('gradient-color-group');
if (colorMode === 'single') {
singleColorGroup.style.display = 'block';
gradientColorGroup.style.display = 'none';
} else if (colorMode === 'gradient') {
singleColorGroup.style.display = 'none';
gradientColorGroup.style.display = 'block';
} else {
singleColorGroup.style.display = 'none';
gradientColorGroup.style.display = 'none';
}
waveConfig.waveColorMode = colorMode;
updateWavePreview();
});
setupColorInput('wave-color', 'wave-color-hex', 'wave-color-hsl', 'waveColor');
setupColorInput('wave-gradient-start', 'wave-gradient-start-hex', 'wave-gradient-start-hsl', 'waveGradientStart');
setupColorInput('wave-gradient-end', 'wave-gradient-end-hex', 'wave-gradient-end-hsl', 'waveGradientEnd');
document.getElementById('wave-direction').addEventListener('change', function() {
waveConfig.waveDirection = this.value;
updateWavePreview();
});
document.getElementById('wave-orientation').addEventListener('change', function() {
waveConfig.waveOrientation = this.value;
updateWavePreview();
});
document.getElementById('wave-position').addEventListener('change', function() {
waveConfig.wavePosition = this.value;
updateWavePreview();
});
document.getElementById('easing-type').addEventListener('change', function() {
waveConfig.easingType = this.value;
updateWavePreview();
});
document.getElementById('enable-particles').addEventListener('change', function() {
const particlesSettings = document.getElementById('particles-settings');
if (this.checked) {
particlesSettings.style.display = 'block';
} else {
particlesSettings.style.display = 'none';
}
waveConfig.enableParticles = this.checked;
updateWavePreview();
});
document.getElementById('enable-breathing').addEventListener('change', function() {
const breathingSettings = document.getElementById('breathing-settings');
if (this.checked) {
breathingSettings.style.display = 'block';
} else {
breathingSettings.style.display = 'none';
}
waveConfig.enableBreathing = this.checked;
updateWavePreview();
});
document.getElementById('enable-mouse-interaction').addEventListener('change', function() {
waveConfig.enableMouseInteraction = this.checked;
updateWavePreview();
});
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 'wave-speed':
waveConfig.waveSpeed = parseFloat(input.value);
break;
case 'wave-amplitude':
waveConfig.waveAmplitude = parseFloat(input.value);
break;
case 'wave-frequency':
waveConfig.waveFrequency = parseFloat(input.value);
break;
case 'wave-turbulence':
waveConfig.waveTurbulence = parseFloat(input.value);
break;
case 'wave-opacity':
waveConfig.waveOpacity = parseFloat(input.value);
break;
case 'wave-glow':
waveConfig.waveGlow = parseFloat(input.value);
break;
case 'particle-density':
waveConfig.particleDensity = parseInt(input.value);
break;
case 'particle-size':
waveConfig.particleSize = parseFloat(input.value);
break;
case 'breathing-speed':
waveConfig.breathingSpeed = parseFloat(input.value);
break;
case 'breathing-intensity':
waveConfig.breathingIntensity = parseFloat(input.value);
break;
}
updateWavePreview();
});
});
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':
generateRandomWave();
break;
case 'b':
document.getElementById('preview-background-picker').click();
break;
}
}
});
updateColorInputs();
updateGradientColorInputs();
setTimeout(() => {
showNotification('BricksFusion Wave Background Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-wave-config', JSON.stringify(waveConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-wave-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(waveConfig, savedConfig);
document.getElementById('wave-color').value = savedConfig.waveColor;
document.getElementById('wave-direction').value = savedConfig.waveDirection;
document.getElementById('wave-orientation').value = savedConfig.waveOrientation;
document.getElementById('wave-position').value = savedConfig.wavePosition;
document.getElementById('wave-color-mode').value = savedConfig.waveColorMode;
document.getElementById('wave-speed').value = savedConfig.waveSpeed;
document.getElementById('wave-amplitude').value = savedConfig.waveAmplitude;
document.getElementById('wave-frequency').value = savedConfig.waveFrequency;
document.getElementById('wave-turbulence').value = savedConfig.waveTurbulence;
document.getElementById('wave-opacity').value = savedConfig.waveOpacity;
document.getElementById('wave-glow').value = savedConfig.waveGlow;
document.getElementById('easing-type').value = savedConfig.easingType;
document.getElementById('enable-particles').checked = savedConfig.enableParticles;
document.getElementById('particle-density').value = savedConfig.particleDensity;
document.getElementById('particle-size').value = savedConfig.particleSize;
document.getElementById('enable-breathing').checked = savedConfig.enableBreathing;
document.getElementById('breathing-speed').value = savedConfig.breathingSpeed;
document.getElementById('breathing-intensity').value = savedConfig.breathingIntensity;
document.getElementById('enable-mouse-interaction').checked = savedConfig.enableMouseInteraction;
document.getElementById('wave-speed-value').textContent = savedConfig.waveSpeed;
document.getElementById('wave-amplitude-value').textContent = savedConfig.waveAmplitude;
document.getElementById('wave-frequency-value').textContent = savedConfig.waveFrequency;
document.getElementById('wave-turbulence-value').textContent = savedConfig.waveTurbulence;
document.getElementById('wave-opacity-value').textContent = savedConfig.waveOpacity;
document.getElementById('wave-glow-value').textContent = savedConfig.waveGlow;
document.getElementById('particle-density-value').textContent = savedConfig.particleDensity;
document.getElementById('particle-size-value').textContent = savedConfig.particleSize;
document.getElementById('breathing-speed-value').textContent = savedConfig.breathingSpeed;
document.getElementById('breathing-intensity-value').textContent = savedConfig.breathingIntensity;
const colorMode = savedConfig.waveColorMode;
const singleColorGroup = document.getElementById('single-color-group');
const gradientColorGroup = document.getElementById('gradient-color-group');
if (colorMode === 'single') {
singleColorGroup.style.display = 'block';
gradientColorGroup.style.display = 'none';
} else if (colorMode === 'gradient') {
singleColorGroup.style.display = 'none';
gradientColorGroup.style.display = 'block';
document.getElementById('wave-gradient-start').value = savedConfig.waveGradientStart;
document.getElementById('wave-gradient-end').value = savedConfig.waveGradientEnd;
updateGradientColorInputs();
} else {
singleColorGroup.style.display = 'none';
gradientColorGroup.style.display = 'none';
}
if (savedConfig.enableParticles) {
document.getElementById('particles-settings').style.display = 'block';
}
if (savedConfig.enableBreathing) {
document.getElementById('breathing-settings').style.display = 'block';
}
updateColorInputs();
updateWavePreview();
}
} catch (e) {
}
}
const originalUpdateWavePreview = updateWavePreview;
updateWavePreview = function() {
originalUpdateWavePreview();
saveConfiguration();
};
loadConfiguration();
}
initializeUI();
});
</script>
</body>
</html>
Wave Background
Creates animated wave background with extensive customization options. Features multiple wave orientations including horizontal, vertical, diagonal, and radial patterns. Supports single color, gradient, or animated rainbow modes with optional particle effects. Includes breathing animation for organic pulsing, mouse interaction for dynamic ripples, and multiple easing functions for varied motion styles. Uses HTML5 Canvas with hardware acceleration and ResizeObserver for responsive scaling. Perfect for hero sections, feature areas, or adding fluid motion to any design.
Wave Background
Watch the waves flow with smooth, organic motion.
Colors
Color style for waves. Single uses one color, gradient blends two colors, rainbow creates animated color cycling.
Default: single
Primary wave color when using single color mode. Also affects particle colors.
Default: #4682B4 (steel blue)
Starting color for gradient mode. Appears at wave peaks.
Default: #4682B4 (steel blue)
Ending color for gradient mode. Blends smoothly from start color.
Default: #FF6347 (tomato red)
Wave Settings
Wave animation speed. Higher creates faster flowing motion.
Default: 1
Wave height. Higher creates more dramatic peaks and valleys.
Default: 1
Number of wave cycles. Higher creates more waves across the space.
Default: 1
Random variation in wave pattern. Higher creates more chaotic, natural-looking motion.
Default: 1
Wave transparency. Lower allows more background visibility.
Default: 0.7
Layer position relative to content. Front displays above, back displays behind.
Default: front
Wave Behavior
Wave flow direction. Options: left-to-right, right-to-left, alternate (back and forth), radial (outward from center).
Default: left-to-right
Wave angle layout. Options: horizontal (standard waves), vertical (sideways waves), diagonal (corner to corner), radial (circular from center).
Default: horizontal
Motion curve style. Options: linear (steady), elastic (bouncy overshoot), bounce (spring-like), sine (smooth), quad (accelerating).
Default: linear
Effects
Blur glow intensity around waves. Creates luminous, ethereal effect.
Default: 0
Adds organic pulsing animation to wave amplitude for living, breathing effect.
Default: off
Speed of breathing pulse animation when enabled.
Default: 1
Strength of breathing amplitude variation when enabled.
Default: 0.5
Adds floating particles that follow wave motion for enhanced depth.
Default: off
Number of floating particles when enabled. Higher creates fuller effect.
Default: 50
Size of individual floating particles when enabled.
Default: 3
Waves respond to mouse movement creating ripple effects where cursor moves.
Default: off
Performance
This element uses HTML5 Canvas with continuous requestAnimationFrame animation for smooth wave motion. Features multiple simultaneous wave layers with gradient fills and optional blur effects. Includes ResizeObserver for responsive scaling and optional particle system with physics-based movement. Mouse interaction adds real-time position tracking with distance calculations. Breathing effect uses trigonometric functions for organic pulsing. Medium weight - suitable for hero backgrounds and feature sections. Performance scales with particle count and glow intensity. Recommended to use single instance per page. Works well on most devices but consider disabling particles and mouse interaction on mobile for better performance.