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>Plasma Flow 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);
will-change: contents;
}
.preview-container canvas {
will-change: contents;
transform: translateZ(0);
backface-visibility: hidden;
}
.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: #6633cc;
}
.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, #6633cc);
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;
}
@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">Plasma Flow</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-plasma-flow
</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">Plasma Flow</h1>
<p class="page-subtitle">Interactive WebGL effects 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 plasma flow effect using the controls below</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste or upload the JavaScript code</li>
<li>To add the effect to any container: go to <strong>Section → Style → Attributes</strong>, add <code>data-plasma-flow</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="plasma-preview" data-plasma-flow="true">
<div class="preview-content">Interactive Plasma Flow Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-plasma" 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="#252525" title="Change Preview Background (B)">
</div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Line 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="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="line-color" value="#6633cc">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="line-color-hex" value="#6633cc" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="line-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Flow Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-flow" title="Reset Flow Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Animation Speed
<span class="help-tooltip" title="Speed of the plasma flow animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="animation-speed-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('animation-speed', 0.5)">↺</button>
</div>
</div>
<input type="range" id="animation-speed" min="0.1" max="2.0" step="0.1" value="0.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Intensity
<span class="help-tooltip" title="Overall intensity of the plasma effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="intensity-value">1.0</span></span>
<button class="reset-btn" onclick="resetParameter('intensity', 1.0)">↺</button>
</div>
</div>
<input type="range" id="intensity" min="0.1" max="3.0" step="0.1" value="1.0">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Line Width
<span class="help-tooltip" title="Thickness of the plasma lines">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="line-width-value">1.0</span></span>
<button class="reset-btn" onclick="resetParameter('line-width', 1.0)">↺</button>
</div>
</div>
<input type="range" id="line-width" min="0.2" max="3.0" step="0.1" value="1.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">Blend Mode</span>
</div>
<select id="blend-mode">
<option value="normal">Normal</option>
<option value="multiply">Multiply</option>
<option value="screen">Screen</option>
<option value="overlay">Overlay</option>
<option value="soft-light">Soft Light</option>
<option value="hard-light">Hard Light</option>
<option value="color-dodge">Color Dodge</option>
<option value="color-burn">Color Burn</option>
<option value="difference">Difference</option>
<option value="exclusion">Exclusion</option>
</select>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let plasmaFlowConfig = {
speed: 0.5,
intensity: 1.0,
lineWidth: 1.0,
lineColor: "#6633cc",
blendMode: "normal"
};
const defaultConfig = { ...plasmaFlowConfig };
let previewInstance = null;
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 hexToRgb(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;
return [r, g, b];
}
class PlasmaFlow {
constructor(container, options = {}) {
this.container = container;
this.canvas = document.createElement('canvas');
this.gl = null;
this.program = null;
this.animationId = null;
this.startTime = Date.now();
this.isDestroyed = false;
this.config = {
speed: options.speed || 0.5,
intensity: options.intensity || 1.0,
lineWidth: options.lineWidth || 1.0,
lineColor: options.lineColor || "#6633cc",
blendMode: options.blendMode || "normal"
};
this.init();
}
init() {
this.setupCanvas();
this.initWebGL();
this.animate();
}
setupCanvas() {
this.canvas.style.position = 'absolute';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvas.style.pointerEvents = 'none';
this.canvas.style.zIndex = '1';
this.canvas.style.mixBlendMode = this.config.blendMode;
this.container.style.position = this.container.style.position || 'relative';
this.container.appendChild(this.canvas);
this.resizeCanvas();
}
resizeCanvas() {
const rect = this.container.getBoundingClientRect();
const width = rect.width;
const height = rect.height;
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768;
const baseDpr = window.devicePixelRatio || 1;
const dpr = isMobile ? baseDpr * 0.7 : baseDpr;
this.canvas.width = width * dpr;
this.canvas.height = height * dpr;
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
if (this.gl) {
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
}
}
initWebGL() {
this.gl = this.canvas.getContext('webgl', {
alpha: true,
premultipliedAlpha: false,
antialias: true
});
if (!this.gl) {
return;
}
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);
const vsSource = `
attribute vec4 aVertexPosition;
void main() {
gl_Position = aVertexPosition;
}
`;
const fsSource = `
precision highp float;
uniform vec2 iResolution;
uniform float iTime;
uniform float iSpeed;
uniform float iIntensity;
uniform vec3 iLineColor;
uniform float iLineWidth;
const float gridSmoothWidth = 0.015;
const float scale = 5.0;
const float lineAmplitude = 1.0;
const float lineFrequency = 0.2;
const float warpFrequency = 0.5;
const float warpAmplitude = 1.0;
const float offsetFrequency = 0.5;
const float minOffsetSpread = 0.6;
const float maxOffsetSpread = 2.0;
const int linesPerGroup = 16;
float drawCircle(vec2 pos, float radius, vec2 coord) {
return smoothstep(radius + gridSmoothWidth, radius, length(coord - pos));
}
float drawSmoothLine(float pos, float halfWidth, float t) {
return smoothstep(halfWidth, 0.0, abs(pos - t));
}
float drawCrispLine(float pos, float halfWidth, float t) {
return smoothstep(halfWidth + gridSmoothWidth, halfWidth, abs(pos - t));
}
float random(float t) {
return (cos(t) + cos(t * 1.3 + 1.3) + cos(t * 1.4 + 1.4)) / 3.0;
}
float getPlasmaY(float x, float horizontalFade, float offset, float speed) {
return random(x * lineFrequency + iTime * speed) * horizontalFade * lineAmplitude + offset;
}
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec2 uv = fragCoord.xy / iResolution.xy;
vec2 space = (fragCoord - iResolution.xy / 2.0) / iResolution.x * 2.0 * scale;
float horizontalFade = 1.0 - (cos(uv.x * 6.28) * 0.5 + 0.5);
float verticalFade = 1.0 - (cos(uv.y * 6.28) * 0.5 + 0.5);
float warpSpeed = 0.2 * iSpeed;
space.y += random(space.x * warpFrequency + iTime * warpSpeed) * warpAmplitude * (0.5 + horizontalFade);
space.x += random(space.y * warpFrequency + iTime * warpSpeed + 2.0) * warpAmplitude * horizontalFade;
vec4 lines = vec4(0.0);
vec4 lineColor = vec4(iLineColor, 1.0);
for(int l = 0; l < linesPerGroup; l++) {
float normalizedLineIndex = float(l) / float(linesPerGroup);
float offsetTime = iTime * iSpeed * 1.33;
float offsetPosition = float(l) + space.x * offsetFrequency;
float rand = random(offsetPosition + offsetTime) * 0.5 + 0.5;
float halfWidth = mix(0.01 * iLineWidth, 0.2 * iLineWidth, rand * horizontalFade) / 2.0;
float offset = random(offsetPosition + offsetTime * (1.0 + normalizedLineIndex)) * mix(minOffsetSpread, maxOffsetSpread, horizontalFade);
float linePosition = getPlasmaY(space.x, horizontalFade, offset, iSpeed);
float line = drawSmoothLine(linePosition, halfWidth, space.y) / 2.0 + drawCrispLine(linePosition, halfWidth * 0.15, space.y);
float circleX = mod(float(l) + iTime * iSpeed, 25.0) - 12.0;
vec2 circlePosition = vec2(circleX, getPlasmaY(circleX, horizontalFade, offset, iSpeed));
float circle = drawCircle(circlePosition, 0.01, space) * 4.0;
line = line + circle;
lines += line * lineColor * rand * iIntensity;
}
vec4 fragColor = lines;
fragColor.a = lines.r * verticalFade;
gl_FragColor = fragColor;
}
`;
this.program = this.createShaderProgram(vsSource, fsSource);
if (!this.program) return;
const positionBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer);
const positions = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];
this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);
this.programInfo = {
attribLocations: {
vertexPosition: this.gl.getAttribLocation(this.program, 'aVertexPosition'),
},
uniformLocations: {
resolution: this.gl.getUniformLocation(this.program, 'iResolution'),
time: this.gl.getUniformLocation(this.program, 'iTime'),
speed: this.gl.getUniformLocation(this.program, 'iSpeed'),
intensity: this.gl.getUniformLocation(this.program, 'iIntensity'),
lineColor: this.gl.getUniformLocation(this.program, 'iLineColor'),
lineWidth: this.gl.getUniformLocation(this.program, 'iLineWidth')
}
};
this.positionBuffer = positionBuffer;
}
createShaderProgram(vsSource, fsSource) {
const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource);
if (!vertexShader || !fragmentShader) return null;
const shaderProgram = this.gl.createProgram();
this.gl.attachShader(shaderProgram, vertexShader);
this.gl.attachShader(shaderProgram, fragmentShader);
this.gl.linkProgram(shaderProgram);
if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
return null;
}
return shaderProgram;
}
loadShader(type, source) {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
this.gl.deleteShader(shader);
return null;
}
return shader;
}
updateOptions(options) {
Object.assign(this.config, options);
if (options.blendMode && this.canvas) {
this.canvas.style.mixBlendMode = options.blendMode;
}
}
animate() {
if (this.isDestroyed || !this.gl || !this.program) return;
const currentTime = (Date.now() - this.startTime) / 1000;
this.gl.clearColor(0.0, 0.0, 0.0, 0.0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
this.gl.useProgram(this.program);
const lineColorRgb = hexToRgb(this.config.lineColor);
this.gl.uniform2f(this.programInfo.uniformLocations.resolution, this.canvas.width, this.canvas.height);
this.gl.uniform1f(this.programInfo.uniformLocations.time, currentTime);
this.gl.uniform1f(this.programInfo.uniformLocations.speed, this.config.speed);
this.gl.uniform1f(this.programInfo.uniformLocations.intensity, this.config.intensity);
this.gl.uniform3f(this.programInfo.uniformLocations.lineColor, ...lineColorRgb);
this.gl.uniform1f(this.programInfo.uniformLocations.lineWidth, this.config.lineWidth);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
this.gl.vertexAttribPointer(this.programInfo.attribLocations.vertexPosition, 2, this.gl.FLOAT, false, 0, 0);
this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition);
this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
this.animationId = requestAnimationFrame(() => this.animate());
}
destroy() {
this.isDestroyed = true;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
if (this.canvas && this.canvas.parentNode) {
this.canvas.parentNode.removeChild(this.canvas);
}
this.gl = null;
this.program = null;
}
}
function initializePreview() {
const container = document.getElementById('plasma-preview');
if (previewInstance) {
previewInstance.destroy();
}
previewInstance = new PlasmaFlow(container, plasmaFlowConfig);
}
function updatePreview() {
plasmaFlowConfig.speed = parseFloat(document.getElementById('animation-speed').value);
plasmaFlowConfig.intensity = parseFloat(document.getElementById('intensity').value);
plasmaFlowConfig.lineWidth = parseFloat(document.getElementById('line-width').value);
plasmaFlowConfig.blendMode = document.getElementById('blend-mode').value;
if (previewInstance) {
previewInstance.updateOptions(plasmaFlowConfig);
}
saveConfiguration();
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = 'notification ' + type;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const codeId = generateUniqueId();
const attributeId = generateUniqueId();
// Obtener el código JavaScript generado
const jsCode = generateJavaScriptCode();
// Crear la estructura JSON completa
const fullSectionData = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_attributes": [
{
"id": attributeId,
"name": "data-plasma-flow"
}
],
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
}
},
"label": "Plasma Section"
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": {}
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Plasma JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(fullSectionData, null, 2);
}
function generateJavaScriptCode() {
return "(function() {\n" +
" 'use strict';\n" +
" \n" +
" window.PlasmaFlowAnimation = window.PlasmaFlowAnimation || {};\n" +
" \n" +
" const PlasmaFlow = {\n" +
" instances: new Map(),\n" +
" \n" +
" vsSource: `\n" +
" attribute vec4 aVertexPosition;\n" +
" void main() {\n" +
" gl_Position = aVertexPosition;\n" +
" }\n" +
" `,\n" +
" \n" +
" fsSource: `\n" +
" precision highp float;\n" +
" uniform vec2 iResolution;\n" +
" uniform float iTime;\n" +
" uniform float iSpeed;\n" +
" uniform float iIntensity;\n" +
" uniform vec3 iLineColor;\n" +
" uniform float iLineWidth;\n" +
" \n" +
" const float gridSmoothWidth = 0.015;\n" +
" const float scale = 5.0;\n" +
" const float lineAmplitude = 1.0;\n" +
" const float lineFrequency = 0.2;\n" +
" const float warpFrequency = 0.5;\n" +
" const float warpAmplitude = 1.0;\n" +
" const float offsetFrequency = 0.5;\n" +
" const float minOffsetSpread = 0.6;\n" +
" const float maxOffsetSpread = 2.0;\n" +
" const int linesPerGroup = 16;\n" +
" \n" +
" float drawCircle(vec2 pos, float radius, vec2 coord) {\n" +
" return smoothstep(radius + gridSmoothWidth, radius, length(coord - pos));\n" +
" }\n" +
" \n" +
" float drawSmoothLine(float pos, float halfWidth, float t) {\n" +
" return smoothstep(halfWidth, 0.0, abs(pos - t));\n" +
" }\n" +
" \n" +
" float drawCrispLine(float pos, float halfWidth, float t) {\n" +
" return smoothstep(halfWidth + gridSmoothWidth, halfWidth, abs(pos - t));\n" +
" }\n" +
" \n" +
" float random(float t) {\n" +
" return (cos(t) + cos(t * 1.3 + 1.3) + cos(t * 1.4 + 1.4)) / 3.0;\n" +
" }\n" +
" \n" +
" float getPlasmaY(float x, float horizontalFade, float offset, float speed) {\n" +
" return random(x * lineFrequency + iTime * speed) * horizontalFade * lineAmplitude + offset;\n" +
" }\n" +
" \n" +
" void main() {\n" +
" vec2 fragCoord = gl_FragCoord.xy;\n" +
" vec2 uv = fragCoord.xy / iResolution.xy;\n" +
" vec2 space = (fragCoord - iResolution.xy / 2.0) / iResolution.x * 2.0 * scale;\n" +
" \n" +
" float horizontalFade = 1.0 - (cos(uv.x * 6.28) * 0.5 + 0.5);\n" +
" float verticalFade = 1.0 - (cos(uv.y * 6.28) * 0.5 + 0.5);\n" +
" \n" +
" float warpSpeed = 0.2 * iSpeed;\n" +
" space.y += random(space.x * warpFrequency + iTime * warpSpeed) * warpAmplitude * (0.5 + horizontalFade);\n" +
" space.x += random(space.y * warpFrequency + iTime * warpSpeed + 2.0) * warpAmplitude * horizontalFade;\n" +
" \n" +
" vec4 lines = vec4(0.0);\n" +
" vec4 lineColor = vec4(iLineColor, 1.0);\n" +
" \n" +
" for(int l = 0; l < linesPerGroup; l++) {\n" +
" float normalizedLineIndex = float(l) / float(linesPerGroup);\n" +
" float offsetTime = iTime * iSpeed * 1.33;\n" +
" float offsetPosition = float(l) + space.x * offsetFrequency;\n" +
" float rand = random(offsetPosition + offsetTime) * 0.5 + 0.5;\n" +
" float halfWidth = mix(0.01 * iLineWidth, 0.2 * iLineWidth, rand * horizontalFade) / 2.0;\n" +
" float offset = random(offsetPosition + offsetTime * (1.0 + normalizedLineIndex)) * mix(minOffsetSpread, maxOffsetSpread, horizontalFade);\n" +
" float linePosition = getPlasmaY(space.x, horizontalFade, offset, iSpeed);\n" +
" float line = drawSmoothLine(linePosition, halfWidth, space.y) / 2.0 + drawCrispLine(linePosition, halfWidth * 0.15, space.y);\n" +
" \n" +
" float circleX = mod(float(l) + iTime * iSpeed, 25.0) - 12.0;\n" +
" vec2 circlePosition = vec2(circleX, getPlasmaY(circleX, horizontalFade, offset, iSpeed));\n" +
" float circle = drawCircle(circlePosition, 0.01, space) * 4.0;\n" +
" \n" +
" line = line + circle;\n" +
" lines += line * lineColor * rand * iIntensity;\n" +
" }\n" +
" \n" +
" vec4 fragColor = lines;\n" +
" fragColor.a = lines.r * verticalFade;\n" +
" \n" +
" gl_FragColor = fragColor;\n" +
" }\n" +
" `,\n" +
" \n" +
" loadShader: function(gl, type, source) {\n" +
" const shader = gl.createShader(type);\n" +
" gl.shaderSource(shader, source);\n" +
" gl.compileShader(shader);\n" +
" \n" +
" if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n" +
" gl.deleteShader(shader);\n" +
" return null;\n" +
" }\n" +
" return shader;\n" +
" },\n" +
" \n" +
" initShaderProgram: function(gl, vsSource, fsSource) {\n" +
" const vertexShader = this.loadShader(gl, gl.VERTEX_SHADER, vsSource);\n" +
" const fragmentShader = this.loadShader(gl, gl.FRAGMENT_SHADER, fsSource);\n" +
" \n" +
" if (!vertexShader || !fragmentShader) return null;\n" +
" \n" +
" const shaderProgram = gl.createProgram();\n" +
" gl.attachShader(shaderProgram, vertexShader);\n" +
" gl.attachShader(shaderProgram, fragmentShader);\n" +
" gl.linkProgram(shaderProgram);\n" +
" \n" +
" if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {\n" +
" return null;\n" +
" }\n" +
" \n" +
" return shaderProgram;\n" +
" },\n" +
" \n" +
" parseColor: function(colorString) {\n" +
" if (colorString.startsWith('#')) {\n" +
" const r = parseInt(colorString.slice(1, 3), 16) / 255;\n" +
" const g = parseInt(colorString.slice(3, 5), 16) / 255;\n" +
" const b = parseInt(colorString.slice(5, 7), 16) / 255;\n" +
" return [r, g, b];\n" +
" }\n" +
" return [0.4, 0.2, 0.8];\n" +
" },\n" +
" \n" +
" createInstance: function(element) {\n" +
" if (this.instances.has(element)) {\n" +
" this.destroyInstance(element);\n" +
" }\n" +
" \n" +
" const canvas = document.createElement('canvas');\n" +
" canvas.style.cssText = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; mix-blend-mode: " + plasmaFlowConfig.blendMode + ";';\n" +
" \n" +
" const originalPosition = getComputedStyle(element).position;\n" +
" if (originalPosition === 'static') {\n" +
" element.style.position = 'relative';\n" +
" }\n" +
" \n" +
" element.appendChild(canvas);\n" +
" \n" +
" const gl = canvas.getContext('webgl', {\n" +
" alpha: true,\n" +
" premultipliedAlpha: false,\n" +
" antialias: true\n" +
" });\n" +
" \n" +
" if (!gl) {\n" +
" canvas.remove();\n" +
" return;\n" +
" }\n" +
" \n" +
" gl.enable(gl.BLEND);\n" +
" gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);\n" +
" \n" +
" const shaderProgram = this.initShaderProgram(gl, this.vsSource, this.fsSource);\n" +
" if (!shaderProgram) {\n" +
" canvas.remove();\n" +
" return;\n" +
" }\n" +
" \n" +
" const positionBuffer = gl.createBuffer();\n" +
" gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n" +
" const positions = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0];\n" +
" gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);\n" +
" \n" +
" const programInfo = {\n" +
" program: shaderProgram,\n" +
" attribLocations: {\n" +
" vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'),\n" +
" },\n" +
" uniformLocations: {\n" +
" resolution: gl.getUniformLocation(shaderProgram, 'iResolution'),\n" +
" time: gl.getUniformLocation(shaderProgram, 'iTime'),\n" +
" speed: gl.getUniformLocation(shaderProgram, 'iSpeed'),\n" +
" intensity: gl.getUniformLocation(shaderProgram, 'iIntensity'),\n" +
" lineColor: gl.getUniformLocation(shaderProgram, 'iLineColor'),\n" +
" lineWidth: gl.getUniformLocation(shaderProgram, 'iLineWidth')\n" +
" },\n" +
" };\n" +
" \n" +
" const instance = {\n" +
" element,\n" +
" canvas,\n" +
" gl,\n" +
" programInfo,\n" +
" positionBuffer,\n" +
" config: {\n" +
" speed: " + plasmaFlowConfig.speed + ",\n" +
" intensity: " + plasmaFlowConfig.intensity + ",\n" +
" lineColor: '" + plasmaFlowConfig.lineColor + "',\n" +
" lineWidth: " + plasmaFlowConfig.lineWidth + ",\n" +
" blendMode: '" + plasmaFlowConfig.blendMode + "'\n" +
" },\n" +
" startTime: Date.now(),\n" +
" animationId: null\n" +
" };\n" +
" \n" +
" const resizeCanvas = () => {\n" +
" const rect = element.getBoundingClientRect();\n" +
" const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768;\n" +
" const baseDpr = window.devicePixelRatio || 1;\n" +
" const dpr = isMobile ? baseDpr * 0.7 : baseDpr;\n" +
" \n" +
" canvas.width = rect.width * dpr;\n" +
" canvas.height = rect.height * dpr;\n" +
" canvas.style.width = rect.width + 'px';\n" +
" canvas.style.height = rect.height + 'px';\n" +
" gl.viewport(0, 0, canvas.width, canvas.height);\n" +
" };\n" +
" \n" +
" resizeCanvas();\n" +
" \n" +
" const render = () => {\n" +
" if (!this.instances.has(element)) return;\n" +
" \n" +
" const currentTime = (Date.now() - instance.startTime) / 1000;\n" +
" \n" +
" gl.clearColor(0.0, 0.0, 0.0, 0.0);\n" +
" gl.clear(gl.COLOR_BUFFER_BIT);\n" +
" \n" +
" gl.useProgram(programInfo.program);\n" +
" \n" +
" const lineColorRgb = this.parseColor(instance.config.lineColor);\n" +
" \n" +
" gl.uniform2f(programInfo.uniformLocations.resolution, canvas.width, canvas.height);\n" +
" gl.uniform1f(programInfo.uniformLocations.time, currentTime);\n" +
" gl.uniform1f(programInfo.uniformLocations.speed, instance.config.speed);\n" +
" gl.uniform1f(programInfo.uniformLocations.intensity, instance.config.intensity);\n" +
" gl.uniform3f(programInfo.uniformLocations.lineColor, ...lineColorRgb);\n" +
" gl.uniform1f(programInfo.uniformLocations.lineWidth, instance.config.lineWidth);\n" +
" \n" +
" gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);\n" +
" gl.vertexAttribPointer(programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0);\n" +
" gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);\n" +
" \n" +
" gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n" +
" \n" +
" instance.animationId = requestAnimationFrame(render);\n" +
" };\n" +
" \n" +
" instance.animationId = requestAnimationFrame(render);\n" +
" this.instances.set(element, instance);\n" +
" \n" +
" let resizeTimeout;\n" +
" window.addEventListener('resize', () => {\n" +
" clearTimeout(resizeTimeout);\n" +
" resizeTimeout = setTimeout(resizeCanvas, 250);\n" +
" });\n" +
" \n" +
" return instance;\n" +
" },\n" +
" \n" +
" destroyInstance: function(element) {\n" +
" const instance = this.instances.get(element);\n" +
" if (!instance) return;\n" +
" \n" +
" if (instance.animationId) {\n" +
" cancelAnimationFrame(instance.animationId);\n" +
" }\n" +
" \n" +
" if (instance.canvas && instance.canvas.parentNode) {\n" +
" instance.canvas.remove();\n" +
" }\n" +
" \n" +
" this.instances.delete(element);\n" +
" },\n" +
" \n" +
" init: function() {\n" +
" const elements = document.querySelectorAll('[data-plasma-flow]');\n" +
" elements.forEach(element => this.createInstance(element));\n" +
" \n" +
" if (window.MutationObserver) {\n" +
" const observer = new MutationObserver(mutations => {\n" +
" mutations.forEach(mutation => {\n" +
" mutation.addedNodes.forEach(node => {\n" +
" if (node.nodeType === 1) {\n" +
" if (node.hasAttribute && node.hasAttribute('data-plasma-flow')) {\n" +
" this.createInstance(node);\n" +
" }\n" +
" node.querySelectorAll('[data-plasma-flow]').forEach(el => {\n" +
" this.createInstance(el);\n" +
" });\n" +
" }\n" +
" });\n" +
" \n" +
" mutation.removedNodes.forEach(node => {\n" +
" if (node.nodeType === 1 && this.instances.has(node)) {\n" +
" this.destroyInstance(node);\n" +
" }\n" +
" });\n" +
" });\n" +
" });\n" +
" \n" +
" observer.observe(document.body, {\n" +
" childList: true,\n" +
" subtree: true,\n" +
" attributes: true,\n" +
" attributeFilter: ['data-plasma-flow']\n" +
" });\n" +
" }\n" +
" }\n" +
" };\n" +
" \n" +
" if (document.readyState === 'loading') {\n" +
" document.addEventListener('DOMContentLoaded', () => PlasmaFlow.init());\n" +
" } else {\n" +
" PlasmaFlow.init();\n" +
" }\n" +
" \n" +
" window.PlasmaFlowAnimation = PlasmaFlow;\n" +
"})();"
}
function copyJsToClipboard() {
const jsCode = generateJavaScriptCode();
navigator.clipboard.writeText(jsCode)
.then(() => {
showNotification('JavaScript code copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = jsCode;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('JavaScript code copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyFullSectionToClipboard() {
const sectionJSON = generateFullSectionJSON();
navigator.clipboard.writeText(sectionJSON)
.then(() => {
showNotification('Full section JSON copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = sectionJSON;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('Full section JSON copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy to clipboard', 'error');
});
}
function generateRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
}
function generateRandomPlasma() {
plasmaFlowConfig.speed = Math.random() * 1.9 + 0.1;
plasmaFlowConfig.intensity = Math.random() * 2.9 + 0.1;
plasmaFlowConfig.lineWidth = Math.random() * 2.8 + 0.2;
plasmaFlowConfig.lineColor = generateRandomColor();
plasmaFlowConfig.blendMode = ['normal', 'multiply', 'screen', 'overlay', 'soft-light', 'hard-light', 'color-dodge', 'color-burn', 'difference', 'exclusion'][Math.floor(Math.random() * 10)];
document.getElementById('animation-speed').value = plasmaFlowConfig.speed;
document.getElementById('intensity').value = plasmaFlowConfig.intensity;
document.getElementById('line-width').value = plasmaFlowConfig.lineWidth;
document.getElementById('line-color').value = plasmaFlowConfig.lineColor;
document.getElementById('blend-mode').value = plasmaFlowConfig.blendMode;
document.getElementById('animation-speed-value').textContent = plasmaFlowConfig.speed.toFixed(1);
document.getElementById('intensity-value').textContent = plasmaFlowConfig.intensity.toFixed(1);
document.getElementById('line-width-value').textContent = plasmaFlowConfig.lineWidth.toFixed(1);
updateColorInputs();
updatePreview();
showNotification('Random plasma flow generated!');
}
function updateColorInputs() {
const colorInput = document.getElementById('line-color');
const hexInput = document.getElementById('line-color-hex');
const hslInput = document.getElementById('line-color-hsl');
if (colorInput && hexInput && hslInput) {
colorInput.value = plasmaFlowConfig.lineColor;
hexInput.value = plasmaFlowConfig.lineColor;
hslInput.value = 'hsl(' + hexToHsl(plasmaFlowConfig.lineColor).h + ', ' + hexToHsl(plasmaFlowConfig.lineColor).s + '%, ' + hexToHsl(plasmaFlowConfig.lineColor).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', plasmaFlowConfig.lineColor);
}
}
}
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 'animation-speed':
plasmaFlowConfig.speed = defaultValue;
break;
case 'intensity':
plasmaFlowConfig.intensity = defaultValue;
break;
case 'line-width':
plasmaFlowConfig.lineWidth = defaultValue;
break;
}
updatePreview();
showNotification(parameterId.replace(/-/g, ' ') + ' reset to default');
}
};
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-plasma-config', JSON.stringify(plasmaFlowConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-plasma-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(plasmaFlowConfig, savedConfig);
document.getElementById('animation-speed').value = savedConfig.speed;
document.getElementById('intensity').value = savedConfig.intensity;
document.getElementById('line-width').value = savedConfig.lineWidth;
document.getElementById('line-color').value = savedConfig.lineColor;
document.getElementById('blend-mode').value = savedConfig.blendMode;
document.getElementById('animation-speed-value').textContent = savedConfig.speed;
document.getElementById('intensity-value').textContent = savedConfig.intensity;
document.getElementById('line-width-value').textContent = savedConfig.lineWidth;
updateColorInputs();
updatePreview();
}
} catch (e) {
}
}
function initializeUI() {
initializePreview();
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-plasma-flow');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-plasma').addEventListener('click', () => {
generateRandomPlasma();
});
const backgroundPicker = document.getElementById('preview-background-picker');
const previewContainer = document.getElementById('plasma-preview');
backgroundPicker.addEventListener('input', (e) => {
const selectedColor = e.target.value;
previewContainer.style.backgroundColor = selectedColor;
showNotification('Preview background changed to ' + selectedColor);
});
previewContainer.style.backgroundColor = '#252525';
document.getElementById('reset-colors').addEventListener('click', () => {
plasmaFlowConfig.lineColor = defaultConfig.lineColor;
document.getElementById('line-color').value = defaultConfig.lineColor;
updateColorInputs();
updatePreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-flow').addEventListener('click', () => {
plasmaFlowConfig.speed = defaultConfig.speed;
plasmaFlowConfig.intensity = defaultConfig.intensity;
plasmaFlowConfig.lineWidth = defaultConfig.lineWidth;
document.getElementById('animation-speed').value = defaultConfig.speed;
document.getElementById('intensity').value = defaultConfig.intensity;
document.getElementById('line-width').value = defaultConfig.lineWidth;
document.getElementById('animation-speed-value').textContent = defaultConfig.speed;
document.getElementById('intensity-value').textContent = defaultConfig.intensity;
document.getElementById('line-width-value').textContent = defaultConfig.lineWidth;
updatePreview();
showNotification('Flow settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
plasmaFlowConfig.blendMode = defaultConfig.blendMode;
document.getElementById('blend-mode').value = defaultConfig.blendMode;
updatePreview();
showNotification('Advanced settings reset');
});
const colorInput = document.getElementById('line-color');
const hexInput = document.getElementById('line-color-hex');
const hslInput = document.getElementById('line-color-hsl');
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');
plasmaFlowConfig.lineColor = color;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', color);
updatePreview();
});
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 + '%)';
plasmaFlowConfig.lineColor = 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);
updatePreview();
} 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;
plasmaFlowConfig.lineColor = 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);
updatePreview();
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;
plasmaFlowConfig.lineColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updatePreview();
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');
}
});
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 'animation-speed':
plasmaFlowConfig.speed = parseFloat(input.value);
break;
case 'intensity':
plasmaFlowConfig.intensity = parseFloat(input.value);
break;
case 'line-width':
plasmaFlowConfig.lineWidth = parseFloat(input.value);
break;
}
updatePreview();
});
});
document.getElementById('blend-mode').addEventListener('change', function() {
plasmaFlowConfig.blendMode = this.value;
updatePreview();
});
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':
generateRandomPlasma();
break;
case 'b':
document.getElementById('preview-background-picker').click();
break;
}
}
});
updateColorInputs();
loadConfiguration();
setTimeout(() => {
showNotification('Bricksfusion Plasma Flow Configurator loaded!');
}, 500);
}
initializeUI();
});
</script>
</body>
</html>
Plasma Flow
Creates flowing plasma wave patterns with animated glowing lines. Features organic flowing movements that pulse and undulate continuously. Moving particles travel along the plasma lines creating dynamic trails. Choose your line color and customize blend modes for different visual effects. Perfect for sci-fi backgrounds, hero sections, or creating energy-themed atmospheres.
Plasma Flow
Flowing plasma waves with animated particles.
Appearance
Color of the plasma lines and particles. Choose any color to match your design theme and create different energy moods.
Default: Purple
How the plasma blends with content behind it. Normal for standard layering. Screen for glowing additive effect. Multiply for darker blending. Experiment to find your perfect look.
Default: Normal
Animation
How quickly the plasma waves move and particles travel. Lower creates slow, hypnotic flow. Higher produces rapid, energetic movement.
Default: Moderate
Visual
Overall brightness and glow intensity of the plasma effect. Lower creates subtle, ambient lighting. Higher produces bold, vibrant energy.
Default: Normal (1.0)
Width of the plasma wave lines. Lower creates delicate, fine streams. Higher makes bold, prominent waves.
Default: Normal (1.0)
Performance
This effect uses WebGL with custom vertex and fragment shaders for GPU-accelerated rendering. Creates procedural plasma patterns using mathematical wave functions. Features animated particles that follow plasma flow paths. Includes WebGL context loss handling for stability. Very resource intensive - limit to 1 instance per page. Not recommended for mobile devices or low-end hardware.