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>Flip Canvas Configurator - BricksFusion</title>
<style>
:root {
--background: #000;
--card-bg: #1e1e1e;
--card-bg-hover: #252525;
--text-primary: #f2f2f7;
--text-secondary: #8e8e93;
--accent: #ef6013;
--accent-hover: #c64c0c;
--border: #2c2c2e;
--shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
--track: #2c2c2e;
--thumb: #ef6013;
--card-radius: 16px;
--input-radius: 8px;
--button-radius: 12px;
--transition: all 0.25s ease;
--font: 'Inter', BlinkMacSystemFont, "San Francisco", "Helvetica Neue", Helvetica, Arial, sans-serif;
--action-bar-height: 70px;
--success: #28a745;
--warning: #ffc107;
--danger: #dc3545;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font);
background-color: var(--background);
color: var(--text-primary);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-bottom: var(--action-bar-height);
}
.action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: var(--action-bar-height);
background: linear-gradient(145deg, #1a1a1a, #0f0f0f);
border-top: 1px solid var(--border);
z-index: 1000;
display: flex;
align-items: center;
padding: 0 1.5rem;
gap: 1rem;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
}
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.breadcrumb-item {
color: var(--text-secondary);
font-size: var(--text-xs);
font-weight: 500;
text-decoration: none;
transition: var(--transition);
padding: 0.5rem 0.75rem;
border-radius: 6px;
}
.breadcrumb-item:hover {
color: var(--text-primary);
background-color: rgba(255, 255, 255, 0.05);
}
.breadcrumb-item.active {
color: var(--accent);
background-color: rgba(239, 96, 19, 0.1);
}
.breadcrumb-separator {
color: var(--text-secondary);
font-size: var(--text-xs);
opacity: 0.5;
}
.action-buttons {
display: flex;
align-items: center;
gap: 0.75rem;
}
.action-btn {
padding: 0.6rem 1rem;
background-color: var(--card-bg);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
font-weight: 500;
border: 1px solid var(--border);
border-radius: var(--button-radius);
cursor: pointer;
transition: var(--transition);
display: flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
white-space: nowrap;
}
.action-btn:hover {
background-color: var(--card-bg-hover);
border-color: var(--accent);
transform: translateY(-1px);
}
.action-btn.primary {
background: linear-gradient(90deg, var(--accent), #ff8c51);
border-color: var(--accent);
color: white;
}
.action-btn.primary:hover {
background: linear-gradient(90deg, var(--accent-hover), #e67a3f);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 96, 19, 0.3);
}
.data-attribute-display {
background-color: rgba(50, 50, 50, 0.8);
border: 1px solid var(--border);
border-radius: 6px;
padding: 0.5rem 0.75rem;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
cursor: pointer;
transition: var(--transition);
user-select: all;
}
.data-attribute-display:hover {
background-color: rgba(239, 96, 19, 0.2);
border-color: var(--accent);
}
.container {
max-width: 100%;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.page-header {
text-align: center;
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
background: linear-gradient(90deg, var(--accent), #ff8c51);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-subtitle {
font-size: var(--text-s);
color: var(--text-secondary);
font-weight: 500;
}
.instructions-toggle {
margin-bottom: 2rem;
}
.instructions-card {
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
transition: var(--transition);
}
.instructions-header {
padding: 1rem 1.5rem;
cursor: pointer;
transition: var(--transition);
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid transparent;
}
.instructions-header:hover {
background-color: var(--card-bg-hover);
}
.instructions-card.expanded .instructions-header {
border-bottom-color: var(--border);
}
.instructions-title {
font-size: var(--text-s);
font-weight: 600;
}
.toggle-icon {
font-size: 1.2em;
transition: transform 0.3s ease;
}
.toggle-icon.expanded {
transform: rotate(180deg);
}
.instructions-content {
padding: 0 1.5rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease, padding 0.3s ease;
}
.instructions-content.show {
max-height: 500px;
padding: 1.5rem;
}
.instructions-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1.5rem;
}
.how-to-use ol {
padding-left: 1.5rem;
}
.how-to-use li {
margin-bottom: 0.75rem;
font-size: var(--text-xs);
color: var(--text-secondary);
line-height: 1.6;
}
.how-to-use strong {
color: var(--text-primary);
font-weight: 600;
}
.how-to-use code {
background-color: rgba(50, 50, 50, 0.5);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: var(--text-xs);
color: #ff8c51;
}
.content {
display: grid;
grid-template-columns: 1fr 500px;
gap: 2rem;
align-items: start;
}
.preview-section {
position: sticky;
top: 2rem;
}
.controls-section {
max-width: 500px;
}
.card {
background-color: var(--card-bg);
border-radius: var(--card-radius);
box-shadow: var(--shadow);
overflow: hidden;
margin-bottom: 1.5rem;
border: 1px solid var(--border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.4);
}
.preview-container {
height: 400px;
width: 100%;
position: relative;
overflow: hidden;
border-radius: var(--card-radius);
background-color: #252525;
border: 1px solid var(--border);
box-shadow: var(--shadow);
}
.preview-content {
color: white;
text-align: center;
font-weight: bold;
font-size: var(--text-s);
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
}
.preview-controls {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
gap: 0.5rem;
z-index: 10;
}
.preview-btn {
padding: 0.5rem;
background-color: rgba(0, 0, 0, 0.7);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 6px;
cursor: pointer;
transition: var(--transition);
font-size: var(--text-xs);
backdrop-filter: blur(5px);
}
.preview-btn:hover {
background-color: var(--accent);
border-color: var(--accent);
}
.preview-btn svg {
width: 18px;
height: 18px;
stroke: currentColor;
}
.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);
}
input[type="text"],
input[type="number"],
input[type="url"],
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);
}
input[type="text"]:focus,
input[type="number"]:focus,
input[type="url"]:focus,
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.radio-group {
display: flex;
flex-direction: column;
gap: 0.8rem;
margin-bottom: 1rem;
}
.radio-option {
display: flex;
align-items: center;
cursor: pointer;
}
.radio-option input[type="radio"] {
margin-right: 0.8rem;
cursor: pointer;
accent-color: var(--accent);
}
.radio-option label {
font-size: var(--text-xs);
color: var(--text-secondary);
cursor: pointer;
}
.radio-option input[type="radio"]:checked + label {
color: var(--text-primary);
font-weight: 500;
}
.image-fields {
margin-bottom: 1rem;
}
.image-field-wrapper {
display: flex;
align-items: center;
margin-bottom: 8px;
width: 100%;
}
.image-url-input {
flex: 1;
margin-bottom: 0;
}
.remove-image-btn {
width: 28px;
height: 28px;
margin-left: 8px;
border: none;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.1);
color: var(--text-secondary);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
}
.remove-image-btn:hover {
background-color: rgba(255, 0, 0, 0.2);
color: #ff4d4d;
}
.add-image-btn {
background-color: rgba(50, 50, 50, 0.5);
border: 1px dashed var(--border);
color: var(--text-secondary);
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-size: var(--text-xs);
margin-bottom: 10px;
transition: var(--transition);
width: 100%;
text-align: center;
}
.add-image-btn:hover {
background-color: rgba(239, 96, 19, 0.1);
border-color: var(--accent);
color: var(--accent);
}
.images-hint {
font-size: var(--text-xs);
color: var(--text-secondary);
margin-top: 0.25rem;
font-style: italic;
}
.toggle-switch-wrapper {
display: flex;
align-items: center;
margin: 0.5rem 0;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--card-bg);
border: 1px solid var(--border);
transition: .4s;
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 4px;
bottom: 3px;
background-color: var(--text-secondary);
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--accent);
border-color: var(--accent);
}
input:checked + .toggle-slider:before {
transform: translateX(26px);
background-color: white;
}
.toggle-label {
margin-left: 10px;
font-size: var(--text-xs);
color: var(--text-secondary);
}
input:checked ~ .toggle-label {
color: var(--accent);
}
.notification {
position: fixed;
bottom: calc(var(--action-bar-height) + 1rem);
left: 50%;
background-color: var(--success);
color: white;
padding: 0.75rem 1rem;
border-radius: var(--input-radius);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1001;
transform: translate(-50%, 200px);
opacity: 0;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
font-size: var(--text-xs);
font-weight: 500;
max-width: 320px;
word-wrap: break-word;
line-height: 1.4;
text-align: center;
}
.notification.show {
transform: translate(-50%, 0);
opacity: 1;
}
@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;
}
.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); }
}
.grid-preview-container {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
overflow: hidden;
border-radius: var(--card-radius);
}
.grid-block {
position: absolute;
overflow: hidden;
transition: transform 0.8s ease-in-out, opacity 0.8s ease-in-out;
}
.block-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: transform 0.8s ease-in-out, opacity 0.8s ease-in-out;
backface-visibility: hidden;
transform-origin: center right;
}
</style>
</head>
<body>
<div class="action-bar">
<nav class="breadcrumb">
<a href="https://bricksfusion.com" class="breadcrumb-item">Home</a>
<span class="breadcrumb-separator">›</span>
<a href="https://bricksfusion.com/visual-effects/" class="breadcrumb-item">Visual effects</a>
<span class="breadcrumb-separator">›</span>
<span class="breadcrumb-item active">Flip Canvas</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-grid-flip
</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">Flip Canvas</h1>
<p class="page-subtitle">Interactive grid 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 flip canvas animation using the controls below</li>
<li>Click <strong>Copy JS</strong> to copy the JavaScript code to clipboard</li>
<li>In Bricks Builder, add a <strong>Code</strong> element</li>
<li>Paste or upload the JavaScript code</li>
<li>To add the effect to any section: go to <strong>Section → Style → Attributes</strong>, add <code>data-grid-flip</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="flip-canvas-preview" data-grid-flip="true">
<div class="preview-content">Interactive Flip Canvas Preview</div>
<div class="grid-preview-container"></div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Grid Settings
<div class="card-actions">
<button class="card-action-btn" id="reset-grid" title="Reset Grid Settings">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Grid Size
<span class="help-tooltip" title="Number of rows and columns in the grid">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="grid-size-value">4</span>x<span id="grid-size-display">4</span></span>
<button class="reset-btn" onclick="resetParameter('grid-size', 4)">↺</button>
</div>
</div>
<input type="range" id="grid-size" min="2" max="6" step="1" value="4">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Transition Time
<span class="help-tooltip" title="Time between automatic image transitions">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="transition-time-value">3</span>s</span>
<button class="reset-btn" onclick="resetParameter('transition-time', 3)">↺</button>
</div>
</div>
<input type="range" id="transition-time" min="1" max="10" step="0.5" value="3">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Empty Blocks
<span class="help-tooltip" title="Percentage of grid blocks that remain empty">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="empty-percentage-value">30</span>%</span>
<button class="reset-btn" onclick="resetParameter('empty-percentage', 30)">↺</button>
</div>
</div>
<input type="range" id="empty-percentage" min="0" max="50" step="5" value="30">
</div>
</div>
</div>
<div class="card">
<div class="card-heading">
Appearance
<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">Animation Style</span>
</div>
<div class="radio-group" id="animation-style-group">
<div class="radio-option">
<input type="radio" id="style-horizontal" name="animation-style" value="horizontal" checked>
<label for="style-horizontal">Horizontal Flip (Default)</label>
</div>
<div class="radio-option">
<input type="radio" id="style-vertical" name="animation-style" value="vertical">
<label for="style-vertical">Vertical Flip</label>
</div>
<div class="radio-option">
<input type="radio" id="style-diagonal" name="animation-style" value="diagonal">
<label for="style-diagonal">Diagonal Flip</label>
</div>
<div class="radio-option">
<input type="radio" id="style-zoom" name="animation-style" value="zoom">
<label for="style-zoom">Zoom Flip</label>
</div>
<div class="radio-option">
<input type="radio" id="style-cube" name="animation-style" value="cube">
<label for="style-cube">3D Cube Rotation</label>
</div>
<div class="radio-option">
<input type="radio" id="style-fade" name="animation-style" value="fade">
<label for="style-fade">Fade Through</label>
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Images</span>
</div>
<div class="image-fields">
<div class="image-field-wrapper">
<input type="url" class="image-url-input" placeholder="Enter image URL" value="https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=1200">
<button type="button" class="remove-image-btn">×</button>
</div>
<div class="image-field-wrapper">
<input type="url" class="image-url-input" placeholder="Enter image URL" value="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200">
<button type="button" class="remove-image-btn">×</button>
</div>
<div class="image-field-wrapper">
<input type="url" class="image-url-input" placeholder="Enter image URL" value="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=1200">
<button type="button" class="remove-image-btn">×</button>
</div>
</div>
<button type="button" class="add-image-btn">+ Add Image</button>
<p class="images-hint">Add at least 2 image URLs for the slideshow</p>
</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">
Animation Duration
<span class="help-tooltip" title="Duration of the flip animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="animation-duration-value">0.8</span>s</span>
<button class="reset-btn" onclick="resetParameter('animation-duration', 0.8)">↺</button>
</div>
</div>
<input type="range" id="animation-duration" min="0.3" max="2" step="0.1" value="0.8">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Stagger Delay
<span class="help-tooltip" title="Maximum delay between block animations">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="stagger-delay-value">0.2</span>s</span>
<button class="reset-btn" onclick="resetParameter('stagger-delay', 0.2)">↺</button>
</div>
</div>
<input type="range" id="stagger-delay" min="0" max="0.5" step="0.05" value="0.2">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Mobile Optimization</span>
</div>
<div class="toggle-switch-wrapper">
<label class="toggle-switch">
<input type="checkbox" id="mobile-optimization" checked>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Enable faster animations on mobile</span>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let flipCanvasConfig = {
gridSize: 4,
transitionTime: 3000,
emptyPercentage: 30,
animationStyle: 'horizontal',
images: [
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=1200',
'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=1200',
'https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=1200'
],
animationDuration: 0.8,
staggerDelay: 0.2,
mobileOptimization: true
};
const defaultConfig = { ...flipCanvasConfig };
function initFlipCanvas() {
const sections = document.querySelectorAll('[data-grid-flip]');
sections.forEach((section) => {
// Si ya está inicializado, lo reinicializamos
if (section.dataset.flipInitialized === 'true') {
section.dataset.flipInitialized = 'false';
}
if (section.dataset.flipInitialized !== 'true') {
const config = {
gridSize: section.hasAttribute('data-grid-size') ? parseInt(section.getAttribute('data-grid-size')) : flipCanvasConfig.gridSize,
transitionTime: section.hasAttribute('data-transition-time') ? parseInt(section.getAttribute('data-transition-time')) : flipCanvasConfig.transitionTime,
emptyPercentage: section.hasAttribute('data-empty-percentage') ? parseInt(section.getAttribute('data-empty-percentage')) : flipCanvasConfig.emptyPercentage,
animationStyle: section.hasAttribute('data-animation-style') ? section.getAttribute('data-animation-style') : flipCanvasConfig.animationStyle,
images: section.hasAttribute('data-images') ? section.getAttribute('data-images').split(',') : flipCanvasConfig.images,
animationDuration: section.hasAttribute('data-animation-duration') ? parseFloat(section.getAttribute('data-animation-duration')) : flipCanvasConfig.animationDuration,
staggerDelay: section.hasAttribute('data-stagger-delay') ? parseFloat(section.getAttribute('data-stagger-delay')) : flipCanvasConfig.staggerDelay,
mobileOptimization: section.hasAttribute('data-mobile-optimization') ? section.getAttribute('data-mobile-optimization') === 'true' : flipCanvasConfig.mobileOptimization
};
setupFlipCanvas(section, config);
section.dataset.flipInitialized = 'true';
}
});
}
function setupFlipCanvas(element, options) {
const gridContainer = element.querySelector('.grid-preview-container');
if (!gridContainer) return;
const gridSize = options.gridSize;
const totalBlocks = gridSize * gridSize;
const blockWidth = 100 / gridSize;
const blockHeight = 100 / gridSize;
if (window.getComputedStyle(element).position === 'static') {
element.style.position = 'relative';
}
// Limpiar contenido anterior
gridContainer.innerHTML = '';
// Cancelar animación anterior si existe
if (element.flipAnimationLoop) {
cancelAnimationFrame(element.flipAnimationLoop);
element.flipAnimationLoop = null;
}
const state = {
currentImageIndex: 0,
blocks: [],
emptyBlocks: new Set(),
nextEmptyBlocks: new Set(),
isAnimating: false
};
for (let i = 0; i < totalBlocks; i++) {
const row = Math.floor(i / gridSize);
const col = i % gridSize;
const block = document.createElement('div');
block.className = 'grid-block';
block.style.cssText = `
position: absolute;
left: ${col * blockWidth}%;
top: ${row * blockHeight}%;
width: ${blockWidth}%;
height: ${blockHeight}%;
overflow: hidden;
transform-style: preserve-3d;
perspective: 1000px;
`;
const currentLayer = document.createElement('div');
const nextLayer = document.createElement('div');
[currentLayer, nextLayer].forEach(layer => {
layer.className = 'block-content';
layer.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transition: transform ${options.animationDuration}s ease-in-out, opacity ${options.animationDuration}s ease-in-out;
backface-visibility: hidden;
`;
switch(options.animationStyle) {
case 'horizontal':
layer.style.transformOrigin = 'center right';
break;
case 'vertical':
layer.style.transformOrigin = 'center bottom';
break;
case 'diagonal':
layer.style.transformOrigin = 'bottom right';
break;
case 'zoom':
case 'fade':
case 'cube':
layer.style.transformOrigin = 'center center';
break;
}
block.appendChild(layer);
});
gridContainer.appendChild(block);
state.blocks.push({
element: block,
currentLayer,
nextLayer,
row,
col
});
}
updateEmptyBlocks(state, Math.floor(totalBlocks * (options.emptyPercentage / 100)));
updateBlocksImage(state, options, true);
startAnimationLoop(state, options, element);
element._flipState = state;
element._flipOptions = options;
}
function updateEmptyBlocks(state, count) {
const nextEmpty = new Set();
while (nextEmpty.size < count) {
nextEmpty.add(Math.floor(Math.random() * state.blocks.length));
}
state.nextEmptyBlocks = nextEmpty;
}
function setBlockBackground(element, imageUrl, block, gridSize) {
const widthPercent = gridSize * 100;
const heightPercent = gridSize * 100;
element.style.background = `url(${imageUrl})`;
element.style.backgroundSize = `${widthPercent}% ${heightPercent}%`;
element.style.backgroundPosition = `${block.col * -100}% ${block.row * -100}%`;
element.style.opacity = '1';
element.style.transform = 'rotateY(0deg)';
}
function clearBlockBackground(element) {
element.style.background = 'none';
element.style.backgroundColor = 'transparent';
element.style.opacity = '0';
}
function updateBlocksImage(state, options, isInitial = false) {
const currentImage = options.images[state.currentImageIndex];
const nextImageIndex = (state.currentImageIndex + 1) % options.images.length;
const nextImage = options.images[nextImageIndex];
const isMobile = window.innerWidth <= 768;
const maxDelay = options.staggerDelay;
const animationStyle = options.animationStyle;
state.blocks.forEach((block, index) => {
if (isInitial) {
if (!state.emptyBlocks.has(index)) {
setBlockBackground(block.currentLayer, currentImage, block, options.gridSize);
clearBlockBackground(block.nextLayer);
if (animationStyle === 'cube') {
block.nextLayer.style.transform = 'rotateY(90deg)';
} else if (animationStyle === 'zoom') {
block.nextLayer.style.transform = 'scale(0.5) rotateY(90deg)';
}
} else {
clearBlockBackground(block.currentLayer);
clearBlockBackground(block.nextLayer);
}
} else {
const willBeEmpty = state.nextEmptyBlocks.has(index);
const isCurrentlyEmpty = state.emptyBlocks.has(index);
if (!willBeEmpty) {
setBlockBackground(block.nextLayer, nextImage, block, options.gridSize);
const delay = Math.random() * maxDelay;
block.currentLayer.style.transitionDelay = `${delay}s`;
block.nextLayer.style.transitionDelay = `${delay}s`;
switch(animationStyle) {
case 'horizontal':
block.currentLayer.style.transform = 'translateX(-100%) rotateY(90deg)';
block.nextLayer.style.transform = 'translateX(0) rotateY(0deg)';
break;
case 'vertical':
block.currentLayer.style.transform = 'translateY(-100%) rotateX(90deg)';
block.nextLayer.style.transform = 'translateY(0) rotateX(0deg)';
break;
case 'diagonal':
block.currentLayer.style.transform = 'translate(-100%, -100%) rotate3d(1, 1, 0, 90deg)';
block.nextLayer.style.transform = 'translate(0, 0) rotate3d(1, 1, 0, 0deg)';
break;
case 'zoom':
block.currentLayer.style.transform = 'scale(0.5) rotateY(90deg)';
block.nextLayer.style.transform = 'scale(1) rotateY(0deg)';
break;
case 'cube':
block.currentLayer.style.transform = 'rotateY(-90deg)';
block.nextLayer.style.transform = 'rotateY(0deg)';
break;
case 'fade':
block.currentLayer.style.transform = 'scale(0.95) rotate3d(1, 1, 1, 5deg)';
block.currentLayer.style.opacity = '0';
block.nextLayer.style.transform = 'scale(1) rotate3d(0, 0, 0, 0deg)';
block.nextLayer.style.opacity = '1';
break;
}
if (animationStyle !== 'fade') {
block.currentLayer.style.opacity = '0';
block.nextLayer.style.opacity = '1';
}
} else {
clearBlockBackground(block.nextLayer);
if (!isCurrentlyEmpty) {
switch(animationStyle) {
case 'horizontal':
block.currentLayer.style.transform = 'translateX(-100%) rotateY(90deg)';
break;
case 'vertical':
block.currentLayer.style.transform = 'translateY(-100%) rotateX(90deg)';
break;
case 'diagonal':
block.currentLayer.style.transform = 'translate(-100%, -100%) rotate3d(1, 1, 0, 90deg)';
break;
case 'zoom':
block.currentLayer.style.transform = 'scale(0.5) rotateY(90deg)';
break;
case 'cube':
block.currentLayer.style.transform = 'rotateY(-90deg)';
break;
case 'fade':
block.currentLayer.style.transform = 'scale(0.95) rotate3d(1, 1, 1, 5deg)';
break;
}
block.currentLayer.style.opacity = '0';
}
}
}
});
if (!isInitial) {
const resetTimeout = (options.animationDuration + options.staggerDelay) * 1000;
setTimeout(() => {
state.blocks.forEach(block => {
const temp = block.currentLayer;
block.currentLayer = block.nextLayer;
block.nextLayer = temp;
block.nextLayer.style.transition = 'none';
switch(animationStyle) {
case 'horizontal':
block.nextLayer.style.transform = 'translateX(100%) rotateY(-90deg)';
break;
case 'vertical':
block.nextLayer.style.transform = 'translateY(100%) rotateX(-90deg)';
break;
case 'diagonal':
block.nextLayer.style.transform = 'translate(100%, 100%) rotate3d(-1, -1, 0, 90deg)';
break;
case 'zoom':
block.nextLayer.style.transform = 'scale(1.5) rotateY(-90deg)';
break;
case 'cube':
block.nextLayer.style.transform = 'rotateY(90deg)';
break;
case 'fade':
block.nextLayer.style.transform = 'scale(1.05) rotate3d(-1, -1, -1, 5deg)';
break;
}
block.nextLayer.style.opacity = '0';
setTimeout(() => {
const duration = options.animationDuration;
block.nextLayer.style.transition = `transform ${duration}s ease-in-out, opacity ${duration}s ease-in-out`;
}, 50);
});
}, resetTimeout);
}
}
function startAnimationLoop(state, options, element) {
let lastTime = 0;
const animate = (currentTime) => {
if (!lastTime) lastTime = currentTime;
const deltaTime = currentTime - lastTime;
if (deltaTime >= options.transitionTime && !state.isAnimating) {
state.isAnimating = true;
updateEmptyBlocks(state, Math.floor(state.blocks.length * (options.emptyPercentage / 100)));
updateBlocksImage(state, options);
setTimeout(() => {
state.emptyBlocks = state.nextEmptyBlocks;
state.currentImageIndex = (state.currentImageIndex + 1) % options.images.length;
state.isAnimating = false;
lastTime = currentTime;
}, (options.animationDuration + options.staggerDelay) * 1000 + 200);
}
element.flipAnimationLoop = requestAnimationFrame(animate);
};
element.flipAnimationLoop = requestAnimationFrame(animate);
}
function updateFlipPreview() {
const preview = document.getElementById('flip-canvas-preview');
if (!preview) return;
// Cancelar animación anterior
if (preview.flipAnimationLoop) {
cancelAnimationFrame(preview.flipAnimationLoop);
preview.flipAnimationLoop = null;
}
// Resetear estado de inicialización
preview.dataset.flipInitialized = 'false';
// Actualizar atributos
preview.setAttribute('data-grid-flip', 'true');
preview.setAttribute('data-grid-size', flipCanvasConfig.gridSize);
preview.setAttribute('data-transition-time', flipCanvasConfig.transitionTime);
preview.setAttribute('data-empty-percentage', flipCanvasConfig.emptyPercentage);
preview.setAttribute('data-animation-style', flipCanvasConfig.animationStyle);
preview.setAttribute('data-images', flipCanvasConfig.images.join(','));
preview.setAttribute('data-animation-duration', flipCanvasConfig.animationDuration);
preview.setAttribute('data-stagger-delay', flipCanvasConfig.staggerDelay);
preview.setAttribute('data-mobile-optimization', flipCanvasConfig.mobileOptimization);
// Reinicializar inmediatamente
initFlipCanvas();
}
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();
// Obtener el JavaScript actual con la configuración del usuario
const jsCode = generateJavaScriptCode();
// Crear el objeto JSON completo de Bricks Builder
const bricksJSON = {
"content": [
{
"id": sectionId,
"name": "section",
"parent": 0,
"children": [containerId, codeId],
"settings": {
"_height": "500",
"_justifyContent": "center",
"_background": {
"color": {
"hex": "#000000"
}
}
}
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [],
"settings": {
"_alignItems": "center",
"_border": {
"radius": {
"top": "15",
"right": "15",
"bottom": "15",
"left": "15"
}
},
"_width": "500",
"_height": "300",
"_attributes": [
{
"id": "oberfs",
"name": "data-grid-flip"
}
],
"_overflow": "hidden"
},
"label": "Flip Canvas Container"
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Flip Canvas JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksJSON, null, 2);
}
function generateJavaScriptCode() {
return `(function() {
if (window.flipCanvasInitialized) return;
window.flipCanvasInitialized = true;
class GridFlipAnimation {
constructor() {
this.resizeTimeout = null;
const containers = document.querySelectorAll('[data-grid-flip]');
if (!containers.length) return;
containers.forEach(container => {
if (!container.hasAttribute('data-initialized')) {
this.initializeContainer(container);
container.setAttribute('data-initialized', 'true');
}
});
window.addEventListener('resize', this.handleResize.bind(this));
}
handleResize() {
if (this.resizeTimeout) {
window.cancelAnimationFrame(this.resizeTimeout);
}
this.resizeTimeout = window.requestAnimationFrame(() => {
const containers = document.querySelectorAll('[data-grid-flip]');
containers.forEach(container => {
const width = window.innerWidth;
const currentGridSize = parseInt(container.getAttribute('data-grid-size')) || ${flipCanvasConfig.gridSize};
if (width <= 480 && currentGridSize > 2) {
container.setAttribute('data-grid-size', '2');
this.reinitialize(container);
}
else if (width <= 768 && currentGridSize > 3) {
container.setAttribute('data-grid-size', '3');
this.reinitialize(container);
}
else if (width > 768 && currentGridSize < ${flipCanvasConfig.gridSize}) {
container.setAttribute('data-grid-size', '${flipCanvasConfig.gridSize}');
this.reinitialize(container);
}
});
});
}
reinitialize(container) {
const existingWrapper = container.querySelector('[data-animation-wrapper]');
if (existingWrapper) {
existingWrapper.remove();
}
container.removeAttribute('data-initialized');
this.initializeContainer(container);
}
initializeContainer(container) {
const imagesAttr = container.getAttribute('data-images') || '${flipCanvasConfig.images.join(',')}';
if (!imagesAttr) return;
const config = {
gridSize: parseInt(container.getAttribute('data-grid-size')) || ${flipCanvasConfig.gridSize},
transitionTime: parseInt(container.getAttribute('data-transition-time')) || ${flipCanvasConfig.transitionTime},
emptyBlocksPercentage: parseInt(container.getAttribute('data-empty-percentage')) || ${flipCanvasConfig.emptyPercentage},
animationStyle: container.getAttribute('data-animation-style') || '${flipCanvasConfig.animationStyle}',
images: imagesAttr.split(',').filter(url => url.trim()),
animationDuration: ${flipCanvasConfig.animationDuration},
staggerDelay: ${flipCanvasConfig.staggerDelay},
mobileOptimization: ${flipCanvasConfig.mobileOptimization}
};
const backgroundWrapper = document.createElement('div');
Object.assign(backgroundWrapper.style, {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
width: '100%',
height: '100%',
zIndex: 0,
overflow: 'hidden',
transform: 'translateZ(0)'
});
backgroundWrapper.setAttribute('data-animation-wrapper', 'true');
const originalPosition = getComputedStyle(container).position;
if (originalPosition === 'static') {
container.style.position = 'relative';
}
container.insertBefore(backgroundWrapper, container.firstChild);
Array.from(container.children).forEach(child => {
if (child !== backgroundWrapper) {
if (getComputedStyle(child).zIndex === 'auto') {
child.style.zIndex = '1';
}
if (getComputedStyle(child).position === 'static') {
child.style.position = 'relative';
}
}
});
Promise.all(config.images.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = resolve;
img.onerror = reject;
img.src = url;
});
})).then(() => this.initializeGrid(backgroundWrapper, config));
}
initializeGrid(container, config) {
const gridWrapper = document.createElement('div');
Object.assign(gridWrapper.style, {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
overflow: 'hidden',
display: 'block'
});
container.appendChild(gridWrapper);
const state = {
currentImageIndex: 0,
blocks: [],
emptyBlocks: new Set(),
nextEmptyBlocks: new Set(),
isAnimating: false
};
const totalBlocks = config.gridSize * config.gridSize;
const blockWidth = 100 / config.gridSize;
const blockHeight = 100 / config.gridSize;
const isMobile = config.mobileOptimization && window.innerWidth <= 768;
const transitionDuration = isMobile ? '${flipCanvasConfig.animationDuration * 0.6}s' : '${flipCanvasConfig.animationDuration}s';
for (let i = 0; i < totalBlocks; i++) {
const row = Math.floor(i / config.gridSize);
const col = i % config.gridSize;
const block = document.createElement('div');
Object.assign(block.style, {
position: 'absolute',
left: \`\${col * blockWidth}%\`,
top: \`\${row * blockHeight}%\`,
width: \`\${blockWidth}%\`,
height: \`\${blockHeight}%\`,
overflow: 'hidden',
transformStyle: 'preserve-3d',
perspective: '1000px'
});
const currentLayer = document.createElement('div');
const nextLayer = document.createElement('div');
[currentLayer, nextLayer].forEach(layer => {
Object.assign(layer.style, {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
transition: \`transform \${transitionDuration} ease-in-out, opacity \${transitionDuration} ease-in-out\`,
backfaceVisibility: 'hidden'
});
switch(config.animationStyle) {
case 'horizontal':
layer.style.transformOrigin = 'center right';
break;
case 'vertical':
layer.style.transformOrigin = 'center bottom';
break;
case 'diagonal':
layer.style.transformOrigin = 'bottom right';
break;
case 'zoom':
case 'fade':
case 'cube':
layer.style.transformOrigin = 'center center';
break;
}
block.appendChild(layer);
});
gridWrapper.appendChild(block);
state.blocks.push({
element: block,
currentLayer,
nextLayer,
row,
col
});
}
this.updateEmptyBlocks(state, Math.floor(totalBlocks * (config.emptyBlocksPercentage / 100)));
this.updateBlocksImage(state, config, true);
this.startAnimationLoop(state, config);
}
updateEmptyBlocks(state, count) {
const nextEmpty = new Set();
while (nextEmpty.size < count) {
nextEmpty.add(Math.floor(Math.random() * state.blocks.length));
}
state.nextEmptyBlocks = nextEmpty;
}
setBlockBackground(element, imageUrl, block, config) {
const widthPercent = config.gridSize * 100;
const heightPercent = config.gridSize * 100;
Object.assign(element.style, {
background: \`url(\${imageUrl})\`,
backgroundSize: \`\${widthPercent}% \${heightPercent}%\`,
backgroundPosition: \`\${block.col * -100}% \${block.row * -100}%\`,
opacity: '1',
transform: 'rotateY(0deg)'
});
}
clearBlockBackground(element) {
Object.assign(element.style, {
background: 'none',
backgroundColor: 'transparent',
opacity: '0'
});
}
updateBlocksImage(state, config, isInitial = false) {
const currentImage = config.images[state.currentImageIndex];
const nextImageIndex = (state.currentImageIndex + 1) % config.images.length;
const nextImage = config.images[nextImageIndex];
const isMobile = config.mobileOptimization && window.innerWidth <= 768;
const maxDelay = isMobile ? ${flipCanvasConfig.staggerDelay * 0.5} : ${flipCanvasConfig.staggerDelay};
const animationStyle = config.animationStyle;
state.blocks.forEach((block, index) => {
if (isInitial) {
if (!state.emptyBlocks.has(index)) {
this.setBlockBackground(block.currentLayer, currentImage, block, config);
this.clearBlockBackground(block.nextLayer);
if (animationStyle === 'cube') {
block.nextLayer.style.transform = 'rotateY(90deg)';
} else if (animationStyle === 'zoom') {
block.nextLayer.style.transform = 'scale(0.5) rotateY(90deg)';
}
} else {
this.clearBlockBackground(block.currentLayer);
this.clearBlockBackground(block.nextLayer);
}
} else {
const willBeEmpty = state.nextEmptyBlocks.has(index);
const isCurrentlyEmpty = state.emptyBlocks.has(index);
if (!willBeEmpty) {
this.setBlockBackground(block.nextLayer, nextImage, block, config);
const delay = Math.random() * maxDelay;
block.currentLayer.style.transitionDelay = \`\${delay}s\`;
block.nextLayer.style.transitionDelay = \`\${delay}s\`;
switch(animationStyle) {
case 'horizontal':
block.currentLayer.style.transform = 'translateX(-100%) rotateY(90deg)';
block.nextLayer.style.transform = 'translateX(0) rotateY(0deg)';
break;
case 'vertical':
block.currentLayer.style.transform = 'translateY(-100%) rotateX(90deg)';
block.nextLayer.style.transform = 'translateY(0) rotateX(0deg)';
break;
case 'diagonal':
block.currentLayer.style.transform = 'translate(-100%, -100%) rotate3d(1, 1, 0, 90deg)';
block.nextLayer.style.transform = 'translate(0, 0) rotate3d(1, 1, 0, 0deg)';
break;
case 'zoom':
block.currentLayer.style.transform = 'scale(0.5) rotateY(90deg)';
block.nextLayer.style.transform = 'scale(1) rotateY(0deg)';
break;
case 'cube':
block.currentLayer.style.transform = 'rotateY(-90deg)';
block.nextLayer.style.transform = 'rotateY(0deg)';
break;
case 'fade':
block.currentLayer.style.transform = 'scale(0.95) rotate3d(1, 1, 1, 5deg)';
block.currentLayer.style.opacity = '0';
block.nextLayer.style.transform = 'scale(1) rotate3d(0, 0, 0, 0deg)';
block.nextLayer.style.opacity = '1';
break;
}
if (animationStyle !== 'fade') {
block.currentLayer.style.opacity = '0';
block.nextLayer.style.opacity = '1';
}
} else {
this.clearBlockBackground(block.nextLayer);
if (!isCurrentlyEmpty) {
switch(animationStyle) {
case 'horizontal':
block.currentLayer.style.transform = 'translateX(-100%) rotateY(90deg)';
break;
case 'vertical':
block.currentLayer.style.transform = 'translateY(-100%) rotateX(90deg)';
break;
case 'diagonal':
block.currentLayer.style.transform = 'translate(-100%, -100%) rotate3d(1, 1, 0, 90deg)';
break;
case 'zoom':
block.currentLayer.style.transform = 'scale(0.5) rotateY(90deg)';
break;
case 'cube':
block.currentLayer.style.transform = 'rotateY(-90deg)';
break;
case 'fade':
block.currentLayer.style.transform = 'scale(0.95) rotate3d(1, 1, 1, 5deg)';
break;
}
block.currentLayer.style.opacity = '0';
}
}
}
});
if (!isInitial) {
const resetTimeout = isMobile ? ${(flipCanvasConfig.animationDuration * 0.6 + flipCanvasConfig.staggerDelay * 0.5) * 1000} : ${(flipCanvasConfig.animationDuration + flipCanvasConfig.staggerDelay) * 1000};
setTimeout(() => {
state.blocks.forEach(block => {
const temp = block.currentLayer;
block.currentLayer = block.nextLayer;
block.nextLayer = temp;
block.nextLayer.style.transition = 'none';
switch(animationStyle) {
case 'horizontal':
block.nextLayer.style.transform = 'translateX(100%) rotateY(-90deg)';
break;
case 'vertical':
block.nextLayer.style.transform = 'translateY(100%) rotateX(-90deg)';
break;
case 'diagonal':
block.nextLayer.style.transform = 'translate(100%, 100%) rotate3d(-1, -1, 0, 90deg)';
break;
case 'zoom':
block.nextLayer.style.transform = 'scale(1.5) rotateY(-90deg)';
break;
case 'cube':
block.nextLayer.style.transform = 'rotateY(90deg)';
break;
case 'fade':
block.nextLayer.style.transform = 'scale(1.05) rotate3d(-1, -1, -1, 5deg)';
break;
}
block.nextLayer.style.opacity = '0';
setTimeout(() => {
const isMobile = config.mobileOptimization && window.innerWidth <= 768;
const transitionDuration = isMobile ? '${flipCanvasConfig.animationDuration * 0.6}s' : '${flipCanvasConfig.animationDuration}s';
block.nextLayer.style.transition = \`transform \${transitionDuration} ease-in-out, opacity \${transitionDuration} ease-in-out\`;
}, 50);
});
}, resetTimeout);
}
}
startAnimationLoop(state, config) {
let lastTime = 0;
const animate = (currentTime) => {
if (!lastTime) lastTime = currentTime;
const deltaTime = currentTime - lastTime;
if (deltaTime >= config.transitionTime && !state.isAnimating) {
state.isAnimating = true;
this.updateEmptyBlocks(state, Math.floor(state.blocks.length * (config.emptyBlocksPercentage / 100)));
this.updateBlocksImage(state, config);
setTimeout(() => {
state.emptyBlocks = state.nextEmptyBlocks;
state.currentImageIndex = (state.currentImageIndex + 1) % config.images.length;
state.isAnimating = false;
lastTime = currentTime;
}, config.mobileOptimization && window.innerWidth <= 768 ? ${(flipCanvasConfig.animationDuration * 0.6 + flipCanvasConfig.staggerDelay * 0.5) * 1000 + 200} : ${(flipCanvasConfig.animationDuration + flipCanvasConfig.staggerDelay) * 1000 + 200});
}
requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
}
}
window.addEventListener('load', () => {
setTimeout(() => new GridFlipAnimation(), 500);
});
})();`;
}
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`);
const displayElement = document.getElementById(`${parameterId}-display`);
if (valueElement) {
valueElement.textContent = defaultValue;
}
if (displayElement) {
displayElement.textContent = defaultValue;
}
switch (parameterId) {
case 'grid-size':
flipCanvasConfig.gridSize = defaultValue;
break;
case 'transition-time':
flipCanvasConfig.transitionTime = defaultValue * 1000;
break;
case 'empty-percentage':
flipCanvasConfig.emptyPercentage = defaultValue;
break;
case 'animation-duration':
flipCanvasConfig.animationDuration = defaultValue;
break;
case 'stagger-delay':
flipCanvasConfig.staggerDelay = defaultValue;
break;
}
updateFlipPreview();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function generateRandomFlip() {
flipCanvasConfig.gridSize = Math.floor(Math.random() * 5) + 2;
flipCanvasConfig.transitionTime = (Math.random() * 9 + 1) * 1000;
flipCanvasConfig.emptyPercentage = Math.floor(Math.random() * 50);
flipCanvasConfig.animationStyle = ['horizontal', 'vertical', 'diagonal', 'zoom', 'cube', 'fade'][Math.floor(Math.random() * 6)];
flipCanvasConfig.animationDuration = Math.random() * 1.7 + 0.3;
flipCanvasConfig.staggerDelay = Math.random() * 0.5;
document.getElementById('grid-size').value = flipCanvasConfig.gridSize;
document.getElementById('transition-time').value = flipCanvasConfig.transitionTime / 1000;
document.getElementById('empty-percentage').value = flipCanvasConfig.emptyPercentage;
document.getElementById('animation-duration').value = flipCanvasConfig.animationDuration;
document.getElementById('stagger-delay').value = flipCanvasConfig.staggerDelay;
document.getElementById('grid-size-value').textContent = flipCanvasConfig.gridSize;
document.getElementById('grid-size-display').textContent = flipCanvasConfig.gridSize;
document.getElementById('transition-time-value').textContent = flipCanvasConfig.transitionTime / 1000;
document.getElementById('empty-percentage-value').textContent = flipCanvasConfig.emptyPercentage;
document.getElementById('animation-duration-value').textContent = flipCanvasConfig.animationDuration;
document.getElementById('stagger-delay-value').textContent = flipCanvasConfig.staggerDelay;
document.querySelector(`input[name="animation-style"][value="${flipCanvasConfig.animationStyle}"]`).checked = true;
updateFlipPreview();
showNotification('Random flip canvas generated!');
}
function updateImagesConfig() {
const imageInputs = document.querySelectorAll('.image-url-input');
const images = Array.from(imageInputs)
.map(input => input.value.trim())
.filter(url => url.length > 0);
if (images.length >= 2) {
flipCanvasConfig.images = images;
updateFlipPreview();
}
}
function addImageField(imageUrl = '') {
const wrapper = document.createElement('div');
wrapper.className = 'image-field-wrapper';
const input = document.createElement('input');
input.type = 'url';
input.className = 'image-url-input';
input.placeholder = 'Enter image URL';
input.value = imageUrl;
input.addEventListener('input', function() {
updateImagesConfig();
});
const removeBtn = document.createElement('button');
removeBtn.type = 'button';
removeBtn.className = 'remove-image-btn';
removeBtn.innerHTML = '×';
removeBtn.addEventListener('click', function() {
if (document.querySelectorAll('.image-field-wrapper').length > 2) {
wrapper.remove();
updateImagesConfig();
} else {
showNotification('You need at least 2 images for the slideshow', 'warning');
}
});
wrapper.appendChild(input);
wrapper.appendChild(removeBtn);
document.querySelector('.image-fields').appendChild(wrapper);
}
function initializeUI() {
setTimeout(() => {
initFlipCanvas();
}, 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');
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-grid-flip');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
const previewContainer = document.getElementById('flip-canvas-preview');
previewContainer.style.backgroundColor = '#252525';
document.getElementById('reset-grid').addEventListener('click', () => {
flipCanvasConfig.gridSize = defaultConfig.gridSize;
flipCanvasConfig.transitionTime = defaultConfig.transitionTime;
flipCanvasConfig.emptyPercentage = defaultConfig.emptyPercentage;
document.getElementById('grid-size').value = defaultConfig.gridSize;
document.getElementById('transition-time').value = defaultConfig.transitionTime / 1000;
document.getElementById('empty-percentage').value = defaultConfig.emptyPercentage;
document.getElementById('grid-size-value').textContent = defaultConfig.gridSize;
document.getElementById('grid-size-display').textContent = defaultConfig.gridSize;
document.getElementById('transition-time-value').textContent = defaultConfig.transitionTime / 1000;
document.getElementById('empty-percentage-value').textContent = defaultConfig.emptyPercentage;
updateFlipPreview();
showNotification('Grid settings reset');
});
document.getElementById('reset-appearance').addEventListener('click', () => {
flipCanvasConfig.animationStyle = defaultConfig.animationStyle;
flipCanvasConfig.images = [...defaultConfig.images];
document.querySelector(`input[name="animation-style"][value="${defaultConfig.animationStyle}"]`).checked = true;
document.querySelector('.image-fields').innerHTML = '';
defaultConfig.images.forEach((url) => {
addImageField(url);
});
updateFlipPreview();
showNotification('Appearance settings reset');
});
document.getElementById('reset-animation').addEventListener('click', () => {
flipCanvasConfig.animationDuration = defaultConfig.animationDuration;
flipCanvasConfig.staggerDelay = defaultConfig.staggerDelay;
flipCanvasConfig.mobileOptimization = defaultConfig.mobileOptimization;
document.getElementById('animation-duration').value = defaultConfig.animationDuration;
document.getElementById('stagger-delay').value = defaultConfig.staggerDelay;
document.getElementById('mobile-optimization').checked = defaultConfig.mobileOptimization;
document.getElementById('animation-duration-value').textContent = defaultConfig.animationDuration;
document.getElementById('stagger-delay-value').textContent = defaultConfig.staggerDelay;
updateFlipPreview();
showNotification('Animation settings reset');
});
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
const valueElement = document.getElementById(`${input.id}-value`);
const displayElement = document.getElementById(`${input.id}-display`);
if (valueElement) {
valueElement.textContent = input.value;
}
if (displayElement) {
displayElement.textContent = input.value;
}
input.addEventListener('input', () => {
if (valueElement) {
valueElement.textContent = input.value;
}
if (displayElement) {
displayElement.textContent = input.value;
}
switch (input.id) {
case 'grid-size':
flipCanvasConfig.gridSize = parseInt(input.value);
break;
case 'transition-time':
flipCanvasConfig.transitionTime = parseFloat(input.value) * 1000;
break;
case 'empty-percentage':
flipCanvasConfig.emptyPercentage = parseInt(input.value);
break;
case 'animation-duration':
flipCanvasConfig.animationDuration = parseFloat(input.value);
break;
case 'stagger-delay':
flipCanvasConfig.staggerDelay = parseFloat(input.value);
break;
}
updateFlipPreview();
});
});
document.querySelectorAll('input[name="animation-style"]').forEach(radio => {
radio.addEventListener('change', function() {
if (this.checked) {
flipCanvasConfig.animationStyle = this.value;
updateFlipPreview();
}
});
});
document.getElementById('mobile-optimization').addEventListener('change', function() {
flipCanvasConfig.mobileOptimization = this.checked;
updateFlipPreview();
});
document.querySelector('.add-image-btn').addEventListener('click', function() {
addImageField();
updateImagesConfig();
});
document.querySelectorAll('.remove-image-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (document.querySelectorAll('.image-field-wrapper').length > 2) {
this.closest('.image-field-wrapper').remove();
updateImagesConfig();
} else {
showNotification('You need at least 2 images for the slideshow', 'warning');
}
});
});
document.querySelectorAll('.image-url-input').forEach(input => {
input.addEventListener('input', updateImagesConfig);
});
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;
}
}
});
setTimeout(() => {
showNotification('BricksFusion Flip Canvas Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-flip-config', JSON.stringify(flipCanvasConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-flip-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(flipCanvasConfig, savedConfig);
document.getElementById('grid-size').value = savedConfig.gridSize;
document.getElementById('transition-time').value = savedConfig.transitionTime / 1000;
document.getElementById('empty-percentage').value = savedConfig.emptyPercentage;
document.getElementById('animation-duration').value = savedConfig.animationDuration;
document.getElementById('stagger-delay').value = savedConfig.staggerDelay;
document.getElementById('mobile-optimization').checked = savedConfig.mobileOptimization;
document.getElementById('grid-size-value').textContent = savedConfig.gridSize;
document.getElementById('grid-size-display').textContent = savedConfig.gridSize;
document.getElementById('transition-time-value').textContent = savedConfig.transitionTime / 1000;
document.getElementById('empty-percentage-value').textContent = savedConfig.emptyPercentage;
document.getElementById('animation-duration-value').textContent = savedConfig.animationDuration;
document.getElementById('stagger-delay-value').textContent = savedConfig.staggerDelay;
document.querySelector(`input[name="animation-style"][value="${savedConfig.animationStyle}"]`).checked = true;
document.querySelector('.image-fields').innerHTML = '';
savedConfig.images.forEach((url) => {
addImageField(url);
});
updateFlipPreview();
}
} catch (e) {
}
}
const originalUpdateFlipPreview = updateFlipPreview;
updateFlipPreview = function() {
originalUpdateFlipPreview();
saveConfiguration();
};
loadConfiguration();
}
initializeUI();
});
</script>
</body>
</html>
Flip Canvas
Creates a grid of blocks that flip between images with 3D transforms. Blocks animate in staggered timing with customizable flip directions. Some blocks can stay empty for visual interest. Perfect for image galleries, hero sections, or dynamic backgrounds.
Grid
Number of blocks per row and column. Creates an NxN grid. Higher values create more blocks and smoother transitions.
Default: 4 (16 blocks total)
Percentage of blocks that remain empty during transition. Creates visual gaps and reveals content below.
Default: 30
Images
List of image URLs separated by commas. Requires at least 2 images. Grid cycles through them continuously.
Default: Empty (element won't initialize)
Animation
Type of flip animation. Horizontal flips left, vertical flips down, diagonal flips diagonally, zoom scales in/out, cube rotates 3D, fade cross-dissolves.
Default: Horizontal
How long each block takes to flip. Shorter is snappy, longer is smooth and dramatic.
Default: 0.8
Maximum random delay between blocks starting to flip. Creates wave-like cascading effect.
Default: 0.2
Time between image changes. How long each image displays before flipping to next.
Default: 3000 (3 seconds)
Mobile
Reduces animation speed by 40% on mobile devices (screen width ≤768px) for better performance and battery life.
Default: Off
Performance
This element uses CSS 3D transforms with preserve-3d and backface-visibility for smooth flips. Each block has two layers that swap positions after animation. Uses requestAnimationFrame for timing loop. Grid calculates background positions to show correct portion of each image. Mobile optimization reduces speeds by 40% automatically.