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>Cosmic Harmony 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, #4287f5);
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;
}
.switch-container {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.2rem;
padding: 0.5rem 0;
}
.switch-label {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.switch {
position: relative;
display: inline-block;
width: 52px;
height: 28px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--track);
transition: var(--transition);
border-radius: 34px;
}
.slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 4px;
bottom: 4px;
background-color: #f2f2f7;
transition: var(--transition);
border-radius: 50%;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
input:checked + .slider {
background-color: var(--accent);
}
input:checked + .slider:before {
transform: translateX(24px);
}
select {
width: 100%;
padding: 0.8rem 1rem;
background-color: rgba(30, 30, 30, 0.7);
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);
margin-bottom: 0.75rem;
outline: none;
}
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.quick-actions {
display: flex;
gap: 0.5rem;
margin-bottom: 1.5rem;
}
.quick-action-btn {
flex: 1;
padding: 0.6rem;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid var(--border);
border-radius: var(--input-radius);
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
font-size: var(--text-xs);
text-align: center;
}
.quick-action-btn:hover {
background-color: var(--accent);
color: white;
border-color: var(--accent);
}
.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;
}
.add-color-btn {
width: 100%;
padding: 0.8rem;
background-color: rgba(30, 30, 30, 0.7);
border: 1px dashed var(--border);
border-radius: var(--input-radius);
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
font-size: var(--text-xs);
text-align: center;
margin-top: 0.5rem;
}
.add-color-btn:hover {
background-color: rgba(50, 50, 50, 0.7);
color: var(--text-primary);
border-color: var(--accent);
}
.remove-color-btn {
padding: 0.5rem;
background-color: rgba(220, 53, 69, 0.2);
border: 1px solid rgba(220, 53, 69, 0.3);
border-radius: 6px;
color: var(--danger);
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
flex-shrink: 0;
}
.remove-color-btn:hover {
background-color: rgba(220, 53, 69, 0.3);
border-color: var(--danger);
}
@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">Cosmic Harmony</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-cosmic-harmony
</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">Cosmic Harmony</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 cosmic harmony 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-cosmic-harmony</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="cosmic-preview" data-cosmic-harmony="true">
<div class="preview-content">Interactive Cosmic Harmony Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-cosmic" title="Randomize (R)">🎲</button>
</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" id="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="color1" value="#4287f5">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="color1-hex" value="#4287f5" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="color1-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
<button class="remove-color-btn" title="Remove color">×</button>
</div>
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="color2" value="#f542e3">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="color2-hex" value="#f542e3" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="color2-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
<button class="remove-color-btn" title="Remove color">×</button>
</div>
</div>
<button class="add-color-btn" id="add-color-btn">+ Add Color</button>
</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 generated per interaction">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="particle-count-value">40</span></span>
<button class="reset-btn" onclick="resetParameter('particle-count', 40)">↺</button>
</div>
</div>
<input type="range" id="particle-count" min="10" max="100" value="40">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Particle Size
<span class="help-tooltip" title="Base size of particles">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="particle-size-value">15</span>px</span>
<button class="reset-btn" onclick="resetParameter('particle-size', 15)">↺</button>
</div>
</div>
<input type="range" id="particle-size" min="5" max="30" step="1" value="15">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Animation Speed
<span class="help-tooltip" title="Speed of particle movement">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="animation-speed-value">0.002</span>x</span>
<button class="reset-btn" onclick="resetParameter('animation-speed', 0.002)">↺</button>
</div>
</div>
<input type="range" id="animation-speed" min="0.0005" max="0.004" step="0.0005" value="0.002">
</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="opacity-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('opacity', 0.5)">↺</button>
</div>
</div>
<input type="range" id="opacity" min="0.1" max="1" step="0.05" value="0.5">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Interaction Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-interaction" title="Reset Interaction Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="switch-container">
<span class="switch-label">
Interactive Mode
<span class="help-tooltip" title="Enable mouse interaction">ℹ</span>
</span>
<label class="switch">
<input type="checkbox" id="interactive-mode" checked>
<span class="slider"></span>
</label>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Auto Animation Interval
<span class="help-tooltip" title="Time between automatic particle bursts">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="interval-value">1500</span>ms</span>
<button class="reset-btn" onclick="resetParameter('interval', 1500)">↺</button>
</div>
</div>
<input type="range" id="interval" min="500" max="3000" step="100" value="1500">
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let cosmicConfig = {
particleCount: 40,
particleSize: 15,
animationSpeed: 0.002,
opacity: 0.5,
interval: 1500,
isInteractive: true,
colors: ['#4287f5', '#f542e3']
};
const defaultConfig = { ...cosmicConfig };
let activeCosmicHarmony = null;
function initCosmicHarmony() {
const sections = document.querySelectorAll('[data-cosmic-harmony]:not([data-cosmic-initialized="true"])');
sections.forEach((section) => {
const isInteractive = section.hasAttribute('data-cosmic-interactive')
? section.getAttribute('data-cosmic-interactive') !== 'false'
: cosmicConfig.isInteractive;
let colors = [];
for (let i = 1; i <= 10; i++) {
const colorAttr = `data-cosmic-color${i}`;
if (section.hasAttribute(colorAttr)) {
colors.push(section.getAttribute(colorAttr));
}
}
if (colors.length === 0) {
colors = cosmicConfig.colors;
}
const particleCount = section.hasAttribute('data-cosmic-particle-count')
? parseInt(section.getAttribute('data-cosmic-particle-count'))
: cosmicConfig.particleCount;
const particleSize = section.hasAttribute('data-cosmic-particle-size')
? parseInt(section.getAttribute('data-cosmic-particle-size'))
: cosmicConfig.particleSize;
const animationSpeed = section.hasAttribute('data-cosmic-animation-speed')
? parseFloat(section.getAttribute('data-cosmic-animation-speed'))
: cosmicConfig.animationSpeed;
const opacity = section.hasAttribute('data-cosmic-opacity')
? parseFloat(section.getAttribute('data-cosmic-opacity'))
: cosmicConfig.opacity;
const interval = section.hasAttribute('data-cosmic-interval')
? parseInt(section.getAttribute('data-cosmic-interval'))
: cosmicConfig.interval;
const options = {
colors,
particleCount,
particleSize,
animationSpeed,
opacity,
interval,
isInteractive
};
setupCosmicHarmony(section, options);
section.dataset.cosmicInitialized = 'true';
if (section.id === 'cosmic-preview') {
activeCosmicHarmony = { element: section, options };
cosmicConfig = {
particleCount: options.particleCount,
particleSize: options.particleSize,
animationSpeed: options.animationSpeed,
opacity: options.opacity,
interval: options.interval,
isInteractive: options.isInteractive,
colors: options.colors
};
}
});
}
function setupCosmicHarmony(element, options) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (window.getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
if (element.firstChild) {
element.insertBefore(canvas, element.firstChild);
} else {
element.appendChild(canvas);
}
Object.assign(canvas.style, {
position: 'absolute',
top: '0',
left: '0',
width: '100%',
height: '100%',
pointerEvents: options.isInteractive ? 'auto' : 'none',
zIndex: '0'
});
const instance = {
element,
canvas,
ctx,
width: 0,
height: 0,
particles: [],
isInteractive: options.isInteractive,
autoAnimationInterval: null,
lastInteractionTime: 0,
resizeCanvas: function() {
const rect = this.element.getBoundingClientRect();
this.width = rect.width;
this.height = rect.height;
if (this.width === 0 || this.height === 0) {
this.width = this.element.clientWidth || 300;
this.height = this.element.clientHeight || 200;
}
this.canvas.width = this.width;
this.canvas.height = this.height;
},
createParticles: function(x, y) {
const colorIndex = Math.floor(Math.random() * options.colors.length);
const color = options.colors[colorIndex];
const particleCount = options.particleCount / 4;
for (let i = 0; i < particleCount; i++) {
this.particles.push({
x: x + (Math.random() * 20 - 10),
y: y + (Math.random() * 20 - 10),
size: Math.random() * options.particleSize + 5,
color: color,
speedX: Math.random() * 3 - 1.5,
speedY: Math.random() * 3 - 1.5,
life: 300
});
}
},
autoAnimate: function() {
const x = Math.random() * this.width;
const y = Math.random() * this.height;
this.createParticles(x, y);
},
animate: function() {
requestAnimationFrame(this.animate.bind(this));
this.ctx.globalCompositeOperation = 'source-over';
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
this.ctx.fillRect(0, 0, this.width, this.height);
this.ctx.globalCompositeOperation = 'screen';
this.particles = this.particles.filter(particle => {
this.updateParticle(particle);
this.drawParticle(particle);
return particle.life > 0 && particle.size > 0.5;
});
},
updateParticle: function(particle) {
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.size -= 0.05;
particle.life--;
if (particle.x < 0 || particle.x > this.width) {
particle.speedX *= -1;
}
if (particle.y < 0 || particle.y > this.height) {
particle.speedY *= -1;
}
},
drawParticle: function(particle) {
const alpha = (particle.life / 300) * options.opacity;
this.ctx.beginPath();
this.ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
const hexColor = particle.color;
const r = parseInt(hexColor.slice(1, 3), 16);
const g = parseInt(hexColor.slice(3, 5), 16);
const b = parseInt(hexColor.slice(5, 7), 16);
this.ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
this.ctx.fill();
},
handleInteraction: function(e) {
const now = Date.now();
if (now - this.lastInteractionTime > 50) {
const rect = this.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
this.createParticles(x, y);
this.lastInteractionTime = now;
}
},
resize: function() {
this.resizeCanvas();
},
init: function() {
this.resizeCanvas();
this.createParticles(this.width / 4, this.height / 4);
this.createParticles(this.width / 2, this.height / 2);
this.createParticles(3 * this.width / 4, 3 * this.height / 4);
this.animate();
this.autoAnimationInterval = setInterval(() => {
this.autoAnimate();
}, options.interval);
if (this.isInteractive) {
this.canvas.addEventListener('mousemove', this.handleInteraction.bind(this));
this.canvas.style.cursor = 'pointer';
}
}
};
instance.init();
const resizeObserver = new ResizeObserver(() => {
instance.resize();
});
resizeObserver.observe(element);
setTimeout(() => {
instance.resizeCanvas();
}, 300);
element._cleanupCosmicHarmony = () => {
if (instance.autoAnimationInterval) {
clearInterval(instance.autoAnimationInterval);
}
resizeObserver.disconnect();
if (canvas && canvas.parentNode) {
canvas.parentNode.removeChild(canvas);
}
element.dataset.cosmicInitialized = 'false';
};
}
function updateCosmicPreview() {
const previews = ['cosmic-preview'];
previews.forEach(previewId => {
const preview = document.getElementById(previewId);
if (!preview) return;
if (preview._cleanupCosmicHarmony) {
preview._cleanupCosmicHarmony();
}
preview.setAttribute('data-cosmic-harmony', 'true');
if (cosmicConfig.isInteractive) {
preview.setAttribute('data-cosmic-interactive', 'true');
} else {
preview.removeAttribute('data-cosmic-interactive');
}
for (let i = 0; i < cosmicConfig.colors.length; i++) {
preview.setAttribute(`data-cosmic-color${i+1}`, cosmicConfig.colors[i]);
}
preview.setAttribute('data-cosmic-particle-count', cosmicConfig.particleCount);
preview.setAttribute('data-cosmic-particle-size', cosmicConfig.particleSize);
preview.setAttribute('data-cosmic-animation-speed', cosmicConfig.animationSpeed);
preview.setAttribute('data-cosmic-opacity', cosmicConfig.opacity);
preview.setAttribute('data-cosmic-interval', cosmicConfig.interval);
});
initCosmicHarmony();
}
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return `hsl(${Math.round(h * 360)}, ${Math.round(s * 100)}%, ${Math.round(l * 100)}%)`;
}
function hslToHex(hsl) {
const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
if (!match) return null;
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let l = parseInt(match[3]) / 100;
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function isValidHex(hex) {
return /^#[0-9A-F]{6}$/i.test(hex);
}
function isValidHsl(hsl) {
return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
}
function formatHex(value) {
let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
if (!hex.startsWith('#')) {
hex = '#' + hex;
}
if (hex.length > 7) {
hex = hex.substring(0, 7);
}
return hex.toUpperCase();
}
function formatHsl(value) {
const cleanValue = value.replace(/[^\d,\s]/g, '');
const numbers = cleanValue.match(/\d+/g);
if (!numbers || numbers.length < 3) {
const partialMatch = value.match(/(\d+)/g);
if (partialMatch && partialMatch.length >= 1) {
const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
return `hsl(${h}, ${s}%, ${l}%)`;
}
return value;
}
let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
return `hsl(${h}, ${s}%, ${l}%)`;
}
function generateRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos
const sectionId = Math.random().toString(36).substring(2, 8);
const containerId = Math.random().toString(36).substring(2, 8);
const codeId = Math.random().toString(36).substring(2, 8);
const attributeId = Math.random().toString(36).substring(2, 8);
// Obtener el JavaScript code escapado
const jsCode = generateJavaScriptCode();
// Crear la estructura del JSON
const fullSectionData = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
},
"_attributes": [
{
"id": attributeId,
"name": "data-cosmic-harmony"
}
]
},
"label": "Cosmic Harmony Section"
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": {},
"label": ""
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Cosmic Harmony JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(fullSectionData, null, 2);
}
function generateJavaScriptCode() {
const colors = cosmicConfig.colors;
const particleCount = cosmicConfig.particleCount;
const particleSize = cosmicConfig.particleSize;
const animationSpeed = cosmicConfig.animationSpeed;
const opacity = cosmicConfig.opacity;
const interval = cosmicConfig.interval;
const isInteractive = cosmicConfig.isInteractive;
return "(function(window) {\n" +
" const CosmicHarmonyAnimation = {\n" +
" instances: [],\n" +
" config: {\n" +
" particleCount: " + particleCount + ",\n" +
" particleSize: " + particleSize + ",\n" +
" animationSpeed: " + animationSpeed + ",\n" +
" opacity: " + opacity + ",\n" +
" interval: " + interval + ",\n" +
" isInteractive: " + isInteractive + ",\n" +
" colors: " + JSON.stringify(colors) + "\n" +
" },\n" +
" init: function(options = {}) {\n" +
" const defaultOptions = {\n" +
" selector: '[data-cosmic-harmony]',\n" +
" interactiveAttr: 'data-cosmic-interactive'\n" +
" };\n" +
" const config = { ...defaultOptions, ...options };\n" +
"\n" +
" const initInstances = () => {\n" +
" document.querySelectorAll(config.selector).forEach(element => {\n" +
" if (!element.hasAttribute('data-cosmic-initialized')) {\n" +
" this.createInstance(element, config);\n" +
" element.setAttribute('data-cosmic-initialized', 'true');\n" +
" }\n" +
" });\n" +
" };\n" +
"\n" +
" initInstances();\n" +
" setTimeout(initInstances, 200);\n" +
" window.addEventListener('load', initInstances);\n" +
" window.addEventListener('DOMContentLoaded', initInstances);\n" +
"\n" +
" const observer = new MutationObserver((mutations) => {\n" +
" let shouldInit = false;\n" +
" mutations.forEach((mutation) => {\n" +
" if (mutation.type === 'childList' || mutation.type === 'attributes') {\n" +
" shouldInit = true;\n" +
" }\n" +
" });\n" +
" if (shouldInit) {\n" +
" initInstances();\n" +
" }\n" +
" });\n" +
"\n" +
" observer.observe(document.body, { \n" +
" childList: true, \n" +
" subtree: true, \n" +
" attributes: true,\n" +
" attributeFilter: ['data-cosmic-harmony']\n" +
" });\n" +
" },\n" +
"\n" +
" createInstance: function(element, config) {\n" +
" const canvas = document.createElement('canvas');\n" +
" const ctx = canvas.getContext('2d');\n" +
" \n" +
" if (window.getComputedStyle(element).position === 'static') {\n" +
" element.style.position = 'relative';\n" +
" }\n" +
"\n" +
" if (element.firstChild) {\n" +
" element.insertBefore(canvas, element.firstChild);\n" +
" } else {\n" +
" element.appendChild(canvas);\n" +
" }\n" +
" \n" +
" let isInteractive = this.config.isInteractive;\n" +
" if (element.hasAttribute(config.interactiveAttr)) {\n" +
" isInteractive = element.getAttribute(config.interactiveAttr) !== 'false';\n" +
" }\n" +
"\n" +
" let colors = this.config.colors;\n" +
" for (let i = 1; i <= 10; i++) {\n" +
" const colorAttr = 'data-cosmic-color' + i;\n" +
" if (element.hasAttribute(colorAttr)) {\n" +
" if (i === 1) colors = [];\n" +
" colors.push(element.getAttribute(colorAttr));\n" +
" }\n" +
" }\n" +
"\n" +
" const particleCount = element.hasAttribute('data-cosmic-particle-count') \n" +
" ? parseInt(element.getAttribute('data-cosmic-particle-count')) \n" +
" : this.config.particleCount;\n" +
"\n" +
" const particleSize = element.hasAttribute('data-cosmic-particle-size') \n" +
" ? parseInt(element.getAttribute('data-cosmic-particle-size')) \n" +
" : this.config.particleSize;\n" +
"\n" +
" const animationSpeed = element.hasAttribute('data-cosmic-animation-speed') \n" +
" ? parseFloat(element.getAttribute('data-cosmic-animation-speed')) \n" +
" : this.config.animationSpeed;\n" +
"\n" +
" const opacity = element.hasAttribute('data-cosmic-opacity') \n" +
" ? parseFloat(element.getAttribute('data-cosmic-opacity')) \n" +
" : this.config.opacity;\n" +
"\n" +
" const interval = element.hasAttribute('data-cosmic-interval') \n" +
" ? parseInt(element.getAttribute('data-cosmic-interval')) \n" +
" : this.config.interval;\n" +
"\n" +
" Object.assign(canvas.style, {\n" +
" position: 'absolute',\n" +
" top: '0',\n" +
" left: '0',\n" +
" width: '100%',\n" +
" height: '100%',\n" +
" pointerEvents: isInteractive ? 'auto' : 'none',\n" +
" zIndex: '0'\n" +
" });\n" +
"\n" +
" const instance = {\n" +
" element,\n" +
" canvas,\n" +
" ctx,\n" +
" width: 0,\n" +
" height: 0,\n" +
" particles: [],\n" +
" isInteractive: isInteractive,\n" +
" autoAnimationInterval: null,\n" +
" lastInteractionTime: 0,\n" +
"\n" +
" resizeCanvas: function() {\n" +
" const rect = this.element.getBoundingClientRect();\n" +
" this.width = rect.width;\n" +
" this.height = rect.height;\n" +
" \n" +
" if (this.width === 0 || this.height === 0) {\n" +
" this.width = this.element.clientWidth || 300;\n" +
" this.height = this.element.clientHeight || 200;\n" +
" }\n" +
" \n" +
" this.canvas.width = this.width;\n" +
" this.canvas.height = this.height;\n" +
" },\n" +
"\n" +
" createParticles: function(x, y) {\n" +
" const colorIndex = Math.floor(Math.random() * colors.length);\n" +
" const color = colors[colorIndex];\n" +
" \n" +
" const particleCountLocal = particleCount / 4;\n" +
" for (let i = 0; i < particleCountLocal; i++) {\n" +
" this.particles.push({\n" +
" x: x + (Math.random() * 20 - 10),\n" +
" y: y + (Math.random() * 20 - 10),\n" +
" size: Math.random() * particleSize + 5,\n" +
" color: color,\n" +
" speedX: Math.random() * 3 - 1.5,\n" +
" speedY: Math.random() * 3 - 1.5,\n" +
" life: 300\n" +
" });\n" +
" }\n" +
" },\n" +
"\n" +
" autoAnimate: function() {\n" +
" const x = Math.random() * this.width;\n" +
" const y = Math.random() * this.height;\n" +
" this.createParticles(x, y);\n" +
" },\n" +
"\n" +
" animate: function() {\n" +
" requestAnimationFrame(this.animate.bind(this));\n" +
" \n" +
" this.ctx.globalCompositeOperation = 'source-over';\n" +
" this.ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';\n" +
" this.ctx.fillRect(0, 0, this.width, this.height);\n" +
" \n" +
" this.ctx.globalCompositeOperation = 'screen';\n" +
" \n" +
" this.particles = this.particles.filter(particle => {\n" +
" this.updateParticle(particle);\n" +
" this.drawParticle(particle);\n" +
" return particle.life > 0 && particle.size > 0.5;\n" +
" });\n" +
" },\n" +
"\n" +
" updateParticle: function(particle) {\n" +
" particle.x += particle.speedX;\n" +
" particle.y += particle.speedY;\n" +
" particle.size -= 0.05;\n" +
" particle.life--;\n" +
" \n" +
" if (particle.x < 0 || particle.x > this.width) {\n" +
" particle.speedX *= -1;\n" +
" }\n" +
" if (particle.y < 0 || particle.y > this.height) {\n" +
" particle.speedY *= -1;\n" +
" }\n" +
" },\n" +
"\n" +
" drawParticle: function(particle) {\n" +
" const alpha = (particle.life / 300) * opacity;\n" +
" \n" +
" this.ctx.beginPath();\n" +
" this.ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);\n" +
" \n" +
" const hexColor = particle.color;\n" +
" const r = parseInt(hexColor.slice(1, 3), 16);\n" +
" const g = parseInt(hexColor.slice(3, 5), 16);\n" +
" const b = parseInt(hexColor.slice(5, 7), 16);\n" +
" this.ctx.fillStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')';\n" +
" this.ctx.fill();\n" +
" },\n" +
"\n" +
" handleInteraction: function(e) {\n" +
" const now = Date.now();\n" +
" if (now - this.lastInteractionTime > 50) {\n" +
" const rect = this.canvas.getBoundingClientRect();\n" +
" const x = e.clientX - rect.left;\n" +
" const y = e.clientY - rect.top;\n" +
" this.createParticles(x, y);\n" +
" this.lastInteractionTime = now;\n" +
" }\n" +
" },\n" +
"\n" +
" resize: function() {\n" +
" this.resizeCanvas();\n" +
" },\n" +
"\n" +
" init: function() {\n" +
" this.resizeCanvas();\n" +
" \n" +
" this.createParticles(this.width / 4, this.height / 4);\n" +
" this.createParticles(this.width / 2, this.height / 2);\n" +
" this.createParticles(3 * this.width / 4, 3 * this.height / 4);\n" +
" \n" +
" this.animate();\n" +
" \n" +
" this.autoAnimationInterval = setInterval(() => {\n" +
" this.autoAnimate();\n" +
" }, interval);\n" +
"\n" +
" if (this.isInteractive) {\n" +
" this.canvas.addEventListener('mousemove', this.handleInteraction.bind(this));\n" +
" this.canvas.style.cursor = 'pointer';\n" +
" }\n" +
" }\n" +
" };\n" +
"\n" +
" instance.init();\n" +
"\n" +
" const resizeObserver = new ResizeObserver(() => {\n" +
" instance.resize();\n" +
" });\n" +
" \n" +
" resizeObserver.observe(element);\n" +
"\n" +
" setTimeout(() => {\n" +
" instance.resizeCanvas();\n" +
" }, 300);\n" +
"\n" +
" this.instances.push(instance);\n" +
" }\n" +
" };\n" +
"\n" +
" window.CosmicHarmonyAnimation = CosmicHarmonyAnimation;\n" +
" if (document.readyState === 'loading') {\n" +
" document.addEventListener('DOMContentLoaded', function() {\n" +
" CosmicHarmonyAnimation.init();\n" +
" });\n" +
" } else {\n" +
" CosmicHarmonyAnimation.init();\n" +
" }\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':
cosmicConfig.particleCount = defaultValue;
break;
case 'particle-size':
cosmicConfig.particleSize = defaultValue;
break;
case 'animation-speed':
cosmicConfig.animationSpeed = defaultValue;
break;
case 'opacity':
cosmicConfig.opacity = defaultValue;
break;
case 'interval':
cosmicConfig.interval = defaultValue;
break;
}
updateCosmicPreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function generateRandomCosmicHarmony() {
const randomColors = Array.from({ length: Math.floor(Math.random() * 3) + 2 }, () => generateRandomColor());
cosmicConfig.colors = randomColors;
cosmicConfig.particleCount = Math.floor(Math.random() * 80) + 20;
cosmicConfig.particleSize = Math.floor(Math.random() * 20) + 10;
updateColorInputs();
document.getElementById('particle-count').value = cosmicConfig.particleCount;
document.getElementById('particle-size').value = cosmicConfig.particleSize;
document.getElementById('particle-count-value').textContent = cosmicConfig.particleCount;
document.getElementById('particle-size-value').textContent = cosmicConfig.particleSize;
updateCosmicPreview();
showNotification('Random cosmic harmony generated!');
}
function updateColorInputs() {
const colorList = document.getElementById('color-list');
colorList.innerHTML = '';
cosmicConfig.colors.forEach((color, index) => {
const colorRow = document.createElement('div');
colorRow.className = 'color-row';
colorRow.innerHTML = `
<div class="color-picker-container" style="--current-color: ${color}">
<input type="color" id="color${index + 1}" value="${color}">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="color${index + 1}-hex" value="${color}" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="color${index + 1}-hsl" value="${hexToHsl(color)}" placeholder="hsl(0, 100%, 50%)">
</div>
<button class="remove-color-btn" title="Remove color">×</button>
`;
colorList.appendChild(colorRow);
});
setupColorInputs();
}
function setupColorInputs() {
const colorInputs = document.querySelectorAll('input[type="color"]');
colorInputs.forEach((input, index) => {
if (index >= cosmicConfig.colors.length) return;
const hexInput = document.getElementById(`color${index + 1}-hex`);
const hslInput = document.getElementById(`color${index + 1}-hsl`);
const container = input.closest('.color-picker-container');
if (container) {
container.style.setProperty('--current-color', input.value);
}
input.addEventListener('input', () => {
const color = input.value;
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);
}
cosmicConfig.colors[index] = color;
updateCosmicPreview();
});
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);
}
cosmicConfig.colors[index] = hex;
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
updateCosmicPreview();
} 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);
}
cosmicConfig.colors[index] = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateCosmicPreview();
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);
}
cosmicConfig.colors[index] = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateCosmicPreview();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
e.target.value = hexToHsl(input.value);
e.target.classList.remove('invalid');
}
});
});
document.querySelectorAll('.remove-color-btn').forEach((button, index) => {
button.addEventListener('click', () => {
if (cosmicConfig.colors.length > 1) {
cosmicConfig.colors.splice(index, 1);
updateColorInputs();
updateCosmicPreview();
showNotification('Color removed');
}
});
});
}
function addColor() {
const newColor = generateRandomColor();
cosmicConfig.colors.push(newColor);
updateColorInputs();
updateCosmicPreview();
showNotification('Color added');
}
function initializeUI() {
initCosmicHarmony();
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-cosmic-harmony');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-cosmic').addEventListener('click', () => {
generateRandomCosmicHarmony();
});
document.getElementById('reset-colors').addEventListener('click', () => {
cosmicConfig.colors = [...defaultConfig.colors];
updateColorInputs();
updateCosmicPreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
cosmicConfig.particleCount = defaultConfig.particleCount;
cosmicConfig.particleSize = defaultConfig.particleSize;
cosmicConfig.animationSpeed = defaultConfig.animationSpeed;
cosmicConfig.opacity = defaultConfig.opacity;
document.getElementById('particle-count').value = defaultConfig.particleCount;
document.getElementById('particle-size').value = defaultConfig.particleSize;
document.getElementById('animation-speed').value = defaultConfig.animationSpeed;
document.getElementById('opacity').value = defaultConfig.opacity;
document.getElementById('particle-count-value').textContent = defaultConfig.particleCount;
document.getElementById('particle-size-value').textContent = defaultConfig.particleSize;
document.getElementById('animation-speed-value').textContent = defaultConfig.animationSpeed;
document.getElementById('opacity-value').textContent = defaultConfig.opacity;
updateCosmicPreview();
showNotification('Animation settings reset');
});
document.getElementById('reset-interaction').addEventListener('click', () => {
cosmicConfig.isInteractive = defaultConfig.isInteractive;
cosmicConfig.interval = defaultConfig.interval;
document.getElementById('interactive-mode').checked = defaultConfig.isInteractive;
document.getElementById('interval').value = defaultConfig.interval;
document.getElementById('interval-value').textContent = defaultConfig.interval;
updateCosmicPreview();
showNotification('Interaction settings reset');
});
document.getElementById('add-color-btn').addEventListener('click', addColor);
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
let 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':
cosmicConfig.particleCount = parseInt(input.value);
break;
case 'particle-size':
cosmicConfig.particleSize = parseInt(input.value);
break;
case 'animation-speed':
cosmicConfig.animationSpeed = parseFloat(input.value);
break;
case 'opacity':
cosmicConfig.opacity = parseFloat(input.value);
break;
case 'interval':
cosmicConfig.interval = parseInt(input.value);
break;
}
updateCosmicPreview();
});
});
document.getElementById('interactive-mode').addEventListener('change', function() {
cosmicConfig.isInteractive = this.checked;
updateCosmicPreview();
});
updateColorInputs();
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':
generateRandomCosmicHarmony();
break;
}
}
});
setTimeout(() => {
showNotification('BricksFusion Cosmic Harmony Configurator loaded!');
}, 500);
}
initializeUI();
});
</script>
</body>
</html>
Cosmic Harmony
Creates glowing interactive particles that respond to mouse movement and appear automatically. Perfect for hero sections, creative backgrounds, or adding cosmic energy to any container.
Cosmic Harmony
Move your mouse to create interactive particles or just watch them appear naturally.
Particles
Number of particles created per burst. More particles create denser effects but use more resources.
Default: 40
Maximum size of each particle. Larger particles are more visible and dramatic.
Default: 15
Transparency of particles. Lower values create ethereal glows, higher values make them more solid.
Default: 0.5
Colors for the particles. Add up to 10 different colors. Particles will randomly pick from your palette. Bright colors work best with the glow effect.
Default: Blue and pink
Animation
Speed of particle movement. Lower is slower and calm, higher is faster and energetic.
Default: 0.002
Time between automatic particle bursts in milliseconds. Shorter intervals create constant activity, longer intervals are more subtle.
Default: 1500
Interaction
Enable mouse interaction. When on, particles appear as you move your mouse. When off, only automatic bursts occur.
Default: On
Performance
This element uses HTML5 Canvas with screen blend mode for glowing effects. Particles fade out naturally over time to maintain performance. It uses requestAnimationFrame for smooth 60fps animations and ResizeObserver to adapt to container changes.