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>Horizontal Flow Carousel 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;
}
.preview-content {
color: white;
text-align: center;
font-weight: bold;
font-size: var(--text-s);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
.preview-controls {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
gap: 0.5rem;
z-index: 10;
}
.preview-btn {
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
font-size: var(--text-xs);
backdrop-filter: blur(5px);
}
.preview-btn:hover {
background-color: var(--accent);
border-color: var(--accent);
}
.card-heading {
padding: 1rem 1.5rem;
font-size: var(--text-s);
font-weight: 600;
border-bottom: 1px solid var(--border);
letter-spacing: 0.3px;
display: flex;
justify-content: space-between;
align-items: center;
}
.card-actions {
display: flex;
gap: 0.5rem;
}
.card-action-btn {
padding: 0.4rem 0.8rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 6px;
cursor: pointer;
font-size: var(--text-xs);
transition: var(--transition);
}
.card-action-btn:hover {
color: var(--text-primary);
border-color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.card-content {
padding: 1.5rem;
}
.control-group {
margin-bottom: 1.5rem;
position: relative;
}
.control-group:last-child {
margin-bottom: 0;
}
.control-label {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.label-text {
font-size: var(--text-xs);
font-weight: 500;
letter-spacing: 0.2px;
display: flex;
align-items: center;
gap: 0.5rem;
}
.help-tooltip {
cursor: help;
opacity: 0.7;
transition: var(--transition);
}
.help-tooltip:hover {
opacity: 1;
color: var(--accent);
}
.value-display {
display: flex;
align-items: center;
gap: 0.5rem;
}
.value-text {
font-size: var(--text-xs);
color: var(--text-secondary);
background-color: rgba(50, 50, 50, 0.5);
padding: 2px 8px;
border-radius: 4px;
min-width: 45px;
text-align: center;
}
.reset-btn {
padding: 0.2rem 0.4rem;
background-color: transparent;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 10px;
transition: var(--transition);
}
.reset-btn:hover {
color: var(--danger);
border-color: var(--danger);
background-color: rgba(220, 53, 69, 0.1);
}
input[type="range"] {
-webkit-appearance: none;
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: #000000;
}
.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, #000000);
border-radius: 6px;
transition: var(--transition);
}
input[type="color"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
cursor: pointer;
background: transparent;
opacity: 0;
z-index: 2;
}
.color-input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.color-label {
font-size: 10px;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-left: 0.25rem;
}
.color-input {
padding: 0.5rem 0.75rem;
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 12px;
transition: var(--transition);
min-width: 0;
}
.color-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.2);
outline: none;
}
.color-input.invalid {
border-color: var(--danger);
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.2);
}
.hex-input,
.hsl-input {
width: 100%;
}
.color-input-group:nth-child(2) {
flex: 0.3;
}
.color-input-group:nth-child(3) {
flex: 0.7;
}
select {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--input-radius);
font-family: var(--font);
font-size: var(--text-xs);
color: var(--text-primary);
background-color: var(--card-bg);
margin-bottom: 0.75rem;
outline: none;
transition: var(--transition);
}
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.notification {
position: fixed;
bottom: calc(var(--action-bar-height) + 1rem);
left: 50%;
background-color: var(--success);
color: white;
padding: 0.75rem 1rem;
border-radius: var(--input-radius);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1001;
transform: translate(-50%, 200px);
opacity: 0;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
font-size: var(--text-xs);
font-weight: 500;
max-width: 320px;
word-wrap: break-word;
line-height: 1.4;
text-align: center;
}
.notification.show {
transform: translate(-50%, 0);
opacity: 1;
}
#fadeflow-container {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
border-radius: var(--card-radius);
}
.fadeflow-preview {
position: relative;
overflow: hidden;
height: 100%;
width: 100%;
border-radius: 8px;
margin: 0;
padding: 0;
}
.fadeflow-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 8px;
margin: 0;
padding: 0;
}
.fadeflow-lane {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
overflow: hidden;
margin: 0;
padding: 0;
}
.fadeflow-track {
display: flex;
height: 100%;
min-width: 100%;
will-change: transform;
backface-visibility: hidden;
perspective: 1000px;
transform: translateZ(0);
}
.fadeflow-item {
height: var(--container-height, 100%);
flex: 0 0 auto;
overflow: hidden;
border-radius: 8px;
transition: transform 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
will-change: transform;
}
.fadeflow-item:hover {
transform: scale(1.02);
z-index: 10;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.5);
}
.fadeflow-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.3s ease;
}
.fadeflow-edge-fade {
position: absolute;
top: 0;
height: 100%;
width: 15%;
z-index: 3;
pointer-events: none;
}
.fadeflow-edge-fade.left {
left: 0;
background: linear-gradient(to right,
rgba(0, 0, 0, 0.95) 0%,
rgba(0, 0, 0, 0.7) 40%,
rgba(0, 0, 0, 0.4) 60%,
rgba(0, 0, 0, 0.1) 80%,
rgba(0, 0, 0, 0) 100%);
}
.fadeflow-edge-fade.right {
right: 0;
background: linear-gradient(to left,
rgba(0, 0, 0, 0.95) 0%,
rgba(0, 0, 0, 0.7) 40%,
rgba(0, 0, 0, 0.4) 60%,
rgba(0, 0, 0, 0.1) 80%,
rgba(0, 0, 0, 0) 100%);
}
.image-url-group {
margin-bottom: 1rem;
}
.image-url-input {
display: flex;
gap: 0.5rem;
}
.url-input {
flex-grow: 1;
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);
outline: none;
transition: var(--transition);
}
.url-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.remove-btn {
background-color: rgba(255, 59, 48, 0.2);
color: #ff3b30;
border: none;
border-radius: var(--input-radius);
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 16px;
transition: var(--transition);
}
.remove-btn:hover {
background-color: rgba(255, 59, 48, 0.3);
}
.add-image-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.6rem 1rem;
background-color: rgba(50, 50, 50, 0.5);
color: var(--text-primary);
border: 1px dashed var(--border);
border-radius: var(--input-radius);
font-size: var(--text-xs);
cursor: pointer;
transition: var(--transition);
width: 100%;
margin-top: 0.5rem;
}
.add-image-btn:hover {
background-color: rgba(80, 80, 80, 0.5);
border-color: var(--text-secondary);
}
.updating {
position: relative;
}
.updating::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent), transparent);
animation: updatePulse 0.6s ease-out;
}
@keyframes updatePulse {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
@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/showcase/" class="breadcrumb-item">Showcase</a>
<span class="breadcrumb-separator">›</span>
<span class="breadcrumb-item active">Horizontal Flow Carousel</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-fadeflow
</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">Horizontal Flow Carousel</h1>
<p class="page-subtitle">Interactive image carousel 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 Horizontal Flow carousel 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 section: go to <strong>Section → Style → Attributes</strong>, add <code>data-fadeflow</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="fadeflow-preview">
<div class="preview-content">Interactive Horizontal Flow Carousel Preview</div>
<div class="preview-controls">
<button class="preview-btn" id="generate-random-palette" title="Generate Random Palette (🎲)">🎲</button>
</div>
<div id="fadeflow-container" data-fadeflow="true"></div>
</div>
</section>
<section class="controls-section">
<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">
Animation Speed
<span class="help-tooltip" title="Duration of one complete cycle in seconds">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="animation-speed-value">10</span>s</span>
<button class="reset-btn" onclick="resetParameter('animation-speed', 10)">↺</button>
</div>
</div>
<input type="range" id="animation-speed" min="2" max="60" step="1" value="10">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Mobile Speed Factor
<span class="help-tooltip" title="Speed multiplier for mobile devices">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="mobile-speed-value">0.5</span>x</span>
<button class="reset-btn" onclick="resetParameter('mobile-speed', 0.5)">↺</button>
</div>
</div>
<input type="range" id="mobile-speed" min="0.1" max="1" step="0.1" value="0.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Animation Direction</span>
</div>
<select id="animation-direction">
<option value="left" selected>Left</option>
<option value="right">Right</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Image Gap
<span class="help-tooltip" title="Space between images in pixels">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="image-gap-value">20</span>px</span>
<button class="reset-btn" onclick="resetParameter('image-gap', 20)">↺</button>
</div>
</div>
<input type="range" id="image-gap" min="5" max="40" step="1" value="20">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Appearance Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-appearance" title="Reset Appearance Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Border Radius
<span class="help-tooltip" title="Roundness of image corners">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="border-radius-value">8</span>px</span>
<button class="reset-btn" onclick="resetParameter('border-radius', 8)">↺</button>
</div>
</div>
<input type="range" id="border-radius" min="0" max="24" step="1" value="8">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Edge Fade Theme</span>
</div>
<select id="blur-theme">
<option value="dark" selected>Dark</option>
<option value="light">Light</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Edge Fade Width
<span class="help-tooltip" title="Width of fade effect as percentage">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="fade-width-value">15</span>%</span>
<button class="reset-btn" onclick="resetParameter('fade-width', 15)">↺</button>
</div>
</div>
<input type="range" id="fade-width" min="5" max="30" step="1" value="15">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Edge Fade Opacity
<span class="help-tooltip" title="Transparency of fade effect">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="fade-opacity-value">0.95</span></span>
<button class="reset-btn" onclick="resetParameter('fade-opacity', 0.95)">↺</button>
</div>
</div>
<input type="range" id="fade-opacity" min="0.5" max="1" step="0.01" value="0.95">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Image Overlay</span>
</div>
<select id="overlay-enabled">
<option value="false">Disabled</option>
<option value="true">Enabled</option>
</select>
</div>
<div class="control-group" id="overlay-controls" style="display: none;">
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="overlay-color" value="#000000">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="overlay-color-hex" value="#000000" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="overlay-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
<div class="control-label">
<span class="label-text">
Overlay Opacity
<span class="help-tooltip" title="Transparency of color overlay">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="overlay-opacity-value">0.3</span></span>
<button class="reset-btn" onclick="resetParameter('overlay-opacity', 0.3)">↺</button>
</div>
</div>
<input type="range" id="overlay-opacity" min="0" max="1" step="0.05" value="0.3">
</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">Image Width Mode</span>
</div>
<select id="image-width-mode">
<option value="auto" selected>Auto (Responsive)</option>
<option value="custom">Custom Width</option>
</select>
</div>
<div class="control-group" id="custom-width-controls" style="display: none;">
<div class="control-label">
<span class="label-text">
Custom Width
<span class="help-tooltip" title="Fixed width for all images">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="custom-image-width-value">300</span>px</span>
<button class="reset-btn" onclick="resetParameter('custom-image-width', 300)">↺</button>
</div>
</div>
<input type="range" id="custom-image-width" min="100" max="800" step="10" value="300">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Carousel Height</span>
</div>
<select id="carousel-height-mode">
<option value="full" selected>Full Height</option>
<option value="custom">Custom Height</option>
</select>
</div>
<div class="control-group" id="custom-height-controls" style="display: none;">
<div class="control-label">
<span class="label-text">
Custom Height
<span class="help-tooltip" title="Height as percentage of container">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="carousel-height-value">70</span>%</span>
<button class="reset-btn" onclick="resetParameter('carousel-height', 70)">↺</button>
</div>
</div>
<input type="range" id="carousel-height" min="10" max="100" step="5" value="70">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Vertical Position</span>
</div>
<select id="vertical-position">
<option value="center" selected>Center</option>
<option value="top">Top</option>
<option value="bottom">Bottom</option>
</select>
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Image URLs
<div class="card-actions">
<button class="card-action-btn" id="reset-images" title="Reset Image URLs">↺</button>
</div>
</div>
<div class="card-content">
<div id="image-urls-container">
<div class="image-url-group">
<div class="control-label">
<span class="label-text">Image 1</span>
</div>
<div class="image-url-input">
<input type="text" placeholder="https://example.com/image1.jpg" class="url-input" data-index="1" value="https://images.pexels.com/photos/2662116/pexels-photo-2662116.jpeg?auto=compress&cs=tinysrgb&w=800">
</div>
</div>
<div class="image-url-group">
<div class="control-label">
<span class="label-text">Image 2</span>
</div>
<div class="image-url-input">
<input type="text" placeholder="https://example.com/image2.jpg" class="url-input" data-index="2" value="https://images.pexels.com/photos/1366957/pexels-photo-1366957.jpeg?auto=compress&cs=tinysrgb&w=800">
</div>
</div>
<div class="image-url-group">
<div class="control-label">
<span class="label-text">Image 3</span>
</div>
<div class="image-url-input">
<input type="text" placeholder="https://example.com/image3.jpg" class="url-input" data-index="3" value="https://images.pexels.com/photos/2559941/pexels-photo-2559941.jpeg?auto=compress&cs=tinysrgb&w=800">
</div>
</div>
<div class="image-url-group">
<div class="control-label">
<span class="label-text">Image 4</span>
</div>
<div class="image-url-input">
<input type="text" placeholder="https://example.com/image4.jpg" class="url-input" data-index="4" value="https://images.pexels.com/photos/2387873/pexels-photo-2387873.jpeg?auto=compress&cs=tinysrgb&w=800">
</div>
</div>
</div>
<button class="add-image-btn" id="add-image-btn">
<span>+</span> Add Another Image
</button>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let fadeFlowConfig = {
speed: 10,
direction: 'left',
imageGap: 20,
borderRadius: 8,
blurTheme: 'dark',
fadeWidth: 15,
fadeOpacity: 0.95,
overlayEnabled: false,
overlayColor: '#000000',
overlayOpacity: 0.3,
heightMode: 'full',
customHeight: 70,
verticalPosition: 'center',
mobileSpeed: 0.5,
imageWidthMode: 'auto',
customImageWidth: 300,
images: [
"https://images.pexels.com/photos/2662116/pexels-photo-2662116.jpeg?auto=compress&cs=tinysrgb&w=800",
"https://images.pexels.com/photos/1366957/pexels-photo-1366957.jpeg?auto=compress&cs=tinysrgb&w=800",
"https://images.pexels.com/photos/2559941/pexels-photo-2559941.jpeg?auto=compress&cs=tinysrgb&w=800",
"https://images.pexels.com/photos/2387873/pexels-photo-2387873.jpeg?auto=compress&cs=tinysrgb&w=800"
]
};
const defaultConfig = { ...fadeFlowConfig };
let activeCarousel = null;
class FadeFlowPreview {
constructor(container, config) {
this.container = container;
this.config = config;
this.track = null;
this.animationId = null;
this.currentX = 0;
this.images = [];
this.clones = [];
this.containerWidth = 0;
this.contentWidth = 0;
this.totalContentWidth = 0;
this.isRunning = false;
this.isVisible = true;
this.lastMeasureTime = 0;
this.resizeObserver = null;
this.intersectionObserver = null;
this.isInitialized = false;
this.init();
}
init() {
this.setupIntersectionObserver();
this.setupResizeObserver();
this.createTrack();
}
setupIntersectionObserver() {
this.intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
this.isVisible = entry.isIntersecting;
if (!this.isVisible) {
this.pause();
} else {
this.resume();
}
});
}, {
threshold: 0.1,
rootMargin: '50px'
});
this.intersectionObserver.observe(this.container);
}
setupResizeObserver() {
let lastWidth = this.container.offsetWidth;
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver(this.debounce(() => {
const currentWidth = this.container.offsetWidth;
if (Math.abs(currentWidth - lastWidth) < 10) return;
lastWidth = currentWidth;
if (this.isVisible) {
this.measureDimensions();
this.createSeamlessLoop();
}
}, 300));
this.resizeObserver.observe(this.container);
} else {
let resizeTimer = null;
const handleResize = () => {
const currentWidth = this.container.offsetWidth;
if (Math.abs(currentWidth - lastWidth) < 10) return;
lastWidth = currentWidth;
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (this.isVisible) {
this.measureDimensions();
this.createSeamlessLoop();
}
}, 300);
};
window.addEventListener('resize', handleResize, { passive: true });
this.legacyResizeHandler = handleResize;
}
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
createTrack() {
this.track = this.container.querySelector('.fadeflow-track');
if (!this.track) return;
this.track.innerHTML = '';
this.images = [];
this.clones = [];
this.isInitialized = false;
this.config.images.forEach(url => {
const item = this.createItem(url);
this.images.push(item);
this.track.appendChild(item);
});
this.track.style.willChange = 'transform';
this.track.style.transform = 'translateZ(0)';
this.track.style.backfaceVisibility = 'hidden';
this.preloadImages().then(() => {
this.measureDimensions();
this.createSeamlessLoop();
this.applyStyles();
this.isInitialized = true;
this.start();
});
}
preloadImages() {
const imageElements = this.images.map(item => item.querySelector('img')).filter(img => img);
const promises = imageElements.map(img => {
return new Promise((resolve) => {
if (img.complete && img.naturalWidth > 0) {
resolve();
} else {
const handleLoad = () => {
img.removeEventListener('load', handleLoad);
img.removeEventListener('error', handleError);
resolve();
};
const handleError = () => {
img.removeEventListener('load', handleLoad);
img.removeEventListener('error', handleError);
resolve();
};
img.addEventListener('load', handleLoad);
img.addEventListener('error', handleError);
setTimeout(() => {
if (!img.complete) {
handleError();
}
}, 3000);
}
});
});
return Promise.all(promises);
}
createItem(url) {
const item = document.createElement('div');
item.className = 'fadeflow-item';
item.style.borderRadius = `${this.config.borderRadius}px`;
item.style.position = 'relative';
const img = document.createElement('img');
img.src = url;
img.alt = '';
img.style.pointerEvents = 'none';
img.onerror = function() {
this.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300' viewBox='0 0 400 300'%3E%3Crect width='400' height='300' fill='%23555555'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='24' fill='%23ffffff'%3EImage not found%3C/text%3E%3C/svg%3E";
};
item.appendChild(img);
if (this.config.overlayEnabled) {
const overlay = document.createElement('div');
overlay.className = 'fadeflow-overlay';
overlay.style.position = 'absolute';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = this.config.overlayColor;
overlay.style.opacity = this.config.overlayOpacity;
overlay.style.borderRadius = `${this.config.borderRadius}px`;
overlay.style.pointerEvents = 'none';
item.appendChild(overlay);
}
return item;
}
measureDimensions() {
const now = Date.now();
if (now - this.lastMeasureTime < 100) return;
this.lastMeasureTime = now;
this.containerWidth = this.container.offsetWidth;
if (this.containerWidth === 0) return;
this.contentWidth = 0;
this.images.forEach(img => {
if (img.offsetWidth > 0) {
this.contentWidth += img.offsetWidth + this.config.imageGap;
}
});
this.contentWidth = Math.max(this.contentWidth - this.config.imageGap, 100);
this.updateItemDimensions();
}
updateItemDimensions() {
const isMobile = window.innerWidth < 768;
const gapSize = this.config.imageGap;
let itemWidth;
if (this.config.imageWidthMode === 'custom') {
itemWidth = this.config.customImageWidth;
} else {
const imagesPerView = isMobile ? 1.5 : 2.5;
const gapSpace = gapSize * (imagesPerView - 1);
itemWidth = Math.floor((this.containerWidth / imagesPerView) - (gapSpace / imagesPerView));
}
let itemHeight;
const containerHeight = this.container.offsetHeight;
if (this.config.heightMode === 'custom') {
const customHeightPx = (containerHeight * this.config.customHeight) / 100;
if (isMobile && this.config.imageWidthMode === 'auto') {
const aspectRatio = 1.2;
itemHeight = Math.min(Math.floor(itemWidth / aspectRatio), Math.floor(customHeightPx));
} else {
itemHeight = Math.floor(customHeightPx);
}
} else {
if (isMobile && this.config.imageWidthMode === 'auto') {
itemHeight = Math.floor(containerHeight);
} else {
itemHeight = Math.floor(containerHeight);
}
}
const allItems = [...this.images, ...this.clones];
allItems.forEach(item => {
item.style.width = `${itemWidth}px`;
item.style.height = `${itemHeight}px`;
item.style.marginRight = `${gapSize}px`;
item.style.flexShrink = '0';
});
this.contentWidth = (itemWidth + gapSize) * this.images.length - gapSize;
}
createSeamlessLoop() {
if (this.contentWidth === 0 || this.containerWidth === 0) return;
this.clones.forEach(clone => clone.remove());
this.clones = [];
const bufferMultiplier = 2.5;
const requiredWidth = this.containerWidth * bufferMultiplier;
const clonesNeeded = Math.ceil(requiredWidth / this.contentWidth) + 1;
for (let i = 0; i < clonesNeeded; i++) {
this.images.forEach(img => {
const clone = img.cloneNode(true);
clone.classList.add('fadeflow-clone');
clone.style.willChange = 'transform';
this.track.appendChild(clone);
this.clones.push(clone);
});
}
this.updateItemDimensions();
this.totalContentWidth = this.contentWidth * (1 + clonesNeeded);
this.initializeSeamlessPosition();
}
initializeSeamlessPosition() {
this.currentX = 0;
this.track.style.transform = `translateX(${this.currentX}px)`;
}
applyStyles() {
const fadeflowContainer = this.container.querySelector('.fadeflow-container');
const fadeflowLane = this.container.querySelector('.fadeflow-lane');
const leftFade = this.container.querySelector('.fadeflow-edge-fade.left');
const rightFade = this.container.querySelector('.fadeflow-edge-fade.right');
if (!fadeflowContainer || !leftFade || !rightFade) return;
const isMobile = window.innerWidth < 768;
if (this.config.heightMode === 'custom') {
const containerHeight = this.container.offsetHeight;
const customHeightPx = (containerHeight * this.config.customHeight) / 100;
fadeflowContainer.style.height = `${customHeightPx}px`;
fadeflowContainer.style.position = 'absolute';
switch (this.config.verticalPosition) {
case 'top':
fadeflowContainer.style.top = '0';
fadeflowContainer.style.bottom = 'auto';
fadeflowContainer.style.transform = 'none';
break;
case 'bottom':
fadeflowContainer.style.bottom = '0';
fadeflowContainer.style.top = 'auto';
fadeflowContainer.style.transform = 'none';
break;
default:
fadeflowContainer.style.top = '50%';
fadeflowContainer.style.bottom = 'auto';
fadeflowContainer.style.transform = 'translateY(-50%)';
break;
}
} else {
fadeflowContainer.style.height = '100%';
fadeflowContainer.style.position = 'relative';
fadeflowContainer.style.transform = 'none';
if (isMobile && fadeflowLane) {
fadeflowLane.style.alignItems = 'flex-start';
}
}
const fadeColor = this.config.blurTheme === 'dark'
? `rgba(0, 0, 0, ${this.config.fadeOpacity})`
: `rgba(255, 255, 255, ${this.config.fadeOpacity})`;
const fadeOutColor = this.config.blurTheme === 'dark'
? 'rgba(0, 0, 0, 0)'
: 'rgba(255, 255, 255, 0)';
leftFade.style.width = `${this.config.fadeWidth}%`;
leftFade.style.background = `linear-gradient(to right, ${fadeColor} 0%, ${fadeOutColor} 100%)`;
rightFade.style.width = `${this.config.fadeWidth}%`;
rightFade.style.background = `linear-gradient(to left, ${fadeColor} 0%, ${fadeOutColor} 100%)`;
}
start() {
if (this.isRunning || !this.isVisible || !this.isInitialized) return;
this.isRunning = true;
this.animate();
}
pause() {
this.isRunning = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
resume() {
if (!this.isRunning && this.isVisible && this.isInitialized) {
this.start();
}
}
animate() {
if (!this.isRunning || !this.isVisible || !this.isInitialized) return;
const isMobile = window.innerWidth < 768;
const baseDuration = this.config.speed;
const deviceFactor = isMobile ? this.config.mobileSpeed : 1;
const speed = (60 / (baseDuration * deviceFactor)) * 0.5;
if (this.config.direction === 'right') {
this.currentX += speed;
if (this.currentX >= this.contentWidth + this.config.imageGap) {
this.currentX = 0;
}
} else {
this.currentX -= speed;
if (Math.abs(this.currentX) >= this.contentWidth + this.config.imageGap) {
this.currentX = 0;
}
}
this.track.style.transform = `translateX(${this.currentX}px)`;
this.animationId = requestAnimationFrame(() => this.animate());
}
destroy() {
this.pause();
this.clones.forEach(clone => clone.remove());
this.clones = [];
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.legacyResizeHandler) {
window.removeEventListener('resize', this.legacyResizeHandler);
}
}
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
this.createTrack();
}
}
function initFadeFlowCarousel() {
const container = document.getElementById('fadeflow-container');
if (!container) return;
const previewContent = document.querySelector('.preview-content');
if (previewContent) {
previewContent.style.display = 'none';
}
if (!activeCarousel) {
container.innerHTML = '';
const preview = document.createElement('div');
preview.className = 'fadeflow-preview';
const fadeflowContainer = document.createElement('div');
fadeflowContainer.className = 'fadeflow-container';
const lane = document.createElement('div');
lane.className = 'fadeflow-lane';
const track = document.createElement('div');
track.className = 'fadeflow-track';
const leftFade = document.createElement('div');
leftFade.className = 'fadeflow-edge-fade left';
const rightFade = document.createElement('div');
rightFade.className = 'fadeflow-edge-fade right';
lane.appendChild(track);
fadeflowContainer.appendChild(lane);
fadeflowContainer.appendChild(leftFade);
fadeflowContainer.appendChild(rightFade);
preview.appendChild(fadeflowContainer);
container.appendChild(preview);
setTimeout(() => {
activeCarousel = new FadeFlowPreview(preview, fadeFlowConfig);
}, 100);
}
}
function updateCarouselPreview() {
if (activeCarousel) {
activeCarousel.destroy();
activeCarousel = null;
}
initFadeFlowCarousel();
}
function hexToRgb(hex) {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
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 rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 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 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 generateRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
}
function generateRandomPalette() {
fadeFlowConfig.speed = Math.floor(Math.random() * 58) + 2;
fadeFlowConfig.mobileSpeed = Math.round((Math.random() * 0.9 + 0.1) * 10) / 10;
fadeFlowConfig.imageGap = Math.floor(Math.random() * 35) + 5;
fadeFlowConfig.borderRadius = Math.floor(Math.random() * 24);
fadeFlowConfig.blurTheme = Math.random() > 0.5 ? 'dark' : 'light';
fadeFlowConfig.fadeWidth = Math.floor(Math.random() * 25) + 5;
fadeFlowConfig.fadeOpacity = Math.round((Math.random() * 0.5 + 0.5) * 100) / 100;
fadeFlowConfig.overlayEnabled = Math.random() > 0.5;
if (fadeFlowConfig.overlayEnabled) {
fadeFlowConfig.overlayColor = generateRandomColor();
fadeFlowConfig.overlayOpacity = Math.round((Math.random() * 0.8 + 0.1) * 100) / 100;
}
document.getElementById('animation-speed').value = fadeFlowConfig.speed;
document.getElementById('mobile-speed').value = fadeFlowConfig.mobileSpeed;
document.getElementById('image-gap').value = fadeFlowConfig.imageGap;
document.getElementById('border-radius').value = fadeFlowConfig.borderRadius;
document.getElementById('blur-theme').value = fadeFlowConfig.blurTheme;
document.getElementById('fade-width').value = fadeFlowConfig.fadeWidth;
document.getElementById('fade-opacity').value = fadeFlowConfig.fadeOpacity;
document.getElementById('overlay-enabled').value = fadeFlowConfig.overlayEnabled;
document.getElementById('animation-speed-value').textContent = fadeFlowConfig.speed;
document.getElementById('mobile-speed-value').textContent = fadeFlowConfig.mobileSpeed;
document.getElementById('image-gap-value').textContent = fadeFlowConfig.imageGap;
document.getElementById('border-radius-value').textContent = fadeFlowConfig.borderRadius;
document.getElementById('fade-width-value').textContent = fadeFlowConfig.fadeWidth;
document.getElementById('fade-opacity-value').textContent = fadeFlowConfig.fadeOpacity;
if (fadeFlowConfig.overlayEnabled) {
updateOverlayColorInputs();
document.getElementById('overlay-opacity').value = fadeFlowConfig.overlayOpacity;
document.getElementById('overlay-opacity-value').textContent = fadeFlowConfig.overlayOpacity;
}
updateOverlayControls();
updateCarouselPreview();
showNotification('Random palette generated!');
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos para todos los elementos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const codeId = generateUniqueId();
const attributeId = generateUniqueId();
// Obtener el JavaScript 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": {
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
}
}
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": {
"_alignItems": "center",
"_attributes": [
{
"id": attributeId,
"name": "data-fadeflow"
}
],
"_width": "100%",
"_height": "400"
},
"label": "Fade Flow Container"
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Fade Flow JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksJSON, null, 2);
}
function generateJavaScriptCode() {
const validUrls = fadeFlowConfig.images.filter(url => url.trim() !== '');
return `(function() {
class FadeFlowCarousel {
constructor() {
this.config = {
speed: ${fadeFlowConfig.speed},
direction: "${fadeFlowConfig.direction}",
imageGap: ${fadeFlowConfig.imageGap},
borderRadius: ${fadeFlowConfig.borderRadius},
blurTheme: "${fadeFlowConfig.blurTheme}",
fadeWidth: ${fadeFlowConfig.fadeWidth},
fadeOpacity: ${fadeFlowConfig.fadeOpacity},
overlayEnabled: ${fadeFlowConfig.overlayEnabled},
overlayColor: "${fadeFlowConfig.overlayColor}",
overlayOpacity: ${fadeFlowConfig.overlayOpacity},
heightMode: "${fadeFlowConfig.heightMode}",
customHeight: ${fadeFlowConfig.customHeight},
verticalPosition: "${fadeFlowConfig.verticalPosition}",
mobileSpeed: ${fadeFlowConfig.mobileSpeed},
imageWidthMode: "${fadeFlowConfig.imageWidthMode}",
customImageWidth: ${fadeFlowConfig.customImageWidth},
images: ${JSON.stringify(validUrls)}
};
this.carousels = [];
this.init();
}
init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.findCarousels());
} else {
this.findCarousels();
}
}
findCarousels() {
document.querySelectorAll('[data-fadeflow]').forEach(container => {
const originalPosition = window.getComputedStyle(container).position;
if (originalPosition === 'static') {
container.style.position = 'relative';
}
this.createCarousel(container);
});
}
createCarousel(container) {
if (!container.querySelector('.fadeflow-container')) {
const fadeflowWrapper = document.createElement('div');
fadeflowWrapper.className = 'fadeflow-wrapper';
fadeflowWrapper.style.position = 'absolute';
fadeflowWrapper.style.top = '0';
fadeflowWrapper.style.left = '0';
fadeflowWrapper.style.width = '100%';
fadeflowWrapper.style.height = '100%';
fadeflowWrapper.style.zIndex = '0';
fadeflowWrapper.style.pointerEvents = 'none';
fadeflowWrapper.style.overflow = 'hidden';
const carouselHTML = \`
<div class="fadeflow-container">
<div class="fadeflow-lane">
<div class="fadeflow-track"></div>
</div>
<div class="fadeflow-edge-fade left"></div>
<div class="fadeflow-edge-fade right"></div>
</div>
\`;
fadeflowWrapper.innerHTML = carouselHTML;
if (container.firstChild) {
container.insertBefore(fadeflowWrapper, container.firstChild);
} else {
container.appendChild(fadeflowWrapper);
}
this.applyStyles(fadeflowWrapper);
const carouselInstance = new FadeFlowInstance(fadeflowWrapper, this.config);
this.carousels.push(carouselInstance);
container._fadeflowInstance = carouselInstance;
}
}
applyStyles(wrapper) {
if (!document.getElementById('fadeflow-styles')) {
const styleEl = document.createElement('style');
styleEl.id = 'fadeflow-styles';
const fadeColor = this.config.blurTheme === 'dark'
? \`rgba(0, 0, 0, \${this.config.fadeOpacity})\`
: \`rgba(255, 255, 255, \${this.config.fadeOpacity})\`;
const fadeOutColor = this.config.blurTheme === 'dark'
? 'rgba(0, 0, 0, 0)'
: 'rgba(255, 255, 255, 0)';
const css = \`
.fadeflow-wrapper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
overflow: hidden;
}
.fadeflow-container {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: \${this.config.borderRadius}px;
margin: 0;
padding: 0;
}
.fadeflow-lane {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
overflow: hidden;
margin: 0;
padding: 0;
}
.fadeflow-track {
display: flex;
height: 100%;
min-width: 100%;
will-change: transform;
backface-visibility: hidden;
perspective: 1000px;
transform: translateZ(0);
margin: 0;
padding: 0;
}
.fadeflow-item {
height: 100%;
flex: 0 0 auto;
overflow: hidden;
border-radius: \${this.config.borderRadius}px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
position: relative;
margin: 0;
padding: 0;
pointer-events: none;
will-change: transform;
flex-shrink: 0;
}
.fadeflow-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
pointer-events: none;
}
.fadeflow-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: \${this.config.overlayColor};
opacity: \${this.config.overlayOpacity};
border-radius: \${this.config.borderRadius}px;
pointer-events: none;
}
.fadeflow-edge-fade {
position: absolute;
top: 0;
height: 100%;
width: \${this.config.fadeWidth}%;
z-index: 3;
pointer-events: none;
}
.fadeflow-edge-fade.left {
left: 0;
background: linear-gradient(to right, \${fadeColor} 0%, \${fadeOutColor} 100%);
}
.fadeflow-edge-fade.right {
right: 0;
background: linear-gradient(to left, \${fadeColor} 0%, \${fadeOutColor} 100%);
}
@media (max-width: 768px) {
.fadeflow-lane {
align-items: flex-start;
}
}
\`;
styleEl.textContent = css;
document.head.appendChild(styleEl);
}
}
}
class FadeFlowInstance {
constructor(wrapper, config) {
this.wrapper = wrapper;
this.config = config;
this.track = wrapper.querySelector('.fadeflow-track');
this.animationId = null;
this.currentX = 0;
this.items = [];
this.clones = [];
this.containerWidth = 0;
this.contentWidth = 0;
this.totalContentWidth = 0;
this.isRunning = false;
this.isVisible = true;
this.isInitialized = false;
this.lastMeasureTime = 0;
this.init();
}
init() {
this.setupIntersectionObserver();
this.setupResizeObserver();
this.createTrack();
}
setupIntersectionObserver() {
this.intersectionObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
this.isVisible = entry.isIntersecting;
if (!this.isVisible) {
this.pause();
} else {
this.resume();
}
});
}, {
threshold: 0.1,
rootMargin: '50px'
});
this.intersectionObserver.observe(this.wrapper);
}
setupResizeObserver() {
let lastWidth = this.wrapper.offsetWidth;
if (window.ResizeObserver) {
this.resizeObserver = new ResizeObserver(this.debounce(() => {
const currentWidth = this.wrapper.offsetWidth;
if (Math.abs(currentWidth - lastWidth) < 10) return;
lastWidth = currentWidth;
if (this.isVisible) {
this.measureDimensions();
this.createSeamlessLoop();
}
}, 300));
this.resizeObserver.observe(this.wrapper);
} else {
let resizeTimer = null;
const handleResize = () => {
const currentWidth = this.wrapper.offsetWidth;
if (Math.abs(currentWidth - lastWidth) < 10) return;
lastWidth = currentWidth;
clearTimeout(resizeTimer);
resizeTimer = setTimeout(() => {
if (this.isVisible) {
this.measureDimensions();
this.createSeamlessLoop();
}
}, 300);
};
window.addEventListener('resize', handleResize, { passive: true });
this.legacyResizeHandler = handleResize;
}
}
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
createTrack() {
this.track = this.wrapper.querySelector('.fadeflow-track');
if (!this.track) return;
this.track.innerHTML = '';
this.items = [];
this.clones = [];
this.isInitialized = false;
this.config.images.forEach(url => {
const item = this.createItem(url);
this.items.push(item);
this.track.appendChild(item);
});
this.track.style.willChange = 'transform';
this.track.style.transform = 'translateZ(0)';
this.track.style.backfaceVisibility = 'hidden';
this.preloadImages().then(() => {
this.measureDimensions();
this.createSeamlessLoop();
this.applyContainerStyles();
this.isInitialized = true;
this.start();
});
}
preloadImages() {
const imageElements = this.items.map(item => item.querySelector('img')).filter(img => img);
const promises = imageElements.map(img => {
return new Promise((resolve) => {
if (img.complete && img.naturalWidth > 0) {
resolve();
} else {
const handleLoad = () => {
img.removeEventListener('load', handleLoad);
img.removeEventListener('error', handleError);
resolve();
};
const handleError = () => {
img.removeEventListener('load', handleLoad);
img.removeEventListener('error', handleError);
resolve();
};
img.addEventListener('load', handleLoad);
img.addEventListener('error', handleError);
setTimeout(() => {
if (!img.complete) {
handleError();
}
}, 3000);
}
});
});
return Promise.all(promises);
}
createItem(url) {
const item = document.createElement('div');
item.className = 'fadeflow-item';
item.style.borderRadius = \`\${this.config.borderRadius}px\`;
item.style.position = 'relative';
const img = document.createElement('img');
img.src = url;
img.alt = '';
img.style.pointerEvents = 'none';
img.onerror = function() {
this.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300' viewBox='0 0 400 300'%3E%3Crect width='400' height='300' fill='%23555555'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='24' fill='%23ffffff'%3EImage not found%3C/text%3E%3C/svg%3E";
};
item.appendChild(img);
if (this.config.overlayEnabled) {
const overlay = document.createElement('div');
overlay.className = 'fadeflow-overlay';
overlay.style.position = 'absolute';
overlay.style.top = '0';
overlay.style.left = '0';
overlay.style.width = '100%';
overlay.style.height = '100%';
overlay.style.backgroundColor = this.config.overlayColor;
overlay.style.opacity = this.config.overlayOpacity;
overlay.style.borderRadius = \`\${this.config.borderRadius}px\`;
overlay.style.pointerEvents = 'none';
item.appendChild(overlay);
}
return item;
}
measureDimensions() {
const now = Date.now();
if (now - this.lastMeasureTime < 100) return;
this.lastMeasureTime = now;
this.containerWidth = this.wrapper.offsetWidth;
if (this.containerWidth === 0) return;
this.contentWidth = 0;
this.items.forEach(item => {
if (item.offsetWidth > 0) {
this.contentWidth += item.offsetWidth + this.config.imageGap;
}
});
this.contentWidth = Math.max(this.contentWidth - this.config.imageGap, 100);
this.updateItemDimensions();
}
updateItemDimensions() {
const isMobile = window.innerWidth < 768;
const gapSize = this.config.imageGap;
let itemWidth;
if (this.config.imageWidthMode === 'custom') {
itemWidth = this.config.customImageWidth;
} else {
const imagesPerView = isMobile ? 1.5 : 2.5;
const gapSpace = gapSize * (imagesPerView - 1);
itemWidth = Math.floor((this.containerWidth / imagesPerView) - (gapSpace / imagesPerView));
}
let itemHeight;
const containerHeight = this.wrapper.offsetHeight;
if (this.config.heightMode === 'custom') {
const customHeightPx = (containerHeight * this.config.customHeight) / 100;
if (isMobile && this.config.imageWidthMode === 'auto') {
const aspectRatio = 1.2;
itemHeight = Math.min(Math.floor(itemWidth / aspectRatio), Math.floor(customHeightPx));
} else {
itemHeight = Math.floor(customHeightPx);
}
} else {
if (isMobile && this.config.imageWidthMode === 'auto') {
itemHeight = Math.floor(containerHeight);
} else {
itemHeight = Math.floor(containerHeight);
}
}
const allItems = [...this.items, ...this.clones];
allItems.forEach(item => {
item.style.width = \`\${itemWidth}px\`;
item.style.height = \`\${itemHeight}px\`;
item.style.marginRight = \`\${gapSize}px\`;
item.style.flexShrink = '0';
});
this.contentWidth = (itemWidth + gapSize) * this.items.length - gapSize;
}
createSeamlessLoop() {
if (this.contentWidth === 0 || this.containerWidth === 0) return;
this.clones.forEach(clone => clone.remove());
this.clones = [];
const bufferMultiplier = 2.5;
const requiredWidth = this.containerWidth * bufferMultiplier;
const clonesNeeded = Math.ceil(requiredWidth / this.contentWidth) + 1;
for (let i = 0; i < clonesNeeded; i++) {
this.items.forEach(item => {
const clone = item.cloneNode(true);
clone.classList.add('fadeflow-clone');
clone.style.willChange = 'transform';
this.track.appendChild(clone);
this.clones.push(clone);
});
}
this.updateItemDimensions();
this.totalContentWidth = this.contentWidth * (1 + clonesNeeded);
this.initializeSeamlessPosition();
}
initializeSeamlessPosition() {
this.currentX = 0;
this.track.style.transform = \`translateX(\${this.currentX}px)\`;
}
applyContainerStyles() {
const fadeflowContainer = this.wrapper.querySelector('.fadeflow-container');
const fadeflowLane = this.wrapper.querySelector('.fadeflow-lane');
const leftFade = this.wrapper.querySelector('.fadeflow-edge-fade.left');
const rightFade = this.wrapper.querySelector('.fadeflow-edge-fade.right');
if (!fadeflowContainer || !leftFade || !rightFade) return;
const isMobile = window.innerWidth < 768;
if (this.config.heightMode === 'custom') {
const containerHeight = this.wrapper.offsetHeight;
const customHeightPx = (containerHeight * this.config.customHeight) / 100;
fadeflowContainer.style.height = \`\${customHeightPx}px\`;
fadeflowContainer.style.position = 'absolute';
switch (this.config.verticalPosition) {
case 'top':
fadeflowContainer.style.top = '0';
fadeflowContainer.style.bottom = 'auto';
fadeflowContainer.style.transform = 'none';
break;
case 'bottom':
fadeflowContainer.style.bottom = '0';
fadeflowContainer.style.top = 'auto';
fadeflowContainer.style.transform = 'none';
break;
default:
fadeflowContainer.style.top = '50%';
fadeflowContainer.style.bottom = 'auto';
fadeflowContainer.style.transform = 'translateY(-50%)';
break;
}
} else {
fadeflowContainer.style.height = '100%';
fadeflowContainer.style.position = 'relative';
fadeflowContainer.style.transform = 'none';
if (isMobile && fadeflowLane) {
fadeflowLane.style.alignItems = 'flex-start';
}
}
const fadeColor = this.config.blurTheme === 'dark'
? \`rgba(0, 0, 0, \${this.config.fadeOpacity})\`
: \`rgba(255, 255, 255, \${this.config.fadeOpacity})\`;
const fadeOutColor = this.config.blurTheme === 'dark'
? 'rgba(0, 0, 0, 0)'
: 'rgba(255, 255, 255, 0)';
leftFade.style.width = \`\${this.config.fadeWidth}%\`;
leftFade.style.background = \`linear-gradient(to right, \${fadeColor} 0%, \${fadeOutColor} 100%)\`;
rightFade.style.width = \`\${this.config.fadeWidth}%\`;
rightFade.style.background = \`linear-gradient(to left, \${fadeColor} 0%, \${fadeOutColor} 100%)\`;
}
start() {
if (this.isRunning || !this.isVisible || !this.isInitialized) return;
this.isRunning = true;
this.animate();
}
pause() {
this.isRunning = false;
if (this.animationId) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
}
resume() {
if (!this.isRunning && this.isVisible && this.isInitialized) {
this.start();
}
}
animate() {
if (!this.isRunning || !this.isVisible || !this.isInitialized) return;
const isMobile = window.innerWidth < 768;
const baseDuration = this.config.speed;
const deviceFactor = isMobile ? this.config.mobileSpeed : 1;
const speed = (60 / (baseDuration * deviceFactor)) * 0.5;
if (this.config.direction === 'right') {
this.currentX += speed;
if (this.currentX >= this.contentWidth + this.config.imageGap) {
this.currentX = 0;
}
} else {
this.currentX -= speed;
if (Math.abs(this.currentX) >= this.contentWidth + this.config.imageGap) {
this.currentX = 0;
}
}
this.track.style.transform = \`translateX(\${this.currentX}px)\`;
this.animationId = requestAnimationFrame(() => this.animate());
}
destroy() {
this.pause();
this.clones.forEach(clone => clone.remove());
this.clones = [];
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
if (this.legacyResizeHandler) {
window.removeEventListener('resize', this.legacyResizeHandler);
}
}
}
new FadeFlowCarousel();
})();`;
}
function copyJsToClipboard() {
const jsCode = generateJavaScriptCode();
navigator.clipboard.writeText(jsCode)
.then(() => {
showNotification('JavaScript code copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = jsCode;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('JavaScript code copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyFullSectionToClipboard() {
const sectionJSON = generateFullSectionJSON();
navigator.clipboard.writeText(sectionJSON)
.then(() => {
showNotification('Full section JSON copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = sectionJSON;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('Full section JSON copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy to clipboard', 'error');
});
}
window.resetParameter = function(parameterId, defaultValue) {
const element = document.getElementById(parameterId);
if (element) {
element.value = defaultValue;
const valueElement = document.getElementById(`${parameterId}-value`);
if (valueElement) {
valueElement.textContent = defaultValue;
}
switch (parameterId) {
case 'animation-speed':
fadeFlowConfig.speed = defaultValue;
updateCarouselPreview();
break;
case 'mobile-speed':
fadeFlowConfig.mobileSpeed = defaultValue;
updateCarouselPreview();
break;
case 'image-gap':
fadeFlowConfig.imageGap = defaultValue;
updateCarouselPreview();
break;
case 'border-radius':
fadeFlowConfig.borderRadius = defaultValue;
updateCarouselPreview();
break;
case 'fade-width':
fadeFlowConfig.fadeWidth = defaultValue;
updateCarouselPreview();
break;
case 'fade-opacity':
fadeFlowConfig.fadeOpacity = defaultValue;
updateCarouselPreview();
break;
case 'overlay-opacity':
fadeFlowConfig.overlayOpacity = defaultValue;
updateCarouselPreview();
break;
case 'custom-image-width':
fadeFlowConfig.customImageWidth = defaultValue;
updateCarouselPreview();
break;
case 'carousel-height':
fadeFlowConfig.customHeight = defaultValue;
updateCarouselPreview();
break;
}
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function updateOverlayColorInputs() {
const colorInput = document.getElementById('overlay-color');
const hexInput = document.getElementById('overlay-color-hex');
const hslInput = document.getElementById('overlay-color-hsl');
if (colorInput && hexInput && hslInput) {
colorInput.value = fadeFlowConfig.overlayColor;
hexInput.value = fadeFlowConfig.overlayColor;
const hsl = hexToRgb(fadeFlowConfig.overlayColor);
if (hsl) {
const hslColor = rgbToHsl(hsl.r, hsl.g, hsl.b);
hslInput.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.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', fadeFlowConfig.overlayColor);
}
}
}
function updateOverlayControls() {
const overlayControls = document.getElementById('overlay-controls');
const overlayEnabled = document.getElementById('overlay-enabled');
if (overlayControls && overlayEnabled) {
overlayControls.style.display = fadeFlowConfig.overlayEnabled ? 'block' : 'none';
overlayEnabled.value = fadeFlowConfig.overlayEnabled ? 'true' : 'false';
}
}
function initializeUI() {
setTimeout(() => {
initFadeFlowCarousel();
}, 100);
const instructionsToggle = document.getElementById('instructions-toggle');
const instructionsContent = document.getElementById('instructions-content');
const instructionsCard = document.getElementById('instructions-card');
const toggleIcon = instructionsToggle?.querySelector('.toggle-icon');
if (instructionsToggle && instructionsContent && instructionsCard && toggleIcon) {
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');
}
});
}
const quickAttribute = document.getElementById('quick-attribute');
if (quickAttribute) {
quickAttribute.addEventListener('click', () => {
copyToClipboard('data-fadeflow');
});
}
const downloadConfig = document.getElementById('download-config');
if (downloadConfig) {
downloadConfig.addEventListener('click', () => {
copyJsToClipboard();
});
}
const copyFullSection = document.getElementById('copy-full-section');
if (copyFullSection) {
copyFullSection.addEventListener('click', () => {
copyFullSectionToClipboard();
});
}
const generateRandomPaletteBtn = document.getElementById('generate-random-palette');
if (generateRandomPaletteBtn) {
generateRandomPaletteBtn.addEventListener('click', () => {
generateRandomPalette();
});
}
const resetAnimation = document.getElementById('reset-animation');
if (resetAnimation) {
resetAnimation.addEventListener('click', () => {
fadeFlowConfig.speed = defaultConfig.speed;
fadeFlowConfig.mobileSpeed = defaultConfig.mobileSpeed;
fadeFlowConfig.direction = defaultConfig.direction;
fadeFlowConfig.imageGap = defaultConfig.imageGap;
const animationSpeed = document.getElementById('animation-speed');
const mobileSpeed = document.getElementById('mobile-speed');
const animationDirection = document.getElementById('animation-direction');
const imageGap = document.getElementById('image-gap');
const animationSpeedValue = document.getElementById('animation-speed-value');
const mobileSpeedValue = document.getElementById('mobile-speed-value');
const imageGapValue = document.getElementById('image-gap-value');
if (animationSpeed) animationSpeed.value = defaultConfig.speed;
if (mobileSpeed) mobileSpeed.value = defaultConfig.mobileSpeed;
if (animationDirection) animationDirection.value = defaultConfig.direction;
if (imageGap) imageGap.value = defaultConfig.imageGap;
if (animationSpeedValue) animationSpeedValue.textContent = defaultConfig.speed;
if (mobileSpeedValue) mobileSpeedValue.textContent = defaultConfig.mobileSpeed;
if (imageGapValue) imageGapValue.textContent = defaultConfig.imageGap;
updateCarouselPreview();
showNotification('Animation settings reset');
});
}
const resetAppearance = document.getElementById('reset-appearance');
if (resetAppearance) {
resetAppearance.addEventListener('click', () => {
fadeFlowConfig.borderRadius = defaultConfig.borderRadius;
fadeFlowConfig.blurTheme = defaultConfig.blurTheme;
fadeFlowConfig.fadeWidth = defaultConfig.fadeWidth;
fadeFlowConfig.fadeOpacity = defaultConfig.fadeOpacity;
fadeFlowConfig.overlayEnabled = defaultConfig.overlayEnabled;
fadeFlowConfig.overlayColor = defaultConfig.overlayColor;
fadeFlowConfig.overlayOpacity = defaultConfig.overlayOpacity;
const borderRadius = document.getElementById('border-radius');
const blurTheme = document.getElementById('blur-theme');
const fadeWidth = document.getElementById('fade-width');
const fadeOpacity = document.getElementById('fade-opacity');
const overlayEnabled = document.getElementById('overlay-enabled');
const overlayOpacity = document.getElementById('overlay-opacity');
const borderRadiusValue = document.getElementById('border-radius-value');
const fadeWidthValue = document.getElementById('fade-width-value');
const fadeOpacityValue = document.getElementById('fade-opacity-value');
const overlayOpacityValue = document.getElementById('overlay-opacity-value');
if (borderRadius) borderRadius.value = defaultConfig.borderRadius;
if (blurTheme) blurTheme.value = defaultConfig.blurTheme;
if (fadeWidth) fadeWidth.value = defaultConfig.fadeWidth;
if (fadeOpacity) fadeOpacity.value = defaultConfig.fadeOpacity;
if (overlayEnabled) overlayEnabled.value = defaultConfig.overlayEnabled;
if (overlayOpacity) overlayOpacity.value = defaultConfig.overlayOpacity;
if (borderRadiusValue) borderRadiusValue.textContent = defaultConfig.borderRadius;
if (fadeWidthValue) fadeWidthValue.textContent = defaultConfig.fadeWidth;
if (fadeOpacityValue) fadeOpacityValue.textContent = defaultConfig.fadeOpacity;
if (overlayOpacityValue) overlayOpacityValue.textContent = defaultConfig.overlayOpacity;
updateOverlayColorInputs();
updateOverlayControls();
updateCarouselPreview();
showNotification('Appearance settings reset');
});
}
const resetAdvanced = document.getElementById('reset-advanced');
if (resetAdvanced) {
resetAdvanced.addEventListener('click', () => {
fadeFlowConfig.imageWidthMode = defaultConfig.imageWidthMode;
fadeFlowConfig.customImageWidth = defaultConfig.customImageWidth;
fadeFlowConfig.heightMode = defaultConfig.heightMode;
fadeFlowConfig.customHeight = defaultConfig.customHeight;
fadeFlowConfig.verticalPosition = defaultConfig.verticalPosition;
const imageWidthMode = document.getElementById('image-width-mode');
const customImageWidth = document.getElementById('custom-image-width');
const carouselHeightMode = document.getElementById('carousel-height-mode');
const carouselHeight = document.getElementById('carousel-height');
const verticalPosition = document.getElementById('vertical-position');
const customImageWidthValue = document.getElementById('custom-image-width-value');
const carouselHeightValue = document.getElementById('carousel-height-value');
if (imageWidthMode) imageWidthMode.value = defaultConfig.imageWidthMode;
if (customImageWidth) customImageWidth.value = defaultConfig.customImageWidth;
if (carouselHeightMode) carouselHeightMode.value = defaultConfig.heightMode;
if (carouselHeight) carouselHeight.value = defaultConfig.customHeight;
if (verticalPosition) verticalPosition.value = defaultConfig.verticalPosition;
if (customImageWidthValue) customImageWidthValue.textContent = defaultConfig.customImageWidth;
if (carouselHeightValue) carouselHeightValue.textContent = defaultConfig.customHeight;
updateAdvancedControls();
updateCarouselPreview();
showNotification('Advanced settings reset');
});
}
const resetImages = document.getElementById('reset-images');
if (resetImages) {
resetImages.addEventListener('click', () => {
fadeFlowConfig.images = [...defaultConfig.images];
document.querySelectorAll('.url-input').forEach((input, index) => {
if (index < defaultConfig.images.length) {
input.value = defaultConfig.images[index];
} else {
input.value = '';
}
});
updateCarouselPreview();
showNotification('Image URLs reset');
});
}
const overlayEnabled = document.getElementById('overlay-enabled');
if (overlayEnabled) {
overlayEnabled.addEventListener('change', function() {
fadeFlowConfig.overlayEnabled = this.value === 'true';
updateOverlayControls();
updateCarouselPreview();
});
}
const colorInput = document.getElementById('overlay-color');
const hexInput = document.getElementById('overlay-color-hex');
const hslInput = document.getElementById('overlay-color-hsl');
if (colorInput && hexInput && hslInput) {
const hsl = hexToRgb(colorInput.value);
if (hsl) {
const hslColor = rgbToHsl(hsl.r, hsl.g, hsl.b);
hslInput.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.l}%)`;
}
colorInput.addEventListener('input', () => {
const color = colorInput.value;
hexInput.value = color;
const rgb = hexToRgb(color);
if (rgb) {
const hslColor = rgbToHsl(rgb.r, rgb.g, rgb.b);
hslInput.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.l}%)`;
}
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
fadeFlowConfig.overlayColor = color;
const colorPickerContainer = colorInput.closest('.color-row')?.querySelector('.color-picker-container');
if (colorPickerContainer) {
colorPickerContainer.style.setProperty('--selected-color', color);
}
updateCarouselPreview();
});
hexInput.addEventListener('input', (e) => {
let hex = e.target.value;
hex = formatHex(hex);
e.target.value = hex;
if (isValidHex(hex)) {
colorInput.value = hex;
const rgb = hexToRgb(hex);
if (rgb) {
const hslColor = rgbToHsl(rgb.r, rgb.g, rgb.b);
hslInput.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.l}%)`;
}
fadeFlowConfig.overlayColor = hex;
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row')?.querySelector('.color-picker-container');
if (colorPickerContainer) {
colorPickerContainer.style.setProperty('--selected-color', hex);
}
updateCarouselPreview();
} 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;
fadeFlowConfig.overlayColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row')?.querySelector('.color-picker-container');
if (colorPickerContainer) {
colorPickerContainer.style.setProperty('--selected-color', hex);
}
updateCarouselPreview();
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;
fadeFlowConfig.overlayColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateCarouselPreview();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
const rgb = hexToRgb(colorInput.value);
if (rgb) {
const hslColor = rgbToHsl(rgb.r, rgb.g, rgb.b);
e.target.value = `hsl(${hslColor.h}, ${hslColor.s}%, ${hslColor.l}%)`;
}
e.target.classList.remove('invalid');
}
});
}
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
const valueElement = document.getElementById(`${input.id}-value`);
if (valueElement) {
valueElement.textContent = input.value;
}
input.addEventListener('input', () => {
if (valueElement) {
valueElement.textContent = input.value;
}
switch (input.id) {
case 'animation-speed':
fadeFlowConfig.speed = parseInt(input.value);
updateCarouselPreview();
break;
case 'mobile-speed':
fadeFlowConfig.mobileSpeed = parseFloat(input.value);
updateCarouselPreview();
break;
case 'image-gap':
fadeFlowConfig.imageGap = parseInt(input.value);
updateCarouselPreview();
break;
case 'border-radius':
fadeFlowConfig.borderRadius = parseInt(input.value);
updateCarouselPreview();
break;
case 'fade-width':
fadeFlowConfig.fadeWidth = parseInt(input.value);
updateCarouselPreview();
break;
case 'fade-opacity':
fadeFlowConfig.fadeOpacity = parseFloat(input.value);
updateCarouselPreview();
break;
case 'overlay-opacity':
fadeFlowConfig.overlayOpacity = parseFloat(input.value);
updateCarouselPreview();
break;
case 'custom-image-width':
fadeFlowConfig.customImageWidth = parseInt(input.value);
updateCarouselPreview();
break;
case 'carousel-height':
fadeFlowConfig.customHeight = parseInt(input.value);
updateCarouselPreview();
break;
}
});
});
const selectInputs = document.querySelectorAll('select');
selectInputs.forEach(select => {
select.addEventListener('change', () => {
switch (select.id) {
case 'animation-direction':
fadeFlowConfig.direction = select.value;
updateCarouselPreview();
break;
case 'blur-theme':
fadeFlowConfig.blurTheme = select.value;
updateCarouselPreview();
break;
case 'image-width-mode':
fadeFlowConfig.imageWidthMode = select.value;
updateAdvancedControls();
updateCarouselPreview();
break;
case 'carousel-height-mode':
fadeFlowConfig.heightMode = select.value;
updateAdvancedControls();
updateCarouselPreview();
break;
case 'vertical-position':
fadeFlowConfig.verticalPosition = select.value;
updateCarouselPreview();
break;
}
});
});
document.querySelectorAll('.url-input').forEach(input => {
input.addEventListener('input', () => {
const newImageUrls = [];
document.querySelectorAll('.url-input').forEach(urlInput => {
const url = urlInput.value.trim();
if (url) {
newImageUrls.push(url);
}
});
fadeFlowConfig.images = newImageUrls;
updateCarouselPreview();
});
});
const addImageBtn = document.getElementById('add-image-btn');
if (addImageBtn) {
addImageBtn.addEventListener('click', addImageInput);
}
function addImageInput() {
const container = document.getElementById('image-urls-container');
const count = container.querySelectorAll('.image-url-group').length + 1;
if (count > 12) {
showNotification('Maximum 12 images allowed', 'warning');
return;
}
const groupDiv = document.createElement('div');
groupDiv.className = 'image-url-group';
groupDiv.style.opacity = '0';
groupDiv.style.transform = 'translateY(10px)';
groupDiv.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
groupDiv.innerHTML = `
<div class="control-label">
<span class="label-text">Image ${count}</span>
</div>
<div class="image-url-input">
<input type="text" placeholder="https://example.com/image${count}.jpg" class="url-input" data-index="${count}">
<button class="remove-btn" title="Remove this image">×</button>
</div>
`;
container.appendChild(groupDiv);
setTimeout(() => {
groupDiv.style.opacity = '1';
groupDiv.style.transform = 'translateY(0)';
}, 10);
const input = groupDiv.querySelector('.url-input');
input.addEventListener('input', () => {
const newImageUrls = [];
document.querySelectorAll('.url-input').forEach(urlInput => {
const url = urlInput.value.trim();
if (url) {
newImageUrls.push(url);
}
});
fadeFlowConfig.images = newImageUrls;
updateCarouselPreview();
});
const removeBtn = groupDiv.querySelector('.remove-btn');
removeBtn.addEventListener('click', function() {
groupDiv.style.opacity = '0';
groupDiv.style.transform = 'translateY(10px)';
setTimeout(() => {
container.removeChild(groupDiv);
updateImageIndexes();
const newImageUrls = [];
document.querySelectorAll('.url-input').forEach(urlInput => {
const url = urlInput.value.trim();
if (url) {
newImageUrls.push(url);
}
});
fadeFlowConfig.images = newImageUrls;
updateCarouselPreview();
}, 300);
});
}
function updateImageIndexes() {
const groups = document.querySelectorAll('.image-url-group');
groups.forEach((group, index) => {
const labelText = group.querySelector('.label-text');
const input = group.querySelector('.url-input');
const newIndex = index + 1;
labelText.textContent = `Image ${newIndex}`;
input.setAttribute('data-index', newIndex);
input.setAttribute('placeholder', `https://example.com/image${newIndex}.jpg`);
});
}
function updateAdvancedControls() {
const customWidthControls = document.getElementById('custom-width-controls');
const customHeightControls = document.getElementById('custom-height-controls');
const imageWidthMode = document.getElementById('image-width-mode');
const carouselHeightMode = document.getElementById('carousel-height-mode');
if (customWidthControls && imageWidthMode) {
customWidthControls.style.display = imageWidthMode.value === 'custom' ? 'block' : 'none';
}
if (customHeightControls && carouselHeightMode) {
customHeightControls.style.display = carouselHeightMode.value === 'custom' ? 'block' : 'none';
}
}
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}%)`;
}
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;
}
}
});
updateOverlayColorInputs();
updateOverlayControls();
updateAdvancedControls();
setTimeout(() => {
showNotification('Horizontal Flow Carousel configurator loaded!');
}, 500);
}
initializeUI();
});
</script>
</body>
</html>
Horizontal Flow
Creates a smooth infinite scrolling carousel that loops continuously without any jumps or breaks. Images scroll smoothly across the screen with elegant fade effects on the edges. Pauses automatically when not visible to save battery. Perfect for showcasing logos, portfolio images, or client testimonials in a modern, eye-catching way.
Images
Add your images that will scroll across the screen. Works best with 4-12 images.
Required
How tall the images are inside the container. 100% fills the full height.
Default: 90
Space between each image. More space creates a cleaner, more breathable look.
Default: 20
Animation
How fast images scroll. Lower numbers = faster scrolling, higher numbers = slower, more elegant movement.
Default: 10
Which way the images move. Left makes them scroll from right to left.
Default: left
Adjusts speed on phones. Lower values = slower on mobile for better performance and visibility.
Default: 0.5
Styling
Roundness of image corners. 0 = sharp corners, higher = more rounded.
Default: 8
Adds a color tint over images. Useful for darkening or creating a specific mood.
Default: off
Color of the tint when overlay is turned on.
Default: black
How strong the tint is. 0 = invisible, 1 = completely covers the image.
Default: 0.3
Edge Effects
Color of the fade effect on edges. Use dark for dark backgrounds, light for bright backgrounds.
Default: dark
How wide the fade effect is on left and right edges. Wider = more gradual fade.
Default: 15
Strength of the edge fade. Higher = stronger fade, completely hides image edges.
Default: 0.95
Performance
Butter-smooth scrolling at 60fps on desktop and 30fps on mobile. Automatically pauses when you scroll past it to save battery. Images are automatically sized and work on all screen sizes. Best performance with 4-12 images. Works great on all modern browsers and devices.