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>Morphing Text 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: #000000;
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: 60px;
z-index: 2;
position: relative;
}
.morphing-text-wrapper {
position: relative;
display: inline-block;
filter: url(#threshold) blur(0.6px);
}
.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);
}
.text-input {
width: 100%;
padding: 0.75rem;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid var(--border);
border-radius: var(--input-radius);
color: var(--text-primary);
font-family: var(--font);
font-size: var(--text-xs);
transition: var(--transition);
margin-bottom: 1rem;
resize: vertical;
line-height: 1.5;
}
.text-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
outline: none;
}
textarea.text-input {
min-height: 80px;
}
.color-list {
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1.5rem;
}
.color-row {
display: flex;
align-items: center;
gap: 1.25rem;
padding: 1rem 1.25rem;
background-color: rgba(30, 30, 30, 0.7);
border: 1px solid var(--border);
border-radius: var(--input-radius);
transition: var(--transition);
}
.color-row:hover {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.1);
}
.color-picker-container {
position: relative;
width: 40px;
height: 40px;
border-radius: 8px;
overflow: hidden;
border: 2px solid var(--border);
cursor: pointer;
transition: var(--transition);
flex-shrink: 0;
background: var(--card-bg);
display: flex;
align-items: center;
justify-content: center;
--selected-color: #FFFFFF;
}
.color-picker-container:hover {
border-color: var(--accent);
transform: scale(1.05);
box-shadow: 0 0 12px rgba(239, 96, 19, 0.3);
}
.color-picker-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: var(--selected-color, #FFFFFF);
border-radius: 6px;
transition: var(--transition);
}
input[type="color"] {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
cursor: pointer;
background: transparent;
opacity: 0;
z-index: 2;
}
.color-input-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.color-label {
font-size: 10px;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-left: 0.25rem;
}
.color-input {
padding: 0.5rem 0.75rem;
background-color: rgba(0, 0, 0, 0.3);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text-primary);
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
font-size: 12px;
transition: var(--transition);
min-width: 0;
}
.color-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 1px rgba(239, 96, 19, 0.2);
outline: none;
}
.color-input.invalid {
border-color: var(--danger);
box-shadow: 0 0 0 1px rgba(220, 53, 69, 0.2);
}
.hex-input,
.hsl-input {
width: 100%;
}
.color-input-group:nth-child(2) {
flex: 0.3;
}
.color-input-group:nth-child(3) {
flex: 0.7;
}
select {
width: 100%;
padding: 0.75rem;
border: 1px solid var(--border);
border-radius: var(--input-radius);
font-family: var(--font);
font-size: var(--text-xs);
color: var(--text-primary);
background-color: var(--card-bg);
margin-bottom: 0.75rem;
outline: none;
transition: var(--transition);
}
select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(239, 96, 19, 0.2);
}
.notification {
position: fixed;
bottom: calc(var(--action-bar-height) + 1rem);
left: 50%;
background-color: var(--success);
color: white;
padding: 0.75rem 1rem;
border-radius: var(--input-radius);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 1001;
transform: translate(-50%, 200px);
opacity: 0;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease;
font-size: var(--text-xs);
font-weight: 500;
max-width: 320px;
word-wrap: break-word;
line-height: 1.4;
text-align: center;
}
.notification.show {
transform: translate(-50%, 0);
opacity: 1;
}
@media (max-width: 1200px) {
.content {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.preview-section {
position: static;
}
.controls-section {
max-width: 100%;
}
}
@media (max-width: 768px) {
.action-bar {
flex-direction: column;
height: auto;
min-height: var(--action-bar-height);
padding: 0.75rem;
}
.breadcrumb {
order: 1;
width: 100%;
}
.action-buttons {
order: 2;
width: 100%;
justify-content: center;
flex-wrap: wrap;
}
body {
padding-bottom: calc(var(--action-bar-height) + 20px);
}
.preview-container {
height: 300px;
}
.page-title {
font-size: 2rem;
}
.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%;
}
}
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);
}
</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/animated-text/" class="breadcrumb-item">Animated text</a>
<span class="breadcrumb-separator">›</span>
<span class="breadcrumb-item active">Morphing Text</span>
</nav>
<div class="action-buttons">
<div class="data-attribute-display" id="quick-attribute" title="Click to copy data attribute">
data-morphing-text
</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">Morphing Text</h1>
<p class="page-subtitle">Interactive text animation 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 morphing text 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-morphing-text</code> as attribute name (leave value empty)</li>
<li>Optionally, add <code>data-texts</code> attribute with comma-separated texts to morph between</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<div class="content">
<section class="preview-section">
<div class="preview-container" id="morphing-preview" data-morphing-text>
<div class="preview-content">
<div id="preview-wrapper"></div>
</div>
</div>
</section>
<section class="controls-section">
<div class="card">
<div class="card-heading">
Text Configuration
<div class="card-actions">
<button class="card-action-btn" id="reset-texts" title="Reset Texts">↺</button>
</div>
</div>
<div class="card-content">
<div class="control-group">
<div class="control-label">
<span class="label-text">
Text Items
<span class="help-tooltip" title="Comma-separated texts to morph between">ℹ</span>
</span>
</div>
<textarea class="text-input" id="text-items" rows="3" placeholder="Enter words separated by commas...">Captivating,Innovative,Mesmerizing,Stunning</textarea>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Text Color</span>
</div>
<div class="color-list">
<div class="color-row">
<div class="color-picker-container">
<input type="color" id="text-color" value="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HEX</span>
<input type="text" class="color-input hex-input" id="text-color-hex" value="#FFFFFF" placeholder="#FFFFFF">
</div>
<div class="color-input-group">
<span class="color-label">HSL</span>
<input type="text" class="color-input hsl-input" id="text-color-hsl" placeholder="hsl(0, 100%, 50%)">
</div>
</div>
</div>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Font Size
<span class="help-tooltip" title="Size of the morphing text">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="font-size-value">40</span>px</span>
<button class="reset-btn" onclick="resetParameter('font-size', 40)">↺</button>
</div>
</div>
<input type="range" id="font-size" min="20" max="100" value="40">
</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">
Morph Time
<span class="help-tooltip" title="Duration of morphing animation">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="morph-time-value">1.5</span>s</span>
<button class="reset-btn" onclick="resetParameter('morph-time', 1.5)">↺</button>
</div>
</div>
<input type="range" id="morph-time" min="0.5" max="3" step="0.1" value="1.5">
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Cooldown Time
<span class="help-tooltip" title="Pause between morphs">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="cooldown-time-value">0.5</span>s</span>
<button class="reset-btn" onclick="resetParameter('cooldown-time', 0.5)">↺</button>
</div>
</div>
<input type="range" id="cooldown-time" min="0" max="2" step="0.1" value="0.5">
</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">Text Align</span>
</div>
<select id="text-align">
<option value="center" selected>Center</option>
<option value="left">Left</option>
<option value="right">Right</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">Font Weight</span>
</div>
<select id="font-weight">
<option value="normal">Normal</option>
<option value="bold" selected>Bold</option>
</select>
</div>
<div class="control-group">
<div class="control-label">
<span class="label-text">
Line Height
<span class="help-tooltip" title="Line height multiplier">ℹ</span>
</span>
<div class="value-display">
<span class="value-text"><span id="line-height-value">1.2</span></span>
<button class="reset-btn" onclick="resetParameter('line-height', 1.2)">↺</button>
</div>
</div>
<input type="range" id="line-height" min="1" max="2" step="0.1" value="1.2">
</div>
</div>
</div>
</section>
</div>
</div>
<svg id="morphing-text-filters" style="width: 0; height: 0; position: absolute; visibility: hidden;">
<defs>
<filter id="threshold">
<feColorMatrix in="SourceGraphic" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 255 -140" />
</filter>
</defs>
</svg>
<div class="notification" id="notification"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let morphConfig = {
texts: ['Captivating', 'Innovative', 'Mesmerizing', 'Stunning'],
morphTime: 1.5,
cooldownTime: 0.5,
fontSize: 40,
textColor: '#FFFFFF',
textAlign: 'center',
fontWeight: 'bold',
lineHeight: 1.2
};
const defaultConfig = { ...morphConfig };
let morphingInstance = null;
function initMorphingText() {
const previewWrapper = document.getElementById('preview-wrapper');
if (!previewWrapper) return;
if (morphingInstance) {
if (morphingInstance.animationFrame) {
cancelAnimationFrame(morphingInstance.animationFrame);
}
previewWrapper.innerHTML = '';
}
const textWrapper = document.createElement('div');
textWrapper.className = 'morphing-text-wrapper';
textWrapper.style.cssText = `
position: relative;
display: inline-block;
width: auto;
height: ${morphConfig.fontSize * morphConfig.lineHeight}px;
text-align: ${morphConfig.textAlign};
`;
const text1 = document.createElement('span');
const text2 = document.createElement('span');
let alignTransform = '';
if (morphConfig.textAlign === 'center') {
alignTransform = 'left: 50%; transform: translateX(-50%);';
} else if (morphConfig.textAlign === 'right') {
alignTransform = 'right: 0;';
} else {
alignTransform = 'left: 0;';
}
const baseTextStyles = `
position: absolute;
display: block !important;
text-align: ${morphConfig.textAlign};
font-size: ${morphConfig.fontSize}px !important;
font-weight: ${morphConfig.fontWeight} !important;
color: ${morphConfig.textColor} !important;
white-space: nowrap;
line-height: ${morphConfig.lineHeight} !important;
margin: 0;
padding: 0;
pointer-events: none;
user-select: none;
opacity: 1;
top: 0;
${alignTransform}
`;
text1.style.cssText = baseTextStyles;
text2.style.cssText = baseTextStyles;
textWrapper.appendChild(text1);
textWrapper.appendChild(text2);
previewWrapper.appendChild(textWrapper);
let textIndex = 0;
let morph = 0;
let cooldown = morphConfig.cooldownTime;
let lastTime = new Date();
text1.textContent = morphConfig.texts[textIndex % morphConfig.texts.length];
text2.textContent = morphConfig.texts[(textIndex + 1) % morphConfig.texts.length];
function setMorphStyles(fraction) {
text2.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;
text2.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;
const invertedFraction = 1 - fraction;
text1.style.filter = `blur(${Math.min(8 / invertedFraction - 8, 100)}px)`;
text1.style.opacity = `${Math.pow(invertedFraction, 0.4) * 100}%`;
text1.textContent = morphConfig.texts[textIndex % morphConfig.texts.length];
text2.textContent = morphConfig.texts[(textIndex + 1) % morphConfig.texts.length];
}
function doMorph() {
morph -= cooldown;
cooldown = 0;
let fraction = morph / morphConfig.morphTime;
if (fraction > 1) {
cooldown = morphConfig.cooldownTime;
fraction = 1;
}
setMorphStyles(fraction);
if (fraction === 1) {
textIndex++;
}
}
function doCooldown() {
morph = 0;
text2.style.filter = "none";
text2.style.opacity = "100%";
text1.style.filter = "none";
text1.style.opacity = "0%";
}
function animate() {
morphingInstance.animationFrame = requestAnimationFrame(animate);
if (document.hidden) {
return;
}
const newTime = new Date();
const dt = (newTime.getTime() - lastTime.getTime()) / 1000;
lastTime = newTime;
cooldown -= dt;
if (cooldown <= 0) {
doMorph();
} else {
doCooldown();
}
}
morphingInstance = {
animationFrame: null,
updateConfig: function(newConfig) {
Object.assign(morphConfig, newConfig);
text1.style.fontSize = `${morphConfig.fontSize}px`;
text2.style.fontSize = `${morphConfig.fontSize}px`;
text1.style.fontWeight = morphConfig.fontWeight;
text2.style.fontWeight = morphConfig.fontWeight;
text1.style.color = morphConfig.textColor;
text2.style.color = morphConfig.textColor;
text1.style.textAlign = morphConfig.textAlign;
text2.style.textAlign = morphConfig.textAlign;
text1.style.lineHeight = morphConfig.lineHeight;
text2.style.lineHeight = morphConfig.lineHeight;
textWrapper.style.height = `${morphConfig.fontSize * morphConfig.lineHeight}px`;
textWrapper.style.textAlign = morphConfig.textAlign;
let alignTransform = '';
if (morphConfig.textAlign === 'center') {
alignTransform = 'left: 50%; transform: translateX(-50%);';
} else if (morphConfig.textAlign === 'right') {
alignTransform = 'right: 0;';
} else {
alignTransform = 'left: 0;';
}
text1.style.cssText = `${text1.style.cssText}; ${alignTransform}`;
text2.style.cssText = `${text2.style.cssText}; ${alignTransform}`;
}
};
animate();
}
function updateMorphingText() {
if (morphingInstance && morphingInstance.updateConfig) {
morphingInstance.updateConfig(morphConfig);
} else {
initMorphingText();
}
}
function showNotification(message, type = 'success') {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = `notification ${type}`;
notification.offsetHeight;
notification.style.visibility = 'visible';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
if (!notification.classList.contains('show')) {
notification.style.visibility = 'hidden';
}
}, 400);
}, 3000);
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
showNotification('Copied to clipboard!');
})
.catch(err => {
showNotification('Failed to copy to clipboard', 'error');
});
}
function hexToHsl(hex) {
const r = parseInt(hex.slice(1, 3), 16) / 255;
const g = parseInt(hex.slice(3, 5), 16) / 255;
const b = parseInt(hex.slice(5, 7), 16) / 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return {
h: Math.round(h * 360),
s: Math.round(s * 100),
l: Math.round(l * 100)
};
}
function hslToHex(hsl) {
const match = hsl.match(/hsl\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)/);
if (!match) return null;
let h = parseInt(match[1]) / 360;
let s = parseInt(match[2]) / 100;
let l = parseInt(match[3]) / 100;
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
const toHex = (c) => {
const hex = Math.round(c * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
function isValidHex(hex) {
return /^#[0-9A-F]{6}$/i.test(hex);
}
function isValidHsl(hsl) {
return /^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/i.test(hsl);
}
function formatHex(value) {
let hex = value.replace(/[^0-9A-Fa-f#]/g, '');
if (!hex.startsWith('#')) {
hex = '#' + hex;
}
if (hex.length > 7) {
hex = hex.substring(0, 7);
}
return hex.toUpperCase();
}
function formatHsl(value) {
const cleanValue = value.replace(/[^\d,\s]/g, '');
const numbers = cleanValue.match(/\d+/g);
if (!numbers || numbers.length < 3) {
const partialMatch = value.match(/(\d+)/g);
if (partialMatch && partialMatch.length >= 1) {
const h = Math.min(360, Math.max(0, parseInt(partialMatch[0]) || 0));
const s = Math.min(100, Math.max(0, parseInt(partialMatch[1]) || 50));
const l = Math.min(100, Math.max(0, parseInt(partialMatch[2]) || 50));
return `hsl(${h}, ${s}%, ${l}%)`;
}
return value;
}
let h = Math.min(360, Math.max(0, parseInt(numbers[0])));
let s = Math.min(100, Math.max(0, parseInt(numbers[1])));
let l = Math.min(100, Math.max(0, parseInt(numbers[2])));
return `hsl(${h}, ${s}%, ${l}%)`;
}
function updateColorInputs() {
const colorInput = document.getElementById('text-color');
const hexInput = document.getElementById('text-color-hex');
const hslInput = document.getElementById('text-color-hsl');
if (colorInput && hexInput && hslInput) {
colorInput.value = morphConfig.textColor;
hexInput.value = morphConfig.textColor;
hslInput.value = `hsl(${hexToHsl(morphConfig.textColor).h}, ${hexToHsl(morphConfig.textColor).s}%, ${hexToHsl(morphConfig.textColor).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', morphConfig.textColor);
}
}
}
function generateUniqueId() {
return Math.random().toString(36).substring(2, 8);
}
function generateFullSectionJSON() {
// Generar IDs únicos para todos los elementos
const sectionId = generateUniqueId();
const containerId = generateUniqueId();
const divId = generateUniqueId();
const 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": "#ffffff"
}
},
"_height": "500"
}
},
{
"id": containerId,
"name": "container",
"parent": sectionId,
"children": [divId],
"settings": {
"_direction": "row",
"_alignSelf": "center",
"_justifyContent": "center",
"_alignItems": "center"
}
},
{
"id": divId,
"name": "div",
"parent": containerId,
"children": [],
"settings": {
"_width": "300",
"_height": "300",
"_border": {
"radius": {
"top": "15",
"right": "15",
"bottom": "15",
"left": "15"
}
},
"_background": {
"color": {
"hex": "#000000"
}
},
"_attributes": [
{
"id": attributeId,
"name": "data-morphing-text"
}
],
"_display": "flex",
"_position": "relative",
"_alignSelf": "center",
"_justifyContent": "center",
"_alignItems": "center"
},
"label": "Morphing text Div"
},
{
"id": codeId,
"name": "code",
"parent": sectionId,
"children": [],
"settings": {
"javascriptCode": jsCode,
"executeCode": true,
"_display": "none"
},
"label": "Morphing Text JS"
}
],
"source": "bricksCopiedElements",
"sourceUrl": "https://test.bricksfusion.com",
"version": "2.0.1",
"globalClasses": [],
"globalElements": []
};
return JSON.stringify(bricksJSON, null, 2);
}
function generateJavaScriptCode() {
return `(function() {
const DEFAULT_CONFIG = {
morphTime: ${morphConfig.morphTime},
cooldownTime: ${morphConfig.cooldownTime},
fontSize: ${morphConfig.fontSize},
textColor: '${morphConfig.textColor}',
textAlign: '${morphConfig.textAlign}',
fontWeight: '${morphConfig.fontWeight}',
lineHeight: ${morphConfig.lineHeight}
};
const DEFAULT_TEXTS = ${JSON.stringify(morphConfig.texts)};
function createSvgFilters() {
if (document.getElementById('morphing-text-filters')) {
return;
}
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.id = 'morphing-text-filters';
svg.style.width = '0';
svg.style.height = '0';
svg.style.position = 'absolute';
svg.style.visibility = 'hidden';
svg.setAttribute('preserveAspectRatio', 'xMidYMid slice');
svg.innerHTML =
'<defs>' +
'<filter id="threshold">' +
'<feColorMatrix ' +
'in="SourceGraphic" ' +
'type="matrix" ' +
'values="1 0 0 0 0 ' +
'0 1 0 0 0 ' +
'0 0 1 0 0 ' +
'0 0 0 255 -140" ' +
'/>' +
'</filter>' +
'</defs>';
document.body.appendChild(svg);
}
function createMorphingText(element) {
const config = {
morphTime: parseFloat(element.getAttribute('data-morph-time')) || DEFAULT_CONFIG.morphTime,
cooldownTime: parseFloat(element.getAttribute('data-cooldown-time')) || DEFAULT_CONFIG.cooldownTime,
fontSize: parseFloat(element.getAttribute('data-font-size')) || DEFAULT_CONFIG.fontSize,
textColor: element.getAttribute('data-text-color') || DEFAULT_CONFIG.textColor,
disableAnimation: false,
textAlign: element.getAttribute('data-text-align') || DEFAULT_CONFIG.textAlign,
fontWeight: element.getAttribute('data-font-weight') || DEFAULT_CONFIG.fontWeight,
lineHeight: parseFloat(element.getAttribute('data-line-height')) || DEFAULT_CONFIG.lineHeight
};
const textsAttr = element.getAttribute('data-texts');
if (!textsAttr) {
element.setAttribute('data-texts', DEFAULT_TEXTS.join(','));
}
const texts = (textsAttr || DEFAULT_TEXTS.join(',')).split(',').map(text => text.trim());
if (texts.length < 2) {
return;
}
const tempSpan = document.createElement('span');
tempSpan.style.cssText =
'position: absolute;' +
'visibility: hidden;' +
'font-size: ' + config.fontSize + 'px;' +
'font-weight: ' + config.fontWeight + ';' +
'white-space: nowrap;';
document.body.appendChild(tempSpan);
let maxWidth = 0;
texts.forEach(text => {
tempSpan.textContent = text;
maxWidth = Math.max(maxWidth, tempSpan.offsetWidth);
});
document.body.removeChild(tempSpan);
const textWrapper = document.createElement('div');
textWrapper.className = 'morphing-text-wrapper';
textWrapper.style.cssText =
'position: relative;' +
'display: inline-block;' +
'width: ' + maxWidth + 'px;' +
'height: ' + (config.fontSize * config.lineHeight) + 'px;' +
'text-align: ' + config.textAlign + ';';
const text1 = document.createElement('span');
const text2 = document.createElement('span');
let alignTransform = '';
if (config.textAlign === 'center') {
alignTransform = 'left: 50%; transform: translateX(-50%);';
} else if (config.textAlign === 'right') {
alignTransform = 'right: 0;';
} else {
alignTransform = 'left: 0;';
}
const baseTextStyles =
'position: absolute;' +
'display: block !important;' +
'text-align: ' + config.textAlign + ';' +
'font-size: ' + config.fontSize + 'px !important;' +
'font-weight: ' + config.fontWeight + ' !important;' +
'color: ' + config.textColor + ' !important;' +
'white-space: nowrap;' +
'line-height: ' + config.lineHeight + ' !important;' +
'margin: 0;' +
'padding: 0;' +
'pointer-events: none;' +
'user-select: none;' +
'opacity: 1;' +
'top: 0;' +
alignTransform;
text1.style.cssText = baseTextStyles;
text2.style.cssText = baseTextStyles;
if (!config.disableAnimation) {
textWrapper.style.filter = 'url(#threshold) blur(0.6px)';
}
textWrapper.appendChild(text1);
textWrapper.appendChild(text2);
element.appendChild(textWrapper);
let textIndex = 0;
let morph = 0;
let cooldown = config.cooldownTime;
let lastTime = new Date();
text1.textContent = texts[textIndex % texts.length];
text2.textContent = texts[(textIndex + 1) % texts.length];
if (config.disableAnimation) {
text2.style.opacity = '0';
return;
}
function setMorphStyles(fraction) {
text2.style.filter = "blur(" + Math.min(8 / fraction - 8, 100) + "px)";
text2.style.opacity = Math.pow(fraction, 0.4) * 100 + "%";
const invertedFraction = 1 - fraction;
text1.style.filter = "blur(" + Math.min(8 / invertedFraction - 8, 100) + "px)";
text1.style.opacity = Math.pow(invertedFraction, 0.4) * 100 + "%";
text1.textContent = texts[textIndex % texts.length];
text2.textContent = texts[(textIndex + 1) % texts.length];
}
function doMorph() {
morph -= cooldown;
cooldown = 0;
let fraction = morph / config.morphTime;
if (fraction > 1) {
cooldown = config.cooldownTime;
fraction = 1;
}
setMorphStyles(fraction);
if (fraction === 1) {
textIndex++;
}
}
function doCooldown() {
morph = 0;
text2.style.filter = "none";
text2.style.opacity = "100%";
text1.style.filter = "none";
text1.style.opacity = "0%";
}
function animate() {
if (document.hidden) {
requestAnimationFrame(animate);
return;
}
const newTime = new Date();
const dt = (newTime.getTime() - lastTime.getTime()) / 1000;
lastTime = newTime;
cooldown -= dt;
if (cooldown <= 0) {
doMorph();
} else {
doCooldown();
}
requestAnimationFrame(animate);
}
animate();
}
function initMorphingTexts() {
createSvgFilters();
const elements = document.querySelectorAll('[data-morphing-text]');
elements.forEach((element) => {
createMorphingText(element);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initMorphingTexts);
} else {
initMorphingTexts();
}
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
if (node.hasAttribute && node.hasAttribute('data-morphing-text')) {
createMorphingText(node);
}
const childElements = node.querySelectorAll && node.querySelectorAll('[data-morphing-text]');
if (childElements && childElements.length > 0) {
childElements.forEach(element => {
createMorphingText(element);
});
}
}
});
}
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();`;
}
function copyJsToClipboard() {
const jsCode = generateJavaScriptCode();
navigator.clipboard.writeText(jsCode)
.then(() => {
showNotification('JavaScript code copied to clipboard!');
})
.catch(err => {
try {
const textArea = document.createElement('textarea');
textArea.value = jsCode;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
showNotification('JavaScript code copied to clipboard!');
} catch (fallbackErr) {
showNotification('Failed to copy to clipboard. Please try again.', 'error');
}
});
}
function copyFullSectionToClipboard() {
const sectionJSON = generateFullSectionJSON();
if (!sectionJSON) {
showNotification('Full section JSON is being prepared', 'warning');
return;
}
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');
}
});
}
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 'font-size':
morphConfig.fontSize = defaultValue;
break;
case 'morph-time':
morphConfig.morphTime = defaultValue;
break;
case 'cooldown-time':
morphConfig.cooldownTime = defaultValue;
break;
case 'line-height':
morphConfig.lineHeight = defaultValue;
break;
}
updateMorphingText();
showNotification(`${parameterId.replace(/-/g, ' ')} reset to default`);
}
};
function initializeUI() {
initMorphingText();
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-morphing-text');
});
document.getElementById('download-config').addEventListener('click', () => {
copyJsToClipboard();
});
document.getElementById('copy-full-section').addEventListener('click', () => {
copyFullSectionToClipboard();
});
document.getElementById('reset-texts').addEventListener('click', () => {
morphConfig.texts = defaultConfig.texts.slice();
morphConfig.textColor = defaultConfig.textColor;
morphConfig.fontSize = defaultConfig.fontSize;
document.getElementById('text-items').value = defaultConfig.texts.join(',');
document.getElementById('font-size').value = defaultConfig.fontSize;
document.getElementById('font-size-value').textContent = defaultConfig.fontSize;
updateColorInputs();
updateMorphingText();
showNotification('Text configuration reset to default');
});
document.getElementById('reset-animation').addEventListener('click', () => {
morphConfig.morphTime = defaultConfig.morphTime;
morphConfig.cooldownTime = defaultConfig.cooldownTime;
document.getElementById('morph-time').value = defaultConfig.morphTime;
document.getElementById('cooldown-time').value = defaultConfig.cooldownTime;
document.getElementById('morph-time-value').textContent = defaultConfig.morphTime;
document.getElementById('cooldown-time-value').textContent = defaultConfig.cooldownTime;
updateMorphingText();
showNotification('Animation settings reset');
});
document.getElementById('reset-advanced').addEventListener('click', () => {
morphConfig.textAlign = defaultConfig.textAlign;
morphConfig.fontWeight = defaultConfig.fontWeight;
morphConfig.lineHeight = defaultConfig.lineHeight;
document.getElementById('text-align').value = defaultConfig.textAlign;
document.getElementById('font-weight').value = defaultConfig.fontWeight;
document.getElementById('line-height').value = defaultConfig.lineHeight;
document.getElementById('line-height-value').textContent = defaultConfig.lineHeight;
updateMorphingText();
showNotification('Advanced settings reset');
});
document.getElementById('text-items').addEventListener('input', (e) => {
const texts = e.target.value.split(',').map(t => t.trim()).filter(t => t);
if (texts.length >= 2) {
morphConfig.texts = texts;
updateMorphingText();
}
});
const colorInput = document.getElementById('text-color');
const hexInput = document.getElementById('text-color-hex');
const hslInput = document.getElementById('text-color-hsl');
hslInput.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
colorInput.addEventListener('input', () => {
const color = colorInput.value;
hexInput.value = color;
hslInput.value = `hsl(${hexToHsl(color).h}, ${hexToHsl(color).s}%, ${hexToHsl(color).l}%)`;
hexInput.classList.remove('invalid');
hslInput.classList.remove('invalid');
morphConfig.textColor = color;
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', color);
updateMorphingText();
});
hexInput.addEventListener('input', (e) => {
let hex = e.target.value;
hex = formatHex(hex);
e.target.value = hex;
if (isValidHex(hex)) {
colorInput.value = hex;
hslInput.value = `hsl(${hexToHsl(hex).h}, ${hexToHsl(hex).s}%, ${hexToHsl(hex).l}%)`;
morphConfig.textColor = hex;
e.target.classList.remove('invalid');
hslInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', hex);
updateMorphingText();
} 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;
morphConfig.textColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
const colorPickerContainer = colorInput.closest('.color-row').querySelector('.color-picker-container');
colorPickerContainer.style.setProperty('--selected-color', hex);
updateMorphingText();
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;
morphConfig.textColor = hex;
e.target.classList.remove('invalid');
hexInput.classList.remove('invalid');
updateMorphingText();
return;
}
}
}
if (!isValidHsl(e.target.value)) {
e.target.value = `hsl(${hexToHsl(colorInput.value).h}, ${hexToHsl(colorInput.value).s}%, ${hexToHsl(colorInput.value).l}%)`;
e.target.classList.remove('invalid');
}
});
const rangeInputs = document.querySelectorAll('input[type="range"]');
rangeInputs.forEach(input => {
const valueElement = document.getElementById(`${input.id}-value`);
if (valueElement) {
valueElement.textContent = input.value;
}
input.addEventListener('input', () => {
if (valueElement) {
valueElement.textContent = input.value;
}
switch (input.id) {
case 'font-size':
morphConfig.fontSize = parseInt(input.value);
break;
case 'morph-time':
morphConfig.morphTime = parseFloat(input.value);
break;
case 'cooldown-time':
morphConfig.cooldownTime = parseFloat(input.value);
break;
case 'line-height':
morphConfig.lineHeight = parseFloat(input.value);
break;
}
updateMorphingText();
});
});
document.getElementById('text-align').addEventListener('change', function() {
morphConfig.textAlign = this.value;
updateMorphingText();
});
document.getElementById('font-weight').addEventListener('change', function() {
morphConfig.fontWeight = this.value;
updateMorphingText();
});
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 Morphing Text Configurator loaded!');
}, 500);
function saveConfiguration() {
try {
localStorage.setItem('bricksfusion-morphing-config', JSON.stringify(morphConfig));
} catch (e) {
}
}
function loadConfiguration() {
try {
const saved = localStorage.getItem('bricksfusion-morphing-config');
if (saved) {
const savedConfig = JSON.parse(saved);
Object.assign(morphConfig, savedConfig);
document.getElementById('text-items').value = savedConfig.texts.join(',');
document.getElementById('font-size').value = savedConfig.fontSize;
document.getElementById('morph-time').value = savedConfig.morphTime;
document.getElementById('cooldown-time').value = savedConfig.cooldownTime;
document.getElementById('text-align').value = savedConfig.textAlign;
document.getElementById('font-weight').value = savedConfig.fontWeight;
document.getElementById('line-height').value = savedConfig.lineHeight;
document.getElementById('font-size-value').textContent = savedConfig.fontSize;
document.getElementById('morph-time-value').textContent = savedConfig.morphTime;
document.getElementById('cooldown-time-value').textContent = savedConfig.cooldownTime;
document.getElementById('line-height-value').textContent = savedConfig.lineHeight;
updateColorInputs();
updateMorphingText();
}
} catch (e) {
}
}
const originalUpdateMorphingText = updateMorphingText;
updateMorphingText = function() {
originalUpdateMorphingText();
saveConfiguration();
};
updateColorInputs();
loadConfiguration();
}
initializeUI();
});
</script>
</body>
</html>
Morphing Text
Creates smooth morphing transitions between text strings using blur and opacity effects. Uses SVG filters with feColorMatrix for organic blending. Features customizable morph duration and cooldown between transitions. Automatically cycles through text array with fluid animations. Perfect for hero headlines, dynamic taglines, or creating attention-grabbing text effects.
Morphing Text
Watch the text morph between different words.
Text List
Add the words or phrases you want to cycle through. Add at least 2 items. The text will automatically morph between them in a continuous loop.
Default: Hello, World
Timing
How long each morphing transition takes. Lower values create quicker, snappier changes. Higher values make smooth, gradual transformations.
Default: 1.0s
Time to wait after each morph completes before starting the next one. Gives readers time to see the text clearly.
Default: 0.25s
Performance
This element uses SVG filters with feColorMatrix for text blending effects. Implements smooth blur and opacity transitions using CSS filters calculated via requestAnimationFrame. Features automatic text cycling with precise timing control. Inherits parent typography styles (font-size, color, weight, family). Very lightweight - suitable for all devices with unlimited instances per page and smooth 60fps performance.