Spaces:
Sleeping
Sleeping
| <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> |