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>SVG Reveal 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: #252525;
border: 1px solid var(--border);
box-shadow: var(--shadow);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;
}
.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;
}
#svg-reveal-preview {
width: 90%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
position: relative;
}
.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: #ef6013;
}
.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, #ef6013);
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);
}
.radio-group {
display: flex;
flex-direction: column;
gap: 0.8rem;
margin-bottom: 1rem;
}
.radio-option {
display: flex;
align-items: center;
cursor: pointer;
}
.radio-option input[type="radio"] {
margin-right: 0.8rem;
cursor: pointer;
accent-color: var(--accent);
}
.radio-option label {
font-size: var(--text-xs);
color: var(--text-secondary);
cursor: pointer;
}
.radio-option input[type="radio"]:checked + label {
color: var(--text-primary);
font-weight: 500;
}
.style-description {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-top: 0.5rem;
padding-left: 1.8rem;
margin-bottom: 0.8rem;
line-height: 1.5;
}
.svg-preview-content {
width: 200px;
height: 200px;
}
.svg-preview-content svg {
width: 100%;
height: 100%;
}
.svg-preview-content path,
.svg-preview-content circle,
.svg-preview-content rect,
.svg-preview-content line,
.svg-preview-content polygon,
.svg-preview-content polyline {
fill: none;
stroke: #ef6013;
stroke-width: 2;
}
.svg-preview-content circle {
fill: #ef6013;
}
.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/visual-effects/" class="breadcrumb-item">Visual effects</a>
<span class="breadcrumb-separator">›</span>
<span class="breadcrumb-item active">SVG Reveal</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-svg-reveal
</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">SVG Reveal</h1>
<p class="page-subtitle">Interactive SVG 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 SVG reveal effect using the controls below</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste the JavaScript code</li>
<li>To add the effect to any SVG element: go to <strong>Section → Style → Attributes</strong>, add <code>data-svg-reveal</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">
<div id="svg-reveal-preview" data-svg-reveal="true">
<div class="svg-preview-content">
<svg viewBox="0 0 200 60" xmlns="http://www.w3.org/2000/svg">
<g fill="#ef6013">
<path d="M20,30 C20,23.4 25.4,18 32,18 C38.6,18 44,23.4 44,30 C44,36.6 38.6,42 32,42 C25.4,42 20,36.6 20,30 Z M24,30 C24,34.4 27.6,38 32,38 C36.4,38 40,34.4 40,30 C40,25.6 36.4,22 32,22 C27.6,22 24,25.6 24,30 Z"/>
<path d="M50,20 L54,20 L54,40 L50,40 Z"/>
<path d="M60,20 L64,20 L64,37 L74,37 L74,40 L60,40 Z"/>
<path d="M80,20 L96,20 L96,23 L84,23 L84,28 L94,28 L94,31 L84,31 L84,37 L96,37 L96,40 L80,40 Z"/>
<path d="M102,20 L106,20 L106,37 L116,37 L116,40 L102,40 Z"/>
<path d="M122,20 L126,20 L126,40 L122,40 Z"/>
<path d="M132,20 L136,20 L148,33 L148,20 L152,20 L152,40 L148,40 L136,27 L136,40 L132,40 Z"/>
<path d="M158,20 L174,20 L174,23 L162,23 L162,28 L172,28 L172,31 L162,31 L162,37 L174,37 L174,40 L158,40 Z"/>
</g>
</svg>
</div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Colors
<div class="card-actions">
<button class="card-action-btn" id="reset-colors" title="Reset Colors">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">Stroke Color</span>
</div>
<div class="color-list">
<div class="color-row">
<div class="color-picker-container" style="--selected-color: #ef6013;">
<input type="color" id="stroke-color" value="#ef6013">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="stroke-color-hex" value="#ef6013" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="stroke-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Fill Color</span>
</div>
<div class="color-list">
<div class="color-row">
<div class="color-picker-container" style="--selected-color: #ffffff;">
<input type="color" id="fill-color" value="#ffffff">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="fill-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="fill-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Animation Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-animation" title="Reset Animation Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Duration
<span class="help-tooltip" title="Total duration of the animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="duration-value">1500</span>ms</span>
<button class="reset-btn" onclick="resetParameter('duration', 1500)">↺</button>
</div>
</div>
<input type="range" id="duration" min="500" max="3000" step="100" value="1500">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Base Delay
<span class="help-tooltip" title="Initial delay before animation starts">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="base-delay-value">0</span>ms</span>
<button class="reset-btn" onclick="resetParameter('base-delay', 0)">↺</button>
</div>
</div>
<input type="range" id="base-delay" min="0" max="1000" step="50" value="0">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Delay Increment
<span class="help-tooltip" title="Delay between each element in sequential mode">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="delay-increment-value">100</span>ms</span>
<button class="reset-btn" onclick="resetParameter('delay-increment', 100)">↺</button>
</div>
</div>
<input type="range" id="delay-increment" min="0" max="300" step="10" value="100">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Stroke Width
<span class="help-tooltip" title="Thickness of the animated stroke">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="stroke-width-value">1</span>px</span>
<button class="reset-btn" onclick="resetParameter('stroke-width', 1)">↺</button>
</div>
</div>
<input type="range" id="stroke-width" min="0.5" max="5" step="0.5" value="1">
</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">Animation Order</span>
</div>
<div class="radio-group">
<div class="radio-option">
<input type="radio" id="order-sequential" name="animation-order" value="sequential" checked>
<label for="order-sequential">Sequential</label>
</div>
<div class="style-description">Each element animates one after another</div>
<div class="radio-option">
<input type="radio" id="order-simultaneous" name="animation-order" value="simultaneous">
<label for="order-simultaneous">Simultaneous</label>
</div>
<div class="style-description">All elements animate at the same time</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Easing Function</span>
</div>
<select id="easing">
<option value="ease">Ease</option>
<option value="ease-in">Ease In</option>
<option value="ease-out">Ease Out</option>
<option value="ease-in-out">Ease In Out</option>
<option value="linear">Linear</option>
<option value="cubic-bezier(0.65, 0, 0.35, 1)" selected>Custom Bounce</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Intersection Threshold
<span class="help-tooltip" title="How much of the SVG must be visible to trigger animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="threshold-value">0.5</span></span>
<button class="reset-btn" onclick="resetParameter('threshold', 0.5)">↺</button>
</div>
</div>
<input type="range" id="threshold" min="0.1" max="1" step="0.1" value="0.5">
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let svgRevealConfig = {
duration: 1500,
baseDelay: 0,
delayIncrement: 100,
animationOrder: 'sequential',
easing: 'cubic-bezier(0.65, 0, 0.35, 1)',
strokeColor: '#ef6013',
fillColor: '#ffffff',
strokeWidth: 1,
threshold: 0.5
};
const defaultConfig = { ...svgRevealConfig };
const previewContainer = document.getElementById('svg-reveal-preview');
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 copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy to clipboard', 'error');
});
}
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 updatePreview() {
resetPreview();
previewContainer.setAttribute('data-svg-reveal', 'true');
previewContainer.setAttribute('data-duration', svgRevealConfig.duration);
previewContainer.setAttribute('data-base-delay', svgRevealConfig.baseDelay);
previewContainer.setAttribute('data-delay-increment', svgRevealConfig.delayIncrement);
previewContainer.setAttribute('data-animation-order', svgRevealConfig.animationOrder);
previewContainer.setAttribute('data-easing', svgRevealConfig.easing);
const svgElements = previewContainer.querySelectorAll('path, line, circle, rect, polygon, polyline');
svgElements.forEach(element => {
element.style.stroke = svgRevealConfig.strokeColor;
element.style.strokeWidth = svgRevealConfig.strokeWidth;
if (element.getAttribute('fill') !== 'none') {
element.style.fill = svgRevealConfig.fillColor;
}
});
initPreviewAnimation();
}
function resetPreview() {
if (window.svgRevealTimeout) {
clearTimeout(window.svgRevealTimeout);
window.svgRevealTimeout = null;
}
Array.from(previewContainer.querySelectorAll('[data-cleanup]')).forEach(element => {
const timeoutId = parseInt(element.getAttribute('data-cleanup'));
if (!isNaN(timeoutId)) {
clearTimeout(timeoutId);
}
element.removeAttribute('data-cleanup');
});
previewContainer.removeAttribute('data-svg-reveal');
Array.from(previewContainer.querySelectorAll('[data-animated-clone]')).forEach(clone => {
clone.remove();
});
Array.from(previewContainer.querySelectorAll('svg *')).forEach(element => {
element.style.opacity = '';
element.style.fill = '';
element.style.stroke = '';
element.style.strokeWidth = '';
});
}
function initPreviewAnimation() {
const svg = previewContainer.querySelector('svg');
if (!svg) return;
const elements = Array.from(svg.querySelectorAll('path, line, circle, rect, polygon, polyline, g')).filter(el => {
return el.tagName.toLowerCase() !== 'g' || Array.from(el.children).length === 0;
});
if (!elements.length) return;
const fragment = document.createDocumentFragment();
const cloneData = new Map();
elements.forEach(element => {
const clone = element.cloneNode(true);
const originalFill = element.getAttribute('fill');
element.style.opacity = '0';
clone.removeAttribute('id');
clone.style.fill = 'none';
clone.style.stroke = svgRevealConfig.strokeColor;
clone.style.strokeWidth = svgRevealConfig.strokeWidth;
let length;
const tagName = element.tagName.toLowerCase();
try {
switch (tagName) {
case 'circle':
const radius = parseFloat(element.getAttribute('r'));
length = 2 * Math.PI * radius;
break;
case 'rect':
const width = parseFloat(element.getAttribute('width'));
const height = parseFloat(element.getAttribute('height'));
length = 2 * (width + height);
break;
case 'line':
const [x1, y1, x2, y2] = ['x1', 'y1', 'x2', 'y2'].map(attr =>
parseFloat(element.getAttribute(attr)) || 0
);
length = Math.hypot(x2 - x1, y2 - y1);
break;
case 'polygon':
case 'polyline':
const points = element.getAttribute('points')?.trim().split(/[\s,]+/).map(Number);
if (points && points.length >= 4) {
length = points.reduce((acc, _, i, arr) => {
if (i % 2 === 0 && i < arr.length - 2) {
return acc + Math.hypot(
arr[i + 2] - arr[i],
arr[i + 3] - arr[i + 1]
);
}
return acc;
}, 0);
} else {
length = 100;
}
break;
default:
length = element.getTotalLength?.() || 100;
}
} catch (error) {
length = 100;
}
clone.style.strokeDasharray = length;
clone.style.strokeDashoffset = length;
clone.setAttribute('data-animated-clone', '');
clone.setAttribute('data-original-fill', originalFill || 'none');
fragment.appendChild(clone);
cloneData.set(clone, { element, length });
});
svg.appendChild(fragment);
const clones = svg.querySelectorAll('[data-animated-clone]');
requestAnimationFrame(() => {
clones.forEach((clone, index) => {
const delay = svgRevealConfig.animationOrder === 'sequential'
? svgRevealConfig.baseDelay + (index * svgRevealConfig.delayIncrement)
: svgRevealConfig.baseDelay;
const { element, length } = cloneData.get(clone);
const originalFill = clone.getAttribute('data-original-fill');
clone.style.transition = `stroke-dashoffset ${svgRevealConfig.duration}ms ${svgRevealConfig.easing} ${delay}ms`;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
clone.style.strokeDashoffset = '0';
});
});
const cleanupTimeout = setTimeout(() => {
element.style.transition = 'opacity 300ms ease';
element.style.opacity = '1';
element.style.stroke = svgRevealConfig.strokeColor;
element.style.strokeWidth = svgRevealConfig.strokeWidth;
if (originalFill !== 'none') {
element.style.fill = svgRevealConfig.fillColor;
}
clone.style.opacity = '0';
setTimeout(() => {
if (clone.parentNode) {
clone.remove();
}
cloneData.delete(clone);
}, 300);
}, delay + svgRevealConfig.duration);
element.setAttribute('data-cleanup', cleanupTimeout);
});
});
const totalDuration = svgRevealConfig.animationOrder === 'sequential'
? svgRevealConfig.baseDelay + (elements.length * svgRevealConfig.delayIncrement) + svgRevealConfig.duration
: svgRevealConfig.baseDelay + svgRevealConfig.duration;
window.svgRevealTimeout = setTimeout(() => {
window.svgRevealTimeout = null;
updatePreview();
}, totalDuration + 1000);
}
function updateColorInputs(colorType) {
const colorInput = document.getElementById(`${colorType}-color`);
const hexInput = document.getElementById(`${colorType}-color-hex`);
const hslInput = document.getElementById(`${colorType}-color-hsl`);
if (colorInput && hexInput && hslInput) {
const color = colorType === 'stroke' ? svgRevealConfig.strokeColor : svgRevealConfig.fillColor;
colorInput.value = color;
hexInput.value = color;
hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).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', color);
}
}
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos para todos los elementos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const divId = generateUniqueId();
const svgId = generateUniqueId();
const codeId = generateUniqueId();
const attributeId = generateUniqueId();
// Obtener el JavaScript actual con la configuración del usuario
const jsCode = generateJavaScriptCode();
// Crear el objeto JSON completo de Bricks Builder
const bricksJSON = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_height": "500",
"_justifyContent": "center"
}
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [divId],
"settings": {
"_alignItems": "center",
"_overflow": "hidden"
}
},
{
"id": divId,
"name": "div",
"parent": containerId,
"children": [svgId],
"settings": {}
},
{
"id": svgId,
"name": "svg",
"parent": divId,
"children": [],
"settings": {
"file": {
"id": 768,
"filename": "logoipsum-297.svg",
"url": "https://test.bricksfusion.com/wp-content/uploads/2024/07/logoipsum-297.svg"
},
"_attributes": [
{
"id": attributeId,
"name": "data-svg-reveal"
}
]
},
"label": "SVG Reveal"
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "SVG Reveal JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksJSON, null, 2);
}
function generateJavaScriptCode() {
return `(function() {
if (window.svgRevealInitialized) return;
window.svgRevealInitialized = true;
const initSVGReveal = () => {
const observerOptions = {
root: null,
rootMargin: '0px',
threshold: ${svgRevealConfig.threshold}
};
const handleSVGAnimation = (svg) => {
try {
Array.from(svg.querySelectorAll('[data-animated-clone]')).forEach(clone => {
clone.remove();
});
Array.from(svg.querySelectorAll('[data-cleanup]')).forEach(element => {
const timeoutId = parseInt(element.getAttribute('data-cleanup'));
if (!isNaN(timeoutId)) {
clearTimeout(timeoutId);
}
element.removeAttribute('data-cleanup');
});
Array.from(svg.querySelectorAll('[data-clone]')).forEach(element => {
element.removeAttribute('data-clone');
element.style.opacity = '';
});
const baseDelay = Math.max(0, parseInt(svg.getAttribute('data-base-delay')) || ${svgRevealConfig.baseDelay});
const delayIncrement = Math.max(0, parseInt(svg.getAttribute('data-delay-increment')) || ${svgRevealConfig.delayIncrement});
const animationOrder = svg.getAttribute('data-animation-order') || '${svgRevealConfig.animationOrder}';
const duration = Math.max(100, parseInt(svg.getAttribute('data-duration')) || ${svgRevealConfig.duration});
const easing = svg.getAttribute('data-easing') || '${svgRevealConfig.easing}';
const elements = Array.from(svg.querySelectorAll('path, line, circle, rect, polygon, polyline, g')).filter(el => {
return el.tagName.toLowerCase() !== 'g' || Array.from(el.children).length === 0;
});
if (!elements.length) return;
const fragment = document.createDocumentFragment();
const cloneData = new Map();
elements.forEach(element => {
const clone = element.cloneNode(true);
const originalFill = element.getAttribute('fill');
element.style.opacity = '0';
clone.removeAttribute('id');
clone.style.fill = 'none';
clone.style.stroke = '${svgRevealConfig.strokeColor}';
clone.style.strokeWidth = '${svgRevealConfig.strokeWidth}';
let length;
const tagName = element.tagName.toLowerCase();
try {
switch (tagName) {
case 'circle':
const radius = parseFloat(element.getAttribute('r'));
length = 2 * Math.PI * radius;
break;
case 'rect':
const width = parseFloat(element.getAttribute('width'));
const height = parseFloat(element.getAttribute('height'));
length = 2 * (width + height);
break;
case 'line':
const [x1, y1, x2, y2] = ['x1', 'y1', 'x2', 'y2'].map(attr =>
parseFloat(element.getAttribute(attr)) || 0
);
length = Math.hypot(x2 - x1, y2 - y1);
break;
case 'polygon':
case 'polyline':
const points = element.getAttribute('points')?.trim().split(/[\\s,]+/).map(Number);
if (points && points.length >= 4) {
length = points.reduce((acc, _, i, arr) => {
if (i % 2 === 0 && i < arr.length - 2) {
return acc + Math.hypot(
arr[i + 2] - arr[i],
arr[i + 3] - arr[i + 1]
);
}
return acc;
}, 0);
} else {
length = 100;
}
break;
default:
length = element.getTotalLength?.() || 100;
}
} catch (error) {
length = 100;
}
clone.style.strokeDasharray = length;
clone.style.strokeDashoffset = length;
element.setAttribute('data-clone', '');
clone.setAttribute('data-animated-clone', '');
clone.setAttribute('data-original-fill', originalFill || 'none');
fragment.appendChild(clone);
cloneData.set(clone, { element, length });
});
elements[0].parentNode.insertBefore(fragment, elements[0]);
const clones = svg.querySelectorAll('[data-animated-clone]');
requestAnimationFrame(() => {
clones.forEach((clone, index) => {
const delay = animationOrder === 'sequential' ? baseDelay + (index * delayIncrement) : baseDelay;
const { element, length } = cloneData.get(clone);
const originalFill = clone.getAttribute('data-original-fill');
clone.style.transition = \`stroke-dashoffset \${duration}ms \${easing} \${delay}ms\`;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
clone.style.strokeDashoffset = '0';
});
});
const cleanupTimeout = setTimeout(() => {
element.style.transition = 'opacity 300ms ease';
element.style.opacity = '1';
element.style.stroke = '${svgRevealConfig.strokeColor}';
element.style.strokeWidth = '${svgRevealConfig.strokeWidth}';
if (originalFill !== 'none') {
element.style.fill = '${svgRevealConfig.fillColor}';
}
clone.style.opacity = '0';
setTimeout(() => {
if (clone.parentNode) {
clone.remove();
}
cloneData.delete(clone);
}, 300);
}, delay + duration);
element.setAttribute('data-cleanup', cleanupTimeout);
});
});
} catch (error) {}
};
const observerCallback = (entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && entry.intersectionRatio >= ${svgRevealConfig.threshold}) {
handleSVGAnimation(entry.target);
observer.unobserve(entry.target);
}
});
};
const observer = new IntersectionObserver(observerCallback, observerOptions);
document.querySelectorAll('[data-svg-reveal]').forEach(svg => {
observer.observe(svg);
});
};
document.addEventListener('DOMContentLoaded', initSVGReveal);
})();`;
}
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();
if (!sectionJSON) {
showNotification('Full section feature is not yet configured', 'warning');
return;
}
navigator.clipboard.writeText(sectionJSON)
.then(() => {
showNotification('Full section JSON copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy full section', '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 'duration':
svgRevealConfig.duration = defaultValue;
break;
case 'base-delay':
svgRevealConfig.baseDelay = defaultValue;
break;
case 'delay-increment':
svgRevealConfig.delayIncrement = defaultValue;
break;
case 'stroke-width':
svgRevealConfig.strokeWidth = defaultValue;
break;
case 'threshold':
svgRevealConfig.threshold = defaultValue;
break;
}
updatePreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function initializeUI() {
updatePreview();
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-svg-reveal');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('reset-colors').addEventListener('click', () => {
svgRevealConfig.strokeColor = defaultConfig.strokeColor;
svgRevealConfig.fillColor = defaultConfig.fillColor;
updateColorInputs('stroke');
updateColorInputs('fill');
updatePreview();
showNotification('Colors reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
svgRevealConfig.duration = defaultConfig.duration;
svgRevealConfig.baseDelay = defaultConfig.baseDelay;
svgRevealConfig.delayIncrement = defaultConfig.delayIncrement;
svgRevealConfig.strokeWidth = defaultConfig.strokeWidth;
document.getElementById('duration').value = defaultConfig.duration;
document.getElementById('base-delay').value = defaultConfig.baseDelay;
document.getElementById('delay-increment').value = defaultConfig.delayIncrement;
document.getElementById('stroke-width').value = defaultConfig.strokeWidth;
document.getElementById('duration-value').textContent = defaultConfig.duration;
document.getElementById('base-delay-value').textContent = defaultConfig.baseDelay;
document.getElementById('delay-increment-value').textContent = defaultConfig.delayIncrement;
document.getElementById('stroke-width-value').textContent = defaultConfig.strokeWidth;
updatePreview();
showNotification('Animation settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
svgRevealConfig.animationOrder = defaultConfig.animationOrder;
svgRevealConfig.easing = defaultConfig.easing;
svgRevealConfig.threshold = defaultConfig.threshold;
document.getElementById('order-sequential').checked = true;
document.getElementById('order-simultaneous').checked = false;
document.getElementById('easing').value = defaultConfig.easing;
document.getElementById('threshold').value = defaultConfig.threshold;
document.getElementById('threshold-value').textContent = defaultConfig.threshold;
updatePreview();
showNotification('Advanced settings reset');
});
function setupColorInputs(colorType) {
const colorInput = document.getElementById(`${colorType}-color`);
const hexInput = document.getElementById(`${colorType}-color-hex`);
const hslInput = document.getElementById(`${colorType}-color-hsl`);
const color = colorType === 'stroke' ? svgRevealConfig.strokeColor : svgRevealConfig.fillColor;
hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
colorInput.addEventListener('input', () => {
const selectedColor = colorInput.value;
hexInput.value = selectedColor;
hslInput.value = `hsl(${hexToHsl(selectedColor).h}, ${hexToHsl(selectedColor).s}%, ${hexToHsl(selectedColor).l}%)`;
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
if (colorType === 'stroke') {
svgRevealConfig.strokeColor = selectedColor;
} else {
svgRevealConfig.fillColor = selectedColor;
}
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', selectedColor);
updatePreview();
});
hexInput.addEventListener('input', (e) => {
let hex = e.target.value;
hex = formatHex(hex);
e.target.value = hex;
if (isValidHex(hex)) {
colorInput.value = hex;
hslInput.value = `hsl(${hexToHsl(hex).h}, ${hexToHsl(hex).s}%, ${hexToHsl(hex).l}%)`;
if (colorType === 'stroke') {
svgRevealConfig.strokeColor = hex;
} else {
svgRevealConfig.fillColor = hex;
}
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', hex);
updatePreview();
} else {
e.target.classList.add('invalid');
}
});
hexInput.addEventListener('blur', (e) => {
if (!isValidHex(e.target.value)) {
e.target.value = colorInput.value;
e.target.classList.remove('invalid');
}
});
hslInput.addEventListener('input', (e) => {
let hsl = e.target.value;
if (isValidHsl(hsl)) {
const hex = hslToHex(hsl);
if (hex) {
colorInput.value = hex;
hexInput.value = hex;
if (colorType === 'stroke') {
svgRevealConfig.strokeColor = hex;
} else {
svgRevealConfig.fillColor = hex;
}
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', hex);
updatePreview();
return;
}
}
e.target.classList.add('invalid');
});
hslInput.addEventListener('blur', (e) => {
let hsl = e.target.value;
if (!isValidHsl(hsl) && hsl.trim()) {
const formatted = formatHsl(hsl);
if (isValidHsl(formatted)) {
e.target.value = formatted;
const hex = hslToHex(formatted);
if (hex) {
colorInput.value = hex;
hexInput.value = hex;
if (colorType === 'stroke') {
svgRevealConfig.strokeColor = hex;
} else {
svgRevealConfig.fillColor = hex;
}
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updatePreview();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
const currentColor = colorType === 'stroke' ? svgRevealConfig.strokeColor : svgRevealConfig.fillColor;
e.target.value = `hsl(${hexToHsl(currentColor).h}, ${hexToHsl(currentColor).s}%, ${hexToHsl(currentColor).l}%)`;
e.target.classList.remove('invalid');
}
});
}
setupColorInputs('stroke');
setupColorInputs('fill');
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 'duration':
svgRevealConfig.duration = parseInt(input.value);
break;
case 'base-delay':
svgRevealConfig.baseDelay = parseInt(input.value);
break;
case 'delay-increment':
svgRevealConfig.delayIncrement = parseInt(input.value);
break;
case 'stroke-width':
svgRevealConfig.strokeWidth = parseFloat(input.value);
break;
case 'threshold':
svgRevealConfig.threshold = parseFloat(input.value);
break;
}
updatePreview();
});
});
document.querySelectorAll('input[name="animation-order"]').forEach(radio => {
radio.addEventListener('change', function() {
if (this.checked) {
svgRevealConfig.animationOrder = this.value;
updatePreview();
}
});
});
document.getElementById('easing').addEventListener('change', function() {
svgRevealConfig.easing = this.value;
updatePreview();
});
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') {
return;
}
if (e.ctrlKey || e.metaKey) {
switch (e.key.toLowerCase()) {
case 'd':
e.preventDefault();
const downloadBtn = document.getElementById('download-config');
if (downloadBtn && downloadBtn.hasAttribute('data-protection-animation')) {
downloadBtn.click();
} else {
copyJsToClipboard();
}
break;
case 's':
e.preventDefault();
const fullSectionBtn = document.getElementById('copy-full-section');
if (fullSectionBtn && fullSectionBtn.hasAttribute('data-protection-animation')) {
fullSectionBtn.click();
} else {
copyFullSectionToClipboard();
}
break;
}
}
});
updateColorInputs('stroke');
updateColorInputs('fill');
setTimeout(() => {
showNotification('BricksFusion SVG Reveal Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-svg-reveal-config', JSON.stringify(svgRevealConfig));
} catch (e) {}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-svg-reveal-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(svgRevealConfig, savedConfig);
document.getElementById('duration').value = savedConfig.duration;
document.getElementById('base-delay').value = savedConfig.baseDelay;
document.getElementById('delay-increment').value = savedConfig.delayIncrement;
document.getElementById('stroke-width').value = savedConfig.strokeWidth;
document.getElementById('threshold').value = savedConfig.threshold;
document.getElementById('easing').value = savedConfig.easing;
document.getElementById('duration-value').textContent = savedConfig.duration;
document.getElementById('base-delay-value').textContent = savedConfig.baseDelay;
document.getElementById('delay-increment-value').textContent = savedConfig.delayIncrement;
document.getElementById('stroke-width-value').textContent = savedConfig.strokeWidth;
document.getElementById('threshold-value').textContent = savedConfig.threshold;
if (savedConfig.animationOrder === 'simultaneous') {
document.getElementById('order-simultaneous').checked = true;
document.getElementById('order-sequential').checked = false;
}
updateColorInputs('stroke');
updateColorInputs('fill');
updatePreview();
}
} catch (e) {}
}
const originalUpdatePreview = updatePreview;
updatePreview = function() {
originalUpdatePreview();
saveConfiguration();
};
loadConfiguration();
}
initializeUI();
});
</script>
</body>
</html>
SVG Reveal
Animates SVG graphics with a smooth drawing effect that reveals them when scrolled into view. Perfect for logos, icons, illustrations, and decorative elements.
Animation
How long it takes to draw each part of the SVG. Shorter is snappier, longer is more dramatic. Sweet spot is usually 1500-2500ms.
Default: 1500ms
Initial wait time before any drawing starts. Gives visitors a moment to focus before the animation begins.
Default: 0ms (starts immediately)
When using Sequential order, this is the delay between each path. Higher values create a more staggered, piece-by-piece reveal.
Default: 100ms
Sequential draws paths one after another, creating a flowing reveal. Simultaneous draws everything at once for instant impact.
Default: Sequential
Animation curve that controls the drawing speed. Ease creates natural motion, Linear is constant speed, Custom lets you fine-tune with cubic-bezier.
Default: Ease (cubic-bezier(0.65, 0, 0.35, 1))
Appearance
Color of the drawing line as it animates. This is what visitors see being drawn. Choose a color that stands out against your background.
Default: Orange (#ef6013)
Final color that fills the shapes after they're drawn. Only applies to closed shapes like circles, rectangles, and filled paths.
Default: White (#ffffff)
Thickness of the drawing line. Thinner lines are more delicate, thicker lines are bolder and easier to see on large screens.
Default: 1.0
Trigger
How much of the SVG needs to be visible before animation starts. 0.5 means halfway into view, 0.2 starts sooner, 0.8 waits until almost fully visible.
Default: 0.5 (50% visible)
Performance
This element uses CSS transitions and stroke-dashoffset animation, making it very efficient. Works with any SVG containing paths, circles, rectangles, lines, polygons, and polylines. The animation only triggers once when scrolled into view, so it won't impact ongoing page performance.