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>Particle Network Configurator - BricksFusion</title>
<style>
:root {
--background: #000;
--card-bg: #1e1e1e;
--card-bg-hover: #252525;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--accent: #ef6013;
--accent-hover: #c64c0c;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
--thumb: #ef6013;
--card-radius: 16px;
--input-radius: 8px;
--button-radius: 12px;
--transition: all 0.25s ease;
--font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
--action-bar-height: 70px;
--success: #28a745;
--warning: #ffc107;
--danger: #dc3545;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
background-color: var(--background);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-bottom: var(--action-bar-height);
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--action-bar-height);
background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
border-top: 1px solid var(--border);
z-index: 1000;
display: flex;
align-items: center;
padding: 0 1.5rem;
gap: 1rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.breadcrumb-item {
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: 500;
text-decoration: none;
transition: var(--transition);
padding: 0.5rem 0.75rem;
border-radius: 6px;
}
.breadcrumb-item:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.05);
}
.breadcrumb-item.active {
color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.breadcrumb-separator {
color: var(--text-secondary);
font-size: var(--text-xs);
opacity: 0.5;
}
.action-buttons {
display: flex;
align-items: center;
gap: 0.75rem;
}
.action-btn {
padding: 0.6rem 1rem;
background-color: var(--card-bg);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
border: 1px solid var(--border);
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
}
.action-btn:hover {
background-color: var(--card-bg-hover);
border-color: var(--accent);
transform: translateY(-1px);
}
.action-btn.primary {
background: linear-gradient(90deg, var(--accent), #ff8c51);
border-color: var(--accent);
color: white;
}
.action-btn.primary:hover {
background: linear-gradient(90deg, var(--accent-hover), #e67a3f);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}
.data-attribute-display {
background-color: rgba(50, 50, 50, 0.8);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
cursor: pointer;
transition: var(--transition);
user-select: all;
}
.data-attribute-display:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.page-header {
text-align: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--accent), #ff8c51);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-subtitle {
font-size: var(--text-s);
color: var(--text-secondary);
font-weight: 500;
}
.instructions-toggle {
margin-bottom: 2rem;
}
.instructions-card {
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: var(--transition);
}
.instructions-header {
padding: 1rem 1.5rem;
cursor: pointer;
transition: var(--transition);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid transparent;
}
.instructions-header:hover {
background-color: var(--card-bg-hover);
}
.instructions-card.expanded .instructions-header {
border-bottom-color: var(--border);
}
.instructions-title {
font-size: var(--text-s);
font-weight: 600;
}
.toggle-icon {
font-size: 1.2em;
transition: transform 0.3s ease;
}
.toggle-icon.expanded {
transform: rotate(180deg);
}
.instructions-content {
padding: 0 1.5rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.instructions-content.show {
max-height: 500px;
padding: 1.5rem;
}
.instructions-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.how-to-use ol {
padding-left: 1.5rem;
}
.how-to-use li {
margin-bottom: 0.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.6;
}
.how-to-use strong {
color: var(--text-primary);
font-weight: 600;
}
.how-to-use code {
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
}
.content {
display: grid;
grid-template-columns: 1fr 500px;
gap: 2rem;
align-items: start;
}
.preview-section {
position: sticky;
top: 2rem;
}
.controls-section {
max-width: 500px;
}
.card {
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.preview-container {
height: 400px;
width: 100%;
position: relative;
overflow: hidden;
border-radius: var(--card-radius);
background-color: #000000;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.preview-content {
color: white;
text-align: center;
font-weight: bold;
font-size: var(--text-s);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
.preview-controls {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
gap: 0.5rem;
z-index: 10;
}
.preview-btn {
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
font-size: var(--text-xs);
backdrop-filter: blur(5px);
}
.preview-btn:hover {
background-color: var(--accent);
border-color: var(--accent);
}
.preview-btn svg {
width: 18px;
height: 18px;
stroke: currentColor;
}
.background-selector-wrapper {
position: relative;
display: inline-block;
}
.background-selector-btn {
position: relative;
}
.background-selector-btn:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
box-shadow: 0 0 8px rgba(239, 96, 19, 0.3);
}
.hidden-color-input {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.card-heading {
padding: 1rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
letter-spacing: 0.3px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-actions {
display: flex;
gap: 0.5rem;
}
.card-action-btn {
padding: 0.4rem 0.8rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
}
.card-action-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.card-content {
padding: 1.5rem;
}
.control-group {
margin-bottom: 1.5rem;
position: relative;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.label-text {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.help-tooltip {
cursor: help;
opacity: 0.7;
transition: var(--transition);
}
.help-tooltip:hover {
opacity: 1;
color: var(--accent);
}
.value-display {
display: flex;
align-items: center;
gap: 0.5rem;
}
.value-text {
font-size: var(--text-xs);
color: var(--text-secondary);
background-color: rgba(50, 50, 50, 0.5);
padding: 2px 8px;
border-radius: 4px;
min-width: 45px;
text-align: center;
}
.reset-btn {
padding: 0.2rem 0.4rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 10px;
transition: var(--transition);
}
.reset-btn:hover {
color: var(--danger);
border-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
input[type="range"] {
-webkit-appearance: none;
width: 100%;
height: 6px;
background: var(--track);
border-radius: 3px;
outline: none;
margin: 0.8rem 0;
position: relative;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
background: var(--thumb);
border-radius: 50%;
cursor: pointer;
transition: var(--transition);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
box-shadow: 0 0 10px rgba(239, 96, 19, 0.5);
}
.color-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
}
.color-row {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem 1.25rem;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid var(--border);
border-radius: var(--input-radius);
transition: var(--transition);
}
.color-row:hover {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.1);
}
.color-picker-container {
position: relative;
width: 40px;
height: 40px;
border-radius: 8px;
overflow: hidden;
border: 2px solid var(--border);
cursor: pointer;
transition: var(--transition);
flex-shrink: 0;
background: var(--card-bg);
display: flex;
align-items: center;
justify-content: center;
--selected-color: #03A9F4;
}
.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, #03A9F4);
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">Particle Network</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-particle-network
</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">Particle Network</h1>
<p class="page-subtitle">Interactive particle animations for Bricks Builder</p>
</div>
<div class="instructions-toggle">
<div class="instructions-card" id="instructions-card">
<div class="instructions-header" id="instructions-toggle">
<div class="instructions-title">
How to Use & Code Information
</div>
<span class="toggle-icon">▼</span>
</div>
<div class="instructions-content" id="instructions-content">
<div class="instructions-grid">
<div class="how-to-use">
<ol>
<li>Customize your particle network animation using the controls below</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste or upload the JavaScript code</li>
<li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-particle-network</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="particle-preview" data-particle-network="true">
<div class="preview-content">Interactive Particle Network Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-network" title="Randomize (R)">🎲</button>
<div class="background-selector-wrapper">
<button class="preview-btn background-selector-btn" id="background-selector">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12,2 2,7 12,12 22,7"/>
<polyline points="2,17 12,22 22,17"/>
<polyline points="2,12 12,17 22,12"/>
</svg>
</button>
<input type="color" id="preview-background-picker" class="hidden-color-input" value="#000000" title="Change Preview Background (B)">
</div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Particle Colors
<div class="card-actions">
<button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
</div>
</div>
<div class="card-content">
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="particle-color" value="#03A9F4">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="particle-color-hex" value="#03A9F4" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="particle-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="line-color" value="#03A9F4">
</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="#03A9F4" 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 class="control-group">
<div class="control-label">
<span class="label-text">
Particle Opacity
<span class="help-tooltip" title="Transparency of particles">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="particle-opacity-value">0.9</span></span>
<button class="reset-btn" onclick="resetParameter('particle-opacity', 0.9)">↺</button>
</div>
</div>
<input type="range" id="particle-opacity" min="0.2" max="1" step="0.05" value="0.9">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Line Opacity
<span class="help-tooltip" title="Transparency of connection lines">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="line-opacity-value">0.7</span></span>
<button class="reset-btn" onclick="resetParameter('line-opacity', 0.7)">↺</button>
</div>
</div>
<input type="range" id="line-opacity" min="0.1" max="1" step="0.05" value="0.7">
</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">
Particle Count
<span class="help-tooltip" title="Number of particles in the animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="particle-count-value">50</span></span>
<button class="reset-btn" onclick="resetParameter('particle-count', 50)">↺</button>
</div>
</div>
<input type="range" id="particle-count" min="10" max="150" value="50">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Particle Size
<span class="help-tooltip" title="Size of individual particles">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="particle-size-value">1.5</span></span>
<button class="reset-btn" onclick="resetParameter('particle-size', 1.5)">↺</button>
</div>
</div>
<input type="range" id="particle-size" min="0.5" max="3" step="0.1" value="1.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Movement Speed
<span class="help-tooltip" title="Speed of particle movement">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="movement-speed-value">0.5</span>x</span>
<button class="reset-btn" onclick="resetParameter('movement-speed', 0.5)">↺</button>
</div>
</div>
<input type="range" id="movement-speed" min="0.1" max="2" 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">
Connection Distance
<span class="help-tooltip" title="Maximum distance for line connections">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="connection-distance-value">150</span>px</span>
<button class="reset-btn" onclick="resetParameter('connection-distance', 150)">↺</button>
</div>
</div>
<input type="range" id="connection-distance" min="50" max="300" value="150">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Interaction Strength
<span class="help-tooltip" title="Mouse interaction force on particles">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="interaction-strength-value">0.03</span></span>
<button class="reset-btn" onclick="resetParameter('interaction-strength', 0.03)">↺</button>
</div>
</div>
<input type="range" id="interaction-strength" min="0.01" max="0.1" step="0.01" value="0.03">
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let particleConfig = {
particleCount: 50,
particleSize: 1.5,
movementSpeed: 0.5,
particleColor: '#03A9F4',
lineColor: '#03A9F4',
particleOpacity: 0.9,
lineOpacity: 0.7,
connectionDistance: 150,
interactionStrength: 0.03
};
const defaultConfig = { ...particleConfig };
let activeNetwork = null;
function initParticleNetwork() {
const sections = document.querySelectorAll('[data-particle-network]:not([data-particle-initialized="true"])');
sections.forEach((section) => {
const particleCount = section.hasAttribute('data-particle-count')
? parseInt(section.getAttribute('data-particle-count'))
: particleConfig.particleCount;
const particleSize = section.hasAttribute('data-particle-size')
? parseFloat(section.getAttribute('data-particle-size'))
: particleConfig.particleSize;
const movementSpeed = section.hasAttribute('data-movement-speed')
? parseFloat(section.getAttribute('data-movement-speed'))
: particleConfig.movementSpeed;
const particleColor = section.hasAttribute('data-particle-color')
? section.getAttribute('data-particle-color')
: particleConfig.particleColor;
const lineColor = section.hasAttribute('data-line-color')
? section.getAttribute('data-line-color')
: particleConfig.lineColor;
const particleOpacity = section.hasAttribute('data-particle-opacity')
? parseFloat(section.getAttribute('data-particle-opacity'))
: particleConfig.particleOpacity;
const lineOpacity = section.hasAttribute('data-line-opacity')
? parseFloat(section.getAttribute('data-line-opacity'))
: particleConfig.lineOpacity;
const connectionDistance = section.hasAttribute('data-connection-distance')
? parseInt(section.getAttribute('data-connection-distance'))
: particleConfig.connectionDistance;
const interactionStrength = section.hasAttribute('data-interaction-strength')
? parseFloat(section.getAttribute('data-interaction-strength'))
: particleConfig.interactionStrength;
const options = {
particleCount,
particleSize,
movementSpeed,
particleColor,
lineColor,
particleOpacity,
lineOpacity,
connectionDistance,
interactionStrength
};
setupParticleNetwork(section, options);
section.dataset.particleInitialized = 'true';
if (section.id === 'particle-preview') {
activeNetwork = { element: section, options };
particleConfig = {
particleCount: options.particleCount,
particleSize: options.particleSize,
movementSpeed: options.movementSpeed,
particleColor: options.particleColor,
lineColor: options.lineColor,
particleOpacity: options.particleOpacity,
lineOpacity: options.lineOpacity,
connectionDistance: options.connectionDistance,
interactionStrength: options.interactionStrength
};
}
});
}
function setupParticleNetwork(element, options) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
element.appendChild(canvas);
if (window.getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
Object.assign(canvas.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
pointerEvents: 'none'
});
canvas.width = element.clientWidth;
canvas.height = element.clientHeight;
element.particleCanvas = canvas;
element.particleCtx = ctx;
element.particles = [];
element.animationFrame = null;
element.mouseX = null;
element.mouseY = null;
for (let i = 0; i < options.particleCount; i++) {
element.particles.push(createParticle(canvas.width, canvas.height, options));
}
setupEventListeners(element);
startAnimation(element, options);
}
function createParticle(width, height, options) {
return {
x: Math.random() * width,
y: Math.random() * height,
radius: Math.random() * options.particleSize + 1,
vx: (Math.random() - 0.5) * options.movementSpeed,
vy: (Math.random() - 0.5) * options.movementSpeed
};
}
function setupEventListeners(element) {
element.addEventListener('mousemove', function(e) {
const rect = element.particleCanvas.getBoundingClientRect();
element.mouseX = e.clientX - rect.left;
element.mouseY = e.clientY - rect.top;
});
element.addEventListener('touchmove', function(e) {
const rect = element.particleCanvas.getBoundingClientRect();
element.mouseX = e.touches[0].clientX - rect.left;
element.mouseY = e.touches[0].clientY - rect.top;
});
element.addEventListener('mouseleave', function() {
element.mouseX = null;
element.mouseY = null;
});
}
function startAnimation(element, options) {
function animate() {
element.animationFrame = requestAnimationFrame(animate);
element.particleCtx.clearRect(0, 0, element.particleCanvas.width, element.particleCanvas.height);
drawConnections(element, options);
element.particles.forEach(particle => {
updateParticle(particle, element.particleCanvas.width, element.particleCanvas.height, element, options);
drawParticle(particle, element.particleCtx, options);
});
}
element.animationFrame = requestAnimationFrame(animate);
element._cleanupNetwork = () => {
if (element.animationFrame) {
cancelAnimationFrame(element.animationFrame);
}
if (element.particleCanvas && element.particleCanvas.parentNode) {
element.particleCanvas.parentNode.removeChild(element.particleCanvas);
}
element.dataset.particleInitialized = 'false';
};
}
function updateParticle(particle, width, height, element, options) {
const maxSpeed = options.movementSpeed;
const currentSpeed = Math.sqrt(particle.vx * particle.vx + particle.vy * particle.vy);
if (currentSpeed > maxSpeed * 1.5 || currentSpeed < maxSpeed * 0.5) {
const factor = 0.95;
const directionX = particle.vx / (currentSpeed || 1);
const directionY = particle.vy / (currentSpeed || 1);
const targetSpeed = (0.5 + Math.random()) * maxSpeed;
particle.vx = particle.vx * factor + directionX * targetSpeed * (1 - factor);
particle.vy = particle.vy * factor + directionY * targetSpeed * (1 - factor);
}
particle.x += particle.vx;
particle.y += particle.vy;
if (particle.x < 0 || particle.x > width) particle.vx *= -1;
if (particle.y < 0 || particle.y > height) particle.vy *= -1;
if (element.mouseX !== null && element.mouseY !== null) {
const dx = element.mouseX - particle.x;
const dy = element.mouseY - particle.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
particle.x += dx * options.interactionStrength;
particle.y += dy * options.interactionStrength;
}
}
}
function drawParticle(particle, ctx, options) {
const targetRadius = Math.random() * options.particleSize + 1;
particle.radius = particle.radius * 0.95 + targetRadius * 0.05;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = hexToRgba(options.particleColor, options.particleOpacity);
ctx.fill();
}
function drawConnections(element, options) {
const particles = element.particles;
const ctx = element.particleCtx;
const connectionDistance = options.connectionDistance;
ctx.lineWidth = 0.7;
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
const dx = particles[i].x - particles[j].x;
const dy = particles[i].y - particles[j].y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < connectionDistance) {
const opacity = (1 - distance / connectionDistance) * options.lineOpacity;
ctx.beginPath();
ctx.moveTo(particles[i].x, particles[i].y);
ctx.lineTo(particles[j].x, particles[j].y);
ctx.strokeStyle = hexToRgba(options.lineColor, opacity);
ctx.stroke();
}
}
}
}
function hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
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 updateParticlePreview() {
const previews = ['particle-preview'];
previews.forEach(previewId => {
const preview = document.getElementById(previewId);
if (!preview) return;
if (preview._cleanupNetwork) {
preview._cleanupNetwork();
}
preview.setAttribute('data-particle-network', 'true');
preview.setAttribute('data-particle-count', particleConfig.particleCount);
preview.setAttribute('data-particle-size', particleConfig.particleSize);
preview.setAttribute('data-movement-speed', particleConfig.movementSpeed);
preview.setAttribute('data-particle-color', particleConfig.particleColor);
preview.setAttribute('data-line-color', particleConfig.lineColor);
preview.setAttribute('data-particle-opacity', particleConfig.particleOpacity);
preview.setAttribute('data-line-opacity', particleConfig.lineOpacity);
preview.setAttribute('data-connection-distance', particleConfig.connectionDistance);
preview.setAttribute('data-interaction-strength', particleConfig.interactionStrength);
});
initParticleNetwork();
}
function generateRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos para todos los elementos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const codeId = generateUniqueId();
const attributeId = generateUniqueId();
// Obtener el JavaScript code
const jsCode = generateJavaScriptCode();
// Crear la estructura completa del JSON de Bricks Builder
const fullSection = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
},
"_attributes": [
{
"id": attributeId,
"name": "data-particle-network"
}
]
},
"label": "Network Section"
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": {}
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Network Section"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(fullSection, null, 2);
}
function generateJavaScriptCode() {
return "(function(window) {\n" +
" const EnhancedParticleNetwork = {\n" +
" instances: [],\n" +
" config: {\n" +
" particleCount: " + particleConfig.particleCount + ",\n" +
" particleSize: " + particleConfig.particleSize + ",\n" +
" movementSpeed: " + particleConfig.movementSpeed + ",\n" +
" particleColor: \"" + particleConfig.particleColor + "\",\n" +
" lineColor: \"" + particleConfig.lineColor + "\",\n" +
" particleOpacity: " + particleConfig.particleOpacity + ",\n" +
" lineOpacity: " + particleConfig.lineOpacity + ",\n" +
" connectionDistance: " + particleConfig.connectionDistance + ",\n" +
" interactionStrength: " + particleConfig.interactionStrength + "\n" +
" },\n" +
" init: function(options = {}) {\n" +
" const defaultOptions = {\n" +
" selector: '[data-particle-network]'\n" +
" };\n" +
" const config = { ...defaultOptions, ...options };\n" +
"\n" +
" const initInstances = () => {\n" +
" document.querySelectorAll(config.selector).forEach(element => {\n" +
" if (!element.hasAttribute('data-particle-initialized')) {\n" +
" this.createInstance(element);\n" +
" element.setAttribute('data-particle-initialized', 'true');\n" +
" }\n" +
" });\n" +
" };\n" +
"\n" +
" initInstances();\n" +
" setTimeout(initInstances, 100);\n" +
" window.addEventListener('load', initInstances);\n" +
"\n" +
" const observer = new MutationObserver((mutations) => {\n" +
" mutations.forEach((mutation) => {\n" +
" if (mutation.type === 'childList') {\n" +
" initInstances();\n" +
" }\n" +
" });\n" +
" });\n" +
"\n" +
" observer.observe(document.body, { childList: true, subtree: true });\n" +
" },\n" +
"\n" +
" createInstance: function(element) {\n" +
" if (!document.getElementById('particle-network-styles')) {\n" +
" const style = document.createElement('style');\n" +
" style.id = 'particle-network-styles';\n" +
" style.textContent = `\n" +
" [data-particle-network] {\n" +
" position: relative !important;\n" +
" overflow: hidden !important;\n" +
" }\n" +
" [data-particle-network] > canvas {\n" +
" position: absolute !important;\n" +
" top: 0 !important;\n" +
" left: 0 !important;\n" +
" width: 100% !important;\n" +
" height: 100% !important;\n" +
" pointer-events: none !important;\n" +
" z-index: 0 !important;\n" +
" }\n" +
" [data-particle-network] > *:not(canvas) {\n" +
" position: relative !important;\n" +
" z-index: 1 !important;\n" +
" }\n" +
" `;\n" +
" document.head.appendChild(style);\n" +
" }\n" +
"\n" +
" const canvas = document.createElement('canvas');\n" +
" const ctx = canvas.getContext('2d', { alpha: true });\n" +
" element.insertBefore(canvas, element.firstChild);\n" +
"\n" +
" canvas.style.opacity = '0';\n" +
" canvas.style.transition = 'opacity 0.3s ease-in';\n" +
"\n" +
" const instance = {\n" +
" element,\n" +
" canvas,\n" +
" ctx,\n" +
" width: 0,\n" +
" height: 0,\n" +
" particles: [],\n" +
" mouseX: null,\n" +
" mouseY: null,\n" +
" animationFrame: null,\n" +
"\n" +
" resizeCanvas: function() {\n" +
" const rect = this.element.getBoundingClientRect();\n" +
" this.width = rect.width;\n" +
" this.height = rect.height;\n" +
" this.canvas.width = this.width * window.devicePixelRatio;\n" +
" this.canvas.height = this.height * window.devicePixelRatio;\n" +
" this.ctx.scale(window.devicePixelRatio, window.devicePixelRatio);\n" +
" },\n" +
"\n" +
" createParticles: function() {\n" +
" this.particles = [];\n" +
" for (let i = 0; i < EnhancedParticleNetwork.config.particleCount; i++) {\n" +
" this.particles.push({\n" +
" x: Math.random() * this.width,\n" +
" y: Math.random() * this.height,\n" +
" radius: Math.random() * EnhancedParticleNetwork.config.particleSize + 1,\n" +
" vx: (Math.random() - 0.5) * EnhancedParticleNetwork.config.movementSpeed,\n" +
" vy: (Math.random() - 0.5) * EnhancedParticleNetwork.config.movementSpeed\n" +
" });\n" +
" }\n" +
" },\n" +
"\n" +
" drawParticle: function(particle) {\n" +
" this.ctx.beginPath();\n" +
" this.ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);\n" +
" this.ctx.fillStyle = this.hexToRgba(EnhancedParticleNetwork.config.particleColor, EnhancedParticleNetwork.config.particleOpacity);\n" +
" this.ctx.fill();\n" +
" },\n" +
"\n" +
" updateParticle: function(particle) {\n" +
" particle.x += particle.vx;\n" +
" particle.y += particle.vy;\n" +
"\n" +
" if (particle.x < 0 || particle.x > this.width) particle.vx *= -1;\n" +
" if (particle.y < 0 || particle.y > this.height) particle.vy *= -1;\n" +
"\n" +
" if (this.mouseX !== null && this.mouseY !== null) {\n" +
" const dx = this.mouseX - particle.x;\n" +
" const dy = this.mouseY - particle.y;\n" +
" const distance = Math.sqrt(dx * dx + dy * dy);\n" +
"\n" +
" if (distance < 100) {\n" +
" particle.x += dx * EnhancedParticleNetwork.config.interactionStrength;\n" +
" particle.y += dy * EnhancedParticleNetwork.config.interactionStrength;\n" +
" }\n" +
" }\n" +
" },\n" +
"\n" +
" drawConnections: function() {\n" +
" const connectionDistance = EnhancedParticleNetwork.config.connectionDistance;\n" +
"\n" +
" this.ctx.lineWidth = 0.7;\n" +
"\n" +
" for (let i = 0; i < this.particles.length; i++) {\n" +
" for (let j = i + 1; j < this.particles.length; j++) {\n" +
" const dx = this.particles[i].x - this.particles[j].x;\n" +
" const dy = this.particles[i].y - this.particles[j].y;\n" +
" const distance = Math.sqrt(dx * dx + dy * dy);\n" +
"\n" +
" if (distance < connectionDistance) {\n" +
" const opacity = (1 - distance / connectionDistance) * EnhancedParticleNetwork.config.lineOpacity;\n" +
" this.ctx.beginPath();\n" +
" this.ctx.moveTo(this.particles[i].x, this.particles[i].y);\n" +
" this.ctx.lineTo(this.particles[j].x, this.particles[j].y);\n" +
" this.ctx.strokeStyle = this.hexToRgba(EnhancedParticleNetwork.config.lineColor, opacity);\n" +
" this.ctx.stroke();\n" +
" }\n" +
" }\n" +
" }\n" +
" },\n" +
"\n" +
" hexToRgba: function(hex, alpha) {\n" +
" const r = parseInt(hex.slice(1, 3), 16);\n" +
" const g = parseInt(hex.slice(3, 5), 16);\n" +
" const b = parseInt(hex.slice(5, 7), 16);\n" +
" return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n" +
" },\n" +
"\n" +
" animate: function() {\n" +
" this.animationFrame = requestAnimationFrame(this.animate.bind(this));\n" +
"\n" +
" this.ctx.clearRect(0, 0, this.width, this.height);\n" +
"\n" +
" this.drawConnections();\n" +
"\n" +
" this.particles.forEach(particle => {\n" +
" this.updateParticle(particle);\n" +
" this.drawParticle(particle);\n" +
" });\n" +
" },\n" +
"\n" +
" setupEventListeners: function() {\n" +
" const resizeObserver = new ResizeObserver(entries => {\n" +
" for (let entry of entries) {\n" +
" if (entry.target === this.element) {\n" +
" this.resize();\n" +
" }\n" +
" }\n" +
" });\n" +
"\n" +
" resizeObserver.observe(this.element);\n" +
"\n" +
" this.element.addEventListener('mousemove', (e) => {\n" +
" const rect = this.canvas.getBoundingClientRect();\n" +
" this.mouseX = e.clientX - rect.left;\n" +
" this.mouseY = e.clientY - rect.top;\n" +
" }, { passive: true });\n" +
"\n" +
" this.element.addEventListener('touchmove', (e) => {\n" +
" const rect = this.canvas.getBoundingClientRect();\n" +
" this.mouseX = e.touches[0].clientX - rect.left;\n" +
" this.mouseY = e.touches[0].clientY - rect.top;\n" +
" }, { passive: true });\n" +
"\n" +
" this.element.addEventListener('mouseleave', () => {\n" +
" this.mouseX = null;\n" +
" this.mouseY = null;\n" +
" });\n" +
" },\n" +
"\n" +
" resize: function() {\n" +
" this.resizeCanvas();\n" +
" this.createParticles();\n" +
" },\n" +
"\n" +
" init: function() {\n" +
" this.resizeCanvas();\n" +
" this.createParticles();\n" +
" this.setupEventListeners();\n" +
"\n" +
" requestAnimationFrame(() => {\n" +
" this.canvas.style.opacity = '1';\n" +
" this.animate();\n" +
" });\n" +
" }\n" +
" };\n" +
"\n" +
" instance.init();\n" +
" this.instances.push(instance);\n" +
" }\n" +
" };\n" +
"\n" +
" window.EnhancedParticleNetwork = EnhancedParticleNetwork;\n" +
" EnhancedParticleNetwork.init();\n" +
"})(window);";
}
function copyJsToClipboard() {
const jsCode = generateJavaScriptCode();
navigator.clipboard.writeText(jsCode)
.then(() => {
showNotification('JavaScript code copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = jsCode;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('JavaScript code copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyFullSectionToClipboard() {
const sectionJSON = generateFullSectionJSON();
navigator.clipboard.writeText(sectionJSON)
.then(() => {
showNotification('Full section JSON copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = sectionJSON;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('Full section JSON copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy to clipboard', 'error');
});
}
window.resetParameter = function(parameterId, defaultValue) {
const element = document.getElementById(parameterId);
if (element) {
element.value = defaultValue;
const valueElement = document.getElementById(`${parameterId}-value`);
if (valueElement) {
valueElement.textContent = defaultValue;
}
switch (parameterId) {
case 'particle-count':
particleConfig.particleCount = defaultValue;
break;
case 'particle-size':
particleConfig.particleSize = defaultValue;
break;
case 'movement-speed':
particleConfig.movementSpeed = defaultValue;
break;
case 'particle-opacity':
particleConfig.particleOpacity = defaultValue;
break;
case 'line-opacity':
particleConfig.lineOpacity = defaultValue;
break;
case 'connection-distance':
particleConfig.connectionDistance = defaultValue;
break;
case 'interaction-strength':
particleConfig.interactionStrength = defaultValue;
break;
}
updateParticlePreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function generateRandomNetwork() {
particleConfig.particleCount = Math.floor(Math.random() * 140) + 10;
particleConfig.particleSize = Math.random() * 2.5 + 0.5;
particleConfig.movementSpeed = Math.random() * 1.9 + 0.1;
particleConfig.particleColor = generateRandomColor();
particleConfig.lineColor = generateRandomColor();
particleConfig.particleOpacity = Math.random() * 0.8 + 0.2;
particleConfig.lineOpacity = Math.random() * 0.9 + 0.1;
particleConfig.connectionDistance = Math.floor(Math.random() * 250) + 50;
particleConfig.interactionStrength = Math.random() * 0.09 + 0.01;
document.getElementById('particle-count').value = particleConfig.particleCount;
document.getElementById('particle-size').value = particleConfig.particleSize;
document.getElementById('movement-speed').value = particleConfig.movementSpeed;
document.getElementById('particle-color').value = particleConfig.particleColor;
document.getElementById('line-color').value = particleConfig.lineColor;
document.getElementById('particle-opacity').value = particleConfig.particleOpacity;
document.getElementById('line-opacity').value = particleConfig.lineOpacity;
document.getElementById('connection-distance').value = particleConfig.connectionDistance;
document.getElementById('interaction-strength').value = particleConfig.interactionStrength;
document.getElementById('particle-count-value').textContent = particleConfig.particleCount;
document.getElementById('particle-size-value').textContent = particleConfig.particleSize;
document.getElementById('movement-speed-value').textContent = particleConfig.movementSpeed;
document.getElementById('particle-opacity-value').textContent = particleConfig.particleOpacity;
document.getElementById('line-opacity-value').textContent = particleConfig.lineOpacity;
document.getElementById('connection-distance-value').textContent = particleConfig.connectionDistance;
document.getElementById('interaction-strength-value').textContent = particleConfig.interactionStrength;
updateColorInputs();
updateParticlePreview();
showNotification('Random particle network generated!');
}
function updateColorInputs() {
const particleColorInput = document.getElementById('particle-color');
const particleHexInput = document.getElementById('particle-color-hex');
const particleHslInput = document.getElementById('particle-color-hsl');
const lineColorInput = document.getElementById('line-color');
const lineHexInput = document.getElementById('line-color-hex');
const lineHslInput = document.getElementById('line-color-hsl');
if (particleColorInput && particleHexInput && particleHslInput) {
particleColorInput.value = particleConfig.particleColor;
particleHexInput.value = particleConfig.particleColor;
particleHslInput.value = `hsl(${hexToHsl(particleConfig.particleColor).h}, ${hexToHsl(particleConfig.particleColor).s}%, ${hexToHsl(particleConfig.particleColor).l}%)`;
particleHexInput.classList.remove('invalid');
particleHslInput.classList.remove('invalid');
const particleColorPickerContainer = particleColorInput.closest('.color-row').querySelector('.color-picker-container');
if (particleColorPickerContainer) {
particleColorPickerContainer.style.setProperty('--selected-color', particleConfig.particleColor);
}
}
if (lineColorInput && lineHexInput && lineHslInput) {
lineColorInput.value = particleConfig.lineColor;
lineHexInput.value = particleConfig.lineColor;
lineHslInput.value = `hsl(${hexToHsl(particleConfig.lineColor).h}, ${hexToHsl(particleConfig.lineColor).s}%, ${hexToHsl(particleConfig.lineColor).l}%)`;
lineHexInput.classList.remove('invalid');
lineHslInput.classList.remove('invalid');
const lineColorPickerContainer = lineColorInput.closest('.color-row').querySelector('.color-picker-container');
if (lineColorPickerContainer) {
lineColorPickerContainer.style.setProperty('--selected-color', particleConfig.lineColor);
}
}
}
function initializeUI() {
initParticleNetwork();
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-particle-network');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-network').addEventListener('click', () => {
generateRandomNetwork();
});
const backgroundPicker = document.getElementById('preview-background-picker');
const previewContainer = document.getElementById('particle-preview');
backgroundPicker.addEventListener('input', (e) => {
const selectedColor = e.target.value;
previewContainer.style.backgroundColor = selectedColor;
showNotification(`Preview background changed to ${selectedColor}`);
});
previewContainer.style.backgroundColor = '#000000';
document.getElementById('reset-colors').addEventListener('click', () => {
particleConfig.particleColor = defaultConfig.particleColor;
particleConfig.lineColor = defaultConfig.lineColor;
particleConfig.particleOpacity = defaultConfig.particleOpacity;
particleConfig.lineOpacity = defaultConfig.lineOpacity;
document.getElementById('particle-color').value = defaultConfig.particleColor;
document.getElementById('line-color').value = defaultConfig.lineColor;
document.getElementById('particle-opacity').value = defaultConfig.particleOpacity;
document.getElementById('line-opacity').value = defaultConfig.lineOpacity;
document.getElementById('particle-opacity-value').textContent = defaultConfig.particleOpacity;
document.getElementById('line-opacity-value').textContent = defaultConfig.lineOpacity;
updateColorInputs();
updateParticlePreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
particleConfig.particleCount = defaultConfig.particleCount;
particleConfig.particleSize = defaultConfig.particleSize;
particleConfig.movementSpeed = defaultConfig.movementSpeed;
document.getElementById('particle-count').value = defaultConfig.particleCount;
document.getElementById('particle-size').value = defaultConfig.particleSize;
document.getElementById('movement-speed').value = defaultConfig.movementSpeed;
document.getElementById('particle-count-value').textContent = defaultConfig.particleCount;
document.getElementById('particle-size-value').textContent = defaultConfig.particleSize;
document.getElementById('movement-speed-value').textContent = defaultConfig.movementSpeed;
updateParticlePreview();
showNotification('Animation settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
particleConfig.connectionDistance = defaultConfig.connectionDistance;
particleConfig.interactionStrength = defaultConfig.interactionStrength;
document.getElementById('connection-distance').value = defaultConfig.connectionDistance;
document.getElementById('interaction-strength').value = defaultConfig.interactionStrength;
document.getElementById('connection-distance-value').textContent = defaultConfig.connectionDistance;
document.getElementById('interaction-strength-value').textContent = defaultConfig.interactionStrength;
updateParticlePreview();
showNotification('Advanced settings reset');
});
function setupColorControls(colorType) {
const colorInput = document.getElementById(`${colorType}-color`);
const hexInput = document.getElementById(`${colorType}-color-hex`);
const hslInput = document.getElementById(`${colorType}-color-hsl`);
const colorProperty = colorType === 'particle' ? 'particleColor' : 'lineColor';
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');
particleConfig[colorProperty] = color;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', color);
updateParticlePreview();
});
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}%)`;
particleConfig[colorProperty] = 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);
updateParticlePreview();
} 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;
particleConfig[colorProperty] = 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);
updateParticlePreview();
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;
particleConfig[colorProperty] = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateParticlePreview();
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');
}
});
}
setupColorControls('particle');
setupColorControls('line');
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 'particle-count':
particleConfig.particleCount = parseInt(input.value);
break;
case 'particle-size':
particleConfig.particleSize = parseFloat(input.value);
break;
case 'movement-speed':
particleConfig.movementSpeed = parseFloat(input.value);
break;
case 'particle-opacity':
particleConfig.particleOpacity = parseFloat(input.value);
break;
case 'line-opacity':
particleConfig.lineOpacity = parseFloat(input.value);
break;
case 'connection-distance':
particleConfig.connectionDistance = parseInt(input.value);
break;
case 'interaction-strength':
particleConfig.interactionStrength = parseFloat(input.value);
break;
}
updateParticlePreview();
});
});
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':
generateRandomNetwork();
break;
case 'b':
document.getElementById('preview-background-picker').click();
break;
}
}
});
updateColorInputs();
setTimeout(() => {
showNotification('BricksFusion Particle Network Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-particle-config', JSON.stringify(particleConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-particle-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(particleConfig, savedConfig);
document.getElementById('particle-count').value = savedConfig.particleCount;
document.getElementById('particle-size').value = savedConfig.particleSize;
document.getElementById('movement-speed').value = savedConfig.movementSpeed;
document.getElementById('particle-color').value = savedConfig.particleColor;
document.getElementById('line-color').value = savedConfig.lineColor;
document.getElementById('particle-opacity').value = savedConfig.particleOpacity;
document.getElementById('line-opacity').value = savedConfig.lineOpacity;
document.getElementById('connection-distance').value = savedConfig.connectionDistance;
document.getElementById('interaction-strength').value = savedConfig.interactionStrength;
document.getElementById('particle-count-value').textContent = savedConfig.particleCount;
document.getElementById('particle-size-value').textContent = savedConfig.particleSize;
document.getElementById('movement-speed-value').textContent = savedConfig.movementSpeed;
document.getElementById('particle-opacity-value').textContent = savedConfig.particleOpacity;
document.getElementById('line-opacity-value').textContent = savedConfig.lineOpacity;
document.getElementById('connection-distance-value').textContent = savedConfig.connectionDistance;
document.getElementById('interaction-strength-value').textContent = savedConfig.interactionStrength;
updateColorInputs();
updateParticlePreview();
}
} catch (e) {
}
}
const originalUpdateParticlePreview = updateParticlePreview;
updateParticlePreview = function() {
originalUpdateParticlePreview();
saveConfiguration();
};
loadConfiguration();
}
initializeUI();
});
</script>
</body>
</html>
Particle Network
Creates animated network of connected particles that float and interact. Particles move organically and connect with lines when close together. Responds to cursor movement by attracting nearby particles. Features customizable particle density, colors, and connection behavior. Perfect for tech backgrounds, hero sections, or adding dynamic visual interest.
Particle Network
Move your mouse to attract the particles.
Particles
Number of particles floating in the network. Lower creates a sparse, minimal look. Higher produces a dense, complex web.
Default: 50 particles
Size of individual particles. Smaller creates delicate dots. Larger makes bold, visible points.
Default: Small (1.5)
How quickly particles drift around. Lower creates gentle, calming motion. Higher produces energetic, lively movement.
Default: Moderate
Colors
Color of the floating particles. Choose any color to match your design theme.
Default: Cyan blue
Color of connection lines between particles. Can match particle color or use a different shade for variety.
Default: Cyan blue
Visibility
Transparency of particles. Lower creates subtle, ghostly dots. Higher makes particles bold and prominent.
Default: Nearly solid (90%)
Transparency of connection lines. Lower creates delicate, barely-visible connections. Higher produces clear, defined network lines.
Default: Visible (70%)
Connections
Maximum distance particles can be apart and still connect with lines. Lower creates fewer, local connections. Higher produces a dense, web-like network.
Default: Medium (150)
How strongly particles are pulled toward your cursor. Lower keeps it subtle and gentle. Higher creates dramatic, magnetic attraction.
Default: Subtle (0.03)
Performance
This element uses Canvas 2D for rendering animated particles with connection detection. Features velocity-based movement with boundary collision. Implements throttled mouse tracking for smooth interaction. Uses IntersectionObserver to pause when off-screen. Moderate performance - suitable for most devices with 2-3 instances per page recommended.