UIKitV3-Automator / index.html
acecalisto3's picture
Update index.html
cf33581 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UIKitV3 Elite - Next-Gen Component Automator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
<style>
/* Base styles with enhanced visuals */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: #2a2a2a;
min-height: 100vh;
position: relative;
overflow-x: hidden;
}
/* TV Static background layer */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
linear-gradient(135deg, #2d2d2d 0%, #3d3d3d 20%, #4d4d4d 40%, #5a5a5a 50%, #4d4d4d 60%, #3d3d3d 80%, #2d2d2d 100%),
repeating-linear-gradient(0deg, transparent, transparent 1px, rgba(255, 255, 255, 0.025) 1px, rgba(255, 255, 255, 0.025) 2px),
repeating-linear-gradient(90deg, transparent, transparent 1px, rgba(0, 0, 0, 0.025) 1px, rgba(0, 0, 0, 0.025) 2px),
repeating-linear-gradient(45deg, transparent, transparent 3px, rgba(255, 255, 255, 0.015) 3px, rgba(255, 255, 255, 0.015) 6px),
repeating-linear-gradient(-45deg, transparent, transparent 3px, rgba(0, 0, 0, 0.015) 3px, rgba(0, 0, 0, 0.015) 6px);
background-size: 100% 100%, 2px 2px, 2px 2px, 6px 6px, 6px 6px;
animation: staticNoise 10s infinite linear;
pointer-events: none;
z-index: 0;
}
@keyframes staticNoise {
0%, 100% { opacity: 1; filter: contrast(1.05); }
33% { opacity: 0.98; filter: contrast(1); }
66% { opacity: 0.96; filter: contrast(1.02); }
}
/* Glass overlay at 55% opacity */
body::after {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg,
rgba(255, 255, 255, 0.025) 0%,
rgba(255, 255, 255, 0.015) 50%,
rgba(255, 255, 255, 0.025) 100%);
backdrop-filter: blur(0.5px);
opacity: 0.55;
pointer-events: none;
z-index: 1;
mix-blend-mode: overlay;
}
/* Animated background particles */
.bg-particles {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.particle {
position: absolute;
background: radial-gradient(circle, rgba(200, 140, 80, 0.15) 0%, transparent 70%);
border-radius: 50%;
animation: float linear infinite;
filter: blur(2px);
}
@keyframes float {
0% { transform: translateY(100vh) scale(0); opacity: 0; }
10% { opacity: 0.3; }
90% { opacity: 0.3; }
100% { transform: translateY(-100vh) scale(1); opacity: 0; }
}
/* Glassmorphism effect */
.glass {
background: rgba(20, 20, 20, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(200, 140, 80, 0.1);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.6);
}
.glass-dark {
background: rgba(18, 18, 18, 0.75);
backdrop-filter: blur(50px) saturate(190%);
-webkit-backdrop-filter: blur(50px) saturate(190%);
border: 1px solid rgba(200, 140, 80, 0.25);
box-shadow:
0 12px 40px 0 rgba(0, 0, 0, 0.9),
inset 0 1px 0 rgba(255, 255, 255, 0.06),
inset 0 0 60px rgba(0, 0, 0, 0.3),
0 0 1px rgba(200, 140, 80, 0.2);
}
.container {
max-width: 100%;
margin: 0;
padding: 0;
position: relative;
z-index: 1;
min-height: 100vh;
width: 100%;
}
/* === SLIDING PANEL LAYOUT CSS === */
/* Sliding Module Base Styles */
.slide-module {
position: fixed;
top: 0;
height: 100vh;
width: 40%;
background: rgba(18, 18, 18, 0.75);
backdrop-filter: blur(50px) saturate(190%);
-webkit-backdrop-filter: blur(50px) saturate(190%);
border: 1px solid rgba(200, 140, 80, 0.25);
box-shadow:
0 0 60px rgba(0, 0, 0, 0.9),
inset 0 1px 0 rgba(255, 255, 255, 0.06),
inset 0 0 60px rgba(0, 0, 0, 0.3),
0 0 1px rgba(200, 140, 80, 0.2);
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 100;
overflow: hidden;
display: flex;
flex-direction: column;
}
/* Left-side modules (Components, Properties, Code Output) */
.slide-module.left {
left: 48px; /* <-- FIX: Changed from 0 to 48px */
border-left: none;
border-right: 1px solid rgba(200, 140, 80, 0.25);
transform: translateX(-100%);
}
.slide-module.left.open {
transform: translateX(0);
}
/* Right-side module (Live Preview) */
.slide-module.right {
right: 48px; /* <-- FIX: Changed from 0 to 48px */
width: 45%; /* Preview panel can be a bit wider */
border-right: none;
border-left: 1px solid rgba(200, 140, 80, 0.25);
transform: translateX(100%);
}
.slide-module.right.open {
transform: translateX(0);
}
/* Make open panels cover the tabs (No longer needed, but keep for safety) */
.slide-module.open {
z-index: 160;
}
/* Module content area */
.module-content {
flex: 1;
overflow-y: auto;
padding: 2rem;
}
/* Module header */
.module-header {
padding: 1.5rem 2rem;
border-bottom: 1px solid rgba(200, 140, 80, 0.15);
background: rgba(200, 140, 80, 0.05);
flex-shrink: 0;
}
/* Module Tabs */
.module-tab {
position: fixed;
top: 50%;
transform: translateY(-50%);
width: 48px;
height: 160px;
background: rgba(20, 20, 20, 0.9);
backdrop-filter: blur(50px) saturate(180%);
-webkit-backdrop-filter: blur(50px) saturate(180%);
border: 1px solid rgba(200, 140, 80, 0.35);
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
z-index: 150;
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.05),
0 0 1px rgba(200, 140, 80, 0.2);
}
/* Left tabs (Components, Properties, Code Output) */
.module-tab.left {
left: 0;
border-left: none;
border-radius: 0 8px 8px 0;
}
.module-tab.left:hover {
background: rgba(25, 25, 25, 0.95);
border-color: rgba(200, 140, 80, 0.5);
box-shadow:
4px 0 28px rgba(0, 0, 0, 0.7),
inset 0 1px 0 rgba(255, 255, 255, 0.07),
0 0 16px rgba(200, 140, 80, 0.2);
}
/* Right tab (Live Preview) */
.module-tab.right {
right: 0;
border-right: none;
border-radius: 8px 0 0 8px;
}
.module-tab.right:hover {
background: rgba(25, 25, 25, 0.95);
border-color: rgba(200, 140, 80, 0.5);
box-shadow:
-4px 0 28px rgba(0, 0, 0, 0.7),
inset 0 1px 0 rgba(255, 255, 255, 0.07),
0 0 16px rgba(200, 140, 80, 0.2);
}
.module-tab-icon {
font-size: 24px;
}
.module-tab-text {
writing-mode: vertical-rl;
text-orientation: mixed;
font-size: 11px;
font-weight: 600;
color: rgba(200, 140, 80, 0.8);
letter-spacing: 0.5px;
text-transform: uppercase;
}
/* Position tabs at different heights for left modules */
#components-tab { top: 20%; }
#properties-tab { top: 50%; }
#code-tab { top: 80%; }
/* === END NEW LAYOUT CSS === */
/* === Pinstripe Separators === */
.pinstripe {
position: fixed;
top: 0;
bottom: 0;
width: 1px;
background: rgba(200, 140, 80, 0.2); /* Subtle border color */
z-index: 140; /* Below tabs (150), above closed panels (100) */
box-shadow: 0 0 12px rgba(0, 0, 0, 0.4);
}
.pinstripe.left {
left: 48px; /* Aligned with the inner edge of left tabs */
}
.pinstripe.right {
right: 48px; /* Aligned with the inner edge of right tab */
}
/* === END NEW CSS === */
/* Enhanced dropdown with smooth animations */
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
background: rgba(26, 26, 26, 0.95);
backdrop-filter: blur(20px);
min-width: 200px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8), 0 0 1px rgba(200, 140, 80, 0.2);
z-index: 10001; /* Needs to be high for when panel is open */
border-radius: 12px;
border: 1px solid rgba(200, 140, 80, 0.15);
animation: dropdownSlide 0.3s ease;
max-height: 400px;
overflow-y: auto;
}
@keyframes dropdownSlide {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.dropdown-content a {
color: white;
padding: 12px 20px;
text-decoration: none;
display: block;
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.dropdown-content a:hover {
background: linear-gradient(90deg, rgba(200, 140, 80, 0.15), transparent);
border-left-color: rgba(200, 140, 80, 0.6);
padding-left: 25px;
}
.dropdown.show .dropdown-content {
display: block;
}
/* Enhanced button with hover effects */
.btn-primary {
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.btn-primary::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.btn-primary:hover::before {
width: 300px;
height: 300px;
}
.btn-primary span {
position: relative;
z-index: 1;
}
/* Loading spinner styles */
.loader {
border: 3px solid rgba(200, 140, 80, 0.1);
border-top: 3px solid rgba(200, 140, 80, 0.6);
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
margin: 20px auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(30, 30, 30, 0.5);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, rgba(180, 120, 60, 0.4) 0%, rgba(200, 140, 80, 0.6) 100%);
border-radius: 10px;
transition: all 0.3s ease;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, rgba(200, 140, 80, 0.6) 0%, rgba(220, 160, 100, 0.7) 100%);
}
/* Input field enhancements */
.input-field {
transition: all 0.3s ease;
position: relative;
}
.input-field:focus {
transform: translateY(-2px);
box-shadow: 0 10px 30px rgba(200, 140, 80, 0.2);
border-color: rgba(200, 140, 80, 0.4);
}
/* Header text animations with SVG path tracing */
.header-title {
display: inline-block;
position: relative;
overflow: visible;
}
.header-svg-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}
.header-path {
fill: none;
stroke: rgba(200, 140, 80, 0.6);
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: tracePath 3s ease forwards;
filter: drop-shadow(0 0 8px rgba(200, 140, 80, 0.4));
}
@keyframes tracePath {
to {
stroke-dashoffset: 0;
}
}
.header-title .char {
display: inline-block;
opacity: 0;
position: relative;
animation: charScribe 0.5s ease forwards;
}
@keyframes charScribe {
0% { opacity: 0; filter: blur(4px); transform: scale(0.8); }
50% { opacity: 0.6; filter: blur(2px); }
100% { opacity: 1; filter: blur(0); transform: scale(1); text-shadow: 0 0 10px rgba(200, 140, 80, 0.3); }
}
.subtitle-text {
opacity: 0;
transform: translateY(10px);
animation: subtitleFade 1s ease forwards 2.5s;
}
@keyframes subtitleFade {
to { opacity: 1; transform: translateY(0); }
}
/* Icon button styles */
.icon-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
background: rgba(200, 140, 80, 0.1);
color: rgba(200, 140, 80, 0.9);
border: 1px solid rgba(200, 140, 80, 0.15);
cursor: pointer;
transition: all 0.3s ease;
}
.icon-btn:hover {
background: rgba(200, 140, 80, 0.2);
border-color: rgba(200, 140, 80, 0.4);
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(200, 140, 80, 0.2);
}
.icon-btn:active {
transform: scale(0.95);
}
/* Platform export dropdown */
.platform-dropdown {
position: relative;
display: inline-block;
}
.platform-options {
display: none;
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
background: rgba(26, 26, 26, 0.98);
backdrop-filter: blur(20px);
min-width: 250px;
border-radius: 12px;
border: 1px solid rgba(200, 140, 80, 0.15);
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.8), 0 0 1px rgba(200, 140, 80, 0.2);
z-index: 10000; /* Needs to be high for when panel is open */
animation: dropdownSlide 0.3s ease;
max-height: 500px;
overflow-y: auto;
}
.platform-options.show {
display: block;
}
.platform-option {
padding: 12px 16px;
color: white;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
border-left: 3px solid transparent;
}
.platform-option:hover {
background: linear-gradient(90deg, rgba(200, 140, 80, 0.15), transparent);
border-left-color: rgba(200, 140, 80, 0.6);
}
.platform-icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(200, 140, 80, 0.15);
border-radius: 6px;
font-size: 12px;
}
/* Animation presets */
.animate-fadeIn { animation: fadeIn 0.5s ease; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.animate-slideInLeft { animation: slideInLeft 0.5s ease; }
@keyframes slideInLeft { from { opacity: 0; transform: translateX(-50px); } to { opacity: 1; transform: translateX(0); } }
.animate-bounce { animation: bounce 0.6s ease; }
@keyframes bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-20px); } }
.animate-pulse { animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
.animate-slideInRight { animation: slideInRight 0.5s ease; }
@keyframes slideInRight { from { opacity: 0; transform: translateX(50px); } to { opacity: 1; transform: translateX(0); } }
.animate-slideInUp { animation: slideInUp 0.5s ease; }
@keyframes slideInUp { from { opacity: 0; transform: translateY(50px); } to { opacity: 1; transform: translateY(0); } }
.animate-shake { animation: shake 0.5s ease; }
@keyframes shake { 0%, 100% { transform: translateX(0); } 10%, 30%, 50%, 70%, 90% { transform: translateX(-10px); } 20%, 40%, 60%, 80% { transform: translateX(10px); } }
.animate-rotate { animation: rotate 0.6s ease; }
@keyframes rotate { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.animate-scale { animation: scale 0.6s ease; }
@keyframes scale { from { transform: scale(0.5); } to { transform: scale(1); } }
/* Tooltip */
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
background-color: rgba(0, 0, 0, 0.9);
color: #fff;
text-align: center;
border-radius: 6px;
padding: 8px 12px;
position: absolute;
z-index: 10001;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s, visibility 0.3s;
white-space: nowrap;
font-size: 12px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
/* Code block enhancements */
pre {
border-radius: 12px;
position: relative;
max-height: calc(100vh - 250px); /* Adjust max-height for panel view */
overflow: auto;
}
/* Notification toast */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: rgba(34, 197, 94, 0.95);
color: white;
padding: 16px 24px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
z-index: 10002;
display: none;
animation: toastSlide 0.3s ease;
}
.toast.show { display: block; }
.toast.error { background: rgba(239, 68, 68, 0.95); }
@keyframes toastSlide {
from { transform: translateX(400px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
/* Animation selection (from 'broken' file) */
.animation-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
margin-top: 10px;
}
.animation-option {
padding: 10px;
background: rgba(200, 140, 80, 0.05);
border: 2px solid rgba(200, 140, 80, 0.15);
border-radius: 8px;
cursor: pointer;
text-align: center;
transition: all 0.3s ease;
font-size: 12px;
color: rgba(200, 140, 80, 0.9);
}
.animation-option:hover {
background: rgba(200, 140, 80, 0.15);
border-color: rgba(200, 140, 80, 0.4);
transform: scale(1.05);
}
.animation-option.selected {
background: rgba(200, 140, 80, 0.2);
border-color: rgba(200, 140, 80, 0.6);
box-shadow: 0 0 10px rgba(200, 140, 80, 0.2);
}
/* Color Palette Controls */
.color-palette-section {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid rgba(200, 140, 80, 0.15);
}
.color-palette-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 12px;
margin-top: 12px;
}
.color-swatch {
position: relative;
height: 60px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid rgba(200, 140, 80, 0.2);
overflow: hidden;
}
.color-swatch:hover {
transform: scale(1.05);
border-color: rgba(200, 140, 80, 0.5);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.color-swatch.selected {
border-color: rgba(200, 140, 80, 0.8);
box-shadow: 0 0 16px rgba(200, 140, 80, 0.4);
}
.color-swatch-label {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.7);
padding: 4px 8px;
font-size: 10px;
text-align: center;
color: white;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.custom-color-input {
display: flex;
gap: 8px;
align-items: center;
margin-top: 12px;
}
.custom-color-input input[type="color"] {
width: 50px;
height: 40px;
border: 2px solid rgba(200, 140, 80, 0.2);
border-radius: 8px;
cursor: pointer;
background: transparent;
}
.custom-color-input input[type="text"] {
flex: 1;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(200, 140, 80, 0.2);
border-radius: 8px;
color: white;
font-size: 12px;
font-family: monospace;
}
/* Responsive adjustments */
@media (max-width: 1024px) {
.slide-module.left {
width: 70vw;
}
.slide-module.right {
width: 75vw;
}
}
@media (max-width: 768px) {
.slide-module.left {
width: 90vw;
}
.slide-module.right {
width: 95vw;
}
.module-tab {
width: 40px;
height: 120px;
}
.module-tab-text {
font-size: 10px;
}
}
</style>
</head>
<body>
<div class="pinstripe left"></div>
<div class="pinstripe right"></div>
<div class="bg-particles" id="particles"></div>
<div class="toast" id="toast">
<span id="toast-message">Action completed!</span>
</div>
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<script>
// === GLOBAL STATE ===
let previewEnabled = false; // Start with preview disabled as panel is closed
let isFullscreen = false;
let isEditable = false;
let currentComponent = null;
let livePreviewDebounce = null;
let selectedColorPalette = 'indigo';
let customColor = '#6366f1';
let selectedAnimation = 'none';
// === UTILITY FUNCTIONS ===
// XSS Protection using DOMPurify
function sanitizeHTML(html) {
if (window.DOMPurify) {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['div', 'span', 'p', 'a', 'button', 'input', 'label', 'img', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'nav', 'ul', 'ol', 'li', 'svg', 'path', 'pre', 'code', 'table', 'thead', 'tbody', 'tr', 'td', 'th'],
ALLOWED_ATTR: ['class', 'id', 'href', 'src', 'alt', 'type', 'placeholder', 'value', 'style', 'viewBox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'd', 'xmlns', 'aria-label', 'role', 'aria-current', 'aria-expanded', 'aria-controls', 'contenteditable']
});
}
return html;
}
// Show toast notification
function showToast(message, isError = false) {
const toast = document.getElementById('toast');
if (!toast) return;
const toastMessage = document.getElementById('toast-message');
toastMessage.textContent = message;
toast.classList.toggle('error', isError);
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
// Create animated particles
function createParticles() {
const particlesContainer = document.getElementById('particles');
if (!particlesContainer) return;
const particleCount = 8;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%';
particle.style.width = Math.random() * 10 + 5 + 'px';
particle.style.height = particle.style.width;
particle.style.animationDuration = (Math.random() * 10 + 10) + 's';
particle.style.animationDelay = Math.random() * 5 + 's';
particlesContainer.appendChild(particle);
}
}
// === RENDER MAIN APP ===
function renderApp() {
// This HTML structure is from 'uikit-tim-hf-backup-broken.html'
document.getElementById('app').innerHTML = `<div class="container">
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 50; width: 600px; pointer-events: none;">
<div class="text-center">
<h1 id="main-title" class="text-5xl font-black mb-4 header-title" style="color: #f5f5f5;"></h1>
<p class="subtitle-text text-gray-400 text-lg tracking-wide">Next-Generation Component Automator</p>
<p class="subtitle-text text-gray-500 text-md tracking-wide" style="animation-delay: 2.7s; margin-top: 1rem;">Click the tabs on the edges</p>
</div>
</div>
<div class="module-tab left" id="components-tab" data-module="components">
<span class="module-tab-icon">🎨</span>
<span class="module-tab-text">Components</span>
</div>
<div class="module-tab left" id="properties-tab" data-module="properties">
<span class="module-tab-icon">βš™οΈ</span>
<span class="module-tab-text">Properties</span>
</div>
<div class="module-tab left" id="code-tab" data-module="code">
<span class="module-tab-icon">πŸ’»</span>
<span class="module-tab-text">Code Output</span>
</div>
<div class="module-tab right" id="preview-tab" data-module="preview">
<span class="module-tab-icon">πŸ‘οΈ</span>
<span class="module-tab-text">Live Preview</span>
</div>
<div class="slide-module left" id="components-module">
<div class="module-header">
<h2 class="text-xl font-semibold text-gray-200 flex items-center gap-3">
<span class="text-2xl">🎨</span>
<span>Select Component</span>
</h2>
</div>
<div class="module-content">
<div class="dropdown" id="component-dropdown">
<button id="dropdown-button" class="btn-primary bg-gradient-to-r from-gray-800 to-gray-900 hover:from-gray-700 hover:to-gray-800 text-gray-200 font-semibold py-3 px-6 rounded-xl inline-flex items-center shadow-lg transition duration-300 border border-gray-700 hover:border-amber-900/30 w-full justify-center">
<span id="selected-option">Select Component</span>
<svg class="fill-current h-5 w-5 ml-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</button>
<div id="dropdown-content" class="dropdown-content">
<a href="#" data-value="Button">πŸ”˜ Button</a>
<a href="#" data-value="Input">✍️ Input</a>
<a href="#" data-value="Card">🎴 Card</a>
<a href="#" data-value="Navbar">πŸ“Š Navbar</a>
<a href="#" data-value="Modal">πŸͺŸ Modal</a>
<a href="#" data-value="Alert">⚠️ Alert</a>
<a href="#" data-value="Badge">🏷️ Badge</a>
<a href="#" data-value="Breadcrumb">🍞 Breadcrumb</a>
<a href="#" data-value="Tabs">πŸ“‘ Tabs</a>
<a href="#" data-value="Pagination">πŸ“„ Pagination</a>
<a href="#" data-value="Hero">🦸 Hero Section</a>
<a href="#" data-value="Footer">πŸ‘£ Footer</a>
</div>
</div>
<div class="mt-8 text-center">
<button id="generate-code-button" class="btn-primary bg-gradient-to-r from-amber-900/40 to-amber-800/40 hover:from-amber-900/60 hover:to-amber-800/60 text-amber-100 font-semibold py-3 px-8 rounded-lg transition duration-300 border border-amber-900/40 hover:border-amber-800/60">
<span>Generate Code</span>
</button>
</div>
</div>
</div>
<div class="slide-module left" id="properties-module">
<div class="module-header">
<h2 class="text-xl font-semibold text-gray-200 flex items-center gap-3">
<span class="text-2xl">βš™οΈ</span>
<span>Component Properties</span>
</h2>
</div>
<div class="module-content">
<div id="inputs-container" class="space-y-3 mb-6">
</div>
<button id="add-input-button" class="mt-6 bg-gradient-to-r from-gray-800 to-gray-900 hover:from-gray-700 hover:to-gray-800 text-gray-200 font-semibold py-2 px-5 rounded-lg transition duration-300 border border-gray-700 hover:border-amber-900/30 w-full">
+ Add Property
</button>
<div class="animation-section mt-8 pt-6 border-t border-gray-700/50">
<h3 class="text-md font-semibold text-gray-300 mb-3">Entry Animation</h3>
<div class="animation-grid" id="animation-grid">
<div class="animation-option selected" data-animation="none">None</div>
<div class="animation-option" data-animation="fadeIn">Fade In</div>
<div class="animation-option" data-animation="slideInLeft">Slide Left</div>
<div class="animation-option" data-animation="slideInRight">Slide Right</div>
<div class="animation-option" data-animation="slideInUp">Slide Up</div>
<div class="animation-option" data-animation="bounce">Bounce</div>
<div class="animation-option" data-animation="pulse">Pulse</div>
<div class="animation-option" data-animation="shake">Shake</div>
<div class="animation-option" data-animation="rotate">Rotate</div>
<div class="animation-option" data-animation="scale">Scale</div>
</div>
</div>
<div class="color-palette-section mt-8">
<h3 class="text-md font-semibold text-gray-300 mb-2">Color Palette</h3>
<p class="text-xs text-gray-400 mb-3">Select or customize colors for your components</p>
<div class="color-palette-grid" id="color-palette-grid">
<div class="color-swatch selected" data-palette="indigo" style="background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);">
<div class="color-swatch-label">Indigo</div>
</div>
<div class="color-swatch" data-palette="blue" style="background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);">
<div class="color-swatch-label">Blue</div>
</div>
<div class="color-swatch" data-palette="green" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">
<div class="color-swatch-label">Green</div>
</div>
<div class="color-swatch" data-palette="red" style="background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);">
<div class="color-swatch-label">Red</div>
</div>
<div class="color-swatch" data-palette="amber" style="background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);">
<div class="color-swatch-label">Amber</div>
</div>
<div class="color-swatch" data-palette="purple" style="background: linear-gradient(135deg, #a855f7 0%, #9333ea 100%);">
<div class="color-swatch-label">Purple</div>
</div>
<div class="color-swatch" data-palette="pink" style="background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);">
<div class="color-swatch-label">Pink</div>
</div>
<div class="color-swatch" data-palette="teal" style="background: linear-gradient(135deg, #14b8a6 0%, #0d9488 100%);">
<div class="color-swatch-label">Teal</div>
</div>
</div>
<div class="custom-color-input">
<input type="color" id="custom-color-picker" value="#6366f1">
<input type="text" id="custom-color-hex" placeholder="#6366f1" value="#6366f1">
<button id="apply-custom-color" class="bg-gradient-to-r from-gray-800 to-gray-900 hover:from-gray-700 hover:to-gray-800 text-gray-200 font-semibold py-2 px-4 rounded-lg transition duration-300 border border-gray-700 hover:border-amber-900/30 text-xs">
Apply
</button>
</div>
</div>
</div>
</div>
<div class="slide-module left" id="code-module">
<div class="module-header">
<div class="flex justify-between items-center w-full">
<div class="flex items-center gap-3">
<span class="text-2xl">πŸ’»</span>
<span class="text-xl font-semibold text-gray-200">Generated Code</span>
</div>
<div class="flex gap-3">
<div class="platform-dropdown">
<button id="platform-button" class="bg-gradient-to-r from-gray-800 to-gray-900 hover:from-gray-700 hover:to-gray-800 text-gray-200 font-semibold py-2 px-4 rounded-lg transition duration-300 border border-gray-700 hover:border-amber-900/30 flex items-center gap-2">
<span>πŸ“¦ Export</span>
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</button>
<div id="platform-options" class="platform-options">
<div class="platform-option" data-platform="html">
<div class="platform-icon">🌐</div>
<span>Plain HTML</span>
</div>
<div class="platform-option" data-platform="react">
<div class="platform-icon">βš›οΈ</div>
<span>React / Next.js</span>
</div>
<div class="platform-option" data-platform="vue">
<div class="platform-icon">πŸ’š</div>
<span>Vue / Nuxt.js</span>
</div>
<div class="platform-option" data-platform="wordpress">
<div class="platform-icon">πŸ“°</div>
<span>WordPress</span>
</div>
<div class="platform-option" data-platform="joomla">
<div class="platform-icon">🎯</div>
<span>Joomla / YooTheme</span>
</div>
<div class="platform-option" data-platform="shopify">
<div class="platform-icon">πŸ›’</div>
<span>Shopify Liquid</span>
</div>
<div class="platform-option" data-platform="webflow">
<div class="platform-icon">🌊</div>
<span>Webflow</span>
</div>
<div class="platform-option" data-platform="angular">
<div class="platform-icon">πŸ…°οΈ</div>
<span>Angular</span>
</div>
<div class="platform-option" data-platform="svelte">
<div class="platform-icon">πŸ”₯</div>
<span>Svelte / SvelteKit</span>
</div>
</div>
</div>
<button id="copy-button" class="bg-gradient-to-r from-gray-800 to-gray-900 hover:from-gray-700 hover:to-gray-800 text-gray-200 font-semibold py-2 px-4 rounded-lg transition duration-300 border border-gray-700 hover:border-amber-900/30">
Copy
</button>
</div>
</div>
</div>
<div class="module-content">
<div id="loading-spinner" class="loader" style="display: none;"></div>
<div class="relative">
<pre class="glass-dark"><code id="generated-code" class="language-html"></code></pre>
</div>
</div>
</div>
<div class="slide-module right" id="preview-module">
<div class="module-header">
<div class="flex justify-between items-center w-full">
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-weight: 600; color: white;">Live Preview</span>
<span id="preview-status" style="font-size: 12px; color: #10b981; display: none;">● Live</span>
</div>
<div style="display: flex; gap: 8px;">
<button class="icon-btn" id="refresh-preview" title="Refresh">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
</svg>
</button>
<button class="icon-btn" id="toggle-editable" title="Toggle Editable">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
</button>
</div>
</div>
</div>
<div class="module-content" style="background: rgba(20, 20, 20, 0.5);">
<div id="viewer-content" contenteditable="false" style="min-height: calc(100vh - 200px);">
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #9ca3af;">
Click the 'Components' tab to start
</div>
</div>
</div>
</div>
</div>`;
}
// Animate header text with SVG path tracing
function animateHeaderText() {
const title = document.getElementById('main-title');
if (!title) return;
const text = 'UIKitV3 Elite';
const chars = text.split('');
// Create SVG path for tracing effect
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('class', 'header-svg-container');
svg.setAttribute('viewBox', '0 0 800 100');
svg.style.position = 'absolute';
svg.style.top = '0';
svg.style.left = '0';
svg.style.width = '100%';
svg.style.height = '100%';
// Create a decorative path that traces under the text
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
const pathData = 'M 50 80 Q 150 60, 250 70 T 450 65 Q 550 68, 650 75 T 750 70';
path.setAttribute('d', pathData);
path.setAttribute('class', 'header-path');
svg.appendChild(path);
// Clear and create character spans
title.innerHTML = '';
title.appendChild(svg);
chars.forEach((char, index) => {
const span = document.createElement('span');
span.className = 'char';
span.textContent = char === ' ' ? '\u00A0' : char;
span.style.animationDelay = `${0.1 + index * 0.1}s`;
title.appendChild(span);
});
}
// === EVENT HANDLERS ===
function toggleDropdown() {
document.getElementById('component-dropdown').classList.toggle('show');
}
function selectOption(event) {
event.preventDefault();
if (event.target.dataset.value) {
const value = event.target.dataset.value;
document.getElementById('selected-option').textContent = value;
document.getElementById('component-dropdown').classList.remove('show');
document.getElementById('inputs-container').innerHTML = '';
currentComponent = value;
addDefaultInputs(value);
if (previewEnabled) {
updateLivePreview();
}
// Automatically open properties panel after selecting component
toggleModule('properties');
}
}
function addDefaultInputs(component) {
const container = document.getElementById('inputs-container');
if (!container) return;
let defaultProps = [];
switch(component) {
case 'Button':
defaultProps = [
{key: 'text', value: 'Click Me'},
{key: 'size', value: 'medium'}
];
break;
case 'Input':
defaultProps = [
{key: 'placeholder', value: 'Enter text...'},
{key: 'type', value: 'text'},
{key: 'label', value: 'Username'}
];
break;
case 'Card':
defaultProps = [
{key: 'title', value: 'Amazing Card'},
{key: 'content', value: 'This is a beautiful card component with modern styling.'},
{key: 'imageUrl', value: 'https://images.unsplash.com/photo-1557821552-17105176677c?w=600&h=400&fit=crop'}
];
break;
case 'Navbar':
defaultProps = [
{key: 'brand', value: 'MyApp'},
{key: 'links', value: 'Home,Features,Pricing,About,Contact'}
];
break;
case 'Modal':
defaultProps = [
{key: 'title', value: 'Welcome!'},
{key: 'body', value: 'This is a beautiful modal dialog with modern design.'},
{key: 'footer', value: 'Close,Save Changes'}
];
break;
case 'Alert':
defaultProps = [
{key: 'text', value: 'This is an important notification!'},
{key: 'type', value: 'info'}
];
break;
case 'Badge':
defaultProps = [
{key: 'text', value: 'New'}
];
break;
case 'Breadcrumb':
defaultProps = [
{key: 'links', value: 'Home,Products,Electronics,Phones'}
];
break;
case 'Tabs':
defaultProps = [
{key: 'tabs', value: 'Overview,Features,Specifications,Reviews'},
{key: 'active', value: 'Overview'}
];
break;
case 'Pagination':
defaultProps = [
{key: 'items', value: 'Previous,1,2,3,4,5,Next'}
];
break;
case 'Hero':
defaultProps = [
{key: 'title', value: 'Welcome to the Future'},
{key: 'subtitle', value: 'Build amazing things with modern components'},
{key: 'buttonText', value: 'Get Started'}
];
break;
case 'Footer':
defaultProps = [
{key: 'company', value: 'MyCompany'},
{key: 'links', value: 'About,Blog,Careers,Contact'},
{key: 'copyright', value: '2024 MyCompany. All rights reserved.'}
];
break;
}
defaultProps.forEach(prop => {
addInput(prop.key, prop.value);
});
}
function addInput(key = '', value = '') {
const container = document.getElementById('inputs-container');
if (!container) return;
const inputGroup = document.createElement('div');
inputGroup.className = 'flex items-center gap-3';
inputGroup.innerHTML = `
<input type="text" class="property-key input-field bg-black/50 text-gray-200 p-3 rounded-lg flex-1 border border-gray-700 focus:outline-none focus:ring-1 focus:ring-amber-900/40 focus:border-amber-900/40" placeholder="Property" value="${key}">
<input type="text" class="property-value input-field bg-black/50 text-gray-200 p-3 rounded-lg flex-[2] border border-gray-700 focus:outline-none focus:ring-1 focus:ring-amber-900/40 focus:border-amber-900/40" placeholder="Value" value="${value}">
<button class="remove-input-button bg-gray-800 hover:bg-gray-700 text-gray-400 hover:text-gray-200 font-semibold py-3 px-4 rounded-lg transition duration-300 border border-gray-700 hover:border-red-900/30">
βœ•
</button>
`;
container.appendChild(inputGroup);
// Add event listener to remove button
inputGroup.querySelector('.remove-input-button').addEventListener('click', removeInput);
// === FIX: Attach listeners regardless of whether preview is open ===
// This ensures live refresh works even if panel was closed when prop was added.
inputGroup.querySelector('.property-key').addEventListener('input', debouncedLivePreview);
inputGroup.querySelector('.property-value').addEventListener('input', debouncedLivePreview);
}
function removeInput(event) {
event.target.closest('.flex').remove();
if (previewEnabled) {
updateLivePreview();
}
}
function debouncedLivePreview() {
clearTimeout(livePreviewDebounce);
livePreviewDebounce = setTimeout(() => {
updateLivePreview();
}, 500);
}
function getSelectedAnimation() {
const selected = document.querySelector('.animation-option.selected');
return selected ? selected.dataset.animation : 'none';
}
function collectProperties() {
const container = document.getElementById('inputs-container');
if (!container) return {};
const inputs = container.children;
let properties = {};
for (let input of inputs) {
const keyEl = input.querySelector('.property-key');
const valueEl = input.querySelector('.property-value');
if (keyEl && valueEl) {
const key = keyEl.value;
const value = valueEl.value;
if (key) {
properties[key] = value;
}
}
}
return properties;
}
function generateCode() {
const component = document.getElementById('selected-option').textContent;
if (component === 'Select Component') {
showToast('Please select a component first.', true);
return;
}
const properties = collectProperties();
const animation = getSelectedAnimation();
const codeOutput = document.getElementById('generated-code');
const loadingSpinner = document.getElementById('loading-spinner');
const copyButton = document.getElementById('copy-button');
if (!codeOutput || !loadingSpinner || !copyButton) return;
codeOutput.textContent = '';
copyButton.textContent = 'Copy';
loadingSpinner.style.display = 'block';
setTimeout(() => {
let generatedHtml = '';
try {
generatedHtml = generateComponentCode(component, properties, animation);
codeOutput.textContent = generatedHtml;
if (window.hljs) {
try {
hljs.highlightElement(codeOutput);
} catch (e) {
console.error("highlight.js failed:", e);
}
}
showToast('Code generated successfully!');
// Automatically open the code panel
toggleModule('code');
} catch (error) {
generatedHtml = `<div class="text-red-400">Error generating code: ${error.message}</div>`;
codeOutput.textContent = generatedHtml;
showToast('Error generating code', true);
}
loadingSpinner.style.display = 'none';
if (previewEnabled) {
updateLivePreview();
}
}, 800);
}
function generateComponentCode(component, props, animation) {
let html = '';
switch(component) {
case 'Button':
html = generateButtonCode(props);
break;
case 'Input':
html = generateInputCode(props);
break;
case 'Card':
html = generateCardCode(props);
break;
case 'Navbar':
html = generateNavbarCode(props);
break;
case 'Modal':
html = generateModalCode(props);
break;
case 'Alert':
html = generateAlertCode(props);
break;
case 'Badge':
html = generateBadgeCode(props);
break;
case 'Breadcrumb':
html = generateBreadcrumbCode(props);
break;
case 'Tabs':
html = generateTabsCode(props);
break;
case 'Pagination':
html = generatePaginationCode(props);
break;
case 'Hero':
html = generateHeroCode(props);
break;
case 'Footer':
html = generateFooterCode(props);
break;
default:
html = `<div class="text-red-400">Error: Component generator not found.</div>`;
}
// Add animation if selected
if (animation && animation !== 'none') {
html = addAnimationToHTML(html, animation);
}
return html;
}
function addAnimationToHTML(html, animation) {
// Add animation class to the root element
const animationStyles = getAnimationStyles(animation);
// Use a regex that finds the first class attribute and inserts the animation class
const regex = /class="([^"]*)"/;
if (regex.test(html)) {
return html.replace(regex, `class="$1 animate-${animation}" style="${animationStyles}"`);
} else {
// If no class attribute, add one
return html.replace(/<([a-z0-9]+)/i, `<$1 class="animate-${animation}" style="${animationStyles}"`);
}
}
function getAnimationStyles(animation) {
const animations = {
fadeIn: 'animation: fadeIn 0.6s ease;',
slideInLeft: 'animation: slideInLeft 0.6s ease;',
slideInRight: 'animation: slideInRight 0.6s ease;',
slideInUp: 'animation: slideInUp 0.6s ease;',
bounce: 'animation: bounce 0.6s ease;',
pulse: 'animation: pulse 2s ease infinite;',
shake: 'animation: shake 0.5s ease;',
rotate: 'animation: rotate 0.6s ease;',
scale: 'animation: scale 0.6s ease;'
};
return animations[animation] || '';
}
// === COMPONENT GENERATORS ===
function generateButtonCode(props) {
const colors = getColorValues();
const text = props.text || 'Button';
const size = props.size || 'medium';
let sizeClasses = 'py-3 px-6 text-base';
switch(size) {
case 'small': sizeClasses = 'py-2 px-4 text-sm'; break;
case 'large': sizeClasses = 'py-4 px-8 text-lg'; break;
}
return `<button class="bg-gradient-to-r from-[${colors.from}] to-[${colors.to}] hover:opacity-90 ${sizeClasses} text-white font-bold rounded-xl shadow-lg transition duration-300 transform hover:scale-105">
${text}
</button>`;
}
function generateInputCode(props) {
const colors = getColorValues();
const placeholder = props.placeholder || '';
const type = props.type || 'text';
const label = props.label || '';
let labelHtml = '';
if (label) {
labelHtml = `<label for="generated-input" class="block text-sm font-semibold text-gray-300 mb-2">
${label}
</label>
`;
}
return `<div>
${labelHtml}<input
type="${type}"
id="generated-input"
class="bg-gray-800 text-white p-3 rounded-xl w-full border border-gray-700 focus:outline-none focus:ring-2 focus:ring-[${colors.from}] transition duration-300"
placeholder="${placeholder}"
>
</div>`;
}
function generateCardCode(props) {
const colors = getColorValues();
const title = props.title || 'Card Title';
const content = props.content || 'Card content goes here.';
const imageUrl = props.imageUrl || '';
let imageHtml = '';
if (imageUrl) {
imageHtml = `<img
class="w-full h-56 object-cover"
src="${imageUrl}"
alt="${title}"
onerror="this.src='https://images.unsplash.com/photo-1557821552-17105176677c?w=600&h=400&fit=crop'"
>
`;
}
return `<div class="max-w-sm bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl shadow-2xl overflow-hidden border border-[${colors.from}/50] transition duration-300 transform hover:scale-105">
${imageHtml}<div class="p-6">
<h3 class="text-2xl font-bold bg-gradient-to-r from-[${colors.from}] to-[${colors.to}] bg-clip-text text-transparent mb-3">${title}</h3>
<p class="text-gray-300 leading-relaxed">${content}</p>
</div>
</div>`;
}
function generateNavbarCode(props) {
const colors = getColorValues();
const brand = props.brand || 'Brand';
const links = (props.links || 'Home,About').split(',').map(link => link.trim());
let linksHtml = links.map(link => `<a href="#" class="text-gray-300 hover:text-white px-4 py-2 rounded-lg transition duration-300 hover:bg-gray-700">${link}</a>`).join('\n ');
return `<nav class="bg-gradient-to-r from-gray-900 to-gray-800 shadow-2xl border-b border-gray-700">
<div class="max-w-7xl mx-auto px-6 py-4">
<div class="flex items-center justify-between">
<div class="flex-shrink-0">
<span class="text-white font-black text-2xl bg-gradient-to-r from-[${colors.from}] to-[${colors.to}] bg-clip-text text-transparent">${brand}</span>
</div>
<div class="hidden md:flex items-center space-x-2">
${linksHtml}
</div>
<div class="md:hidden">
<button class="text-gray-400 hover:text-white p-2">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
</div>
</nav>`;
}
function generateModalCode(props) {
const colors = getColorValues();
const title = props.title || 'Modal Title';
const body = props.body || 'Modal body text goes here.';
const footerButtons = (props.footer || 'Close').split(',').map(btn => btn.trim());
let buttonsHtml = footerButtons.map((btnText, index) => {
const isPrimary = (index === footerButtons.length - 1) && footerButtons.length > 1;
const colorClasses = isPrimary
? `bg-gradient-to-r from-[${colors.to}] to-[${colors.from}] hover:from-[${colors.hover}] hover:to-[${colors.to}]`
: 'bg-gradient-to-r from-gray-600 to-gray-700 hover:from-gray-700 hover:to-gray-800';
return `<button class="${colorClasses} text-white font-bold py-2 px-6 rounded-lg transition duration-300 transform hover:scale-105">${btnText}</button>`;
}).join('\n ');
return `<div class="fixed inset-0 bg-black bg-opacity-70 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div class="bg-gradient-to-br from-gray-800 to-gray-900 rounded-2xl shadow-2xl max-w-lg w-full border border-gray-700 overflow-hidden">
<div class="flex justify-between items-center p-6 border-b border-gray-700 bg-gradient-to-r from-[${colors.from}/30] to-[${colors.to}/30]">
<h3 class="text-2xl font-bold text-white">${title}</h3>
<button class="text-gray-400 hover:text-white transition duration-300 transform hover:rotate-90">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div class="p-6">
<p class="text-gray-300 leading-relaxed">${body}</p>
</div>
<div class="flex justify-end items-center p-6 gap-3 border-t border-gray-700 bg-gray-800/50">
${buttonsHtml}
</div>
</div>
</div>`;
}
function generateAlertCode(props) {
const colors = getColorValues();
const text = props.text || 'Alert text.';
const type = props.type || 'info';
let bgFrom = '#1e40af/80';
let bgTo = '#1d4ed8/80';
let borderColor = '#3b82f6';
let textClass = 'text-blue-200';
let iconClass = 'text-blue-400';
let iconSvg = `<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>`;
switch(type) {
case 'primary':
bgFrom = `${colors.from}/80`;
bgTo = `${colors.to}/80`;
borderColor = colors.from;
textClass = 'text-white';
iconClass = `text-[${colors.from}]`;
break;
case 'info':
bgFrom = '#1e40af/80';
bgTo = '#1d4ed8/80';
borderColor = '#3b82f6';
textClass = 'text-blue-200';
iconClass = 'text-blue-400';
break;
case 'success':
bgFrom = '#065f46/80';
bgTo = '#047857/80';
borderColor = '#10b981';
textClass = 'text-green-200';
iconClass = 'text-green-400';
iconSvg = `<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path></svg>`;
break;
case 'warning':
bgFrom = '#92400e/80';
bgTo = '#854d0e/80';
borderColor = '#f59e0b';
textClass = 'text-yellow-200';
iconClass = 'text-yellow-400';
iconSvg = `<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path></svg>`;
break;
case 'danger':
bgFrom = '#991b1b/80';
bgTo = '#b91c1c/80';
borderColor = '#ef4444';
textClass = 'text-red-200';
iconClass = 'text-red-400';
iconSvg = `<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"></path></svg>`;
break;
}
const bgClass = `from-[${bgFrom}] to-[${bgTo}] border-[${borderColor}]`;
return `<div class="flex p-5 rounded-xl bg-gradient-to-r ${bgClass} ${textClass} border-l-4 shadow-lg backdrop-blur-sm" role="alert">
<span class="flex-shrink-0 ${iconClass}">${iconSvg}</span>
<div class="ml-4 text-sm font-semibold">${text}</div>
<button class="ml-auto text-white hover:opacity-75 transition duration-300">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
</button>
</div>`;
}
function generateBadgeCode(props) {
const colors = getColorValues();
const text = props.text || 'Badge';
return `<span class="inline-flex items-center px-4 py-1.5 rounded-full text-xs font-bold text-white shadow-lg transition duration-300 transform hover:scale-110 bg-gradient-to-r from-[${colors.from}] to-[${colors.to}]">
${text}
</span>`;
}
function generateBreadcrumbCode(props) {
const colors = getColorValues();
const links = (props.links || 'Home,Page').split(',').map(link => link.trim());
const separatorSvg = `<svg class="w-5 h-5 text-gray-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd"></path></svg>`;
let linksHtml = links.map((link, index) => {
const isLast = index === links.length - 1;
if (isLast) {
return `<li aria-current="page"><div class="flex items-center gap-2">${index > 0 ? separatorSvg : ''}<span class="text-sm font-semibold text-[${colors.from}]">${link}</span></div></li>`;
} else {
return `<li><div class="flex items-center gap-2">${index > 0 ? separatorSvg : ''}<a href="#" class="text-sm font-medium text-gray-300 hover:text-white transition duration-300">${link}</a></div></li>`;
}
}).join('\n ');
return `<nav class="flex bg-gray-800/50 backdrop-blur-sm px-5 py-3 rounded-xl border border-gray-700" aria-label="Breadcrumb">
<ol class="inline-flex items-center space-x-1">${linksHtml}</ol>
</nav>`;
}
function generateTabsCode(props) {
const colors = getColorValues();
const tabs = (props.tabs || 'Tab 1,Tab 2').split(',').map(tab => tab.trim());
const activeTab = props.active || tabs[0];
let tabsHtml = tabs.map(tab => {
const isActive = tab === activeTab;
const activeClasses = `text-[${colors.from}] border-[${colors.from}] bg-[${colors.from}/20]`;
const inactiveClasses = 'text-gray-400 border-transparent hover:text-gray-300 hover:border-gray-500';
return `<li><a href="#" class="inline-block px-6 py-3 border-b-2 rounded-t-lg transition duration-300 ${isActive ? activeClasses : inactiveClasses}">${tab}</a></li>`;
}).join('\n ');
return `<div class="bg-gray-800/50 backdrop-blur-sm rounded-xl border border-gray-700 overflow-hidden">
<ul class="flex flex-wrap text-sm font-medium text-center border-b border-gray-700">${tabsHtml}</ul>
</div>`;
}
function generatePaginationCode(props) {
const colors = getColorValues();
const items = (props.items || '1,2,3').split(',').map(item => item.trim());
let itemsHtml = items.map(item => {
const isText = isNaN(parseInt(item));
const classes = isText ? 'px-4' : 'w-10';
return `<li><a href="#" class="flex items-center justify-center ${classes} h-10 leading-tight text-gray-300 bg-gray-800 border border-gray-700 rounded-lg hover:bg-[${colors.from}/20] hover:text-[${colors.from}] transition duration-300 transform hover:scale-110">${item}</a></li>`;
}).join('\n ');
return `<nav aria-label="Page navigation"><ul class="inline-flex items-center gap-2">${itemsHtml}</ul></nav>`;
}
function generateHeroCode(props) {
const colors = getColorValues();
const title = props.title || 'Welcome to the Future';
const subtitle = props.subtitle || 'Build amazing things with modern components';
const buttonText = props.buttonText || 'Get Started';
return `<div class="relative overflow-hidden bg-gradient-to-br from-[${colors.from}] via-[${colors.to}] to-[${colors.hover}] py-24 px-6">
<div class="absolute inset-0 bg-black/30"></div>
<div class="relative max-w-4xl mx-auto text-center">
<h1 class="text-6xl font-black text-white mb-6 leading-tight">${title}</h1>
<p class="text-xl text-gray-200 mb-10 max-w-2xl mx-auto">${subtitle}</p>
<button class="bg-gradient-to-r from-[${colors.from}] to-[${colors.to}] hover:from-[${colors.hover}] hover:to-[${colors.from}] text-white font-bold py-4 px-10 rounded-xl text-lg shadow-2xl transition duration-300 transform hover:scale-105">
${buttonText}
</button>
</div>
</div>`;
}
function generateFooterCode(props) {
const colors = getColorValues();
const company = props.company || 'MyCompany';
const links = (props.links || 'About,Blog,Careers,Contact').split(',').map(link => link.trim());
const copyright = props.copyright || '2024 MyCompany. All rights reserved.';
let linksHtml = links.map(link => `<a href="#" class="text-gray-400 hover:text-white transition duration-300">${link}</a>`).join('\n ');
return `<footer class="bg-gradient-to-br from-gray-900 to-gray-800 border-t border-gray-700">
<div class="max-w-7xl mx-auto px-6 py-12">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<div>
<h3 class="text-2xl font-black text-white mb-4 bg-gradient-to-r from-[${colors.from}] to-[${colors.to}] bg-clip-text text-transparent">${company}</h3>
<p class="text-gray-400">Building the future, one component at a time.</p>
</div>
<div>
<h4 class="text-lg font-bold text-white mb-4">Quick Links</h4>
<div class="flex flex-col space-y-2">${linksHtml}</div>
</div>
<div>
<h4 class="text-lg font-bold text-white mb-4">Stay Connected</h4>
<div class="flex space-x-4">
<a href="#" class="text-gray-400 hover:text-white transition duration-300">
<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>
</a>
</div>
</div>
</div>
<div class="border-t border-gray-700 mt-8 pt-8 text-center text-gray-400 text-sm">${copyright}</div>
</div>
</footer>`;
}
// === PLATFORM EXPORT ===
function exportToPlatform(platform, html) {
let exported = '';
switch(platform) {
case 'html': exported = html; break;
case 'react': exported = convertToReact(html); break;
case 'vue': exported = convertToVue(html); break;
case 'wordpress': exported = convertToWordPress(html); break;
case 'joomla': exported = convertToJoomla(html); break;
case 'shopify': exported = convertToShopify(html); break;
case 'webflow': exported = html + '\n\n'; break;
case 'angular': exported = convertToAngular(html); break;
case 'svelte': exported = convertToSvelte(html); break;
default: exported = html;
}
return exported;
}
function convertToReact(html) {
let react = html
.replace(/class=/g, 'className=')
.replace(/for=/g, 'htmlFor=')
.replace(/stroke-width=/g, 'strokeWidth=')
.replace(/stroke-linecap=/g, 'strokeLinecap=')
.replace(/stroke-linejoin=/g, 'strokeLinejoin=')
.replace(/fill-rule=/g, 'fillRule=')
.replace(/clip-rule=/g, 'clipRule=');
return `import React from 'react';\n\nexport default function Component() {\n return (\n ${react}\n );\n}\n\n// For Next.js, this component is ready to use!\n// Import Tailwind CSS in your _app.js or layout.js`;
}
function convertToVue(html) {
return `<template>\n ${html}\n</template>\n\n<` + `script setup>\n// Vue 3 Composition API\n// For Nuxt.js, this component is ready to use!\n</` + `script>\n\n<style scoped>\n/* Add any component-specific styles here */\n</style>`;
}
function convertToWordPress(html) {
return `<?php\n/**\n * Custom Component Block\n */\n?>\n\n<div class="wp-block-custom-component">\n ${html}\n</div>\n\n<?php\n// Add this to your theme's functions.php:\n// Make sure to enqueue Tailwind CSS\n?>`;
}
function convertToJoomla(html) {
return `<?php\n/**\n * @package Joomla.Site\n * @subpackage mod_custom_component\n */\n\ndefined('_JEXEC') or die;\n?>\n\n<div class="mod-custom-component">\n ${html}\n</div>\n\n\n`;
}
function convertToShopify(html) {
return `{% comment %}\n Shopify Liquid Template\n Section: custom-component\n{% endcomment %}\n\n<div class="custom-component-section">\n ${html}\n</div>\n\n{% schema %}\n{\n "name": "Custom Component",\n "settings": [],\n "presets": [{\n "name": "Custom Component"\n }]\n}\n{% endschema %}\n\n`;
}
function convertToAngular(html) {
return `import { Component } from '@angular/core';\n\n@Component({\n selector: 'app-custom-component',\n template: \\\`\n ${html}\n \\\`,\n styles: [\\\`\n /* Component-specific styles */\n \\\`]\n})\nexport class CustomComponent {\n constructor() {}\n}\n\n// Make sure to add Tailwind CSS to your Angular project\n// via angular.json or styles.css`;
}
function convertToSvelte(html) {
return `<` + `script>\n // Svelte component\n // For SvelteKit, this component is ready to use!\n</` + `script>\n\n${html}\n\n<style>\n /* Add any component-specific styles here */\n</style>`;
}
// === LIVE PREVIEW ===
function updateLivePreview() {
if (!previewEnabled) return;
const component = document.getElementById('selected-option').textContent;
const viewerContent = document.getElementById('viewer-content');
if (component === 'Select Component') {
viewerContent.innerHTML = `
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #9ca3af;">
Select a component to see live preview
</div>
`;
return;
}
const properties = collectProperties();
const animation = getSelectedAnimation();
const html = generateComponentCode(component, properties, animation);
const sanitized = sanitizeHTML(html);
viewerContent.innerHTML = sanitized;
}
function toggleEditable() {
isEditable = !isEditable;
const viewerContent = document.getElementById('viewer-content');
if (!viewerContent) return;
viewerContent.contentEditable = isEditable;
// Add a visual indicator for editable mode
viewerContent.style.outline = isEditable ? '2px dashed rgba(200, 140, 80, 0.5)' : 'none';
viewerContent.style.boxShadow = isEditable ? 'inset 0 0 20px rgba(0,0,0,0.3)' : 'none';
if (isEditable) {
showToast('Edit mode enabled');
} else {
showToast('Edit mode disabled');
}
}
function copyToClipboard() {
const codeEl = document.getElementById('generated-code');
if (!codeEl) return;
const code = codeEl.textContent;
const copyButton = document.getElementById('copy-button');
if (!code || code.includes('Generated code will appear here')) {
showToast('No code to copy', true);
return;
}
navigator.clipboard.writeText(code).then(() => {
if (copyButton) copyButton.textContent = 'Copied!';
showToast('Code copied to clipboard!');
setTimeout(() => {
if (copyButton) copyButton.textContent = 'Copy';
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
showToast('Failed to copy code', true);
});
}
// === NEW MODULE TOGGLE FUNCTIONALITY ===
/**
* Toggles a slide-out module.
* If a module on the left is opened, all other left modules are closed.
* The right module is independent.
*/
function toggleModule(moduleName) {
const module = document.getElementById(`${moduleName}-module`);
if (!module) {
console.error('Module not found:', `${moduleName}-module`);
return;
}
const isLeft = module.classList.contains('left');
const side = isLeft ? 'left' : 'right';
const wasOpen = module.classList.contains('open');
const statusEl = document.getElementById('preview-status');
// 1. Get all modules on the same side
const sideModules = document.querySelectorAll(`.slide-module.${side}`);
// 2. If it was already open, just close it.
if (wasOpen) {
module.classList.remove('open');
if (moduleName === 'preview') {
previewEnabled = false;
if (statusEl) statusEl.style.display = 'none';
}
} else {
// 3. If it was closed, close all others on its side...
sideModules.forEach(mod => {
mod.classList.remove('open');
});
// ...and then open this one.
module.classList.add('open');
if (moduleName === 'preview') {
previewEnabled = true;
if (statusEl) statusEl.style.display = 'inline';
updateLivePreview(); // Refresh preview when opening
}
}
}
function attachModuleListeners() {
// Module tab click handlers
const tabs = document.querySelectorAll('.module-tab');
tabs.forEach(tab => {
const moduleName = tab.dataset.module;
tab.addEventListener('click', function(e) {
e.stopPropagation();
toggleModule(moduleName);
});
});
}
// === ATTACH EVENT LISTENERS (DOM-CONTENT-LOADED SAFE) ===
function attachGlobalEventListeners() {
const dropdownButton = document.getElementById('dropdown-button');
const dropdownContent = document.getElementById('dropdown-content');
if (dropdownButton) dropdownButton.addEventListener('click', toggleDropdown);
if (dropdownContent) dropdownContent.addEventListener('click', selectOption);
document.addEventListener('click', (e) => {
const dropdown = document.getElementById('component-dropdown');
if (dropdown && !dropdown.contains(e.target)) {
dropdown.classList.remove('show');
}
});
const addInputBtn = document.getElementById('add-input-button');
if (addInputBtn) addInputBtn.addEventListener('click', () => addInput());
const genCodeBtn = document.getElementById('generate-code-button');
if (genCodeBtn) genCodeBtn.addEventListener('click', generateCode);
const copyBtn = document.getElementById('copy-button');
if (copyBtn) copyBtn.addEventListener('click', copyToClipboard);
const toggleEditBtn = document.getElementById('toggle-editable');
if (toggleEditBtn) toggleEditBtn.addEventListener('click', toggleEditable);
const refreshBtn = document.getElementById('refresh-preview');
if (refreshBtn) refreshBtn.addEventListener('click', updateLivePreview);
// Animation selection
document.querySelectorAll('.animation-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.animation-option').forEach(opt => {
opt.classList.remove('selected');
});
this.classList.add('selected');
selectedAnimation = this.dataset.animation; // Update global state
if (previewEnabled) {
updateLivePreview();
}
});
});
// Platform export dropdown
const platformButton = document.getElementById('platform-button');
const platformOptions = document.getElementById('platform-options');
if(platformButton) {
platformButton.addEventListener('click', (e) => {
e.stopPropagation();
if(platformOptions) platformOptions.classList.toggle('show');
});
}
document.addEventListener('click', (e) => {
if (platformButton && !platformButton.contains(e.target) && platformOptions && !platformOptions.contains(e.target)) {
platformOptions.classList.remove('show');
}
});
document.querySelectorAll('.platform-option').forEach(option => {
option.addEventListener('click', function() {
const platform = this.dataset.platform;
const codeEl = document.getElementById('generated-code');
if (!codeEl) return;
const currentCode = codeEl.textContent;
if (!currentCode || currentCode.includes('Generated code will appear here')) {
showToast('Generate code first before exporting', true);
return;
}
const exported = exportToPlatform(platform, currentCode);
codeEl.textContent = exported;
if (window.hljs) {
try {
hljs.highlightElement(codeEl);
} catch (e) {
console.error("highlight.js failed:", e);
}
}
if (platformOptions) platformOptions.classList.remove('show');
showToast(`Exported to ${this.textContent.trim()}`);
});
});
}
// === COLOR PALETTE FUNCTIONALITY ===
function initColorPalette() {
// Color swatch selection
document.querySelectorAll('.color-swatch').forEach(swatch => {
swatch.addEventListener('click', function() {
document.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
this.classList.add('selected');
selectedColorPalette = this.dataset.palette;
if (previewEnabled) updateLivePreview();
});
});
// Custom color picker
const colorPicker = document.getElementById('custom-color-picker');
const colorHex = document.getElementById('custom-color-hex');
const applyBtn = document.getElementById('apply-custom-color');
if (colorPicker && colorHex) {
colorPicker.addEventListener('input', (e) => {
colorHex.value = e.target.value;
});
colorHex.addEventListener('input', (e) => {
const hex = e.target.value;
if (/^#[0-9A-F]{6}$/i.test(hex)) {
colorPicker.value = hex;
}
});
applyBtn.addEventListener('click', () => {
customColor = colorPicker.value;
selectedColorPalette = 'custom';
document.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
if (previewEnabled) updateLivePreview();
showToast('Custom color applied!');
});
}
}
// Get color values based on selected palette
function getColorValues() {
const palettes = {
indigo: { from: '#6366f1', to: '#8b5cf6', hover: '#4f46e5' },
blue: { from: '#3b82f6', to: '#2563eb', hover: '#1d4ed8' },
green: { from: '#10b981', to: '#059669', hover: '#047857' },
red: { from: '#ef4444', to: '#dc2626', hover: '#b91c1c' },
amber: { from: '#f59e0b', to: '#d97706', hover: '#b45309' },
purple: { from: '#a855f7', to: '#9333ea', hover: '#7e22ce' },
pink: { from: '#ec4899', to: '#db2777', hover: '#be185d' },
teal: { from: '#14b8a6', to: '#0d9488', hover: '#0f766e' },
custom: { from: customColor, to: customColor, hover: customColor }
};
return palettes[selectedColorPalette] || palettes.indigo;
}
// === INITIALIZATION ===
document.addEventListener('DOMContentLoaded', () => {
try {
renderApp();
animateHeaderText();
attachGlobalEventListeners(); // Attach event listeners
attachModuleListeners(); // Attach the module tab listeners
initColorPalette();
createParticles();
// Open the components panel by default to guide the user
setTimeout(() => {
toggleModule('components');
}, 500);
} catch (e) {
console.error('❌ App initialization failed:', e);
document.getElementById('app').innerHTML = '<div style="color: white; padding: 50px;">Error: ' + e.message + '</div>';
}
});
</script>
</body>
</html>