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>Fire Plasma Effect Configurator - BricksFusion</title>
<style>
:root {
--background: #000;
--card-bg: #1e1e1e;
--card-bg-hover: #252525;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--accent: #ef6013;
--accent-hover: #c64c0c;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
--thumb: #ef6013;
--card-radius: 16px;
--input-radius: 8px;
--button-radius: 12px;
--transition: all 0.25s ease;
--font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
--action-bar-height: 70px;
--success: #28a745;
--warning: #ffc107;
--danger: #dc3545;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
background-color: var(--background);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-bottom: var(--action-bar-height);
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--action-bar-height);
background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
border-top: 1px solid var(--border);
z-index: 1000;
display: flex;
align-items: center;
padding: 0 1.5rem;
gap: 1rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.breadcrumb-item {
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: 500;
text-decoration: none;
transition: var(--transition);
padding: 0.5rem 0.75rem;
border-radius: 6px;
}
.breadcrumb-item:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.05);
}
.breadcrumb-item.active {
color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.breadcrumb-separator {
color: var(--text-secondary);
font-size: var(--text-xs);
opacity: 0.5;
}
.action-buttons {
display: flex;
align-items: center;
gap: 0.75rem;
}
.action-btn {
padding: 0.6rem 1rem;
background-color: var(--card-bg);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
border: 1px solid var(--border);
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
}
.action-btn:hover {
background-color: var(--card-bg-hover);
border-color: var(--accent);
transform: translateY(-1px);
}
.action-btn.primary {
background: linear-gradient(90deg, var(--accent), #ff8c51);
border-color: var(--accent);
color: white;
}
.action-btn.primary:hover {
background: linear-gradient(90deg, var(--accent-hover), #e67a3f);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}
.data-attribute-display {
background-color: rgba(50, 50, 50, 0.8);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
cursor: pointer;
transition: var(--transition);
user-select: all;
}
.data-attribute-display:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.page-header {
text-align: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--accent), #ff8c51);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-subtitle {
font-size: var(--text-s);
color: var(--text-secondary);
font-weight: 500;
}
.instructions-toggle {
margin-bottom: 2rem;
}
.instructions-card {
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: var(--transition);
}
.instructions-header {
padding: 1rem 1.5rem;
cursor: pointer;
transition: var(--transition);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid transparent;
}
.instructions-header:hover {
background-color: var(--card-bg-hover);
}
.instructions-card.expanded .instructions-header {
border-bottom-color: var(--border);
}
.instructions-title {
font-size: var(--text-s);
font-weight: 600;
}
.toggle-icon {
font-size: 1.2em;
transition: transform 0.3s ease;
}
.toggle-icon.expanded {
transform: rotate(180deg);
}
.instructions-content {
padding: 0 1.5rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.instructions-content.show {
max-height: 500px;
padding: 1.5rem;
}
.instructions-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.how-to-use ol {
padding-left: 1.5rem;
}
.how-to-use li {
margin-bottom: 0.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.6;
}
.how-to-use strong {
color: var(--text-primary);
font-weight: 600;
}
.how-to-use code {
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
}
.content {
display: grid;
grid-template-columns: 1fr 500px;
gap: 2rem;
align-items: start;
}
.preview-section {
position: sticky;
top: 2rem;
}
.controls-section {
max-width: 500px;
}
.card {
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.preview-container {
height: 400px;
width: 100%;
position: relative;
overflow: hidden;
border-radius: var(--card-radius);
background-color: #000000;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.preview-content {
color: white;
text-align: center;
font-weight: bold;
font-size: var(--text-s);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
.preview-controls {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
gap: 0.5rem;
z-index: 10;
}
.preview-btn {
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
font-size: var(--text-xs);
backdrop-filter: blur(5px);
}
.preview-btn:hover {
background-color: var(--accent);
border-color: var(--accent);
}
.card-heading {
padding: 1rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
letter-spacing: 0.3px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-actions {
display: flex;
gap: 0.5rem;
}
.card-action-btn {
padding: 0.4rem 0.8rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
}
.card-action-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.card-content {
padding: 1.5rem;
}
.control-group {
margin-bottom: 1.5rem;
position: relative;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.label-text {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.help-tooltip {
cursor: help;
opacity: 0.7;
transition: var(--transition);
}
.help-tooltip:hover {
opacity: 1;
color: var(--accent);
}
.value-display {
display: flex;
align-items: center;
gap: 0.5rem;
}
.value-text {
font-size: var(--text-xs);
color: var(--text-secondary);
background-color: rgba(50, 50, 50, 0.5);
padding: 2px 8px;
border-radius: 4px;
min-width: 45px;
text-align: center;
}
.reset-btn {
padding: 0.2rem 0.4rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 10px;
transition: var(--transition);
}
.reset-btn:hover {
color: var(--danger);
border-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
background: var(--track);
border-radius: 3px;
outline: none;
margin: 0.8rem 0;
position: relative;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--thumb);
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 10px rgba(239, 96, 19, 0.5);
}
.color-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: linear-gradient(45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(-45deg, #f0f0f0 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #f0f0f0 75%),
linear-gradient(-45deg, transparent 75%, #f0f0f0 75%);
background-size: 8px 8px;
background-position: 0 0, 0 4px, 4px -4px, -4px 0px;
}
.color-picker-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--current-color, #cc0000);
border-radius: 6px;
z-index: 1;
}
.color-picker-container:hover {
border-color: var(--accent);
transform: scale(1.05);
}
input[type="color"] {
position: absolute;
top: -2px;
left: -2px;
width: calc(100% + 4px);
height: calc(100% + 4px);
border: none;
cursor: pointer;
background: transparent;
z-index: 2;
opacity: 0;
}
.color-input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.color-label {
font-size: 10px;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-left: 0.25rem;
}
.color-input {
padding: 0.5rem 0.75rem;
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 12px;
transition: var(--transition);
min-width: 0;
}
.color-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.2);
outline: none;
}
.color-input.invalid {
border-color: var(--danger);
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.2);
}
.hex-input,
.hsl-input {
width: 100%;
}
.color-input-group:nth-child(2) {
flex: 0.3;
}
.color-input-group:nth-child(3) {
flex: 0.7;
}
select {
width: 100%;
padding: 0.75rem;
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--input-radius);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
transition: var(--transition);
cursor: pointer;
}
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.2);
outline: none;
}
select option {
background-color: var(--card-bg);
color: var(--text-primary);
}
.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">Fire Plasma</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-fire-plasma
</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">Fire Plasma</h1>
<p class="page-subtitle">Interactive plasma 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 fire plasma effect using the controls below</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste or upload the JavaScript code</li>
<li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-fire-plasma</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="fire-preview" data-fire-plasma="true">
<div class="preview-content">Interactive Fire Plasma Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-fire" title="Randomize (R)">🎲</button>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Fire Plasma Colors
<div class="card-actions">
<button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Color Preset</span>
</div>
<select id="color-preset">
<option value="red">Red Fire</option>
<option value="blue">Blue Flame</option>
<option value="green">Green Fire</option>
<option value="purple">Purple Plasma</option>
<option value="orange">Orange Blaze</option>
<option value="teal">Teal Flame</option>
<option value="pink">Pink Fire</option>
<option value="yellow">Yellow Flame</option>
<option value="rainbow">Rainbow Fire</option>
<option value="galaxy">Galaxy Plasma</option>
</select>
</div>
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="base-color" value="#cc0000">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="base-color-hex" value="#cc0000" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="base-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="accent-color" value="#ff3333">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="accent-color-hex" value="#ff3333" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="accent-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="highlight-color" value="#ff8080">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="highlight-color-hex" value="#ff8080" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="highlight-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Animation Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-animation" title="Reset Animation Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Turbulence Intensity
<span class="help-tooltip" title="Controls the chaos and movement of the plasma effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="turbulence-intensity-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('turbulence-intensity', 0.5)">↺</button>
</div>
</div>
<input type="range" id="turbulence-intensity" min="0.1" max="1.0" step="0.1" value="0.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Animation Speed
<span class="help-tooltip" title="Controls how fast the plasma moves and flows">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="animation-speed-value">1</span>x</span>
<button class="reset-btn" onclick="resetParameter('animation-speed', 1)">↺</button>
</div>
</div>
<input type="range" id="animation-speed" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Glow Intensity
<span class="help-tooltip" title="Controls the brightness and glow of the effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="glow-intensity-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('glow-intensity', 0.5)">↺</button>
</div>
</div>
<input type="range" id="glow-intensity" min="0.1" max="1" step="0.1" value="0.5">
</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">
Spark Density
<span class="help-tooltip" title="Controls how many bright sparks appear in the effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="spark-density-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('spark-density', 0.5)">↺</button>
</div>
</div>
<input type="range" id="spark-density" min="0.1" max="1" step="0.1" value="0.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Frame Rate
<span class="help-tooltip" title="Controls the smoothness of the animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="fps-value">60</span> FPS</span>
<button class="reset-btn" onclick="resetParameter('fps', 60)">↺</button>
</div>
</div>
<input type="range" id="fps" min="30" max="60" step="10" value="60">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Noise Pattern</span>
</div>
<select id="noise-pattern">
<option value="turbulent">Turbulent</option>
<option value="smooth">Smooth</option>
<option value="chaotic">Chaotic</option>
<option value="directional">Directional</option>
</select>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let fireConfig = {
baseColor: '#cc0000',
accentColor: '#ff3333',
highlightColor: '#ff8080',
turbulenceIntensity: 0.5,
animationSpeed: 1,
glowIntensity: 0.5,
sparkDensity: 0.5,
fps: 60,
noisePattern: 'turbulent',
colorPreset: 'red'
};
const defaultConfig = { ...fireConfig };
let activeFirePlasma = null;
function initFirePlasma() {
const sections = document.querySelectorAll('[data-fire-plasma]:not([data-fire-initialized="true"])');
sections.forEach((section) => {
// For preview element, always use current fireConfig values
let baseColor, accentColor, highlightColor, turbulence, speed, glow, sparks, fps, pattern;
if (section.id === 'fire-preview') {
console.log('Initializing preview with current fireConfig:', fireConfig);
baseColor = fireConfig.baseColor;
accentColor = fireConfig.accentColor;
highlightColor = fireConfig.highlightColor;
turbulence = fireConfig.turbulenceIntensity;
speed = fireConfig.animationSpeed;
glow = fireConfig.glowIntensity;
sparks = fireConfig.sparkDensity;
fps = fireConfig.fps;
pattern = fireConfig.noisePattern;
} else {
// For other elements, use data attributes or fallback to fireConfig
baseColor = section.hasAttribute('data-fire-plasma-base-color')
? section.getAttribute('data-fire-plasma-base-color')
: fireConfig.baseColor;
accentColor = section.hasAttribute('data-fire-plasma-accent-color')
? section.getAttribute('data-fire-plasma-accent-color')
: fireConfig.accentColor;
highlightColor = section.hasAttribute('data-fire-plasma-highlight-color')
? section.getAttribute('data-fire-plasma-highlight-color')
: fireConfig.highlightColor;
turbulence = section.hasAttribute('data-fire-plasma-turbulence')
? parseFloat(section.getAttribute('data-fire-plasma-turbulence'))
: fireConfig.turbulenceIntensity;
speed = section.hasAttribute('data-fire-plasma-speed')
? parseFloat(section.getAttribute('data-fire-plasma-speed'))
: fireConfig.animationSpeed;
glow = section.hasAttribute('data-fire-plasma-glow')
? parseFloat(section.getAttribute('data-fire-plasma-glow'))
: fireConfig.glowIntensity;
sparks = section.hasAttribute('data-fire-plasma-sparks')
? parseFloat(section.getAttribute('data-fire-plasma-sparks'))
: fireConfig.sparkDensity;
fps = section.hasAttribute('data-fire-plasma-fps')
? parseInt(section.getAttribute('data-fire-plasma-fps'))
: fireConfig.fps;
pattern = section.hasAttribute('data-fire-plasma-pattern')
? section.getAttribute('data-fire-plasma-pattern')
: fireConfig.noisePattern;
}
const options = {
baseColor,
accentColor,
highlightColor,
turbulenceIntensity: turbulence,
animationSpeed: speed,
glowIntensity: glow,
sparkDensity: sparks,
fps,
noisePattern: pattern
};
setupFirePlasma(section, options);
section.dataset.fireInitialized = 'true';
if (section.id === 'fire-preview') {
activeFirePlasma = { element: section, options };
// Don't overwrite fireConfig if it's already been modified by user
console.log('Preview element initialized with config:', fireConfig);
}
});
}
function setupFirePlasma(element, options) {
const wrapper = document.createElement('div');
wrapper.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;overflow:hidden;pointer-events:none;z-index:0;';
const containerStyle = window.getComputedStyle(element);
wrapper.style.borderRadius = containerStyle.borderRadius;
if (element.firstChild) {
element.insertBefore(wrapper, element.firstChild);
} else {
element.appendChild(wrapper);
}
Array.from(element.children).forEach(child => {
if (child !== wrapper) {
if (getComputedStyle(child).position === 'static') {
child.style.position = 'relative';
}
child.style.zIndex = '1';
}
});
if (window.getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
if (!window.THREE) {
loadThreeJs().then(() => {
createFirePlasmaEffect(wrapper, options);
});
} else {
createFirePlasmaEffect(wrapper, options);
}
}
function loadThreeJs() {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
function createFirePlasmaEffect(wrapper, options) {
const rect = wrapper.getBoundingClientRect();
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(rect.width, rect.height);
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
renderer.domElement.style.display = 'block';
wrapper.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const geometry = new THREE.PlaneGeometry(2, 2);
const baseColor = hexToRgb(options.baseColor);
const accentColor = hexToRgb(options.accentColor);
const highlightColor = hexToRgb(options.highlightColor);
let noisePatternValue = 0;
switch (options.noisePattern) {
case 'turbulent': noisePatternValue = 0; break;
case 'smooth': noisePatternValue = 1; break;
case 'chaotic': noisePatternValue = 2; break;
case 'directional': noisePatternValue = 3; break;
default: noisePatternValue = 0;
}
const uniforms = {
time: { value: 0 },
resolution: { value: new THREE.Vector2(rect.width, rect.height) },
colorBase: { value: new THREE.Vector3(baseColor.r, baseColor.g, baseColor.b) },
colorAccent: { value: new THREE.Vector3(accentColor.r, accentColor.g, accentColor.b) },
colorHighlight: { value: new THREE.Vector3(highlightColor.r, highlightColor.g, highlightColor.b) },
turbulence: { value: options.turbulenceIntensity },
sparkDensity: { value: options.sparkDensity },
animationSpeed: { value: options.animationSpeed },
glowIntensity: { value: options.glowIntensity },
noisePattern: { value: noisePatternValue }
};
const material = new THREE.ShaderMaterial({
uniforms: uniforms,
fragmentShader: getFragmentShader(),
transparent: true
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
wrapper.fireRenderer = renderer;
wrapper.fireScene = scene;
wrapper.fireCamera = camera;
wrapper.fireUniforms = uniforms;
wrapper.fireMesh = mesh;
startFireAnimation(wrapper, options);
const resizeObserver = new ResizeObserver(() => {
const newRect = wrapper.getBoundingClientRect();
renderer.setSize(newRect.width, newRect.height);
uniforms.resolution.value.set(newRect.width, newRect.height);
});
resizeObserver.observe(wrapper);
wrapper._fireCleanup = () => {
resizeObserver.disconnect();
if (wrapper.fireAnimationFrame) {
cancelAnimationFrame(wrapper.fireAnimationFrame);
}
if (wrapper.fireRenderer) {
wrapper.fireRenderer.dispose();
}
};
}
function startFireAnimation(wrapper, options) {
let lastTime = 0;
let fpsInterval = 1000 / options.fps;
// Almacenar referencia para poder actualizar dinámicamente
wrapper.fireAnimationSpeed = options.animationSpeed;
wrapper.fireFps = options.fps;
function animate(currentTime) {
wrapper.fireAnimationFrame = requestAnimationFrame(animate);
// Actualizar FPS dinámicamente
fpsInterval = 1000 / wrapper.fireFps;
if (currentTime - lastTime >= fpsInterval) {
lastTime = currentTime - ((currentTime - lastTime) % fpsInterval);
// Usar la velocidad de animación actualizada dinámicamente
wrapper.fireUniforms.time.value = currentTime * 0.001 * wrapper.fireAnimationSpeed;
wrapper.fireRenderer.render(wrapper.fireScene, wrapper.fireCamera);
}
}
animate(0);
// Función para actualizar dinámicamente los parámetros de animación
wrapper.updateAnimationParams = function(newSpeed, newFps) {
wrapper.fireAnimationSpeed = newSpeed;
wrapper.fireFps = newFps;
};
}
function getFragmentShader() {
return `
#ifdef GL_ES
precision highp float;
#endif
uniform float time;
uniform vec2 resolution;
uniform vec3 colorBase;
uniform vec3 colorAccent;
uniform vec3 colorHighlight;
uniform float turbulence;
uniform float sparkDensity;
uniform float animationSpeed;
uniform float glowIntensity;
uniform float noisePattern;
#define NUM_OCTAVES 5
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float fbm(vec2 st) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < NUM_OCTAVES; i++) {
value += amplitude * noise(st * frequency);
st *= 2.0;
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
float patternNoise(vec2 p, float pattern) {
if (pattern < 0.5) {
vec2 q = vec2(fbm(p + vec2(0.0, 0.0)),
fbm(p + vec2(5.2, 1.3)));
return fbm(p + 4.0 * q);
} else if (pattern < 1.5) {
return fbm(p * 3.0);
} else if (pattern < 2.5) {
vec2 q = vec2(fbm(p),
fbm(p + vec2(1.0)));
vec2 r = vec2(fbm(p + 4.0*q + vec2(1.7,9.2)),
fbm(p + 4.0*q + vec2(8.3,2.8)));
return fbm(p + 4.0*r);
} else {
float angle = time * 0.2;
vec2 dir = vec2(cos(angle), sin(angle));
return fbm(p + dir * fbm(p * 2.0) * 2.0);
}
}
vec2 rotate(vec2 v, float a) {
float s = sin(a);
float c = cos(a);
mat2 m = mat2(c, -s, s, c);
return m * v;
}
void main() {
vec2 st = gl_FragCoord.xy / resolution.xy;
st.x *= resolution.x / resolution.y;
vec2 distort = vec2(
patternNoise(st + time * 0.1, noisePattern),
patternNoise(st + vec2(1.0) - time * 0.15, noisePattern)
);
distort = rotate(distort, time * 0.1);
vec2 newSt = st + distort * turbulence * 0.3;
float f1 = patternNoise(newSt * 3.0 + time * 0.1, noisePattern);
float f2 = patternNoise(newSt * 2.0 - time * 0.05, noisePattern);
float f3 = patternNoise(newSt * 4.0 + time * 0.2, noisePattern);
float f = f1 * 0.5 + f2 * 0.3 + f3 * 0.2;
vec3 color = mix(colorBase, colorAccent, clamp(f * 1.5, 0.0, 1.0));
color = mix(color, colorHighlight, clamp(f * f * 3.0, 0.0, 1.0));
float actualSparkDensity = 0.98 - sparkDensity * 0.1;
float spark = step(actualSparkDensity, random(newSt + mod(time, 10.0)));
color += spark * colorHighlight * 0.8;
float glow = glowIntensity * 0.1 * (1.0 + sin(time * 2.0)) * (1.0 - distance(newSt, vec2(0.5)));
color += glow * colorHighlight;
gl_FragColor = vec4(color, 1.0);
}`;
}
function updateFirePreview() {
const preview = document.getElementById('fire-preview');
if (!preview) return;
// Find wrapper more robustly
let wrapper = null;
// Method 1: Look for wrapper with absolute positioning
wrapper = preview.querySelector('div[style*="position:absolute"]');
// Method 2: Look for wrapper with fireUniforms property
if (!wrapper) {
const children = preview.querySelectorAll('div');
for (let child of children) {
if (child.fireUniforms) {
wrapper = child;
break;
}
}
}
// Method 3: Look for first child div
if (!wrapper) {
wrapper = preview.querySelector('div:first-child');
}
console.log('Wrapper found:', !!wrapper, 'Has uniforms:', !!(wrapper && wrapper.fireUniforms));
if (wrapper && wrapper.fireUniforms) {
try {
const baseColor = hexToRgb(fireConfig.baseColor);
const accentColor = hexToRgb(fireConfig.accentColor);
const highlightColor = hexToRgb(fireConfig.highlightColor);
console.log('Updating colors:', { baseColor: fireConfig.baseColor, accentColor: fireConfig.accentColor, highlightColor: fireConfig.highlightColor });
// Update uniforms safely
wrapper.fireUniforms.colorBase.value.set(baseColor.r, baseColor.g, baseColor.b);
wrapper.fireUniforms.colorAccent.value.set(accentColor.r, accentColor.g, accentColor.b);
wrapper.fireUniforms.colorHighlight.value.set(highlightColor.r, highlightColor.g, highlightColor.b);
wrapper.fireUniforms.turbulence.value = fireConfig.turbulenceIntensity;
wrapper.fireUniforms.sparkDensity.value = fireConfig.sparkDensity;
wrapper.fireUniforms.glowIntensity.value = fireConfig.glowIntensity;
let noisePatternValue = 0;
switch (fireConfig.noisePattern) {
case 'turbulent': noisePatternValue = 0; break;
case 'smooth': noisePatternValue = 1; break;
case 'chaotic': noisePatternValue = 2; break;
case 'directional': noisePatternValue = 3; break;
default: noisePatternValue = 0;
}
wrapper.fireUniforms.noisePattern.value = noisePatternValue;
// Update animation speed and FPS dynamically
if (wrapper.updateAnimationParams) {
wrapper.updateAnimationParams(fireConfig.animationSpeed, fireConfig.fps);
}
console.log('Preview updated successfully');
} catch (error) {
console.error('Error updating uniforms:', error);
// If update fails, reinitialize
reinitializePreview();
}
} else {
console.log('Wrapper or uniforms not found, reinitializing...');
reinitializePreview();
}
}
function reinitializePreview() {
const preview = document.getElementById('fire-preview');
if (!preview) return;
console.log('Reinitializing preview...');
// Clean up completely
if (preview._fireCleanup) {
preview._fireCleanup();
}
// Remove existing wrapper elements
const wrappers = preview.querySelectorAll('div[style*="position:absolute"]');
wrappers.forEach(wrapper => {
if (wrapper.parentNode) {
wrapper.parentNode.removeChild(wrapper);
}
});
// Also remove any div with fireUniforms
const allDivs = preview.querySelectorAll('div');
allDivs.forEach(div => {
if (div.fireUniforms) {
if (div.parentNode) {
div.parentNode.removeChild(div);
}
}
});
// Remove initialization flag
preview.removeAttribute('data-fire-initialized');
// Set new attributes
preview.setAttribute('data-fire-plasma', 'true');
preview.setAttribute('data-fire-plasma-base-color', fireConfig.baseColor);
preview.setAttribute('data-fire-plasma-accent-color', fireConfig.accentColor);
preview.setAttribute('data-fire-plasma-highlight-color', fireConfig.highlightColor);
preview.setAttribute('data-fire-plasma-turbulence', fireConfig.turbulenceIntensity);
preview.setAttribute('data-fire-plasma-speed', fireConfig.animationSpeed);
preview.setAttribute('data-fire-plasma-glow', fireConfig.glowIntensity);
preview.setAttribute('data-fire-plasma-sparks', fireConfig.sparkDensity);
preview.setAttribute('data-fire-plasma-fps', fireConfig.fps);
preview.setAttribute('data-fire-plasma-pattern', fireConfig.noisePattern);
// Reinitialize
setTimeout(() => {
initFirePlasma();
console.log('Preview reinitialized');
}, 100);
}
function forceUpdatePreview() {
console.log('Force updating preview with config:', fireConfig);
reinitializePreview();
}
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
}
function hslToHex(hsl) {
const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
if (!match) return null;
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let l = parseInt(match[3]) / 100;
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function hexToRgb(hex) {
hex = hex.replace(/^#/, '');
const bigint = parseInt(hex, 16);
return {
r: ((bigint >> 16) & 255) / 255,
g: ((bigint >> 8) & 255) / 255,
b: (bigint & 255) / 255
};
}
function isValidHex(hex) {
return /^#[0-9A-F]{6}$/i.test(hex);
}
function isValidHsl(hsl) {
return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
}
function formatHex(value) {
let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
if (!hex.startsWith('#')) {
hex = '#' + hex;
}
if (hex.length > 7) {
hex = hex.substring(0, 7);
}
return hex.toUpperCase();
}
function formatHsl(value) {
const cleanValue = value.replace(/[^\d,\s]/g, '');
const numbers = cleanValue.match(/\d+/g);
if (!numbers || numbers.length < 3) {
const partialMatch = value.match(/(\d+)/g);
if (partialMatch && partialMatch.length >= 1) {
const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
return `hsl(${h}, ${s}%, ${l}%)`;
}
return value;
}
let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
return `hsl(${h}, ${s}%, ${l}%)`;
}
function generateRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const codeId = generateUniqueId();
const attributeId = generateUniqueId();
// Obtener el código JavaScript actual
const jsCode = generateJavaScriptCode();
// Crear el objeto JSON completo
const fullSectionJSON = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
},
"_attributes": [
{
"id": attributeId,
"name": "data-fire-plasma"
}
]
},
"label": "Fire Effect Section"
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": [],
"label": "Content Container"
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Fire effect JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(fullSectionJSON, null, 2);
}
function generateJavaScriptCode() {
const baseColor = fireConfig.baseColor;
const accentColor = fireConfig.accentColor;
const highlightColor = fireConfig.highlightColor;
const turbulence = fireConfig.turbulenceIntensity;
const speed = fireConfig.animationSpeed;
const glow = fireConfig.glowIntensity;
const sparks = fireConfig.sparkDensity;
const fps = fireConfig.fps;
const pattern = fireConfig.noisePattern;
return `(function() {
class FirePlasmaEffect {
constructor() {
this.instances = [];
this.three = null;
this.isInitialized = false;
this.config = {
baseColor: "${baseColor}",
accentColor: "${accentColor}",
highlightColor: "${highlightColor}",
turbulenceIntensity: ${turbulence},
animationSpeed: ${speed},
glowIntensity: ${glow},
sparkDensity: ${sparks},
fps: ${fps},
noisePattern: "${pattern}"
};
}
async init(options = {}) {
if (this.isInitialized) return;
const defaultOptions = {
selector: '[data-fire-plasma]'
};
this.options = { ...defaultOptions, ...options };
await this.loadThreeJS();
this.initInstances();
setTimeout(() => this.initInstances(), 100);
window.addEventListener('load', () => this.initInstances());
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
this.initInstances();
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
window.addEventListener('resize', () => this.resize());
this.isInitialized = true;
}
async loadThreeJS() {
return new Promise((resolve, reject) => {
if (window.THREE) {
this.three = window.THREE;
resolve();
} else {
const script = document.createElement('script');
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
script.onload = () => {
this.three = window.THREE;
resolve();
};
script.onerror = reject;
document.head.appendChild(script);
}
});
}
initInstances() {
document.querySelectorAll(this.options.selector).forEach(element => {
if (!element.hasAttribute('data-fire-initialized')) {
this.createInstance(element);
element.setAttribute('data-fire-initialized', 'true');
}
});
}
createInstance(element) {
if (window.getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
const wrapper = document.createElement('div');
wrapper.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;overflow:hidden;pointer-events:none;z-index:0;';
const containerStyle = window.getComputedStyle(element);
wrapper.style.borderRadius = containerStyle.borderRadius;
element.insertBefore(wrapper, element.firstChild);
Array.from(element.children).forEach(child => {
if (child !== wrapper) {
child.style.position = 'relative';
child.style.zIndex = '1';
}
});
const renderer = new this.three.WebGLRenderer({ alpha: true, antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
const rect = element.getBoundingClientRect();
renderer.setSize(rect.width, rect.height);
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
renderer.domElement.style.display = 'block';
renderer.domElement.style.borderRadius = containerStyle.borderRadius;
wrapper.appendChild(renderer.domElement);
const scene = new this.three.Scene();
const camera = new this.three.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const geometry = new this.three.PlaneGeometry(2, 2);
const fragmentShader = this.getFragmentShader();
const baseColor = this.hexToRgb(
element.getAttribute('data-fire-plasma-base-color') || this.config.baseColor
);
const accentColor = this.hexToRgb(
element.getAttribute('data-fire-plasma-accent-color') || this.config.accentColor
);
const highlightColor = this.hexToRgb(
element.getAttribute('data-fire-plasma-highlight-color') || this.config.highlightColor
);
const turbulence = parseFloat(
element.getAttribute('data-fire-plasma-turbulence') || this.config.turbulenceIntensity
);
const speed = parseFloat(
element.getAttribute('data-fire-plasma-speed') || this.config.animationSpeed
);
const glow = parseFloat(
element.getAttribute('data-fire-plasma-glow') || this.config.glowIntensity
);
const sparks = parseFloat(
element.getAttribute('data-fire-plasma-sparks') || this.config.sparkDensity
);
const fps = parseInt(
element.getAttribute('data-fire-plasma-fps') || this.config.fps
);
const pattern = element.getAttribute('data-fire-plasma-pattern') || this.config.noisePattern;
let noisePatternValue = 0;
switch (pattern) {
case 'turbulent': noisePatternValue = 0; break;
case 'smooth': noisePatternValue = 1; break;
case 'chaotic': noisePatternValue = 2; break;
case 'directional': noisePatternValue = 3; break;
default: noisePatternValue = 0;
}
const uniforms = {
time: { value: 0 },
resolution: { value: new this.three.Vector2() },
colorBase: { value: new this.three.Vector3(baseColor.r, baseColor.g, baseColor.b) },
colorAccent: { value: new this.three.Vector3(accentColor.r, accentColor.g, accentColor.b) },
colorHighlight: { value: new this.three.Vector3(highlightColor.r, highlightColor.g, highlightColor.b) },
turbulence: { value: turbulence },
sparkDensity: { value: sparks },
animationSpeed: { value: speed },
glowIntensity: { value: glow },
noisePattern: { value: noisePatternValue }
};
const material = new this.three.ShaderMaterial({
uniforms: uniforms,
fragmentShader: fragmentShader,
transparent: true
});
const mesh = new this.three.Mesh(geometry, material);
scene.add(mesh);
const instance = {
element,
wrapper,
renderer,
scene,
camera,
uniforms,
fps,
animationSpeed: speed,
lastFrameTime: 0,
resize: () => {
const rect = element.getBoundingClientRect();
renderer.setSize(rect.width, rect.height);
uniforms.resolution.value.set(rect.width, rect.height);
renderer.domElement.style.width = '100%';
renderer.domElement.style.height = '100%';
renderer.domElement.style.display = 'block';
const currentStyle = window.getComputedStyle(element);
wrapper.style.borderRadius = currentStyle.borderRadius;
renderer.domElement.style.borderRadius = currentStyle.borderRadius;
}
};
this.instances.push(instance);
instance.resize();
this.startAnimation(instance);
}
startAnimation(instance) {
const fpsInterval = 1000 / instance.fps;
const self = this;
function animate(currentTime) {
instance.animationFrame = requestAnimationFrame(animate);
if (currentTime - instance.lastFrameTime >= fpsInterval) {
instance.lastFrameTime = currentTime - ((currentTime - instance.lastFrameTime) % fpsInterval);
if (self.isElementInViewport(instance.element)) {
instance.uniforms.time.value = currentTime * 0.001 * instance.animationSpeed;
instance.renderer.render(instance.scene, instance.camera);
}
}
}
animate(0);
}
resize() {
this.instances.forEach(instance => instance.resize());
}
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
);
}
hexToRgb(hex) {
hex = hex.replace(/^#/, '');
const bigint = parseInt(hex, 16);
return {
r: ((bigint >> 16) & 255) / 255,
g: ((bigint >> 8) & 255) / 255,
b: (bigint & 255) / 255
};
}
getFragmentShader() {
return \`
#ifdef GL_ES
precision highp float;
#endif
uniform float time;
uniform vec2 resolution;
uniform vec3 colorBase;
uniform vec3 colorAccent;
uniform vec3 colorHighlight;
uniform float turbulence;
uniform float sparkDensity;
uniform float animationSpeed;
uniform float glowIntensity;
uniform float noisePattern;
#define NUM_OCTAVES 5
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
float noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
float fbm(vec2 st) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < NUM_OCTAVES; i++) {
value += amplitude * noise(st * frequency);
st *= 2.0;
amplitude *= 0.5;
frequency *= 2.0;
}
return value;
}
float patternNoise(vec2 p, float pattern) {
if (pattern < 0.5) {
vec2 q = vec2(fbm(p + vec2(0.0, 0.0)),
fbm(p + vec2(5.2, 1.3)));
return fbm(p + 4.0 * q);
} else if (pattern < 1.5) {
return fbm(p * 3.0);
} else if (pattern < 2.5) {
vec2 q = vec2(fbm(p),
fbm(p + vec2(1.0)));
vec2 r = vec2(fbm(p + 4.0*q + vec2(1.7,9.2)),
fbm(p + 4.0*q + vec2(8.3,2.8)));
return fbm(p + 4.0*r);
} else {
float angle = time * 0.2;
vec2 dir = vec2(cos(angle), sin(angle));
return fbm(p + dir * fbm(p * 2.0) * 2.0);
}
}
vec2 rotate(vec2 v, float a) {
float s = sin(a);
float c = cos(a);
mat2 m = mat2(c, -s, s, c);
return m * v;
}
void main() {
vec2 st = gl_FragCoord.xy / resolution.xy;
st.x *= resolution.x / resolution.y;
vec2 distort = vec2(
patternNoise(st + time * 0.1, noisePattern),
patternNoise(st + vec2(1.0) - time * 0.15, noisePattern)
);
distort = rotate(distort, time * 0.1);
vec2 newSt = st + distort * turbulence * 0.3;
float f1 = patternNoise(newSt * 3.0 + time * 0.1, noisePattern);
float f2 = patternNoise(newSt * 2.0 - time * 0.05, noisePattern);
float f3 = patternNoise(newSt * 4.0 + time * 0.2, noisePattern);
float f = f1 * 0.5 + f2 * 0.3 + f3 * 0.2;
vec3 color = mix(colorBase, colorAccent, clamp(f * 1.5, 0.0, 1.0));
color = mix(color, colorHighlight, clamp(f * f * 3.0, 0.0, 1.0));
float actualSparkDensity = 0.98 - sparkDensity * 0.1;
float spark = step(actualSparkDensity, random(newSt + mod(time, 10.0)));
color += spark * colorHighlight * 0.8;
float glow = glowIntensity * 0.1 * (1.0 + sin(time * 2.0)) * (1.0 - distance(newSt, vec2(0.5)));
color += glow * colorHighlight;
gl_FragColor = vec4(color, 1.0);
}
\`;
}
}
window.firePlasmaEffect = new FirePlasmaEffect();
window.firePlasmaEffect.init();
})();`;
}
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) {
if (parameterId === 'animation-speed') {
valueElement.textContent = defaultValue;
} else if (parameterId === 'fps') {
valueElement.textContent = defaultValue;
} else {
valueElement.textContent = defaultValue;
}
}
switch (parameterId) {
case 'turbulence-intensity':
fireConfig.turbulenceIntensity = defaultValue;
break;
case 'animation-speed':
fireConfig.animationSpeed = defaultValue;
break;
case 'glow-intensity':
fireConfig.glowIntensity = defaultValue;
break;
case 'spark-density':
fireConfig.sparkDensity = defaultValue;
break;
case 'fps':
fireConfig.fps = defaultValue;
break;
}
forceUpdatePreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function generateRandomFire() {
const randomColors = Array.from({ length: 3 }, () => generateRandomColor());
fireConfig.baseColor = randomColors[0];
fireConfig.accentColor = randomColors[1];
fireConfig.highlightColor = randomColors[2];
randomColors.forEach((color, index) => {
const ids = ['base-color', 'accent-color', 'highlight-color'];
const colorInput = document.getElementById(ids[index]);
const hexInput = document.getElementById(`${ids[index]}-hex`);
const hslInput = document.getElementById(`${ids[index]}-hsl`);
colorInput.value = color;
hexInput.value = color;
hslInput.value = hexToHsl(color);
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', color);
}
});
forceUpdatePreview();
showNotification('Random fire colors generated!');
}
function updateColorInputsForPreset(preset) {
let baseColor, accentColor, highlightColor;
switch(preset) {
case 'red':
baseColor = '#cc0000';
accentColor = '#ff3333';
highlightColor = '#ff8080';
break;
case 'blue':
baseColor = '#1a33cc';
accentColor = '#3366ff';
highlightColor = '#80a6ff';
break;
case 'green':
baseColor = '#1a8019';
accentColor = '#4dcc33';
highlightColor = '#99ff66';
break;
case 'purple':
baseColor = '#80198c';
accentColor = '#cc33d9';
highlightColor = '#ff99ff';
break;
case 'orange':
baseColor = '#e66600';
accentColor = '#ff9900';
highlightColor = '#ffcc4d';
break;
case 'teal':
baseColor = '#008080';
accentColor = '#33b3b3';
highlightColor = '#66e6e6';
break;
case 'pink':
baseColor = '#e6669a';
accentColor = '#ff99cc';
highlightColor = '#ffcce6';
break;
case 'yellow':
baseColor = '#ccb31a';
accentColor = '#ffe633';
highlightColor = '#fff980';
break;
case 'rainbow':
baseColor = '#ff0000';
accentColor = '#00ff00';
highlightColor = '#0000ff';
break;
case 'galaxy':
baseColor = '#1a0033';
accentColor = '#660099';
highlightColor = '#cc80ff';
break;
default:
baseColor = '#cc0000';
accentColor = '#ff3333';
highlightColor = '#ff8080';
}
fireConfig.baseColor = baseColor;
fireConfig.accentColor = accentColor;
fireConfig.highlightColor = highlightColor;
fireConfig.colorPreset = preset;
updateColorInputs();
forceUpdatePreview();
}
function updateColorInputs() {
const colors = [fireConfig.baseColor, fireConfig.accentColor, fireConfig.highlightColor];
const ids = ['base-color', 'accent-color', 'highlight-color'];
colors.forEach((color, index) => {
const colorInput = document.getElementById(ids[index]);
const hexInput = document.getElementById(`${ids[index]}-hex`);
const hslInput = document.getElementById(`${ids[index]}-hsl`);
if (colorInput && hexInput && hslInput) {
colorInput.value = color;
hexInput.value = color;
hslInput.value = hexToHsl(color);
const container = colorInput.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', color);
}
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
}
});
}
function initializeUI() {
initFirePlasma();
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-fire-plasma');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-fire').addEventListener('click', () => {
generateRandomFire();
});
// Add debug capabilities
window.debugFirePreview = function() {
console.log('=== DEBUG INFO ===');
console.log('Current fireConfig:', fireConfig);
const preview = document.getElementById('fire-preview');
console.log('Preview element:', preview);
if (preview) {
const wrapper = preview.querySelector('div[style*="position:absolute"]');
console.log('Wrapper found:', !!wrapper);
if (wrapper) {
console.log('Wrapper has uniforms:', !!wrapper.fireUniforms);
if (wrapper.fireUniforms) {
console.log('Current uniform values:', {
colorBase: wrapper.fireUniforms.colorBase.value,
colorAccent: wrapper.fireUniforms.colorAccent.value,
colorHighlight: wrapper.fireUniforms.colorHighlight.value
});
}
}
}
console.log('=== END DEBUG ===');
};
window.forceUpdatePreview = forceUpdatePreview;
document.getElementById('reset-colors').addEventListener('click', () => {
fireConfig.baseColor = defaultConfig.baseColor;
fireConfig.accentColor = defaultConfig.accentColor;
fireConfig.highlightColor = defaultConfig.highlightColor;
fireConfig.colorPreset = defaultConfig.colorPreset;
document.getElementById('color-preset').value = defaultConfig.colorPreset;
updateColorInputs();
forceUpdatePreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
fireConfig.turbulenceIntensity = defaultConfig.turbulenceIntensity;
fireConfig.animationSpeed = defaultConfig.animationSpeed;
fireConfig.glowIntensity = defaultConfig.glowIntensity;
document.getElementById('turbulence-intensity').value = defaultConfig.turbulenceIntensity;
document.getElementById('animation-speed').value = defaultConfig.animationSpeed;
document.getElementById('glow-intensity').value = defaultConfig.glowIntensity;
document.getElementById('turbulence-intensity-value').textContent = defaultConfig.turbulenceIntensity;
document.getElementById('animation-speed-value').textContent = defaultConfig.animationSpeed;
document.getElementById('glow-intensity-value').textContent = defaultConfig.glowIntensity;
forceUpdatePreview();
showNotification('Animation settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
fireConfig.sparkDensity = defaultConfig.sparkDensity;
fireConfig.fps = defaultConfig.fps;
fireConfig.noisePattern = defaultConfig.noisePattern;
document.getElementById('spark-density').value = defaultConfig.sparkDensity;
document.getElementById('fps').value = defaultConfig.fps;
document.getElementById('noise-pattern').value = defaultConfig.noisePattern;
document.getElementById('spark-density-value').textContent = defaultConfig.sparkDensity;
document.getElementById('fps-value').textContent = defaultConfig.fps;
forceUpdatePreview();
showNotification('Advanced settings reset');
});
document.getElementById('color-preset').addEventListener('change', function() {
console.log('Changing color preset to:', this.value);
fireConfig.colorPreset = this.value;
updateColorInputsForPreset(this.value);
});
const colorInputs = document.querySelectorAll('input[type="color"]');
colorInputs.forEach((input, index) => {
const ids = ['base-color', 'accent-color', 'highlight-color'];
const hexInput = document.getElementById(`${ids[index]}-hex`);
const hslInput = document.getElementById(`${ids[index]}-hsl`);
hslInput.value = hexToHsl(input.value);
const container = input.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', input.value);
}
input.addEventListener('input', () => {
const color = input.value;
console.log('Changing color', ids[index], 'to:', color);
hexInput.value = color;
hslInput.value = hexToHsl(color);
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
const container = input.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', color);
}
if (ids[index] === 'base-color') fireConfig.baseColor = color;
else if (ids[index] === 'accent-color') fireConfig.accentColor = color;
else if (ids[index] === 'highlight-color') fireConfig.highlightColor = color;
console.log('Color config updated:', fireConfig);
updateFirePreview();
});
hexInput.addEventListener('input', (e) => {
let hex = e.target.value;
hex = formatHex(hex);
e.target.value = hex;
if (isValidHex(hex)) {
input.value = hex;
hslInput.value = hexToHsl(hex);
const container = input.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', hex);
}
if (ids[index] === 'base-color') fireConfig.baseColor = hex;
else if (ids[index] === 'accent-color') fireConfig.accentColor = hex;
else if (ids[index] === 'highlight-color') fireConfig.highlightColor = hex;
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
updateFirePreview();
} else {
e.target.classList.add('invalid');
}
});
hexInput.addEventListener('blur', (e) => {
if (!isValidHex(e.target.value)) {
e.target.value = input.value;
e.target.classList.remove('invalid');
}
});
hslInput.addEventListener('input', (e) => {
let hsl = e.target.value;
if (isValidHsl(hsl)) {
const hex = hslToHex(hsl);
if (hex) {
input.value = hex;
hexInput.value = hex;
const container = input.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', hex);
}
if (ids[index] === 'base-color') fireConfig.baseColor = hex;
else if (ids[index] === 'accent-color') fireConfig.accentColor = hex;
else if (ids[index] === 'highlight-color') fireConfig.highlightColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateFirePreview();
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) {
input.value = hex;
hexInput.value = hex;
const container = input.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', hex);
}
if (ids[index] === 'base-color') fireConfig.baseColor = hex;
else if (ids[index] === 'accent-color') fireConfig.accentColor = hex;
else if (ids[index] === 'highlight-color') fireConfig.highlightColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateFirePreview();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
e.target.value = hexToHsl(input.value);
e.target.classList.remove('invalid');
}
});
});
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
let valueElement;
switch (input.id) {
case 'turbulence-intensity':
valueElement = document.getElementById('turbulence-intensity-value');
break;
case 'animation-speed':
valueElement = document.getElementById('animation-speed-value');
break;
case 'glow-intensity':
valueElement = document.getElementById('glow-intensity-value');
break;
case 'spark-density':
valueElement = document.getElementById('spark-density-value');
break;
case 'fps':
valueElement = document.getElementById('fps-value');
break;
default:
valueElement = document.getElementById(`${input.id}-value`);
}
if (valueElement) {
if (input.id === 'animation-speed') {
valueElement.textContent = input.value;
} else if (input.id === 'fps') {
valueElement.textContent = input.value;
} else {
valueElement.textContent = input.value;
}
}
input.addEventListener('input', () => {
console.log('Changing', input.id, 'to:', input.value);
if (valueElement) {
if (input.id === 'animation-speed') {
valueElement.textContent = input.value;
} else if (input.id === 'fps') {
valueElement.textContent = input.value;
} else {
valueElement.textContent = input.value;
}
}
switch (input.id) {
case 'turbulence-intensity':
fireConfig.turbulenceIntensity = parseFloat(input.value);
break;
case 'animation-speed':
fireConfig.animationSpeed = parseFloat(input.value);
break;
case 'glow-intensity':
fireConfig.glowIntensity = parseFloat(input.value);
break;
case 'spark-density':
fireConfig.sparkDensity = parseFloat(input.value);
break;
case 'fps':
fireConfig.fps = parseInt(input.value);
break;
}
console.log('Updating fireConfig:', fireConfig);
updateFirePreview();
});
});
document.getElementById('noise-pattern').addEventListener('change', function() {
console.log('Changing noise pattern to:', this.value);
fireConfig.noisePattern = this.value;
updateFirePreview();
});
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'SELECT') {
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':
generateRandomFire();
break;
}
}
});
updateColorInputs();
console.log('Configurator initialized. fireConfig:', fireConfig);
console.log('Preview initialized correctly');
// Force an initial preview update to ensure it's working
setTimeout(() => {
console.log('Forcing initial preview update...');
forceUpdatePreview();
}, 1000);
setTimeout(() => {
showNotification('BricksFusion Fire Plasma Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-fire-config', JSON.stringify(fireConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-fire-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(fireConfig, savedConfig);
document.getElementById('base-color').value = savedConfig.baseColor;
document.getElementById('base-color-hex').value = savedConfig.baseColor;
document.getElementById('base-color-hsl').value = hexToHsl(savedConfig.baseColor);
document.getElementById('accent-color').value = savedConfig.accentColor;
document.getElementById('accent-color-hex').value = savedConfig.accentColor;
document.getElementById('accent-color-hsl').value = hexToHsl(savedConfig.accentColor);
document.getElementById('highlight-color').value = savedConfig.highlightColor;
document.getElementById('highlight-color-hex').value = savedConfig.highlightColor;
document.getElementById('highlight-color-hsl').value = hexToHsl(savedConfig.highlightColor);
document.getElementById('turbulence-intensity').value = savedConfig.turbulenceIntensity;
document.getElementById('animation-speed').value = savedConfig.animationSpeed;
document.getElementById('glow-intensity').value = savedConfig.glowIntensity;
document.getElementById('spark-density').value = savedConfig.sparkDensity;
document.getElementById('fps').value = savedConfig.fps;
document.getElementById('noise-pattern').value = savedConfig.noisePattern;
document.getElementById('color-preset').value = savedConfig.colorPreset || 'red';
document.getElementById('turbulence-intensity-value').textContent = savedConfig.turbulenceIntensity;
document.getElementById('animation-speed-value').textContent = savedConfig.animationSpeed;
document.getElementById('glow-intensity-value').textContent = savedConfig.glowIntensity;
document.getElementById('spark-density-value').textContent = savedConfig.sparkDensity;
document.getElementById('fps-value').textContent = savedConfig.fps;
updateFirePreview();
}
} catch (e) {
}
}
const originalUpdateFirePreview = updateFirePreview;
updateFirePreview = function() {
originalUpdateFirePreview();
saveConfiguration();
};
loadConfiguration();
let performanceMonitor = {
animationFrames: 0,
lastCheck: Date.now(),
checkPerformance() {
this.animationFrames++;
const now = Date.now();
if (now - this.lastCheck >= 1000) {
const fps = this.animationFrames;
this.animationFrames = 0;
this.lastCheck = now;
if (fps < 30) {
}
}
}
};
setInterval(() => {
performanceMonitor.checkPerformance();
}, 1000);
const observeElements = () => {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.remove('loading');
}
});
});
document.querySelectorAll('.card').forEach(card => {
observer.observe(card);
});
};
observeElements();
function initTooltips() {
const helpTooltips = document.querySelectorAll('.help-tooltip');
helpTooltips.forEach(tooltip => {
tooltip.addEventListener('mouseenter', (e) => {
const title = e.target.getAttribute('title');
if (title) {
const tooltipEl = document.createElement('div');
tooltipEl.className = 'custom-tooltip';
tooltipEl.textContent = title;
tooltipEl.style.cssText = `
position: fixed;
background: var(--card-bg);
color: var(--text-primary);
padding: 0.5rem 0.75rem;
border-radius: 6px;
font-size: 12px;
z-index: 1002;
border: 1px solid var(--border);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
max-width: 200px;
word-wrap: break-word;
`;
const rect = e.target.getBoundingClientRect();
tooltipEl.style.left = (rect.left + rect.width / 2 - 100) + 'px';
tooltipEl.style.top = (rect.top - 40) + 'px';
document.body.appendChild(tooltipEl);
e.target._tooltip = tooltipEl;
}
});
tooltip.addEventListener('mouseleave', (e) => {
if (e.target._tooltip) {
document.body.removeChild(e.target._tooltip);
e.target._tooltip = null;
}
});
});
}
initTooltips();
function handleResize() {
if (window.innerWidth <= 768) {
document.body.style.paddingBottom = 'calc(var(--action-bar-height) + 20px)';
} else {
document.body.style.paddingBottom = 'var(--action-bar-height)';
}
}
window.addEventListener('resize', handleResize);
handleResize();
function initAccessibility() {
document.getElementById('fire-preview').setAttribute('aria-label', 'Fire plasma effect preview');
document.getElementById('quick-attribute').setAttribute('aria-label', 'Copy data attribute to clipboard');
}
initAccessibility();
function handleErrors() {
window.addEventListener('error', (e) => {
showNotification('An error occurred. Please refresh.', 'error');
});
window.addEventListener('unhandledrejection', (e) => {
showNotification('A network error occurred.', 'error');
});
}
handleErrors();
function checkVersion() {
const currentVersion = '1.0.0';
const storedVersion = localStorage.getItem('bricksfusion-fire-configurator-version');
if (storedVersion && storedVersion !== currentVersion) {
showNotification('Configurator updated!', 'info');
}
localStorage.setItem('bricksfusion-fire-configurator-version', currentVersion);
}
checkVersion();
function trackEvent(eventName, eventData = {}) {
}
document.getElementById('download-config').addEventListener('click', () => {
trackEvent('download_js', { preset: fireConfig.colorPreset });
});
document.getElementById('copy-full-section').addEventListener('click', () => {
trackEvent('copy_full_section', { preset: fireConfig.colorPreset });
});
document.getElementById('randomize-fire').addEventListener('click', () => {
trackEvent('randomize_fire');
});
}
initializeUI();
});
</script>
</body>
</html>
Fire Plasma
Creates animated fire and plasma effects using WebGL shaders. Features turbulent flames, glowing embers, and sparks with customizable colors and patterns. Perfect for hero sections, dramatic backgrounds, or adding energy to any design.
Fire Plasma
Animated fire effect powered by WebGL shaders.
Colors
Deep color at the core of the fire effect. Typically dark red, orange, or blue for different flame types.
Default: #cc0000 (deep red)
Mid-tone color that blends with base. Creates the main body of flames. Brighter than base color.
Default: #ff3333 (bright red)
Brightest color for hot spots and sparks. Creates peaks and intense areas. Usually white, yellow, or cyan.
Default: #ff8080 (light red)
Animation
How fast the fire moves and flows. Lower is slow and calm, higher is fast and intense.
Default: 1
How chaotic and wild the flames are. Higher values create more distortion and movement.
Default: 0.5
Effects
Strength of the pulsing glow effect. Creates breathing, living fire with varied brightness.
Default: 0.5
Amount of bright sparks and embers. Higher values add more twinkling points of light.
Default: 0.5
Pattern
Type of movement pattern. Turbulent creates classic fire, smooth is flowing plasma, chaotic is wild flames, directional has wind-blown motion.
Default: Turbulent
Performance
Target frames per second. 60 is smooth but intensive, 30 saves resources on weaker devices.
Default: 60
Performance Warning
This element uses Three.js with complex GLSL fragment shaders for GPU-accelerated rendering. Uses fractal brownian motion (FBM) with 5 octaves of Perlin noise for organic patterns. Runs constantly at target FPS with real-time distortion and color mixing calculations. Very resource intensive - not recommended for multiple instances or low-end devices.