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>Ripple Effect Configurator - BricksFusion</title>
<style>
:root {
--background: #000;
--card-bg: #1e1e1e;
--card-bg-hover: #252525;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--accent: #ef6013;
--accent-hover: #c64c0c;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
--thumb: #ef6013;
--card-radius: 16px;
--input-radius: 8px;
--button-radius: 12px;
--transition: all 0.25s ease;
--font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
--action-bar-height: 70px;
--success: #28a745;
--warning: #ffc107;
--danger: #dc3545;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
background-color: var(--background);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-bottom: var(--action-bar-height);
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--action-bar-height);
background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
border-top: 1px solid var(--border);
z-index: 1000;
display: flex;
align-items: center;
padding: 0 1.5rem;
gap: 1rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.breadcrumb-item {
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: 500;
text-decoration: none;
transition: var(--transition);
padding: 0.5rem 0.75rem;
border-radius: 6px;
}
.breadcrumb-item:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.05);
}
.breadcrumb-item.active {
color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.breadcrumb-separator {
color: var(--text-secondary);
font-size: var(--text-xs);
opacity: 0.5;
}
.action-buttons {
display: flex;
align-items: center;
gap: 0.75rem;
}
.action-btn {
padding: 0.6rem 1rem;
background-color: var(--card-bg);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
border: 1px solid var(--border);
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
}
.action-btn:hover {
background-color: var(--card-bg-hover);
border-color: var(--accent);
transform: translateY(-1px);
}
.action-btn.primary {
background: linear-gradient(90deg, var(--accent), #ff8c51);
border-color: var(--accent);
color: white;
}
.action-btn.primary:hover {
background: linear-gradient(90deg, var(--accent-hover), #e67a3f);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}
.data-attribute-display {
background-color: rgba(50, 50, 50, 0.8);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
cursor: pointer;
transition: var(--transition);
user-select: all;
}
.data-attribute-display:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.page-header {
text-align: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--accent), #ff8c51);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-subtitle {
font-size: var(--text-s);
color: var(--text-secondary);
font-weight: 500;
}
.instructions-toggle {
margin-bottom: 2rem;
}
.instructions-card {
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: var(--transition);
}
.instructions-header {
padding: 1rem 1.5rem;
cursor: pointer;
transition: var(--transition);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid transparent;
}
.instructions-header:hover {
background-color: var(--card-bg-hover);
}
.instructions-card.expanded .instructions-header {
border-bottom-color: var(--border);
}
.instructions-title {
font-size: var(--text-s);
font-weight: 600;
}
.toggle-icon {
font-size: 1.2em;
transition: transform 0.3s ease;
}
.toggle-icon.expanded {
transform: rotate(180deg);
}
.instructions-content {
padding: 0 1.5rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.instructions-content.show {
max-height: 500px;
padding: 1.5rem;
}
.instructions-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.how-to-use ol {
padding-left: 1.5rem;
}
.how-to-use li {
margin-bottom: 0.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.6;
}
.how-to-use strong {
color: var(--text-primary);
font-weight: 600;
}
.how-to-use code {
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
}
.content {
display: grid;
grid-template-columns: 1fr 500px;
gap: 2rem;
align-items: start;
}
.preview-section {
position: sticky;
top: 2rem;
}
.controls-section {
max-width: 500px;
}
.card {
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.preview-container {
height: 400px;
width: 100%;
position: relative;
overflow: hidden;
border-radius: var(--card-radius);
background: linear-gradient(135deg, #e91e63, #9c27b0);
border: 1px solid var(--border);
box-shadow: var(--shadow);
display: flex;
align-items: center;
justify-content: 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: relative;
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);
}
.text-color-group {
display: flex;
gap: 0.75rem;
align-items: center;
}
.text-color-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: var(--text-xs);
color: var(--text-secondary);
}
.text-color-toggle input[type="checkbox"] {
appearance: none;
width: 18px;
height: 18px;
border: 2px solid var(--border);
border-radius: 4px;
cursor: pointer;
transition: var(--transition);
position: relative;
}
.text-color-toggle input[type="checkbox"]:checked {
background-color: var(--accent);
border-color: var(--accent);
}
.text-color-toggle input[type="checkbox"]:checked::after {
content: "✓";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 12px;
font-weight: bold;
}
.color-input-container {
flex: 1;
transition: var(--transition);
}
.color-input-container.disabled {
opacity: 0.5;
pointer-events: none;
}
.ripple-button {
position: relative;
padding: 1rem 2rem;
background: linear-gradient(135deg, #e91e63, #9c27b0);
color: white;
border: 2px solid #e91e63;
border-radius: 12px;
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 600;
cursor: pointer;
transition: transform 0.6s ease;
-webkit-tap-highlight-color: transparent;
transform: translateZ(0);
overflow: hidden;
}
.ripple-wrapper {
position: absolute;
inset: 0;
overflow: hidden;
border-radius: inherit;
-webkit-mask-image: -webkit-radial-gradient(white, black);
mask-image: radial-gradient(white, black);
transform: translateZ(0);
}
.ripple-effect {
position: absolute;
width: 80px;
height: 80px;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 50%;
opacity: 0;
mix-blend-mode: overlay;
transition: opacity 0.6s ease;
pointer-events: none;
will-change: transform, opacity;
box-shadow: 0 0 20px 0 rgba(255, 255, 255, 0.7);
transform: translate(-50%, -50%) scale(1);
touch-action: none;
}
.ripple-content {
position: relative;
z-index: 2;
transition: color 0.6s ease;
}
.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/button-template/" class="breadcrumb-item">Buttons</a>
<span class="breadcrumb-separator">›</span>
<span class="breadcrumb-item active">Ripple Effect</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-ripple
</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">Ripple Effect</h1>
<p class="page-subtitle">Interactive cursor following 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 ripple effect using the controls below</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste or upload the JavaScript code</li>
<li>To add the effect to any button or link: go to <strong>Section → Style → Attributes</strong>, add <code>data-ripple</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="ripple-preview" style="background: linear-gradient(135deg, #e91e63, #9c27b0);">
<button class="ripple-button" id="ripple-demo" data-ripple>
<div class="ripple-wrapper">
<div class="ripple-effect"></div>
</div>
<div class="ripple-content">Interactive Ripple Effect Preview</div>
</button>
<div class="preview-controls">
<button class="preview-btn" id="randomize-ripple" 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="#e91e63" title="Change Preview Background (B)">
</div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Ripple 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="ripple-color" value="#ffffff">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="ripple-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="ripple-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Text Color on Overlap
<span class="help-tooltip" title="Color of text when ripple overlaps">ℹ</span>
</span>
</div>
<div class="text-color-group">
<div class="color-input-container" id="text-color-container">
<input type="color" id="text-color" value="#000000">
</div>
<div class="text-color-toggle">
<input type="checkbox" id="auto-text-color" checked>
<label for="auto-text-color">Auto Inverse</label>
</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">
Ripple Size
<span class="help-tooltip" title="Size of the ripple effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="ripple-size-value">80</span>px</span>
<button class="reset-btn" onclick="resetParameter('ripple-size', 80)">↺</button>
</div>
</div>
<input type="range" id="ripple-size" min="40" max="150" step="5" value="80">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Button Scale
<span class="help-tooltip" title="Scale of button on hover">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="button-scale-value">1.05</span></span>
<button class="reset-btn" onclick="resetParameter('button-scale', 1.05)">↺</button>
</div>
</div>
<input type="range" id="button-scale" min="1.0" max="1.2" step="0.01" value="1.05">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Animation Duration
<span class="help-tooltip" title="Duration of animations">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="animation-duration-value">0.6</span>s</span>
<button class="reset-btn" onclick="resetParameter('animation-duration', 0.6)">↺</button>
</div>
</div>
<input type="range" id="animation-duration" min="0.3" max="1.5" step="0.1" value="0.6">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Pulse Speed
<span class="help-tooltip" title="Speed of pulse animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="pulse-speed-value">0.05</span></span>
<button class="reset-btn" onclick="resetParameter('pulse-speed', 0.05)">↺</button>
</div>
</div>
<input type="range" id="pulse-speed" min="0.01" max="0.15" step="0.01" value="0.05">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Pulse Intensity
<span class="help-tooltip" title="Intensity of pulse effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="pulse-intensity-value">0.05</span></span>
<button class="reset-btn" onclick="resetParameter('pulse-intensity', 0.05)">↺</button>
</div>
</div>
<input type="range" id="pulse-intensity" min="0.01" max="0.2" step="0.01" value="0.05">
</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">Blend Mode</span>
</div>
<select id="blend-mode">
<option value="overlay">Overlay</option>
<option value="multiply">Multiply</option>
<option value="screen">Screen</option>
<option value="soft-light">Soft Light</option>
<option value="hard-light">Hard Light</option>
<option value="normal">Normal</option>
</select>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let rippleConfig = {
color: '#ffffff',
size: 80,
duration: 0.6,
scale: 1.05,
pulseSpeed: 0.05,
pulseIntensity: 0.05,
textColor: '#000000',
autoInvert: true,
blendMode: 'overlay'
};
const defaultConfig = { ...rippleConfig };
let currentAnimationFrame;
let currentTime = 0;
function initRippleEffects() {
const buttons = document.querySelectorAll('[data-ripple]:not([data-ripple-initialized="true"])');
buttons.forEach(button => {
setupRippleButton(button);
button.dataset.rippleInitialized = 'true';
});
}
function setupRippleButton(button) {
if (!button.querySelector('.ripple-wrapper')) {
const wrapper = document.createElement('div');
wrapper.className = 'ripple-wrapper';
const ripple = document.createElement('div');
ripple.className = 'ripple-effect';
const content = document.createElement('div');
content.className = 'ripple-content';
content.innerHTML = button.innerHTML;
button.innerHTML = '';
wrapper.appendChild(ripple);
button.appendChild(wrapper);
button.appendChild(content);
}
const ripple = button.querySelector('.ripple-effect');
const content = button.querySelector('.ripple-content');
updateRippleStyles(button, ripple, content);
button.addEventListener('mousemove', e => moveRipple(e, button, ripple, content));
button.addEventListener('mouseenter', e => showRipple(e, button, ripple, content));
button.addEventListener('mouseleave', () => hideRipple(ripple, content, button));
button.addEventListener('touchstart', e => showRipple(e, button, ripple, content), { passive: true });
button.addEventListener('touchend', () => hideRipple(ripple, content, button), { passive: true });
button.addEventListener('touchmove', e => moveRipple(e, button, ripple, content), { passive: true });
}
function updateRippleStyles(button, ripple, content) {
const rgba = hexToRgba(rippleConfig.color, 0.7);
button.style.transition = `transform ${rippleConfig.duration}s ease`;
ripple.style.width = rippleConfig.size + 'px';
ripple.style.height = rippleConfig.size + 'px';
ripple.style.backgroundColor = rgba;
ripple.style.boxShadow = `0 0 20px 0 ${rgba}`;
ripple.style.transition = `opacity ${rippleConfig.duration}s ease`;
ripple.style.mixBlendMode = rippleConfig.blendMode;
content.style.transition = `color ${rippleConfig.duration}s ease`;
}
function updateConfig() {
const buttons = document.querySelectorAll('[data-ripple]');
buttons.forEach(button => {
const ripple = button.querySelector('.ripple-effect');
const content = button.querySelector('.ripple-content');
if (ripple && content) {
updateRippleStyles(button, ripple, content);
}
});
}
function hexToRgba(hex, alpha = 0.7) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
};
}
function hslToHex(hsl) {
const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
if (!match) return null;
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let l = parseInt(match[3]) / 100;
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function isValidHex(hex) {
return /^#[0-9A-F]{6}$/i.test(hex);
}
function isValidHsl(hsl) {
return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
}
function formatHex(value) {
let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
if (!hex.startsWith('#')) {
hex = '#' + hex;
}
if (hex.length > 7) {
hex = hex.substring(0, 7);
}
return hex.toUpperCase();
}
function formatHsl(value) {
const cleanValue = value.replace(/[^\d,\s]/g, '');
const numbers = cleanValue.match(/\d+/g);
if (!numbers || numbers.length < 3) {
const partialMatch = value.match(/(\d+)/g);
if (partialMatch && partialMatch.length >= 1) {
const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
return `hsl(${h}, ${s}%, ${l}%)`;
}
return value;
}
let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
return `hsl(${h}, ${s}%, ${l}%)`;
}
function getInverseColor(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
const invR = (255 - r).toString(16).padStart(2, '0');
const invG = (255 - g).toString(16).padStart(2, '0');
const invB = (255 - b).toString(16).padStart(2, '0');
return `#${invR}${invG}${invB}`;
}
function getTextColor() {
if (rippleConfig.autoInvert) {
return getInverseColor(rippleConfig.color);
} else {
return rippleConfig.textColor;
}
}
function checkOverlap(rect1, rect2) {
return !(rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom);
}
function moveRipple(e, button, ripple, content) {
const rect = button.getBoundingClientRect();
const x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
const y = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top;
ripple.style.left = `${x}px`;
ripple.style.top = `${y}px`;
const rippleRect = ripple.getBoundingClientRect();
const contentRect = content.getBoundingClientRect();
if (checkOverlap(rippleRect, contentRect)) {
content.style.color = getTextColor();
} else {
content.style.color = '';
}
if (!currentAnimationFrame) {
const animate = () => {
currentTime += rippleConfig.pulseSpeed;
const scaleX = 1 + Math.sin(currentTime) * rippleConfig.pulseIntensity;
const scaleY = 1 + Math.cos(currentTime) * rippleConfig.pulseIntensity;
ripple.style.transform = `translate(-50%, -50%) scale(${scaleX}, ${scaleY})`;
currentAnimationFrame = requestAnimationFrame(animate);
};
animate();
}
}
function showRipple(e, button, ripple, content) {
ripple.style.opacity = '1';
moveRipple(e, button, ripple, content);
button.style.transform = `scale(${rippleConfig.scale})`;
}
function hideRipple(ripple, content, button) {
ripple.style.opacity = '0';
content.style.color = '';
button.style.transform = 'scale(1)';
if (currentAnimationFrame) {
cancelAnimationFrame(currentAnimationFrame);
currentAnimationFrame = null;
}
currentTime = 0;
}
function generateRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
}
function generateRandomRipple() {
rippleConfig.color = generateRandomColor();
rippleConfig.size = Math.floor(Math.random() * 110) + 40;
rippleConfig.duration = Math.random() * 1.2 + 0.3;
rippleConfig.scale = Math.random() * 0.2 + 1.0;
rippleConfig.pulseSpeed = Math.random() * 0.14 + 0.01;
rippleConfig.pulseIntensity = Math.random() * 0.19 + 0.01;
rippleConfig.blendMode = ['overlay', 'multiply', 'screen', 'soft-light', 'hard-light', 'normal'][Math.floor(Math.random() * 6)];
document.getElementById('ripple-color').value = rippleConfig.color;
document.getElementById('ripple-size').value = rippleConfig.size;
document.getElementById('animation-duration').value = rippleConfig.duration;
document.getElementById('button-scale').value = rippleConfig.scale;
document.getElementById('pulse-speed').value = rippleConfig.pulseSpeed;
document.getElementById('pulse-intensity').value = rippleConfig.pulseIntensity;
document.getElementById('blend-mode').value = rippleConfig.blendMode;
document.getElementById('ripple-size-value').textContent = rippleConfig.size;
document.getElementById('animation-duration-value').textContent = rippleConfig.duration;
document.getElementById('button-scale-value').textContent = rippleConfig.scale;
document.getElementById('pulse-speed-value').textContent = rippleConfig.pulseSpeed;
document.getElementById('pulse-intensity-value').textContent = rippleConfig.pulseIntensity;
updateColorInputs();
updateConfig();
showNotification('Random ripple effect generated!');
}
function updateColorInputs() {
const colorInput = document.getElementById('ripple-color');
const hexInput = document.getElementById('ripple-color-hex');
const hslInput = document.getElementById('ripple-color-hsl');
if (colorInput && hexInput && hslInput) {
colorInput.value = rippleConfig.color;
hexInput.value = rippleConfig.color;
hslInput.value = `hsl(${hexToHsl(rippleConfig.color).h}, ${hexToHsl(rippleConfig.color).s}%, ${hexToHsl(rippleConfig.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', rippleConfig.color);
}
}
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos para todos los elementos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const divId = generateUniqueId();
const buttonId = generateUniqueId();
const codeId = 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": {}
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [divId],
"settings": {"_alignItems": "center"}
},
{
"id": divId,
"name": "div",
"parent": containerId,
"children": [buttonId],
"settings": {}
},
{
"id": buttonId,
"name": "button",
"parent": divId,
"children": [],
"settings": {
"text": "Ripple button",
"_attributes": [{"id": "tr4yc7", "name": "data-ripple"}],
"_border": {"radius": {"top": "15", "right": "15", "bottom": "15", "left": "15"}},
"_typography": {"color": {"hex": "#ffffff"}, "font-weight": "400", "font-size": "18"},
"_background": {"color": {"hex": "#000000"}}
},
"label": "Ripple button"
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"_display": "none",
"javascriptCode": jsCode,
"executeCode": true
},
"label": "Ripple button JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksJSON, null, 2);
}
function generateJavaScriptCode() {
return `(function() {
'use strict';
if (window.rippleEffectInitialized) return;
window.rippleEffectInitialized = true;
class RippleEffect {
constructor() {
this.config = {
color: '${rippleConfig.color}',
size: ${rippleConfig.size},
duration: ${rippleConfig.duration},
scale: ${rippleConfig.scale},
pulseSpeed: ${rippleConfig.pulseSpeed},
pulseIntensity: ${rippleConfig.pulseIntensity},
textColor: '${rippleConfig.textColor}',
autoInvert: ${rippleConfig.autoInvert},
blendMode: '${rippleConfig.blendMode}'
};
this.init();
}
init() {
document.addEventListener('DOMContentLoaded', () => {
const buttons = document.querySelectorAll('[data-ripple]');
buttons.forEach(button => this.setupButton(button));
});
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.matches('[data-ripple]')) {
this.setupButton(node);
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
setupButton(button) {
if (button.hasAttribute('data-ripple-initialized')) return;
const config = {
size: parseInt(button.getAttribute('data-ripple-size')) || this.config.size,
color: button.getAttribute('data-ripple-color') || this.config.color,
duration: parseFloat(button.getAttribute('data-ripple-duration')) || this.config.duration,
scale: parseFloat(button.getAttribute('data-ripple-scale')) || this.config.scale,
pulseSpeed: parseFloat(button.getAttribute('data-ripple-pulse-speed')) || this.config.pulseSpeed,
pulseIntensity: parseFloat(button.getAttribute('data-ripple-pulse-intensity')) || this.config.pulseIntensity,
textColor: button.getAttribute('data-ripple-text-color') || this.config.textColor,
autoInvert: button.getAttribute('data-ripple-auto-invert') === 'true' || (button.getAttribute('data-ripple-auto-invert') === null ? this.config.autoInvert : false),
blendMode: button.getAttribute('data-ripple-blend-mode') || this.config.blendMode
};
const wrapper = document.createElement('div');
wrapper.className = \`ripple-wrapper-\${this.generateUniqueId()}\`;
const ripple = document.createElement('div');
const content = document.createElement('div');
content.className = \`ripple-content-\${this.generateUniqueId()}\`;
content.innerHTML = button.innerHTML;
button.innerHTML = '';
ripple.className = \`ripple-effect-\${this.generateUniqueId()}\`;
wrapper.appendChild(ripple);
button.appendChild(wrapper);
button.appendChild(content);
let animationFrame;
let time = 0;
const showRipple = (e) => {
ripple.style.opacity = '1';
this.moveRipple(e, button, ripple, content, config, animationFrame, time);
button.style.transform = \`scale(\${config.scale})\`;
};
const hideRipple = () => {
ripple.style.opacity = '0';
content.style.color = '';
button.style.transform = 'scale(1)';
if (animationFrame) {
cancelAnimationFrame(animationFrame);
animationFrame = null;
}
time = 0;
};
const moveRipple = (e) => {
this.moveRipple(e, button, ripple, content, config, animationFrame, time);
};
button.addEventListener('touchstart', showRipple, { passive: true });
button.addEventListener('touchend', hideRipple, { passive: true });
button.addEventListener('touchmove', moveRipple, { passive: true });
button.addEventListener('mousemove', moveRipple);
button.addEventListener('mouseenter', showRipple);
button.addEventListener('mouseleave', hideRipple);
this.addStyles(button, wrapper, ripple, content, config);
button.setAttribute('data-ripple-initialized', 'true');
}
generateUniqueId() {
return \`id-\${Math.random().toString(36).substr(2, 9)}\`;
}
hexToRgba(hex, alpha = 0.7) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return \`rgba(\${r}, \${g}, \${b}, \${alpha})\`;
}
getInverseColor(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
const invR = (255 - r).toString(16).padStart(2, '0');
const invG = (255 - g).toString(16).padStart(2, '0');
const invB = (255 - b).toString(16).padStart(2, '0');
return \`#\${invR}\${invG}\${invB}\`;
}
getTextColor(config) {
if (config.autoInvert) {
return this.getInverseColor(config.color);
} else {
return config.textColor;
}
}
moveRipple(e, button, ripple, content, config, animationFrame, time) {
const rect = button.getBoundingClientRect();
const x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
const y = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top;
ripple.style.left = \`\${x}px\`;
ripple.style.top = \`\${y}px\`;
const rippleRect = ripple.getBoundingClientRect();
const contentRect = content.getBoundingClientRect();
if (this.checkOverlap(rippleRect, contentRect)) {
content.style.color = this.getTextColor(config);
} else {
content.style.color = '';
}
if (!animationFrame) {
const animate = () => {
time += config.pulseSpeed;
const scaleX = 1 + Math.sin(time) * config.pulseIntensity;
const scaleY = 1 + Math.cos(time) * config.pulseIntensity;
ripple.style.transform = \`translate(-50%, -50%) scale(\${scaleX}, \${scaleY})\`;
animationFrame = requestAnimationFrame(animate);
};
animate();
}
}
checkOverlap(rect1, rect2) {
return !(rect1.right < rect2.left ||
rect1.left > rect2.right ||
rect1.bottom < rect2.top ||
rect1.top > rect2.bottom);
}
addStyles(button, wrapper, ripple, content, config) {
const uniqueStyles = document.createElement('style');
const rippleColor = this.hexToRgba(config.color, 0.7);
uniqueStyles.textContent = \`
[data-ripple] {
position: relative !important;
transition: transform \${config.duration}s ease !important;
cursor: pointer !important;
-webkit-tap-highlight-color: transparent;
transform: translateZ(0);
overflow: hidden;
}
.\${wrapper.className} {
position: absolute !important;
inset: 0 !important;
overflow: hidden !important;
border-radius: inherit !important;
-webkit-mask-image: -webkit-radial-gradient(white, black);
mask-image: radial-gradient(white, black);
transform: translateZ(0);
}
.\${ripple.className} {
position: absolute !important;
width: \${config.size}px !important;
height: \${config.size}px !important;
background-color: \${rippleColor} !important;
border-radius: 50% !important;
opacity: 0;
mix-blend-mode: \${config.blendMode};
transition: opacity \${config.duration}s ease !important;
pointer-events: none !important;
will-change: transform, opacity !important;
box-shadow: 0 0 20px 0 \${rippleColor};
transform: translate(-50%, -50%) scale(1);
touch-action: none;
}
.\${content.className} {
position: relative !important;
z-index: 2 !important;
transition: color \${config.duration}s ease !important;
}
@supports (-webkit-touch-callout: none) {
.\${wrapper.className} {
transform: translateZ(0);
}
}
\`;
document.head.appendChild(uniqueStyles);
}
}
new RippleEffect();
})();`;
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy to clipboard', 'error');
});
}
function copyJsToClipboard() {
const jsCode = generateJavaScriptCode();
copyToClipboard(jsCode);
}
function copyFullSectionToClipboard() {
const sectionJSON = generateFullSectionJSON();
copyToClipboard(sectionJSON);
}
window.resetParameter = function(parameterId, defaultValue) {
const element = document.getElementById(parameterId);
if (element) {
element.value = defaultValue;
const valueElement = document.getElementById(`${parameterId}-value`);
if (valueElement) {
if (parameterId.includes('duration')) {
valueElement.textContent = defaultValue;
} else if (parameterId.includes('size')) {
valueElement.textContent = defaultValue;
} else {
valueElement.textContent = defaultValue;
}
}
switch (parameterId) {
case 'ripple-size':
rippleConfig.size = defaultValue;
break;
case 'button-scale':
rippleConfig.scale = defaultValue;
break;
case 'animation-duration':
rippleConfig.duration = defaultValue;
break;
case 'pulse-speed':
rippleConfig.pulseSpeed = defaultValue;
break;
case 'pulse-intensity':
rippleConfig.pulseIntensity = defaultValue;
break;
}
updateConfig();
saveConfiguration();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-ripple-config', JSON.stringify(rippleConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-ripple-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(rippleConfig, savedConfig);
document.getElementById('ripple-color').value = savedConfig.color;
document.getElementById('text-color').value = savedConfig.textColor;
document.getElementById('auto-text-color').checked = savedConfig.autoInvert;
document.getElementById('ripple-size').value = savedConfig.size;
document.getElementById('button-scale').value = savedConfig.scale;
document.getElementById('animation-duration').value = savedConfig.duration;
document.getElementById('pulse-speed').value = savedConfig.pulseSpeed;
document.getElementById('pulse-intensity').value = savedConfig.pulseIntensity;
document.getElementById('blend-mode').value = savedConfig.blendMode;
document.getElementById('ripple-size-value').textContent = savedConfig.size;
document.getElementById('button-scale-value').textContent = savedConfig.scale;
document.getElementById('animation-duration-value').textContent = savedConfig.duration;
document.getElementById('pulse-speed-value').textContent = savedConfig.pulseSpeed;
document.getElementById('pulse-intensity-value').textContent = savedConfig.pulseIntensity;
updateColorInputs();
toggleTextColorInput();
updateConfig();
}
} catch (e) {
}
}
function toggleTextColorInput() {
const autoTextColorCheckbox = document.getElementById('auto-text-color');
const textColorContainer = document.getElementById('text-color-container');
if (autoTextColorCheckbox.checked) {
textColorContainer.classList.add('disabled');
} else {
textColorContainer.classList.remove('disabled');
}
rippleConfig.autoInvert = autoTextColorCheckbox.checked;
updateConfig();
saveConfiguration();
}
function initializeUI() {
initRippleEffects();
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-ripple');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('randomize-ripple').addEventListener('click', () => {
generateRandomRipple();
});
const backgroundPicker = document.getElementById('preview-background-picker');
const previewContainer = document.getElementById('ripple-preview');
backgroundPicker.addEventListener('input', (e) => {
const selectedColor = e.target.value;
previewContainer.style.background = selectedColor;
showNotification(`Preview background changed to ${selectedColor}`);
});
document.getElementById('reset-colors').addEventListener('click', () => {
rippleConfig.color = defaultConfig.color;
rippleConfig.textColor = defaultConfig.textColor;
rippleConfig.autoInvert = defaultConfig.autoInvert;
document.getElementById('ripple-color').value = defaultConfig.color;
document.getElementById('text-color').value = defaultConfig.textColor;
document.getElementById('auto-text-color').checked = defaultConfig.autoInvert;
updateColorInputs();
toggleTextColorInput();
updateConfig();
saveConfiguration();
showNotification('Colors reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
rippleConfig.size = defaultConfig.size;
rippleConfig.scale = defaultConfig.scale;
rippleConfig.duration = defaultConfig.duration;
rippleConfig.pulseSpeed = defaultConfig.pulseSpeed;
rippleConfig.pulseIntensity = defaultConfig.pulseIntensity;
document.getElementById('ripple-size').value = defaultConfig.size;
document.getElementById('button-scale').value = defaultConfig.scale;
document.getElementById('animation-duration').value = defaultConfig.duration;
document.getElementById('pulse-speed').value = defaultConfig.pulseSpeed;
document.getElementById('pulse-intensity').value = defaultConfig.pulseIntensity;
document.getElementById('ripple-size-value').textContent = defaultConfig.size;
document.getElementById('button-scale-value').textContent = defaultConfig.scale;
document.getElementById('animation-duration-value').textContent = defaultConfig.duration;
document.getElementById('pulse-speed-value').textContent = defaultConfig.pulseSpeed;
document.getElementById('pulse-intensity-value').textContent = defaultConfig.pulseIntensity;
updateConfig();
saveConfiguration();
showNotification('Animation settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
rippleConfig.blendMode = defaultConfig.blendMode;
document.getElementById('blend-mode').value = defaultConfig.blendMode;
updateConfig();
saveConfiguration();
showNotification('Advanced settings reset');
});
const colorInput = document.getElementById('ripple-color');
const hexInput = document.getElementById('ripple-color-hex');
const hslInput = document.getElementById('ripple-color-hsl');
const textColorInput = document.getElementById('text-color');
const autoTextColorCheckbox = document.getElementById('auto-text-color');
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');
rippleConfig.color = color;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', color);
updateConfig();
saveConfiguration();
});
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}%)`;
rippleConfig.color = 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);
updateConfig();
saveConfiguration();
} 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;
rippleConfig.color = 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);
updateConfig();
saveConfiguration();
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;
rippleConfig.color = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateConfig();
saveConfiguration();
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');
}
});
textColorInput.addEventListener('input', () => {
rippleConfig.textColor = textColorInput.value;
updateConfig();
saveConfiguration();
});
autoTextColorCheckbox.addEventListener('change', toggleTextColorInput);
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
const valueElement = document.getElementById(`${input.id}-value`);
if (valueElement) {
if (input.id.includes('duration')) {
valueElement.textContent = input.value;
} else if (input.id.includes('size')) {
valueElement.textContent = input.value;
} else {
valueElement.textContent = input.value;
}
}
input.addEventListener('input', () => {
if (valueElement) {
if (input.id.includes('duration')) {
valueElement.textContent = input.value;
} else if (input.id.includes('size')) {
valueElement.textContent = input.value;
} else {
valueElement.textContent = input.value;
}
}
switch (input.id) {
case 'ripple-size':
rippleConfig.size = parseInt(input.value);
break;
case 'button-scale':
rippleConfig.scale = parseFloat(input.value);
break;
case 'animation-duration':
rippleConfig.duration = parseFloat(input.value);
break;
case 'pulse-speed':
rippleConfig.pulseSpeed = parseFloat(input.value);
break;
case 'pulse-intensity':
rippleConfig.pulseIntensity = parseFloat(input.value);
break;
}
updateConfig();
saveConfiguration();
});
});
document.getElementById('blend-mode').addEventListener('change', function() {
rippleConfig.blendMode = this.value;
updateConfig();
saveConfiguration();
});
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();
}
break;
case 's':
e.preventDefault();
const fullSectionBtn = document.getElementById('copy-full-section');
if (fullSectionBtn && fullSectionBtn.hasAttribute('data-protection-animation')) {
fullSectionBtn.click();
}
break;
}
} else {
switch (e.key.toLowerCase()) {
case 'r':
generateRandomRipple();
break;
case 'b':
document.getElementById('preview-background-picker').click();
break;
}
}
});
updateColorInputs();
toggleTextColorInput();
loadConfiguration();
setTimeout(() => {
showNotification('BricksFusion Ripple Effect Configurator loaded!');
}, 500);
}
initializeUI();
});
</script>
</body>
</html>
Ripple Button
Creates interactive ripple effect on buttons that follows cursor movement on hover. Features expanding circular waves that emanate from mouse position. Customize color, speed, blur, and animation style. Option for single or multiple simultaneous ripples. Perfect for CTAs, navigation buttons, or adding tactile feedback to interactive elements.
Ripple Button
Hover over the button to see the ripple effect.
Appearance
Color of the ripple wave. Choose any color to match your button design. Use semi-transparent colors for subtle overlay effects.
Default: Black
Transparency of the ripple. Lower creates subtle, ghostly waves. Higher produces bold, prominent ripples.
Default: Solid (1.0)
Softness of ripple edges. Lower keeps edges crisp and defined. Higher creates smooth, diffused waves.
Default: Sharp (0)
Animation
How quickly the ripple expands from cursor position. Lower creates slow, graceful waves. Higher produces quick, snappy effects.
Default: Moderate (0.6s)
Easing curve for the expansion. Ease-out starts fast, ends slow. Ease-in-out creates smooth acceleration. Linear maintains constant speed. Bounce adds playful spring effect.
Default: Ease-out
Size
Maximum size of the ripple relative to button. Lower creates contained waves within button bounds. Higher produces expansive ripples that exceed button edges.
Default: Normal (1.0)
Behavior
When enabled, creates multiple ripples as cursor moves. When disabled, maintains single ripple that follows cursor smoothly.
Default: Disabled (single ripple)
Performance
This element uses CSS transforms and transitions for smooth animations. Creates circular span elements that scale from center. Features mouse tracking with requestAnimationFrame for smooth cursor following. Includes automatic cleanup on mouse leave. Very lightweight - suitable for all devices with unlimited instances per page and smooth 60fps performance.