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>Snowfall Canvas 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: linear-gradient(180deg, #0e2039 0%, #1f3c63 100%);
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: #ffffff;
}
.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, #ffffff);
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">Snowfall Canvas</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-snowfall
</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">Snowfall Canvas</h1>
<p class="page-subtitle">Interactive winter effects for Bricks Builder</p>
</div>
<div class="instructions-toggle">
<div class="instructions-card" id="instructions-card">
<div class="instructions-header" id="instructions-toggle">
<div class="instructions-title">
How to Use & Code Information
</div>
<span class="toggle-icon">▼</span>
</div>
<div class="instructions-content" id="instructions-content">
<div class="instructions-grid">
<div class="how-to-use">
<ol>
<li>Customize your snowfall 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-snowfall</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="snowfall-preview" data-snowfall="true">
<div class="preview-content">Interactive Snowfall Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="randomize-snowfall" 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="#0e2039" title="Change Preview Background (B)">
</div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Snowflake Color
<div class="card-actions">
<button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
</div>
</div>
<div class="card-content">
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="snowflake-color" value="#ffffff">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="snowflake-color-hex" value="#ffffff" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="snowflake-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Opacity
<span class="help-tooltip" title="Transparency of snowflakes">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="opacity-value">0.8</span></span>
<button class="reset-btn" onclick="resetParameter('opacity', 0.8)">↺</button>
</div>
</div>
<input type="range" id="opacity" min="0.3" max="1" step="0.05" value="0.8">
</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">
Snowflake Density
<span class="help-tooltip" title="Number of snowflakes in the animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="density-value">100</span></span>
<button class="reset-btn" onclick="resetParameter('density', 100)">↺</button>
</div>
</div>
<input type="range" id="density" min="50" max="300" step="10" value="100">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Fall Speed
<span class="help-tooltip" title="Speed multiplier for falling animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="fall-speed-value">1</span>x</span>
<button class="reset-btn" onclick="resetParameter('fall-speed', 1)">↺</button>
</div>
</div>
<input type="range" id="fall-speed" min="0.2" max="3" step="0.1" value="1">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Wind Intensity
<span class="help-tooltip" title="Horizontal movement intensity">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="wind-intensity-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('wind-intensity', 0.5)">↺</button>
</div>
</div>
<input type="range" id="wind-intensity" min="0" max="2" step="0.1" value="0.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Maximum Size
<span class="help-tooltip" title="Maximum size of snowflakes">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="max-size-value">4</span>px</span>
<button class="reset-btn" onclick="resetParameter('max-size', 4)">↺</button>
</div>
</div>
<input type="range" id="max-size" min="2" max="8" step="0.5" value="4">
</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">Snowflake Style</span>
</div>
<select id="snowflake-style">
<option value="circle">Circle Flakes</option>
<option value="custom">Crystal Flakes</option>
<option value="mixed">Mixed Style</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Movement Pattern</span>
</div>
<select id="movement-pattern">
<option value="natural">Natural Drift</option>
<option value="swirling">Swirling Motion</option>
<option value="straight">Straight Down</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Z-Index
<span class="help-tooltip" title="Layer position (higher = front)">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="z-index-value">2</span></span>
<button class="reset-btn" onclick="resetParameter('z-index', 2)">↺</button>
</div>
</div>
<input type="range" id="z-index" min="1" max="10" step="1" value="2">
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let snowfallConfig = {
density: 100,
fallSpeed: 1,
windIntensity: 0.5,
snowflakeColor: '#ffffff',
maxSize: 4,
opacity: 0.8,
snowflakeStyle: 'circle',
movementPattern: 'natural',
zIndex: 2
};
const defaultConfig = { ...snowfallConfig };
let activeSnowfall = null;
function initSnowfall() {
const sections = document.querySelectorAll('[data-snowfall]:not([data-snowfall-initialized="true"])');
sections.forEach((section) => {
const density = section.hasAttribute('data-snowfall-density')
? parseInt(section.getAttribute('data-snowfall-density'))
: snowfallConfig.density;
const fallSpeed = section.hasAttribute('data-snowfall-fall-speed')
? parseFloat(section.getAttribute('data-snowfall-fall-speed'))
: snowfallConfig.fallSpeed;
const windIntensity = section.hasAttribute('data-snowfall-wind-intensity')
? parseFloat(section.getAttribute('data-snowfall-wind-intensity'))
: snowfallConfig.windIntensity;
const snowflakeColor = section.hasAttribute('data-snowfall-color')
? section.getAttribute('data-snowfall-color')
: snowfallConfig.snowflakeColor;
const maxSize = section.hasAttribute('data-snowfall-max-size')
? parseFloat(section.getAttribute('data-snowfall-max-size'))
: snowfallConfig.maxSize;
const opacity = section.hasAttribute('data-snowfall-opacity')
? parseFloat(section.getAttribute('data-snowfall-opacity'))
: snowfallConfig.opacity;
const snowflakeStyle = section.hasAttribute('data-snowfall-style')
? section.getAttribute('data-snowfall-style')
: snowfallConfig.snowflakeStyle;
const movementPattern = section.hasAttribute('data-snowfall-movement')
? section.getAttribute('data-snowfall-movement')
: snowfallConfig.movementPattern;
const zIndex = section.hasAttribute('data-snowfall-z-index')
? parseInt(section.getAttribute('data-snowfall-z-index'))
: snowfallConfig.zIndex;
const options = {
density,
fallSpeed,
windIntensity,
snowflakeColor,
maxSize,
opacity,
snowflakeStyle,
movementPattern,
zIndex
};
setupSnowfall(section, options);
section.dataset.snowfallInitialized = 'true';
if (section.id === 'snowfall-preview') {
activeSnowfall = { element: section, options };
snowfallConfig = {
density: options.density,
fallSpeed: options.fallSpeed,
windIntensity: options.windIntensity,
snowflakeColor: options.snowflakeColor,
maxSize: options.maxSize,
opacity: options.opacity,
snowflakeStyle: options.snowflakeStyle,
movementPattern: options.movementPattern,
zIndex: options.zIndex
};
}
});
}
function setupSnowfall(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',
zIndex: options.zIndex.toString()
});
canvas.width = element.clientWidth;
canvas.height = element.clientHeight;
element.snowfallCanvas = canvas;
element.snowfallCtx = ctx;
element.snowflakes = [];
element.animationFrame = null;
for (let i = 0; i < options.density; i++) {
element.snowflakes.push(createSnowflake(canvas.width, canvas.height, options));
}
startSnowfallAnimation(element, options);
}
function createSnowflake(width, height, options) {
const maxRadius = options.maxSize;
const minRadius = maxRadius / 2;
return {
x: Math.random() * width,
y: Math.random() * height - height,
radius: Math.random() * (maxRadius - minRadius) + minRadius,
density: Math.random() * 0.8 + 0.2,
speed: (Math.random() * 1 + 0.5) * options.fallSpeed,
stepCount: Math.random() * 360,
opacity: Math.random() * (options.opacity - 0.3) + 0.3,
style: options.snowflakeStyle === 'mixed' ?
(Math.random() > 0.5 ? 'circle' : 'custom') : options.snowflakeStyle,
rotation: Math.random() * Math.PI * 2,
rotationSpeed: (Math.random() - 0.5) * 0.02,
wobbleFrequency: Math.random() * 0.05 + 0.01
};
}
function startSnowfallAnimation(element, options) {
function animate() {
element.animationFrame = requestAnimationFrame(animate);
element.snowfallCtx.clearRect(0, 0, element.snowfallCanvas.width, element.snowfallCanvas.height);
element.snowflakes.forEach(flake => {
updateSnowflake(flake, element.snowfallCanvas.width, element.snowfallCanvas.height, options);
drawSnowflake(flake, element.snowfallCtx, options);
});
}
element.animationFrame = requestAnimationFrame(animate);
element._cleanupSnowfall = () => {
if (element.animationFrame) {
cancelAnimationFrame(element.animationFrame);
}
if (element.snowfallCanvas && element.snowfallCanvas.parentNode) {
element.snowfallCanvas.parentNode.removeChild(element.snowfallCanvas);
}
element.dataset.snowfallInitialized = 'false';
};
}
function updateSnowflake(flake, width, height, options) {
const speedFactor = flake.density * flake.speed;
flake.y += speedFactor;
if (options.movementPattern === 'natural' || options.movementPattern === 'swirling') {
flake.stepCount += flake.wobbleFrequency;
if (options.movementPattern === 'natural') {
flake.x += Math.sin(flake.stepCount) * 0.3 + (options.windIntensity * (1 - flake.density));
} else if (options.movementPattern === 'swirling') {
flake.x += Math.sin(flake.stepCount) * 1.2 * options.windIntensity;
flake.y += Math.cos(flake.stepCount) * 0.1;
}
} else if (options.movementPattern === 'straight') {
flake.x += options.windIntensity * 0.1 * (1 - flake.density);
}
if (flake.style === 'custom') {
flake.rotation += flake.rotationSpeed;
}
if (flake.y > height) {
flake.y = -flake.radius * 2;
flake.x = Math.random() * width;
}
if (flake.x < -flake.radius * 2) flake.x = width + flake.radius * 2;
if (flake.x > width + flake.radius * 2) flake.x = -flake.radius * 2;
}
function drawSnowflake(flake, ctx, options) {
const hexToRgb = (hex) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : { r: 255, g: 255, b: 255 };
};
const rgb = hexToRgb(options.snowflakeColor);
const color = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${flake.opacity})`;
ctx.fillStyle = color;
if (flake.style === 'circle') {
ctx.beginPath();
ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
ctx.fill();
} else if (flake.style === 'custom') {
ctx.save();
ctx.translate(flake.x, flake.y);
ctx.rotate(flake.rotation);
const arms = 6;
const innerRadius = flake.radius * 0.3;
for (let i = 0; i < arms; i++) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -flake.radius);
ctx.strokeStyle = color;
ctx.lineWidth = flake.radius / 4;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, -flake.radius * 0.5);
ctx.lineTo(flake.radius * 0.3, -flake.radius * 0.7);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, -flake.radius * 0.5);
ctx.lineTo(-flake.radius * 0.3, -flake.radius * 0.7);
ctx.stroke();
ctx.rotate(Math.PI * 2 / arms);
}
ctx.beginPath();
ctx.arc(0, 0, innerRadius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
function updateSnowfallPreview() {
const previews = ['snowfall-preview'];
previews.forEach(previewId => {
const preview = document.getElementById(previewId);
if (!preview) return;
if (preview._cleanupSnowfall) {
preview._cleanupSnowfall();
}
preview.setAttribute('data-snowfall', 'true');
preview.setAttribute('data-snowfall-density', snowfallConfig.density);
preview.setAttribute('data-snowfall-fall-speed', snowfallConfig.fallSpeed);
preview.setAttribute('data-snowfall-wind-intensity', snowfallConfig.windIntensity);
preview.setAttribute('data-snowfall-color', snowfallConfig.snowflakeColor);
preview.setAttribute('data-snowfall-max-size', snowfallConfig.maxSize);
preview.setAttribute('data-snowfall-opacity', snowfallConfig.opacity);
preview.setAttribute('data-snowfall-style', snowfallConfig.snowflakeStyle);
preview.setAttribute('data-snowfall-movement', snowfallConfig.movementPattern);
preview.setAttribute('data-snowfall-z-index', snowfallConfig.zIndex);
});
initSnowfall();
}
function generateRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const codeId = generateUniqueId();
// Generar el JavaScript escapado
const jsCode = generateJavaScriptCode();
// Crear la estructura JSON completa
const bricksData = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
},
"_attributes": [
{
"id": generateUniqueId(),
"name": "data-snowfall"
}
]
},
"label": "Snowfall Section"
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": []
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Snowfall JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksData, null, 2);
}
function generateJavaScriptCode() {
return "(function() {\n" +
" const ATTR_NAME = 'data-snowfall';\n" +
" \n" +
" function initSnowfall() {\n" +
" const sections = document.querySelectorAll(`[${ATTR_NAME}]`);\n" +
" sections.forEach(setupSnowfall);\n" +
" }\n" +
"\n" +
" function setupSnowfall(section) {\n" +
" if (section.querySelector('.snowfall-canvas-element')) {\n" +
" return;\n" +
" }\n" +
" \n" +
" const canvas = document.createElement('canvas');\n" +
" canvas.className = 'snowfall-canvas-element';\n" +
" const ctx = canvas.getContext('2d');\n" +
" \n" +
" const config = {\n" +
" density: " + snowfallConfig.density + ",\n" +
" fallSpeed: " + snowfallConfig.fallSpeed + ",\n" +
" windIntensity: " + snowfallConfig.windIntensity + ",\n" +
" snowflakeColor: '" + snowfallConfig.snowflakeColor + "',\n" +
" maxSize: " + snowfallConfig.maxSize + ",\n" +
" opacity: " + snowfallConfig.opacity + ",\n" +
" snowflakeStyle: '" + snowfallConfig.snowflakeStyle + "',\n" +
" movementPattern: '" + snowfallConfig.movementPattern + "',\n" +
" zIndex: " + snowfallConfig.zIndex + "\n" +
" };\n" +
"\n" +
" const wrapper = document.createElement('div');\n" +
" wrapper.className = 'snowfall-wrapper';\n" +
" wrapper.style.position = 'absolute';\n" +
" wrapper.style.top = '0';\n" +
" wrapper.style.left = '0';\n" +
" wrapper.style.width = '100%';\n" +
" wrapper.style.height = '100%';\n" +
" wrapper.style.overflow = 'hidden';\n" +
" wrapper.style.zIndex = config.zIndex.toString();\n" +
" wrapper.style.pointerEvents = 'none';\n" +
" \n" +
" canvas.style.position = 'absolute';\n" +
" canvas.style.top = '0';\n" +
" canvas.style.left = '0';\n" +
" canvas.style.width = '100%';\n" +
" canvas.style.height = '100%';\n" +
" canvas.style.pointerEvents = 'none';\n" +
" canvas.style.display = 'block';\n" +
" \n" +
" wrapper.appendChild(canvas);\n" +
" \n" +
" const sectionPosition = window.getComputedStyle(section).position;\n" +
" if (sectionPosition === 'static') {\n" +
" section.style.position = 'relative';\n" +
" }\n" +
" \n" +
" section.insertBefore(wrapper, section.firstChild);\n" +
"\n" +
" const hexToRgb = (hex) => {\n" +
" const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n" +
" return result ? {\n" +
" r: parseInt(result[1], 16),\n" +
" g: parseInt(result[2], 16),\n" +
" b: parseInt(result[3], 16)\n" +
" } : { r: 255, g: 255, b: 255 };\n" +
" };\n" +
"\n" +
" let snowflakes = [];\n" +
" let animationFrame;\n" +
"\n" +
" function createSnowflake(width, height) {\n" +
" const maxRadius = config.maxSize;\n" +
" const minRadius = maxRadius / 2;\n" +
" \n" +
" return {\n" +
" x: Math.random() * width,\n" +
" y: Math.random() * height - height,\n" +
" radius: Math.random() * (maxRadius - minRadius) + minRadius,\n" +
" density: Math.random() * 0.8 + 0.2,\n" +
" speed: (Math.random() * 1 + 0.5) * config.fallSpeed,\n" +
" stepCount: Math.random() * 360,\n" +
" opacity: Math.random() * (config.opacity - 0.3) + 0.3,\n" +
" style: config.snowflakeStyle === 'mixed' ? \n" +
" (Math.random() > 0.5 ? 'circle' : 'custom') : config.snowflakeStyle,\n" +
" rotation: Math.random() * Math.PI * 2,\n" +
" rotationSpeed: (Math.random() - 0.5) * 0.02,\n" +
" wobbleFrequency: Math.random() * 0.05 + 0.01\n" +
" };\n" +
" }\n" +
"\n" +
" function updateSnowflake(flake, width, height) {\n" +
" const speedFactor = flake.density * flake.speed;\n" +
" \n" +
" flake.y += speedFactor;\n" +
" \n" +
" if (config.movementPattern === 'natural' || config.movementPattern === 'swirling') {\n" +
" flake.stepCount += flake.wobbleFrequency;\n" +
" \n" +
" if (config.movementPattern === 'natural') {\n" +
" flake.x += Math.sin(flake.stepCount) * 0.3 + (config.windIntensity * (1 - flake.density));\n" +
" } else if (config.movementPattern === 'swirling') {\n" +
" flake.x += Math.sin(flake.stepCount) * 1.2 * config.windIntensity;\n" +
" flake.y += Math.cos(flake.stepCount) * 0.1;\n" +
" }\n" +
" } else if (config.movementPattern === 'straight') {\n" +
" flake.x += config.windIntensity * 0.1 * (1 - flake.density);\n" +
" }\n" +
" \n" +
" if (flake.style === 'custom') {\n" +
" flake.rotation += flake.rotationSpeed;\n" +
" }\n" +
" \n" +
" if (flake.y > height) {\n" +
" flake.y = -flake.radius * 2;\n" +
" flake.x = Math.random() * width;\n" +
" }\n" +
" \n" +
" if (flake.x < -flake.radius * 2) flake.x = width + flake.radius * 2;\n" +
" if (flake.x > width + flake.radius * 2) flake.x = -flake.radius * 2;\n" +
" }\n" +
"\n" +
" function drawSnowflake(flake, ctx) {\n" +
" const rgb = hexToRgb(config.snowflakeColor);\n" +
" const color = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${flake.opacity})`;\n" +
" \n" +
" ctx.fillStyle = color;\n" +
" \n" +
" if (flake.style === 'circle') {\n" +
" ctx.beginPath();\n" +
" ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);\n" +
" ctx.fill();\n" +
" } else if (flake.style === 'custom') {\n" +
" ctx.save();\n" +
" ctx.translate(flake.x, flake.y);\n" +
" ctx.rotate(flake.rotation);\n" +
" \n" +
" const arms = 6;\n" +
" const innerRadius = flake.radius * 0.3;\n" +
" \n" +
" for (let i = 0; i < arms; i++) {\n" +
" ctx.beginPath();\n" +
" ctx.moveTo(0, 0);\n" +
" ctx.lineTo(0, -flake.radius);\n" +
" ctx.strokeStyle = color;\n" +
" ctx.lineWidth = flake.radius / 4;\n" +
" ctx.stroke();\n" +
" \n" +
" ctx.beginPath();\n" +
" ctx.moveTo(0, -flake.radius * 0.5);\n" +
" ctx.lineTo(flake.radius * 0.3, -flake.radius * 0.7);\n" +
" ctx.stroke();\n" +
" \n" +
" ctx.beginPath();\n" +
" ctx.moveTo(0, -flake.radius * 0.5);\n" +
" ctx.lineTo(-flake.radius * 0.3, -flake.radius * 0.7);\n" +
" ctx.stroke();\n" +
" \n" +
" ctx.rotate(Math.PI * 2 / arms);\n" +
" }\n" +
" \n" +
" ctx.beginPath();\n" +
" ctx.arc(0, 0, innerRadius, 0, Math.PI * 2);\n" +
" ctx.fill();\n" +
" \n" +
" ctx.restore();\n" +
" }\n" +
" }\n" +
"\n" +
" function resizeCanvas() {\n" +
" const rect = section.getBoundingClientRect();\n" +
" canvas.width = rect.width;\n" +
" canvas.height = rect.height;\n" +
" \n" +
" if (canvas.width > 0 && canvas.height > 0) {\n" +
" initSnowflakes();\n" +
" } else {\n" +
" setTimeout(resizeCanvas, 500);\n" +
" }\n" +
" }\n" +
"\n" +
" function initSnowflakes() {\n" +
" snowflakes = [];\n" +
" \n" +
" for (let i = 0; i < config.density; i++) {\n" +
" snowflakes.push(createSnowflake(canvas.width, canvas.height));\n" +
" }\n" +
" }\n" +
"\n" +
" function animate() {\n" +
" animationFrame = requestAnimationFrame(animate);\n" +
" \n" +
" ctx.clearRect(0, 0, canvas.width, canvas.height);\n" +
" \n" +
" snowflakes.forEach(flake => {\n" +
" updateSnowflake(flake, canvas.width, canvas.height);\n" +
" drawSnowflake(flake, ctx);\n" +
" });\n" +
" }\n" +
"\n" +
" if (window.ResizeObserver) {\n" +
" const resizeObserver = new ResizeObserver(() => {\n" +
" resizeCanvas();\n" +
" });\n" +
" \n" +
" resizeObserver.observe(section);\n" +
" } else {\n" +
" window.addEventListener('resize', resizeCanvas);\n" +
" }\n" +
" \n" +
" resizeCanvas();\n" +
" animate();\n" +
"\n" +
" return function cleanup() {\n" +
" if (animationFrame) {\n" +
" cancelAnimationFrame(animationFrame);\n" +
" }\n" +
" if (window.ResizeObserver) {\n" +
" resizeObserver.unobserve(section);\n" +
" } else {\n" +
" window.removeEventListener('resize', resizeCanvas);\n" +
" }\n" +
" section.removeChild(wrapper);\n" +
" };\n" +
" }\n" +
"\n" +
" function initialize() {\n" +
" initSnowfall();\n" +
" \n" +
" setInterval(function() {\n" +
" const sections = document.querySelectorAll(`[${ATTR_NAME}]`);\n" +
" sections.forEach(section => {\n" +
" if (!section.querySelector('.snowfall-canvas-element')) {\n" +
" setupSnowfall(section);\n" +
" }\n" +
" });\n" +
" }, 2000);\n" +
" }\n" +
"\n" +
" if (document.readyState === 'loading') {\n" +
" document.addEventListener('DOMContentLoaded', initialize);\n" +
" } else {\n" +
" initialize();\n" +
" }\n" +
" \n" +
" window.addEventListener('load', function() {\n" +
" initSnowfall();\n" +
" \n" +
" setTimeout(function() {\n" +
" initSnowfall();\n" +
" }, 1000);\n" +
" });\n" +
" \n" +
" window.initBricksFusionSnowfall = initSnowfall;\n" +
"})();";
}
function copyJsToClipboard() {
const jsCode = generateJavaScriptCode();
navigator.clipboard.writeText(jsCode)
.then(() => {
showNotification('JavaScript code copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = jsCode;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('JavaScript code copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyFullSectionToClipboard() {
const sectionJSON = generateFullSectionJSON();
navigator.clipboard.writeText(sectionJSON)
.then(() => {
showNotification('Full section JSON copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = sectionJSON;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('Full section JSON copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy to clipboard', 'error');
});
}
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 'density':
snowfallConfig.density = defaultValue;
break;
case 'fall-speed':
snowfallConfig.fallSpeed = defaultValue;
break;
case 'wind-intensity':
snowfallConfig.windIntensity = defaultValue;
break;
case 'max-size':
snowfallConfig.maxSize = defaultValue;
break;
case 'opacity':
snowfallConfig.opacity = defaultValue;
break;
case 'z-index':
snowfallConfig.zIndex = defaultValue;
break;
}
updateSnowfallPreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function generateRandomSnowfall() {
snowfallConfig.density = Math.floor(Math.random() * 250) + 50;
snowfallConfig.fallSpeed = Math.random() * 2.8 + 0.2;
snowfallConfig.windIntensity = Math.random() * 2;
snowfallConfig.snowflakeColor = generateRandomColor();
snowfallConfig.maxSize = Math.random() * 6 + 2;
snowfallConfig.opacity = Math.random() * 0.7 + 0.3;
snowfallConfig.snowflakeStyle = ['circle', 'custom', 'mixed'][Math.floor(Math.random() * 3)];
snowfallConfig.movementPattern = ['natural', 'swirling', 'straight'][Math.floor(Math.random() * 3)];
snowfallConfig.zIndex = Math.floor(Math.random() * 10) + 1;
document.getElementById('density').value = snowfallConfig.density;
document.getElementById('fall-speed').value = snowfallConfig.fallSpeed;
document.getElementById('wind-intensity').value = snowfallConfig.windIntensity;
document.getElementById('snowflake-color').value = snowfallConfig.snowflakeColor;
document.getElementById('max-size').value = snowfallConfig.maxSize;
document.getElementById('opacity').value = snowfallConfig.opacity;
document.getElementById('snowflake-style').value = snowfallConfig.snowflakeStyle;
document.getElementById('movement-pattern').value = snowfallConfig.movementPattern;
document.getElementById('z-index').value = snowfallConfig.zIndex;
document.getElementById('density-value').textContent = snowfallConfig.density;
document.getElementById('fall-speed-value').textContent = snowfallConfig.fallSpeed;
document.getElementById('wind-intensity-value').textContent = snowfallConfig.windIntensity;
document.getElementById('max-size-value').textContent = snowfallConfig.maxSize;
document.getElementById('opacity-value').textContent = snowfallConfig.opacity;
document.getElementById('z-index-value').textContent = snowfallConfig.zIndex;
updateColorInputs();
updateSnowfallPreview();
showNotification('Random snowfall generated!');
}
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 updateColorInputs() {
const colorInput = document.getElementById('snowflake-color');
const hexInput = document.getElementById('snowflake-color-hex');
const hslInput = document.getElementById('snowflake-color-hsl');
if (colorInput && hexInput && hslInput) {
colorInput.value = snowfallConfig.snowflakeColor;
hexInput.value = snowfallConfig.snowflakeColor;
hslInput.value = `hsl(${hexToHsl(snowfallConfig.snowflakeColor).h}, ${hexToHsl(snowfallConfig.snowflakeColor).s}%, ${hexToHsl(snowfallConfig.snowflakeColor).l}%)`;
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
if (colorPickerContainer) {
colorPickerContainer.style.setProperty('--selected-color', snowfallConfig.snowflakeColor);
}
}
}
function initializeUI() {
initSnowfall();
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-snowfall');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-snowfall').addEventListener('click', () => {
generateRandomSnowfall();
});
const backgroundPicker = document.getElementById('preview-background-picker');
const previewContainer = document.getElementById('snowfall-preview');
backgroundPicker.addEventListener('input', (e) => {
const selectedColor = e.target.value;
previewContainer.style.background = selectedColor;
showNotification(`Preview background changed to ${selectedColor}`);
});
previewContainer.style.background = 'linear-gradient(180deg, #0e2039 0%, #1f3c63 100%)';
document.getElementById('reset-colors').addEventListener('click', () => {
snowfallConfig.snowflakeColor = defaultConfig.snowflakeColor;
snowfallConfig.opacity = defaultConfig.opacity;
document.getElementById('snowflake-color').value = defaultConfig.snowflakeColor;
document.getElementById('opacity').value = defaultConfig.opacity;
document.getElementById('opacity-value').textContent = defaultConfig.opacity;
updateColorInputs();
updateSnowfallPreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
snowfallConfig.density = defaultConfig.density;
snowfallConfig.fallSpeed = defaultConfig.fallSpeed;
snowfallConfig.windIntensity = defaultConfig.windIntensity;
snowfallConfig.maxSize = defaultConfig.maxSize;
document.getElementById('density').value = defaultConfig.density;
document.getElementById('fall-speed').value = defaultConfig.fallSpeed;
document.getElementById('wind-intensity').value = defaultConfig.windIntensity;
document.getElementById('max-size').value = defaultConfig.maxSize;
document.getElementById('density-value').textContent = defaultConfig.density;
document.getElementById('fall-speed-value').textContent = defaultConfig.fallSpeed;
document.getElementById('wind-intensity-value').textContent = defaultConfig.windIntensity;
document.getElementById('max-size-value').textContent = defaultConfig.maxSize;
updateSnowfallPreview();
showNotification('Animation settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
snowfallConfig.snowflakeStyle = defaultConfig.snowflakeStyle;
snowfallConfig.movementPattern = defaultConfig.movementPattern;
snowfallConfig.zIndex = defaultConfig.zIndex;
document.getElementById('snowflake-style').value = defaultConfig.snowflakeStyle;
document.getElementById('movement-pattern').value = defaultConfig.movementPattern;
document.getElementById('z-index').value = defaultConfig.zIndex;
document.getElementById('z-index-value').textContent = defaultConfig.zIndex;
updateSnowfallPreview();
showNotification('Advanced settings reset');
});
const colorInput = document.getElementById('snowflake-color');
const hexInput = document.getElementById('snowflake-color-hex');
const hslInput = document.getElementById('snowflake-color-hsl');
hslInput.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
colorInput.addEventListener('input', () => {
const color = colorInput.value;
hexInput.value = color;
hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
snowfallConfig.snowflakeColor = color;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', color);
updateSnowfallPreview();
});
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}%)`;
snowfallConfig.snowflakeColor = 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);
updateSnowfallPreview();
} 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;
snowfallConfig.snowflakeColor = 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);
updateSnowfallPreview();
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;
snowfallConfig.snowflakeColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateSnowfallPreview();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
e.target.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
e.target.classList.remove('invalid');
}
});
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
const valueElement = document.getElementById(`${input.id}-value`);
if (valueElement) {
valueElement.textContent = input.value;
}
input.addEventListener('input', () => {
if (valueElement) {
valueElement.textContent = input.value;
}
switch (input.id) {
case 'density':
snowfallConfig.density = parseInt(input.value);
break;
case 'fall-speed':
snowfallConfig.fallSpeed = parseFloat(input.value);
break;
case 'wind-intensity':
snowfallConfig.windIntensity = parseFloat(input.value);
break;
case 'max-size':
snowfallConfig.maxSize = parseFloat(input.value);
break;
case 'opacity':
snowfallConfig.opacity = parseFloat(input.value);
break;
case 'z-index':
snowfallConfig.zIndex = parseInt(input.value);
break;
}
updateSnowfallPreview();
});
});
document.getElementById('snowflake-style').addEventListener('change', function() {
snowfallConfig.snowflakeStyle = this.value;
updateSnowfallPreview();
});
document.getElementById('movement-pattern').addEventListener('change', function() {
snowfallConfig.movementPattern = this.value;
updateSnowfallPreview();
});
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':
generateRandomSnowfall();
break;
case 'b':
document.getElementById('preview-background-picker').click();
break;
}
}
});
updateColorInputs();
setTimeout(() => {
showNotification('BricksFusion Snowfall Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-snowfall-config', JSON.stringify(snowfallConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-snowfall-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(snowfallConfig, savedConfig);
document.getElementById('density').value = savedConfig.density;
document.getElementById('fall-speed').value = savedConfig.fallSpeed;
document.getElementById('wind-intensity').value = savedConfig.windIntensity;
document.getElementById('snowflake-color').value = savedConfig.snowflakeColor;
document.getElementById('max-size').value = savedConfig.maxSize;
document.getElementById('opacity').value = savedConfig.opacity;
document.getElementById('snowflake-style').value = savedConfig.snowflakeStyle;
document.getElementById('movement-pattern').value = savedConfig.movementPattern;
document.getElementById('z-index').value = savedConfig.zIndex;
document.getElementById('density-value').textContent = savedConfig.density;
document.getElementById('fall-speed-value').textContent = savedConfig.fallSpeed;
document.getElementById('wind-intensity-value').textContent = savedConfig.windIntensity;
document.getElementById('max-size-value').textContent = savedConfig.maxSize;
document.getElementById('opacity-value').textContent = savedConfig.opacity;
document.getElementById('z-index-value').textContent = savedConfig.zIndex;
updateColorInputs();
updateSnowfallPreview();
}
} catch (e) {
}
}
const originalUpdateSnowfallPreview = updateSnowfallPreview;
updateSnowfallPreview = function() {
originalUpdateSnowfallPreview();
saveConfiguration();
};
loadConfiguration();
}
initializeUI();
});
</script>
</body>
</html>
Snowfall
Adds realistic falling snow to any section. Perfect for holiday campaigns, winter themes, or adding a magical touch to your pages.
Let it snow!
Appearance
Choose the color of your snowflakes. White is classic, but try light blue for a cooler look or any color to match your brand.
Default: White (#ffffff)
Maximum size of the snowflakes in pixels. Bigger snowflakes are more visible but look heavier. Try 3-5 for realistic snow.
Default: 4
How see-through the snowflakes are. 1.0 is solid, 0.5 is half transparent. Lower values create a softer, dreamier effect.
Default: 0.8
Shape of the snowflakes. Circle is simple dots, Custom is detailed snowflake shapes, Mixed combines both for variety.
Default: Circle
Animation
How many snowflakes appear on screen. More flakes = heavier snowfall. Start with 50-100 for light snow, 150+ for a blizzard.
Default: 100
How fast snowflakes fall. Lower is gentle and floaty, higher is faster and more dramatic. Sweet spot is usually 0.8-1.5.
Default: 1.0
How much the wind pushes snowflakes sideways. 0 is straight down, higher values create diagonal falling and drifting.
Default: 0.5
How snowflakes move. Natural adds gentle wobbling like real snow, Swirling creates spiraling paths, Straight is a simple downward fall.
Default: Natural
Advanced
Controls if snow appears in front of or behind your content. Higher numbers put snow on top of everything. Keep it low if you want snow behind text.
Default: 2
Performance
This element uses canvas animation, so higher density settings will use more resources. The effect automatically pauses when scrolled out of view to save battery. Keep density under 150 for best performance on mobile devices.