| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Lance 3.0: Unified Multimodal Modeling</title> |
| |
| |
| <meta name="description" content="Lance is a state-of-the-art unified multimodal model by multi-task synergy, capable of video and image generation, editing, and video/image understanding."> |
| <meta name="keywords" content="multimodal, video generation, image generation, video understanding, text-to-video, image editing, AI model"> |
| |
| |
| <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&family=Outfit:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> |
| |
| |
| <script src="https://unpkg.com/lucide@latest"></script> |
|
|
| <style> |
| |
| :root { |
| --bg-base: #070a13; |
| --bg-obsidian: #0b0f19; |
| --glass-bg: rgba(11, 15, 25, 0.42); |
| --glass-bg-hover: rgba(22, 32, 53, 0.55); |
| --glass-border: rgba(255, 255, 255, 0.08); |
| --glass-border-focus: rgba(249, 115, 22, 0.4); |
| |
| --text-primary: #f8fafc; |
| --text-secondary: #e2e8f0; |
| --text-muted: #94a3b8; |
| |
| --accent-orange: #f97316; |
| --accent-pink: #ec4899; |
| --accent-gradient: linear-gradient(135deg, #f97316 0%, #ec4899 100%); |
| --accent-gradient-hover: linear-gradient(135deg, #ea580c 0%, #db2777 100%); |
| |
| --shadow-premium: 0 16px 48px -15px rgba(0, 0, 0, 0.7); |
| --shadow-glow: 0 0 25px rgba(249, 115, 22, 0.25); |
| |
| --font-outfit: 'Outfit', sans-serif; |
| --font-inter: 'Inter', sans-serif; |
| --font-mono: 'JetBrains Mono', monospace; |
| |
| --transition-smooth: all 0.5s cubic-bezier(0.16, 1, 0.3, 1); |
| --transition-fast: all 0.15s ease; |
| } |
| |
| * { |
| box-sizing: border-box; |
| margin: 0; |
| padding: 0; |
| } |
| |
| html { |
| scroll-behavior: smooth; |
| } |
| |
| body { |
| background-color: var(--bg-base); |
| color: var(--text-primary); |
| font-family: var(--font-inter); |
| min-height: 100vh; |
| overflow-x: hidden; |
| line-height: 1.5; |
| } |
| |
| |
| .viewport-bg-container { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100vw; |
| height: 100vh; |
| z-index: 1; |
| pointer-events: none; |
| overflow: hidden; |
| } |
| |
| .fs-bg-element { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| opacity: 0; |
| transition: opacity 1s cubic-bezier(0.16, 1, 0.3, 1), transform 15s ease; |
| } |
| |
| .fs-bg-element.active { |
| opacity: 1; |
| transform: scale(1.02); |
| } |
| |
| .fs-bg-video { |
| pointer-events: auto; |
| } |
| |
| |
| .fs-bg-overlay { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: linear-gradient(180deg, rgba(7, 10, 19, 0.35) 0%, rgba(7, 10, 19, 0.2) 60%, rgba(7, 10, 19, 0.8) 100%); |
| z-index: 2; |
| pointer-events: none; |
| } |
| |
| |
| .cinematic-viewport { |
| height: 100vh; |
| width: 100%; |
| position: relative; |
| z-index: 10; |
| display: flex; |
| flex-direction: column; |
| justify-content: space-between; |
| padding: 30px 40px; |
| overflow: hidden; |
| } |
| |
| @media (max-width: 768px) { |
| .cinematic-viewport { |
| padding: 20px; |
| } |
| } |
| |
| |
| header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| width: 100%; |
| } |
| |
| .brand-link { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| text-decoration: none; |
| } |
| |
| .brand-logo { |
| height: 30px; |
| width: auto; |
| object-fit: contain; |
| } |
| |
| .brand-divider { |
| width: 1px; |
| height: 16px; |
| background: rgba(255, 255, 255, 0.3); |
| } |
| |
| .brand-name { |
| font-family: var(--font-outfit); |
| font-size: 19px; |
| font-weight: 700; |
| color: #fff; |
| letter-spacing: -0.5px; |
| } |
| |
| |
| .nav-links-pill { |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| backdrop-filter: blur(12px); |
| -webkit-backdrop-filter: blur(12px); |
| border-radius: 100px; |
| padding: 4px 8px; |
| display: flex; |
| gap: 6px; |
| } |
| |
| @media (max-width: 768px) { |
| .nav-links-pill { |
| display: none; |
| } |
| } |
| |
| .nav-link-btn { |
| background: transparent; |
| border: none; |
| color: rgba(255, 255, 255, 0.75); |
| font-family: var(--font-outfit); |
| font-size: 13px; |
| font-weight: 600; |
| padding: 8px 16px; |
| border-radius: 100px; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| text-decoration: none; |
| } |
| |
| .nav-link-btn:hover { |
| color: #fff; |
| background: rgba(255, 255, 255, 0.05); |
| } |
| |
| .lang-toggle { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| } |
| |
| .lang-btn-active { |
| background: #fff; |
| color: #070a13; |
| border: none; |
| font-family: var(--font-outfit); |
| font-size: 11px; |
| font-weight: 700; |
| padding: 6px 12px; |
| border-radius: 100px; |
| cursor: pointer; |
| } |
| |
| .lang-btn-inactive { |
| background: transparent; |
| border: none; |
| color: rgba(255, 255, 255, 0.6); |
| font-family: var(--font-outfit); |
| font-size: 11px; |
| font-weight: 600; |
| cursor: pointer; |
| text-decoration: none; |
| transition: var(--transition-fast); |
| } |
| |
| .lang-btn-inactive:hover { |
| color: #fff; |
| } |
| |
| |
| .viewport-center-trigger { |
| position: absolute; |
| top: 48%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| z-index: 15; |
| text-align: center; |
| width: 90%; |
| max-width: 680px; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 20px; |
| transition: all 0.6s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| |
| .viewport-center-trigger.output-mode { |
| top: auto; |
| bottom: 24px; |
| left: 24px; |
| transform: none; |
| width: calc(100% - 48px); |
| max-width: 560px; |
| gap: 0px; |
| } |
| |
| .viewport-center-trigger.output-mode .center-prompt-card { |
| border-radius: 14px; |
| padding: 8px 12px; |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); |
| background: rgba(10, 15, 30, 0.55); |
| } |
| |
| .viewport-center-trigger.output-mode .center-prompt-header { |
| display: none; |
| } |
| |
| .viewport-center-trigger.output-mode .center-prompt-body textarea.prompt-textarea { |
| height: 24px; |
| font-size: 13px; |
| } |
| |
| .viewport-center-trigger.output-mode .center-prompt-footer { |
| border-top: none; |
| padding-top: 4px; |
| margin-top: 0; |
| } |
| |
| .viewport-center-trigger.output-mode .center-options-btn { |
| padding: 6px 12px; |
| font-size: 12px; |
| } |
| |
| .viewport-center-trigger.output-mode .center-generate-btn { |
| padding: 6px 16px; |
| font-size: 12px; |
| } |
| |
| .viewport-center-trigger.output-mode .quick-examples-container { |
| display: none; |
| } |
| |
| |
| .viewport-bottom.output-hidden { |
| opacity: 0; |
| pointer-events: none; |
| transform: translateY(30px); |
| transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| .viewport-bottom { |
| transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| .center-prompt-card { |
| width: 100%; |
| background: rgba(10, 15, 30, 0.28); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| backdrop-filter: blur(24px); |
| -webkit-backdrop-filter: blur(24px); |
| border-radius: 16px; |
| padding: 12px 14px; |
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); |
| display: flex; |
| flex-direction: column; |
| gap: 8px; |
| transition: border-color var(--transition-smooth), box-shadow var(--transition-smooth); |
| } |
| |
| .center-prompt-card:focus-within { |
| border-color: rgba(249, 115, 22, 0.4); |
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), 0 0 15px rgba(249, 115, 22, 0.15); |
| } |
| |
| .center-prompt-header { |
| display: flex; |
| justify-content: flex-start; |
| align-items: center; |
| padding-left: 4px; |
| } |
| |
| .active-task-pill { |
| background: rgba(249, 115, 22, 0.15); |
| border: 1px solid rgba(249, 115, 22, 0.3); |
| color: #fdba74; |
| font-size: 11px; |
| font-weight: 600; |
| text-transform: uppercase; |
| letter-spacing: 0.05em; |
| padding: 4px 10px; |
| border-radius: 6px; |
| font-family: var(--font-outfit); |
| } |
| |
| .center-prompt-body { |
| width: 100%; |
| } |
| |
| .center-prompt-body textarea.prompt-textarea { |
| width: 100%; |
| height: 44px; |
| background: transparent; |
| border: none; |
| outline: none; |
| resize: none; |
| color: #fff; |
| font-size: 14px; |
| line-height: 1.6; |
| font-family: var(--font-inter); |
| padding: 4px; |
| } |
| |
| .center-prompt-body textarea.prompt-textarea::placeholder { |
| color: rgba(255, 255, 255, 0.35); |
| } |
| |
| .center-prompt-footer { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| border-top: 1px solid rgba(255, 255, 255, 0.06); |
| padding-top: 8px; |
| margin-top: 2px; |
| } |
| |
| .center-options-btn { |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| color: rgba(255, 255, 255, 0.7); |
| padding: 8px 16px; |
| border-radius: 10px; |
| font-size: 13px; |
| font-weight: 500; |
| font-family: var(--font-outfit); |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| transition: var(--transition-fast); |
| } |
| |
| .center-options-btn:hover { |
| background: rgba(255, 255, 255, 0.1); |
| border-color: rgba(255, 255, 255, 0.2); |
| color: #fff; |
| } |
| |
| .center-generate-btn { |
| background: linear-gradient(135deg, #f97316 0%, #f43f5e 100%); |
| border: none; |
| color: #fff; |
| padding: 8px 20px; |
| border-radius: 10px; |
| font-size: 13px; |
| font-weight: 600; |
| font-family: var(--font-outfit); |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| box-shadow: 0 4px 15px rgba(244, 63, 94, 0.3); |
| transition: var(--transition-smooth); |
| } |
| |
| .center-generate-btn:hover { |
| transform: translateY(-1px); |
| box-shadow: 0 6px 20px rgba(244, 63, 94, 0.45); |
| filter: brightness(1.15); |
| } |
| |
| |
| .quick-examples-container { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 12px; |
| width: 100%; |
| padding: 0 8px; |
| animation: fadeIn 0.8s ease-in-out; |
| } |
| |
| .quick-examples-label { |
| font-size: 12px; |
| color: rgba(255, 255, 255, 0.4); |
| font-family: var(--font-outfit); |
| white-space: nowrap; |
| } |
| |
| .quick-examples-list { |
| display: flex; |
| flex-wrap: wrap; |
| gap: 8px; |
| justify-content: center; |
| } |
| |
| .quick-example-pill { |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| backdrop-filter: blur(8px); |
| -webkit-backdrop-filter: blur(8px); |
| color: rgba(255, 255, 255, 0.65); |
| padding: 4px 12px 4px 6px; |
| border-radius: 100px; |
| font-size: 12px; |
| font-family: var(--font-inter); |
| cursor: pointer; |
| transition: var(--transition-fast); |
| max-width: 260px; |
| white-space: nowrap; |
| overflow: hidden; |
| } |
| |
| .quick-example-pill:hover { |
| background: rgba(249, 115, 22, 0.12); |
| border-color: rgba(249, 115, 22, 0.3); |
| color: #fdba74; |
| transform: translateY(-1px); |
| } |
| |
| .quick-example-thumb { |
| width: 20px; |
| height: 20px; |
| border-radius: 50%; |
| object-fit: cover; |
| border: 1px solid rgba(255, 255, 255, 0.15); |
| background: #000; |
| flex-shrink: 0; |
| } |
| |
| .quick-example-thumb-icon { |
| display: inline-flex; |
| align-items: center; |
| justify-content: center; |
| width: 20px; |
| height: 20px; |
| border-radius: 50%; |
| background: rgba(249, 115, 22, 0.1); |
| border: 1px solid rgba(249, 115, 22, 0.2); |
| color: var(--accent-orange); |
| font-size: 9px; |
| flex-shrink: 0; |
| } |
| |
| .quick-example-text { |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| display: inline-block; |
| } |
| |
| |
| .viewport-bottom { |
| display: flex; |
| justify-content: space-between; |
| align-items: flex-end; |
| width: 100%; |
| padding-bottom: 10px; |
| gap: 40px; |
| position: relative; |
| z-index: 15; |
| } |
| |
| @media (max-width: 900px) { |
| .viewport-bottom { |
| flex-direction: column; |
| align-items: flex-start; |
| gap: 20px; |
| } |
| } |
| |
| |
| .bottom-caption { |
| max-width: 580px; |
| text-align: left; |
| } |
| |
| .caption-title { |
| font-family: var(--font-outfit); |
| font-size: clamp(36px, 5.5vw, 68px); |
| font-weight: 800; |
| color: #fff; |
| line-height: 1.05; |
| margin-bottom: 12px; |
| letter-spacing: -2px; |
| } |
| |
| .caption-desc { |
| font-size: 13.5px; |
| color: rgba(255, 255, 255, 0.85); |
| line-height: 1.55; |
| font-family: var(--font-inter); |
| } |
| |
| |
| .bottom-actions { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| flex-wrap: wrap; |
| } |
| |
| .action-pill { |
| background: rgba(255, 255, 255, 0.08); |
| border: 1px solid rgba(255, 255, 255, 0.12); |
| backdrop-filter: blur(12px); |
| -webkit-backdrop-filter: blur(12px); |
| color: #fff; |
| padding: 10px 20px; |
| border-radius: 100px; |
| font-family: var(--font-outfit); |
| font-weight: 600; |
| font-size: 13px; |
| text-decoration: none; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| display: inline-flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .action-pill:hover { |
| background: rgba(255, 255, 255, 0.18); |
| border-color: rgba(255, 255, 255, 0.25); |
| transform: translateY(-1px); |
| } |
| |
| .action-pill.primary { |
| background: #fff; |
| color: #070a13; |
| border-color: #fff; |
| } |
| |
| .action-pill.primary:hover { |
| background: rgba(255, 255, 255, 0.9); |
| color: #000; |
| } |
| |
| .circle-action-btn { |
| width: 38px; |
| height: 38px; |
| border-radius: 50%; |
| background: rgba(255, 255, 255, 0.08); |
| border: 1px solid rgba(255, 255, 255, 0.12); |
| backdrop-filter: blur(12px); |
| -webkit-backdrop-filter: blur(12px); |
| color: #fff; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| } |
| |
| .circle-action-btn:hover { |
| background: rgba(255, 255, 255, 0.18); |
| transform: scale(1.05); |
| } |
| |
| .circle-action-btn i { |
| width: 16px; |
| height: 16px; |
| } |
| |
| |
| |
| .controls-sidebar { |
| position: fixed; |
| top: 90px; |
| left: -460px; |
| width: 420px; |
| max-height: calc(100vh - 140px); |
| overflow-y: auto; |
| z-index: 100; |
| background: rgba(10, 15, 30, 0.28); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| backdrop-filter: blur(30px); |
| -webkit-backdrop-filter: blur(30px); |
| border-radius: 24px; |
| padding: 24px; |
| box-shadow: var(--shadow-premium); |
| transition: left 0.5s cubic-bezier(0.16, 1, 0.3, 1); |
| scrollbar-width: none; |
| } |
| |
| .controls-sidebar::-webkit-scrollbar { |
| display: none; |
| } |
| |
| .controls-sidebar.open { |
| left: 30px; |
| } |
| |
| |
| .panel-title { |
| font-family: var(--font-outfit); |
| font-size: 17px; |
| font-weight: 700; |
| margin-bottom: 18px; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); |
| padding-bottom: 10px; |
| } |
| |
| .panel-title-left { |
| display: flex; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .panel-title i { |
| color: var(--accent-orange); |
| width: 18px; |
| height: 18px; |
| } |
| |
| .close-sidebar-btn { |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 4px; |
| border-radius: 50%; |
| transition: var(--transition-fast); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .close-sidebar-btn:hover { |
| color: #ef4444; |
| background: rgba(239, 68, 68, 0.1); |
| } |
| |
| .close-sidebar-btn i { |
| width: 16px; |
| height: 16px; |
| } |
| |
| |
| .form-group { |
| margin-bottom: 18px; |
| } |
| |
| .form-label { |
| display: block; |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--text-secondary); |
| margin-bottom: 6px; |
| text-transform: uppercase; |
| letter-spacing: 0.5px; |
| } |
| |
| .prompt-textarea { |
| width: 100%; |
| min-height: 110px; |
| background: rgba(15, 23, 42, 0.45); |
| border: 1px solid var(--glass-border); |
| border-radius: 14px; |
| padding: 14px; |
| color: var(--text-primary); |
| font-family: var(--font-inter); |
| font-size: 14px; |
| line-height: 1.5; |
| resize: vertical; |
| outline: none; |
| transition: var(--transition-fast); |
| } |
| |
| .prompt-textarea:focus { |
| border-color: var(--glass-border-focus); |
| box-shadow: 0 0 15px rgba(249, 115, 22, 0.15); |
| } |
| |
| .prompt-textarea::placeholder { |
| color: rgba(255, 255, 255, 0.35); |
| } |
| |
| |
| .controls-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); |
| gap: 12px; |
| margin-bottom: 18px; |
| } |
| |
| .select-wrapper { |
| position: relative; |
| } |
| |
| .select-control { |
| width: 100%; |
| background: rgba(255, 255, 255, 0.04); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| border-radius: 10px; |
| padding: 8px 12px; |
| color: var(--text-primary); |
| font-family: var(--font-outfit); |
| font-size: 13px; |
| font-weight: 600; |
| outline: none; |
| cursor: pointer; |
| appearance: none; |
| -webkit-appearance: none; |
| transition: var(--transition-fast); |
| } |
| |
| .select-control:focus { |
| border-color: var(--glass-border-focus); |
| } |
| |
| .select-wrapper::after { |
| content: '▼'; |
| font-size: 8px; |
| color: var(--text-secondary); |
| position: absolute; |
| right: 12px; |
| top: 50%; |
| transform: translateY(-50%); |
| pointer-events: none; |
| } |
| |
| |
| .upload-zone { |
| width: 100%; |
| border: 1.5px dashed rgba(255, 255, 255, 0.08); |
| border-radius: 14px; |
| padding: 24px 16px; |
| text-align: center; |
| background: rgba(255, 255, 255, 0.02); |
| cursor: pointer; |
| transition: var(--transition-smooth); |
| display: none; |
| } |
| |
| .upload-zone.dragover { |
| border-color: var(--accent-orange); |
| background: rgba(249, 115, 22, 0.05); |
| } |
| |
| .upload-zone-content { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| gap: 8px; |
| } |
| |
| .upload-icon { |
| width: 38px; |
| height: 38px; |
| border-radius: 50%; |
| background: rgba(255, 255, 255, 0.02); |
| border: 1px solid var(--glass-border); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: var(--text-secondary); |
| transition: var(--transition-fast); |
| } |
| |
| .upload-title { |
| font-family: var(--font-outfit); |
| font-size: 14px; |
| font-weight: 600; |
| color: var(--text-primary); |
| } |
| |
| .upload-desc { |
| font-size: 11px; |
| color: rgba(255, 255, 255, 0.4); |
| } |
| |
| .file-input { |
| display: none; |
| } |
| |
| |
| .preview-zone { |
| display: none; |
| width: 100%; |
| border: 1px solid var(--glass-border); |
| border-radius: 14px; |
| padding: 12px; |
| background: rgba(15, 23, 42, 0.35); |
| flex-direction: column; |
| gap: 10px; |
| } |
| |
| .preview-media-container { |
| width: 100%; |
| height: 200px; |
| border-radius: 10px; |
| overflow: hidden; |
| background: rgba(0, 0, 0, 0.3); |
| border: 1px solid rgba(255, 255, 255, 0.05); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .preview-media-container img, |
| .preview-media-container video { |
| width: 100%; |
| height: 100%; |
| object-fit: contain; |
| } |
| |
| .preview-bottom-row { |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| gap: 10px; |
| } |
| |
| .preview-info { |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .preview-name { |
| font-size: 12px; |
| font-weight: 600; |
| color: var(--text-primary); |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .preview-size { |
| font-size: 10px; |
| color: rgba(255, 255, 255, 0.4); |
| margin-top: 1px; |
| } |
| |
| .clear-file-btn { |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| cursor: pointer; |
| padding: 6px; |
| border-radius: 50%; |
| transition: var(--transition-fast); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .clear-file-btn:hover { |
| color: #ef4444; |
| background: rgba(239, 68, 68, 0.1); |
| } |
| |
| |
| .accordion { |
| margin-top: 18px; |
| border: 1px solid var(--glass-border); |
| border-radius: 14px; |
| background: rgba(15, 23, 42, 0.2); |
| overflow: hidden; |
| transition: var(--transition-smooth); |
| } |
| |
| .accordion-header { |
| width: 100%; |
| padding: 10px 16px; |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| font-family: var(--font-outfit); |
| font-size: 12px; |
| font-weight: 700; |
| letter-spacing: 0.5px; |
| text-transform: uppercase; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: space-between; |
| } |
| |
| .accordion-header:hover { |
| color: var(--text-primary); |
| } |
| |
| .accordion-header i { |
| width: 14px; |
| height: 14px; |
| transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| .accordion.open .accordion-header i { |
| transform: rotate(180deg); |
| } |
| |
| .accordion-content { |
| padding: 0 16px; |
| max-height: 0; |
| overflow: hidden; |
| transition: max-height 0.35s cubic-bezier(0.16, 1, 0.3, 1), padding 0.35s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| .accordion.open .accordion-content { |
| padding: 8px 16px 16px; |
| max-height: 400px; |
| } |
| |
| .number-control { |
| width: 100%; |
| background: rgba(15, 23, 42, 0.5); |
| border: 1px solid var(--glass-border); |
| border-radius: 8px; |
| padding: 8px 10px; |
| color: var(--text-primary); |
| font-family: var(--font-mono); |
| font-size: 12px; |
| outline: none; |
| } |
| |
| .slider-wrapper { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .slider-control { |
| flex: 1; |
| height: 4px; |
| background: rgba(255, 255, 255, 0.08); |
| border-radius: 4px; |
| outline: none; |
| -webkit-appearance: none; |
| accent-color: var(--accent-orange); |
| cursor: pointer; |
| } |
| |
| .slider-val { |
| min-width: 28px; |
| text-align: right; |
| font-family: var(--font-mono); |
| font-size: 11px; |
| font-weight: 500; |
| color: var(--accent-orange); |
| } |
| |
| |
| .gen-btn { |
| width: 100%; |
| margin-top: 18px; |
| padding: 12px 24px; |
| background: var(--accent-gradient); |
| border: none; |
| border-radius: 14px; |
| color: #0f172a; |
| font-family: var(--font-outfit); |
| font-size: 15px; |
| font-weight: 700; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| box-shadow: var(--shadow-glow); |
| transition: var(--transition-smooth); |
| } |
| |
| .gen-btn:hover:not(:disabled) { |
| transform: translateY(-1px); |
| box-shadow: 0 0 35px rgba(249, 115, 22, 0.35); |
| background: var(--accent-gradient-hover); |
| } |
| |
| .gen-btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| box-shadow: none; |
| } |
| |
| |
| |
| .floating-right-container { |
| position: fixed; |
| top: 90px; |
| right: 30px; |
| z-index: 100; |
| display: flex; |
| flex-direction: column; |
| align-items: flex-end; |
| gap: 20px; |
| pointer-events: none; |
| } |
| |
| @media (max-width: 1024px) { |
| .floating-right-container { |
| right: 20px; |
| left: 20px; |
| align-items: stretch; |
| pointer-events: auto; |
| } |
| } |
| |
| |
| .status-panel { |
| background: rgba(11, 15, 25, 0.55); |
| border: 1px solid var(--glass-border); |
| border-radius: 16px; |
| padding: 14px 18px; |
| display: none; |
| align-items: center; |
| justify-content: space-between; |
| gap: 16px; |
| width: 380px; |
| box-shadow: var(--shadow-premium); |
| backdrop-filter: blur(20px); |
| -webkit-backdrop-filter: blur(20px); |
| animation: slide-in 0.3s cubic-bezier(0.16, 1, 0.3, 1); |
| pointer-events: auto; |
| } |
| |
| @keyframes slide-in { |
| from { transform: translateY(-10px); opacity: 0; } |
| to { transform: translateY(0); opacity: 1; } |
| } |
| |
| .status-left { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| min-width: 0; |
| } |
| |
| .spinner { |
| width: 20px; |
| height: 20px; |
| border: 2px solid rgba(249, 115, 22, 0.1); |
| border-radius: 50%; |
| border-top-color: var(--accent-orange); |
| animation: spin 1s linear infinite; |
| flex-shrink: 0; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| .status-details { |
| min-width: 0; |
| } |
| |
| .status-title { |
| font-family: var(--font-outfit); |
| font-size: 13px; |
| font-weight: 700; |
| color: #fff; |
| } |
| |
| .status-desc { |
| font-size: 11px; |
| color: var(--text-secondary); |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| margin-top: 1px; |
| } |
| |
| .status-timer { |
| font-family: var(--font-mono); |
| font-size: 12px; |
| font-weight: 500; |
| color: var(--accent-orange); |
| background: rgba(249, 115, 22, 0.08); |
| border: 1px solid rgba(249, 115, 22, 0.15); |
| padding: 3px 8px; |
| border-radius: 5px; |
| flex-shrink: 0; |
| } |
| |
| |
| .terminal-container { |
| width: 380px; |
| padding: 24px; |
| background: rgba(7, 10, 19, 0.65); |
| color: var(--text-primary); |
| font-family: var(--font-mono); |
| font-size: 14px; |
| line-height: 1.6; |
| overflow-y: auto; |
| border-radius: 20px; |
| border: 1px solid var(--glass-border); |
| backdrop-filter: blur(24px); |
| -webkit-backdrop-filter: blur(24px); |
| box-shadow: var(--shadow-premium); |
| display: none; |
| position: relative; |
| max-height: 350px; |
| pointer-events: auto; |
| } |
| |
| .terminal-container::-webkit-scrollbar { |
| width: 6px; |
| } |
| |
| .terminal-container::-webkit-scrollbar-thumb { |
| background: var(--glass-border); |
| border-radius: 3px; |
| } |
| |
| .terminal-header { |
| color: var(--accent-orange); |
| font-family: var(--font-outfit); |
| font-weight: 700; |
| font-size: 11px; |
| text-transform: uppercase; |
| letter-spacing: 1px; |
| margin-bottom: 12px; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .copy-btn { |
| position: absolute; |
| top: 14px; |
| right: 14px; |
| background: rgba(255, 255, 255, 0.03); |
| border: 1px solid var(--glass-border); |
| color: var(--text-secondary); |
| padding: 6px; |
| border-radius: 6px; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| } |
| |
| .copy-btn:hover { |
| color: #fff; |
| background: rgba(255, 255, 255, 0.08); |
| } |
| |
| .copy-btn i { |
| width: 12px; |
| height: 12px; |
| } |
| |
| |
| .output-actions-bar { |
| display: none; |
| background: rgba(15, 23, 42, 0.45); |
| border: 1px solid var(--glass-border); |
| backdrop-filter: blur(16px); |
| -webkit-backdrop-filter: blur(16px); |
| border-radius: 100px; |
| padding: 6px 12px; |
| gap: 12px; |
| align-items: center; |
| box-shadow: var(--shadow-premium); |
| animation: slide-in 0.4s cubic-bezier(0.16, 1, 0.3, 1); |
| pointer-events: auto; |
| } |
| |
| .action-icon-btn { |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| width: 34px; |
| height: 34px; |
| border-radius: 50%; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: var(--transition-fast); |
| } |
| |
| .action-icon-btn:hover { |
| color: #fff; |
| background: rgba(255, 255, 255, 0.08); |
| } |
| |
| .action-icon-btn i { |
| width: 16px; |
| height: 16px; |
| } |
| |
| .action-pill-btn { |
| background: var(--accent-gradient); |
| border: none; |
| color: #0f172a; |
| font-family: var(--font-outfit); |
| font-weight: 700; |
| font-size: 12px; |
| padding: 6px 14px; |
| border-radius: 100px; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| display: inline-flex; |
| align-items: center; |
| gap: 4px; |
| } |
| |
| .action-pill-btn:hover { |
| background: var(--accent-gradient-hover); |
| } |
| |
| |
| @keyframes pulse-highlight { |
| 0%, 100% { border-color: var(--glass-border); } |
| 50% { border-color: var(--accent-orange); box-shadow: 0 0 25px rgba(249, 115, 22, 0.2); } |
| } |
| |
| .highlight-pulse { |
| animation: pulse-highlight 1.2s ease 2; |
| } |
| |
| |
| |
| .examples-section-fold { |
| background: linear-gradient(180deg, #f8f9fb 0%, #ffffff 100%); |
| position: relative; |
| z-index: 10; |
| margin-top: 0px; |
| padding: 60px 40px 40px; |
| border-top: 1px solid rgba(0, 0, 0, 0.06); |
| display: none; |
| } |
| |
| @media (max-width: 768px) { |
| .examples-section-fold { |
| padding: 40px 20px 20px; |
| } |
| } |
| |
| .examples-container { |
| max-width: 1200px; |
| margin: 0 auto; |
| } |
| |
| .section-header { |
| font-family: var(--font-outfit); |
| font-size: 20px; |
| font-weight: 700; |
| margin-bottom: 20px; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| color: #1e293b; |
| } |
| |
| .section-header i { |
| color: var(--accent-orange); |
| width: 18px; |
| height: 18px; |
| } |
| |
| .examples-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); |
| gap: 20px; |
| } |
| |
| .example-card { |
| background: #ffffff; |
| border: 1px solid rgba(0, 0, 0, 0.08); |
| border-radius: 18px; |
| padding: 16px; |
| cursor: pointer; |
| transition: var(--transition-smooth); |
| display: flex; |
| flex-direction: column; |
| gap: 12px; |
| position: relative; |
| overflow: hidden; |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); |
| } |
| |
| .example-card:hover { |
| transform: translateY(-3px); |
| border-color: rgba(249, 115, 22, 0.35); |
| box-shadow: 0 8px 24px rgba(249, 115, 22, 0.1); |
| } |
| |
| .example-media-preview { |
| width: 100%; |
| height: 200px; |
| border-radius: 10px; |
| overflow: hidden; |
| background: #f1f5f9; |
| } |
| |
| .example-media-preview img, |
| .example-media-preview video { |
| width: 100%; |
| height: 100%; |
| object-fit: contain; |
| } |
| |
| .example-prompt { |
| font-size: 12.5px; |
| color: #475569; |
| line-height: 1.45; |
| display: -webkit-box; |
| -webkit-line-clamp: 3; |
| -webkit-box-orient: vertical; |
| overflow: hidden; |
| font-weight: 500; |
| } |
| |
| .example-card:hover .example-prompt { |
| color: #1e293b; |
| } |
| |
| .example-badge { |
| position: absolute; |
| top: 12px; |
| right: 12px; |
| background: rgba(255, 255, 255, 0.92); |
| border: 1px solid rgba(0, 0, 0, 0.08); |
| color: var(--accent-orange); |
| font-size: 8px; |
| font-family: var(--font-outfit); |
| font-weight: 700; |
| padding: 2px 6px; |
| border-radius: 99px; |
| text-transform: uppercase; |
| } |
| |
| |
| |
| .lightbox-modal { |
| display: none; |
| position: fixed; |
| z-index: 1000; |
| top: 0; |
| left: 0; |
| width: 100vw; |
| height: 100vh; |
| background: rgba(5, 7, 13, 0.96); |
| align-items: center; |
| justify-content: center; |
| opacity: 0; |
| transition: opacity 0.3s ease; |
| } |
| |
| .lightbox-modal.open { |
| display: flex; |
| opacity: 1; |
| } |
| |
| .lightbox-content { |
| max-width: 92%; |
| max-height: 88%; |
| border-radius: 12px; |
| overflow: hidden; |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| transform: scale(0.95); |
| transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| .lightbox-modal.open .lightbox-content { |
| transform: scale(1); |
| } |
| |
| .lightbox-img { |
| width: auto; |
| height: auto; |
| max-width: 100%; |
| max-height: 88vh; |
| display: block; |
| object-fit: contain; |
| } |
| |
| .lightbox-close { |
| position: absolute; |
| top: 24px; |
| right: 24px; |
| background: rgba(255, 255, 255, 0.05); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| color: #fff; |
| padding: 10px; |
| border-radius: 50%; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .lightbox-close:hover { |
| background: rgba(239, 68, 68, 0.2); |
| color: #ef4444; |
| } |
| |
| .lightbox-close i { |
| width: 18px; |
| height: 18px; |
| } |
| |
| |
| |
| footer { |
| margin-top: 50px; |
| text-align: center; |
| border-top: 1px solid var(--glass-border); |
| padding-top: 20px; |
| color: rgba(255, 255, 255, 0.35); |
| font-size: 12px; |
| } |
| |
| footer a { |
| color: var(--text-secondary); |
| text-decoration: none; |
| } |
| |
| footer a:hover { |
| color: var(--accent-orange); |
| } |
| |
| |
| .examples-section-fold footer { |
| color: rgba(0, 0, 0, 0.35); |
| border-top-color: rgba(0, 0, 0, 0.08); |
| } |
| |
| .examples-section-fold footer a { |
| color: #64748b; |
| } |
| |
| .examples-section-fold footer a:hover { |
| color: var(--accent-orange); |
| } |
| |
| |
| |
| |
| .top-task-selector-container { |
| position: absolute; |
| top: 24px; |
| left: 50%; |
| transform: translateX(-50%); |
| z-index: 100; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| max-width: 90vw; |
| } |
| |
| .top-task-nav-bar { |
| background: rgba(10, 16, 32, 0.45); |
| border: 1px solid var(--glass-border); |
| backdrop-filter: blur(20px); |
| -webkit-backdrop-filter: blur(20px); |
| border-radius: 100px; |
| padding: 4px; |
| display: flex; |
| gap: 4px; |
| box-shadow: var(--shadow-premium); |
| } |
| |
| .task-nav-btn { |
| background: transparent; |
| border: none; |
| color: var(--text-secondary); |
| font-family: var(--font-outfit); |
| font-weight: 600; |
| font-size: 12.5px; |
| padding: 7px 15px; |
| border-radius: 100px; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| white-space: nowrap; |
| display: flex; |
| align-items: center; |
| gap: 6px; |
| } |
| |
| .task-nav-btn:hover { |
| color: #fff; |
| background: rgba(255, 255, 255, 0.05); |
| } |
| |
| .task-nav-btn.active { |
| background: var(--accent-gradient); |
| color: #0f172a; |
| box-shadow: 0 4px 12px rgba(249, 115, 22, 0.2); |
| } |
| |
| @media (max-width: 900px) { |
| .top-task-selector-container { |
| position: relative; |
| top: 0; |
| left: 0; |
| transform: none; |
| margin: 20px auto 10px; |
| width: calc(100% - 40px); |
| } |
| .top-task-nav-bar { |
| width: 100%; |
| overflow-x: auto; |
| justify-content: flex-start; |
| padding: 6px; |
| border-radius: 14px; |
| -webkit-overflow-scrolling: touch; |
| } |
| .top-task-nav-bar::-webkit-scrollbar { |
| display: none; |
| } |
| } |
| |
| |
| .input-media-preview { |
| display: flex; |
| flex-direction: column; |
| background: rgba(255, 255, 255, 0.04); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| border-radius: 12px; |
| padding: 8px; |
| margin-top: 10px; |
| gap: 8px; |
| animation: slide-in 0.3s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| .input-preview-thumbnail { |
| width: 100%; |
| height: 200px; |
| border-radius: 8px; |
| overflow: hidden; |
| background: rgba(0, 0, 0, 0.3); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| border: 1px solid rgba(255, 255, 255, 0.1); |
| } |
| |
| .input-preview-thumbnail img, |
| .input-preview-thumbnail video { |
| width: 100%; |
| height: 100%; |
| object-fit: contain; |
| } |
| |
| .input-preview-bottom-row { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| } |
| |
| .input-preview-info { |
| display: flex; |
| flex-direction: column; |
| min-width: 0; |
| flex-grow: 1; |
| } |
| |
| .input-preview-name { |
| font-size: 13px; |
| font-weight: 600; |
| color: #fff; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .input-preview-size { |
| font-size: 11px; |
| color: var(--text-secondary); |
| margin-top: 2px; |
| } |
| |
| .input-preview-clear { |
| background: rgba(255, 255, 255, 0.04); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| color: var(--text-secondary); |
| width: 28px; |
| height: 28px; |
| border-radius: 50%; |
| cursor: pointer; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| transition: var(--transition-fast); |
| flex-shrink: 0; |
| } |
| |
| .input-preview-clear:hover { |
| background: rgba(239, 68, 68, 0.15); |
| color: #ef4444; |
| border-color: rgba(239, 68, 68, 0.3); |
| } |
| |
| .input-preview-clear i { |
| width: 14px; |
| height: 14px; |
| } |
| |
| |
| .fs-bg-element.ambient-blur { |
| filter: blur(50px) brightness(0.35); |
| transform: scale(1.15); |
| object-fit: cover; |
| transition: opacity 0.8s ease-in-out; |
| } |
| |
| |
| .centered-output-container { |
| position: fixed; |
| top: 48%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| z-index: 5; |
| display: none; |
| justify-content: center; |
| align-items: center; |
| width: 80vw; |
| height: 65vh; |
| pointer-events: none; |
| } |
| |
| .centered-output-container.active { |
| display: flex; |
| animation: zoom-in-premium 0.5s cubic-bezier(0.16, 1, 0.3, 1); |
| } |
| |
| @keyframes zoom-in-premium { |
| from { transform: translate(-50%, -48%) scale(0.95); opacity: 0; } |
| to { transform: translate(-50%, -50%) scale(1); opacity: 1; } |
| } |
| |
| .output-media-card { |
| background: rgba(15, 23, 42, 0.3); |
| border: 1px solid rgba(255, 255, 255, 0.08); |
| border-radius: 20px; |
| padding: 10px; |
| box-shadow: 0 30px 60px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.1); |
| backdrop-filter: blur(16px); |
| -webkit-backdrop-filter: blur(16px); |
| max-width: 100%; |
| max-height: 100%; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| pointer-events: auto; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .centered-media-element { |
| max-width: 100%; |
| max-height: 100%; |
| width: auto; |
| height: auto; |
| object-fit: contain; |
| border-radius: 12px; |
| display: none; |
| } |
| |
| .centered-media-element.active { |
| display: block; |
| } |
| |
| |
| .terminal-container.centered-large { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| width: 90%; |
| max-width: 800px; |
| max-height: 60vh; |
| margin: 0; |
| z-index: 100; |
| background: rgba(7, 10, 19, 0.85); |
| border: 1px solid rgba(249, 115, 22, 0.3); |
| box-shadow: 0 30px 60px rgba(0, 0, 0, 0.8), 0 0 30px rgba(249, 115, 22, 0.15); |
| font-size: 15px; |
| line-height: 1.7; |
| padding: 28px; |
| display: none; |
| pointer-events: auto; |
| } |
| |
| .close-terminal-btn { |
| position: absolute; |
| top: 14px; |
| right: 48px; |
| background: rgba(255, 255, 255, 0.04); |
| border: 1px solid var(--glass-border); |
| color: var(--text-secondary); |
| padding: 6px; |
| border-radius: 6px; |
| cursor: pointer; |
| transition: var(--transition-fast); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| } |
| |
| .close-terminal-btn:hover { |
| background: rgba(239, 68, 68, 0.15); |
| color: #ef4444; |
| border-color: rgba(239, 68, 68, 0.3); |
| } |
| |
| .close-terminal-btn i { |
| width: 14px; |
| height: 14px; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| |
| <div class="viewport-bg-container"> |
| |
| |
| <img id="bg-fallback" class="fs-bg-element active" src="assets/default_background.jpg" alt="Lance Immersive Backdrop"> |
| |
| |
| <img id="bg-image-output" class="fs-bg-element" alt="Lance Output Canvas"> |
| |
| |
| <video id="bg-video-output" class="fs-bg-element fs-bg-video" loop playsinline></video> |
| |
| |
| <div class="fs-bg-overlay"></div> |
| </div> |
|
|
| |
| <div class="centered-output-container" id="centered-media-viewport"> |
| <div class="output-media-card"> |
| <img id="centered-image-output" class="centered-media-element" alt="Lance Generated Image Output"> |
| <video id="centered-video-output" class="centered-media-element" loop playsinline controls></video> |
| </div> |
| </div> |
|
|
| |
| <div class="cinematic-viewport"> |
| |
| |
| <header> |
| <a href="#" class="brand-link"> |
| |
| <img src="https://huggingface.co/spaces/bytedance-research/Lance/resolve/main/assets/logo/lance-logo.png" class="brand-logo" alt="ByteDance Logo"> |
| <div class="brand-divider"></div> |
| <span class="brand-name">ByteDance</span> |
| </a> |
| |
| |
| <div class="top-task-selector-container"> |
| <div class="top-task-nav-bar" id="top-task-navbar"> |
| |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div class="viewport-center-trigger"> |
| <div class="center-prompt-card"> |
| <div class="center-prompt-header"> |
| <span class="active-task-pill" id="center-task-badge">Text to Video</span> |
| </div> |
| <div class="center-prompt-body"> |
| <textarea class="prompt-textarea" id="prompt-input" placeholder="Describe what you want to generate..."></textarea> |
| |
| |
| <div class="input-media-preview" id="center-media-preview" style="display: none;"> |
| <div class="input-preview-thumbnail" id="center-preview-thumbnail"></div> |
| <div class="input-preview-bottom-row"> |
| <div class="input-preview-info"> |
| <span class="input-preview-name" id="center-preview-name">file_name.mp4</span> |
| <span class="input-preview-size" id="center-preview-size">0.0 MB</span> |
| </div> |
| <button class="input-preview-clear" id="center-preview-clear" title="Remove attachment"> |
| <i data-lucide="x"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
| <div class="center-prompt-footer"> |
| <button class="center-options-btn" onclick="toggleSidebar()" title="Configure settings and inputs"> |
| <i data-lucide="sliders"></i> |
| <span>Model Controls</span> |
| </button> |
| |
| <button class="center-options-btn" id="center-attach-btn" title="Attach input media (image or video)" style="display: none;"> |
| <i data-lucide="paperclip"></i> |
| <span>Attach Media</span> |
| </button> |
| <button class="center-generate-btn" id="generate-trigger"> |
| <i data-lucide="sparkles"></i> |
| <span>Generate</span> |
| </button> |
| </div> |
| </div> |
| |
| |
| <div class="quick-examples-container" id="quick-examples-box"> |
| <span class="quick-examples-label">Try these:</span> |
| <div class="quick-examples-list" id="quick-examples-wrapper"> |
| |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="viewport-bottom"> |
| <div class="bottom-caption"> |
| <h2 class="caption-title">Lance</h2> |
| <p class="caption-desc" style="max-width: 600px;"> |
| Lance is a 3B native unified multimodal model for image and video understanding, generation, and editing, trained from scratch within a training budget of no more than 128 GPUs using a staged multi-task recipe. |
| </p> |
| </div> |
| |
| <footer class="main-footer" style="font-size: 11px; color: rgba(255, 255, 255, 0.35); font-family: var(--font-inter);"> |
| <p>Powered by <a href="https://gradio.app" target="_blank" rel="noopener noreferrer" style="color: rgba(255, 255, 255, 0.5); text-decoration: none;">Gradio.Server</a></p> |
| </footer> |
| </div> |
|
|
| </div> |
|
|
|
|
|
|
| |
| <div class="controls-sidebar" id="sidebar-controls-pane"> |
| <div class="panel-title"> |
| <div class="panel-title-left"> |
| <i data-lucide="sliders"></i> |
| <span>Model Controls</span> |
| </div> |
| <button class="close-sidebar-btn" onclick="toggleSidebar()" title="Close controls"> |
| <i data-lucide="x"></i> |
| </button> |
| </div> |
|
|
| |
| <div class="form-group" style="display: none;"> |
| <label class="form-label">Task family</label> |
| <div class="select-wrapper"> |
| <select class="select-control" id="task-select-dropdown"> |
| |
| </select> |
| </div> |
| </div> |
|
|
| |
| <div class="form-group" id="media-upload-container"> |
| <label class="form-label" id="media-field-label" for="file-selector">Media Input</label> |
| <div class="upload-zone" id="drag-drop-zone"> |
| <div class="upload-zone-content"> |
| <div class="upload-icon"> |
| <i data-lucide="upload-cloud" id="upload-icon-element"></i> |
| </div> |
| <span class="upload-title" id="upload-title-element">Upload video file</span> |
| <span class="upload-desc" id="upload-desc-element">Drag & drop your file here, or click to browse</span> |
| </div> |
| <input type="file" id="file-selector" class="file-input"> |
| </div> |
| |
| <div class="preview-zone" id="file-preview-card"> |
| <div class="preview-media-container" id="preview-media-wrapper"> |
| |
| </div> |
| <div class="preview-bottom-row"> |
| <div class="preview-info"> |
| <div class="preview-name" id="preview-filename">file_name.mp4</div> |
| <div class="preview-size" id="preview-filesize">0.0 MB</div> |
| </div> |
| <button class="clear-file-btn" id="clear-file-trigger" title="Remove file"> |
| <i data-lucide="trash-2"></i> |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="controls-grid"> |
| <div class="form-group" id="res-group"> |
| <label class="form-label" for="res-control">Resolution</label> |
| <div class="select-wrapper"> |
| <select class="select-control" id="res-control"> |
| |
| </select> |
| </div> |
| </div> |
| <div class="form-group" id="ar-group"> |
| <label class="form-label" for="ar-control">Aspect Ratio</label> |
| <div class="select-wrapper"> |
| <select class="select-control" id="ar-control"> |
| |
| </select> |
| </div> |
| </div> |
| <div class="form-group" id="duration-group"> |
| <label class="form-label" for="duration-control">Duration</label> |
| <div class="select-wrapper"> |
| <select class="select-control" id="duration-control"> |
| |
| </select> |
| </div> |
| </div> |
| <div class="form-group" id="rife-group"> |
| <label class="form-label" for="rife-control">Interpolation</label> |
| <div class="select-wrapper"> |
| <select class="select-control" id="rife-control"> |
| <option value="Yes" selected>Yes</option> |
| <option value="No">No</option> |
| </select> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="accordion" id="adv-accordion"> |
| <button class="accordion-header" id="adv-accordion-toggle"> |
| <span>Advanced Parameters</span> |
| <i data-lucide="chevron-down"></i> |
| </button> |
| <div class="accordion-content"> |
| <div class="form-group"> |
| <label class="form-label" for="seed-control">Seed (-1 for random)</label> |
| <input type="number" class="number-control" id="seed-control" value="42" min="-1" step="1"> |
| </div> |
| <div class="form-group"> |
| <label class="form-label" for="steps-control">Inference Timesteps</label> |
| <div class="slider-wrapper"> |
| <input type="range" class="slider-control" id="steps-control" min="1" max="50" step="1" value="30"> |
| <span class="slider-val" id="steps-val">30</span> |
| </div> |
| </div> |
| <div class="form-group"> |
| <label class="form-label" for="shift-control">Timestep Shift</label> |
| <div class="slider-wrapper"> |
| <input type="range" class="slider-control" id="shift-control" min="0.0" max="10.0" step="0.5" value="3.5"> |
| <span class="slider-val" id="shift-val">3.5</span> |
| </div> |
| </div> |
| <div class="form-group"> |
| <label class="form-label" for="cfg-control">CFG Text Scale</label> |
| <div class="slider-wrapper"> |
| <input type="range" class="slider-control" id="cfg-control" min="1.0" max="12.0" step="0.5" value="4.0"> |
| <span class="slider-val" id="cfg-val">4.0</span> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| </div> |
|
|
| |
| <div class="floating-right-container"> |
| |
| |
| <div class="status-panel" id="status-card"> |
| <div class="status-left"> |
| <div class="spinner"></div> |
| <div class="status-details"> |
| <div class="status-title" id="status-title-text">Processing request...</div> |
| <div class="status-desc" id="status-desc-text">In Gradio server queue</div> |
| </div> |
| </div> |
| <div class="status-timer" id="status-elapsed-timer">0.0s</div> |
| </div> |
|
|
| |
| <div class="terminal-container" id="output-text-container"> |
| <div class="terminal-header"> |
| <i data-lucide="terminal"></i> Response Output |
| </div> |
| <button class="close-terminal-btn" id="close-terminal-trigger" title="Close response console"> |
| <i data-lucide="x"></i> |
| </button> |
| <button class="copy-btn" id="copy-clipboard-btn" title="Copy response to clipboard"> |
| <i data-lucide="copy"></i> |
| </button> |
| <div id="output-text-content">Response will print here...</div> |
| </div> |
|
|
| |
| <div class="output-actions-bar" id="output-actions-card"> |
| |
| <button class="action-icon-btn" id="bg-volume-toggle" title="Unmute video sound"> |
| <i data-lucide="volume-x"></i> |
| </button> |
| |
| |
| <button class="action-icon-btn" id="bg-lightbox-trigger" title="Open fullscreen zoom"> |
| <i data-lucide="maximize-2"></i> |
| </button> |
|
|
| |
| <a id="bg-download-link" class="action-pill-btn" download="lance_generation" target="_blank" rel="noopener noreferrer"> |
| <i data-lucide="download"></i> |
| <span>Download</span> |
| </a> |
| </div> |
| </div> |
|
|
|
|
|
|
| |
| <div class="lightbox-modal" id="lightbox-modal-element"> |
| <button class="lightbox-close" id="lightbox-close-trigger" title="Close zoom"> |
| <i data-lucide="x"></i> |
| </button> |
| <div class="lightbox-content"> |
| <img class="lightbox-img" id="lightbox-full-img" src="" alt="Lance High-Res Output"> |
| </div> |
| </div> |
|
|
| |
| <script> |
| function scrollToMain() { |
| window.scrollTo({ top: 0, behavior: 'smooth' }); |
| } |
| |
| |
| |
| function toggleSidebar() { |
| const pane = document.getElementById("sidebar-controls-pane"); |
| pane.classList.toggle("open"); |
| } |
| </script> |
|
|
| |
| <script type="module"> |
| import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; |
| |
| |
| let appConfig = null; |
| let activeTask = null; |
| let selectedFile = null; |
| let uploadedFileReference = null; |
| let gradioClient = null; |
| let activeJob = null; |
| let timerInterval = null; |
| let timerStart = 0; |
| let videoMuted = true; |
| |
| |
| const bgFallback = document.getElementById("bg-fallback"); |
| const bgImageOutput = document.getElementById("bg-image-output"); |
| const bgVideoOutput = document.getElementById("bg-video-output"); |
| const outputActionsCard = document.getElementById("output-actions-card"); |
| const bgVolumeToggle = document.getElementById("bg-volume-toggle"); |
| const bgLightboxTrigger = document.getElementById("bg-lightbox-trigger"); |
| const bgDownloadLink = document.getElementById("bg-download-link"); |
| |
| |
| const taskSelectDropdown = document.getElementById("task-select-dropdown"); |
| const promptInput = document.getElementById("prompt-input"); |
| const promptFieldLabel = document.getElementById("prompt-field-label"); |
| const sidebarControlsPane = document.getElementById("sidebar-controls-pane"); |
| |
| |
| const mediaUploadContainer = document.getElementById("media-upload-container"); |
| const mediaFieldLabel = document.getElementById("media-field-label"); |
| const dragDropZone = document.getElementById("drag-drop-zone"); |
| const fileSelector = document.getElementById("file-selector"); |
| const filePreviewCard = document.getElementById("file-preview-card"); |
| const previewMediaWrapper = document.getElementById("preview-media-wrapper"); |
| const previewFilename = document.getElementById("preview-filename"); |
| const previewFilesize = document.getElementById("preview-filesize"); |
| const clearFileTrigger = document.getElementById("clear-file-trigger"); |
| const uploadIconElement = document.getElementById("upload-icon-element"); |
| const uploadTitleElement = document.getElementById("upload-title-element"); |
| const uploadDescElement = document.getElementById("upload-desc-element"); |
| |
| |
| const resControl = document.getElementById("res-control"); |
| const arControl = document.getElementById("ar-control"); |
| const durationControl = document.getElementById("duration-control"); |
| const resGroup = document.getElementById("res-group"); |
| const arGroup = document.getElementById("ar-group"); |
| const durationGroup = document.getElementById("duration-group"); |
| const rifeControl = document.getElementById("rife-control"); |
| const rifeGroup = document.getElementById("rife-group"); |
| |
| |
| const advAccordion = document.getElementById("adv-accordion"); |
| const advAccordionToggle = document.getElementById("adv-accordion-toggle"); |
| const seedControl = document.getElementById("seed-control"); |
| const stepsControl = document.getElementById("steps-control"); |
| const stepsVal = document.getElementById("steps-val"); |
| const shiftControl = document.getElementById("shift-control"); |
| const shiftVal = document.getElementById("shift-val"); |
| const cfgControl = document.getElementById("cfg-control"); |
| const cfgVal = document.getElementById("cfg-val"); |
| |
| |
| const topTaskNavbar = document.getElementById("top-task-navbar"); |
| const centerAttachBtn = document.getElementById("center-attach-btn"); |
| const centerMediaPreview = document.getElementById("center-media-preview"); |
| const centerPreviewThumbnail = document.getElementById("center-preview-thumbnail"); |
| const centerPreviewName = document.getElementById("center-preview-name"); |
| const centerPreviewSize = document.getElementById("center-preview-size"); |
| const centerPreviewClear = document.getElementById("center-preview-clear"); |
| const centeredMediaViewport = document.getElementById("centered-media-viewport"); |
| const centeredImageOutput = document.getElementById("centered-image-output"); |
| const centeredVideoOutput = document.getElementById("centered-video-output"); |
| const closeTerminalTrigger = document.getElementById("close-terminal-trigger"); |
| |
| |
| |
| const generateTrigger = document.getElementById("generate-trigger"); |
| const statusCard = document.getElementById("status-card"); |
| const statusTitleText = document.getElementById("status-title-text"); |
| const statusDescText = document.getElementById("status-desc-text"); |
| const statusElapsedTimer = document.getElementById("status-elapsed-timer"); |
| |
| |
| const outputTextContainer = document.getElementById("output-text-container"); |
| const outputTextContent = document.getElementById("output-text-content"); |
| const copyClipboardBtn = document.getElementById("copy-clipboard-btn"); |
| |
| |
| const lightboxModalElement = document.getElementById("lightbox-modal-element"); |
| const lightboxCloseTrigger = document.getElementById("lightbox-close-trigger"); |
| const lightboxFullImg = document.getElementById("lightbox-full-img"); |
| |
| |
| window.addEventListener("DOMContentLoaded", async () => { |
| lucide.createIcons(); |
| |
| |
| try { |
| gradioClient = await Client.connect(window.location.origin, { |
| events: ["status", "data"] |
| }); |
| console.log("Connected to Gradio Server Client engine successfully"); |
| } catch (err) { |
| console.error("Gradio Client connection failed:", err); |
| } |
| |
| |
| try { |
| const response = await fetch("/api/config"); |
| appConfig = await response.json(); |
| console.log("Fetched app config successfully:", appConfig); |
| initializeUI(); |
| } catch (err) { |
| console.error("Config fetch failed:", err); |
| } |
| }); |
| |
| |
| function initializeUI() { |
| if (!appConfig) return; |
| |
| |
| taskSelectDropdown.innerHTML = ""; |
| topTaskNavbar.innerHTML = ""; |
| appConfig.TASK_CHOICES.forEach((taskLabel, idx) => { |
| const internalTask = appConfig.TASK_LABEL_TO_INTERNAL[taskLabel]; |
| |
| |
| const opt = document.createElement("option"); |
| opt.value = internalTask; |
| opt.textContent = taskLabel; |
| if (idx === 0) opt.selected = true; |
| taskSelectDropdown.appendChild(opt); |
| |
| |
| const btn = document.createElement("button"); |
| btn.className = "task-nav-btn"; |
| btn.setAttribute("data-task", internalTask); |
| |
| |
| let iconName = "sparkles"; |
| if (internalTask === "t2v") iconName = "video"; |
| else if (internalTask === "t2i") iconName = "image"; |
| else if (internalTask === "video_edit") iconName = "film"; |
| else if (internalTask === "image_edit") iconName = "scissors"; |
| else if (internalTask === "x2t_video") iconName = "help-circle"; |
| else if (internalTask === "x2t_image") iconName = "help-circle"; |
| |
| btn.innerHTML = `<i data-lucide="${iconName}"></i><span>${taskLabel}</span>`; |
| if (idx === 0) btn.classList.add("active"); |
| |
| btn.addEventListener("click", () => { |
| document.querySelectorAll(".task-nav-btn").forEach(b => b.classList.remove("active")); |
| btn.classList.add("active"); |
| switchTask(internalTask, taskLabel); |
| }); |
| topTaskNavbar.appendChild(btn); |
| }); |
| |
| |
| taskSelectDropdown.addEventListener("change", (e) => { |
| const internalTaskName = e.target.value; |
| const taskLabel = e.target.options[e.target.selectedIndex].text; |
| switchTask(internalTaskName, taskLabel); |
| }); |
| |
| |
| advAccordionToggle.addEventListener("click", () => { |
| advAccordion.classList.toggle("open"); |
| }); |
| |
| |
| stepsControl.addEventListener("input", (e) => stepsVal.textContent = e.target.value); |
| shiftControl.addEventListener("input", (e) => shiftVal.textContent = parseFloat(e.target.value).toFixed(1)); |
| cfgControl.addEventListener("input", (e) => cfgVal.textContent = parseFloat(e.target.value).toFixed(1)); |
| |
| |
| dragDropZone.addEventListener("click", () => fileSelector.click()); |
| dragDropZone.addEventListener("dragover", (e) => { |
| e.preventDefault(); |
| dragDropZone.classList.add("dragover"); |
| }); |
| dragDropZone.addEventListener("dragleave", () => dragDropZone.classList.remove("dragover")); |
| dragDropZone.addEventListener("drop", (e) => { |
| e.preventDefault(); |
| dragDropZone.classList.remove("dragover"); |
| if (e.dataTransfer.files.length > 0) { |
| handleSelectedFile(e.dataTransfer.files[0]); |
| } |
| }); |
| |
| fileSelector.addEventListener("change", (e) => { |
| if (e.target.files.length > 0) { |
| handleSelectedFile(e.target.files[0]); |
| } |
| }); |
| |
| clearFileTrigger.addEventListener("click", () => clearSelectedFile()); |
| |
| |
| generateTrigger.addEventListener("click", () => triggerGeneration()); |
| |
| |
| bgLightboxTrigger.addEventListener("click", () => { |
| if (bgImageOutput.src && bgImageOutput.classList.contains("active")) { |
| lightboxFullImg.src = bgImageOutput.src; |
| lightboxModalElement.classList.add("open"); |
| } |
| }); |
| |
| lightboxCloseTrigger.addEventListener("click", () => lightboxModalElement.classList.remove("open")); |
| lightboxModalElement.addEventListener("click", (e) => { |
| if (e.target === lightboxModalElement) { |
| lightboxModalElement.classList.remove("open"); |
| } |
| }); |
| |
| |
| bgVolumeToggle.addEventListener("click", () => { |
| videoMuted = !videoMuted; |
| bgVideoOutput.muted = videoMuted; |
| centeredVideoOutput.muted = videoMuted; |
| |
| if (videoMuted) { |
| bgVolumeToggle.innerHTML = '<i data-lucide="volume-x"></i>'; |
| bgVolumeToggle.title = "Unmute video sound"; |
| } else { |
| bgVolumeToggle.innerHTML = '<i data-lucide="volume-2"></i>'; |
| bgVolumeToggle.title = "Mute video sound"; |
| } |
| lucide.createIcons(); |
| }); |
| |
| |
| copyClipboardBtn.addEventListener("click", () => { |
| const text = outputTextContent.innerText; |
| navigator.clipboard.writeText(text).then(() => { |
| const originalHTML = copyClipboardBtn.innerHTML; |
| copyClipboardBtn.innerHTML = '<i data-lucide="check" style="color:#22c55e"></i>'; |
| lucide.createIcons(); |
| setTimeout(() => { |
| copyClipboardBtn.innerHTML = originalHTML; |
| lucide.createIcons(); |
| }, 2000); |
| }); |
| }); |
| |
| |
| centerAttachBtn.addEventListener("click", () => { |
| fileSelector.click(); |
| }); |
| |
| |
| centerPreviewClear.addEventListener("click", (e) => { |
| e.stopPropagation(); |
| clearSelectedFile(); |
| }); |
| |
| |
| closeTerminalTrigger.addEventListener("click", () => { |
| resetOutputViewer(); |
| }); |
| |
| |
| if (taskSelectDropdown.options.length > 0) { |
| const opt = taskSelectDropdown.options[0]; |
| switchTask(opt.value, opt.text); |
| } |
| } |
| |
| |
| function switchTask(internalTask, taskLabel) { |
| activeTask = internalTask; |
| console.log("Switching task page to:", activeTask); |
| |
| |
| if (taskSelectDropdown.value !== activeTask) { |
| taskSelectDropdown.value = activeTask; |
| } |
| |
| |
| document.querySelectorAll(".task-nav-btn").forEach(btn => { |
| if (btn.getAttribute("data-task") === activeTask) { |
| btn.classList.add("active"); |
| } else { |
| btn.classList.remove("active"); |
| } |
| }); |
| |
| |
| sidebarControlsPane.classList.add("highlight-pulse"); |
| setTimeout(() => sidebarControlsPane.classList.remove("highlight-pulse"), 1200); |
| |
| |
| clearSelectedFile(); |
| |
| |
| const centerTaskBadge = document.getElementById("center-task-badge"); |
| if (centerTaskBadge) { |
| centerTaskBadge.textContent = taskLabel; |
| } |
| if (activeTask === "t2v" || activeTask === "t2i") { |
| if (promptFieldLabel) promptFieldLabel.textContent = "Prompt"; |
| promptInput.placeholder = "Describe what you want to generate..."; |
| promptInput.value = ""; |
| } else if (activeTask === "image_edit" || activeTask === "video_edit") { |
| if (promptFieldLabel) promptFieldLabel.textContent = "Instruction"; |
| promptInput.placeholder = "Describe the edit or changes you want..."; |
| promptInput.value = ""; |
| } else { |
| if (promptFieldLabel) promptFieldLabel.textContent = "Question"; |
| promptInput.placeholder = "Ask a question about the uploaded media..."; |
| promptInput.value = ""; |
| } |
| |
| |
| const isEdit = activeTask === "image_edit" || activeTask === "video_edit"; |
| const isUnderstanding = activeTask === "x2t_video" || activeTask === "x2t_image"; |
| const isVideoTask = activeTask === "video_edit" || activeTask === "x2t_video"; |
| |
| if (isEdit || isUnderstanding) { |
| mediaUploadContainer.style.display = "block"; |
| centerAttachBtn.style.display = "flex"; |
| mediaFieldLabel.textContent = isVideoTask ? "Input Video" : "Input Image"; |
| |
| |
| uploadIconElement.setAttribute("data-lucide", isVideoTask ? "video" : "image"); |
| uploadTitleElement.textContent = isVideoTask ? "Upload Input Video" : "Upload Input Image"; |
| uploadDescElement.textContent = isVideoTask ? "MP4 video formats supported" : "PNG, JPG or JPEG supported"; |
| lucide.createIcons(); |
| |
| dragDropZone.style.display = "block"; |
| } else { |
| mediaUploadContainer.style.display = "none"; |
| centerAttachBtn.style.display = "none"; |
| dragDropZone.style.display = "none"; |
| } |
| |
| |
| populateOptions(activeTask); |
| |
| |
| renderQuickExamples(activeTask); |
| |
| } |
| |
| |
| function populateOptions(task) { |
| const isVideo = task === "t2v" || task === "video_edit" || task === "x2t_video"; |
| const isImage = task === "t2i" || task === "image_edit" || task === "x2t_image"; |
| const isTextToVisual = task === "t2v" || task === "t2i"; |
| const isT2V = task === "t2v"; |
| |
| |
| resGroup.style.display = (isVideo && isT2V) ? "block" : "none"; |
| arGroup.style.display = isTextToVisual ? "block" : "none"; |
| durationGroup.style.display = isT2V ? "block" : "none"; |
| rifeGroup.style.display = (task === "t2v" || task === "video_edit") ? "block" : "none"; |
| |
| |
| resControl.innerHTML = ""; |
| appConfig.VIDEO_RESOLUTION_DISPLAY_CHOICES.forEach(res => { |
| const opt = document.createElement("option"); |
| opt.value = res[1]; |
| opt.textContent = res[0]; |
| if (res[1] === appConfig.DEFAULT_RESOLUTION) opt.selected = true; |
| resControl.appendChild(opt); |
| }); |
| |
| |
| arControl.innerHTML = ""; |
| appConfig.ASPECT_RATIO_CHOICES.forEach(ratio => { |
| const opt = document.createElement("option"); |
| opt.value = ratio; |
| opt.textContent = ratio; |
| const defaultRatio = isImage ? appConfig.DEFAULT_IMAGE_ASPECT_RATIO : appConfig.DEFAULT_VIDEO_ASPECT_RATIO; |
| if (ratio === defaultRatio) opt.selected = true; |
| arControl.appendChild(opt); |
| }); |
| |
| |
| durationControl.innerHTML = ""; |
| for (let s = 1; s <= appConfig.MAX_VIDEO_DURATION_SECONDS; s++) { |
| const opt = document.createElement("option"); |
| opt.value = s; |
| opt.textContent = `${s}s`; |
| if (s === appConfig.DEFAULT_VIDEO_DURATION_SECONDS) opt.selected = true; |
| durationControl.appendChild(opt); |
| } |
| } |
| |
| |
| function renderThumbnail(wrapperElement, fileOrUrl, isVideoType) { |
| wrapperElement.innerHTML = ""; |
| if (isVideoType) { |
| const video = document.createElement("video"); |
| video.src = typeof fileOrUrl === "string" ? fileOrUrl : URL.createObjectURL(fileOrUrl); |
| video.muted = true; |
| video.playsInline = true; |
| wrapperElement.appendChild(video); |
| video.addEventListener("loadeddata", () => { |
| video.currentTime = 0.5; |
| }); |
| } else { |
| const img = document.createElement("img"); |
| img.src = typeof fileOrUrl === "string" ? fileOrUrl : URL.createObjectURL(fileOrUrl); |
| wrapperElement.appendChild(img); |
| } |
| } |
| |
| |
| function handleSelectedFile(file) { |
| if (!file) return; |
| selectedFile = file; |
| |
| const sizeText = `${(selectedFile.size / (1024 * 1024)).toFixed(2)} MB`; |
| const isVideoType = selectedFile.type.startsWith("video/"); |
| |
| |
| previewFilename.textContent = selectedFile.name; |
| previewFilesize.textContent = sizeText; |
| renderThumbnail(previewMediaWrapper, selectedFile, isVideoType); |
| |
| |
| centerPreviewName.textContent = selectedFile.name; |
| centerPreviewSize.textContent = sizeText; |
| renderThumbnail(centerPreviewThumbnail, selectedFile, isVideoType); |
| |
| |
| dragDropZone.style.display = "none"; |
| filePreviewCard.style.display = "flex"; |
| centerMediaPreview.style.display = "flex"; |
| uploadedFileReference = null; |
| } |
| |
| function clearSelectedFile() { |
| selectedFile = null; |
| uploadedFileReference = null; |
| fileSelector.value = ""; |
| filePreviewCard.style.display = "none"; |
| centerMediaPreview.style.display = "none"; |
| |
| previewMediaWrapper.innerHTML = ""; |
| centerPreviewThumbnail.innerHTML = ""; |
| |
| const isEdit = activeTask === "image_edit" || activeTask === "video_edit"; |
| const isUnderstanding = activeTask === "x2t_video" || activeTask === "x2t_image"; |
| if (isEdit || isUnderstanding) { |
| dragDropZone.style.display = "block"; |
| } |
| } |
| |
| |
| function populateExample(exampleRow) { |
| console.log("Loading recommended case example:", exampleRow); |
| |
| |
| if (!sidebarControlsPane.classList.contains("open")) { |
| sidebarControlsPane.classList.add("open"); |
| } |
| |
| |
| let newInternalTask = activeTask; |
| if (exampleRow[5] && exampleRow[5].startsWith("t2v_")) newInternalTask = "t2v"; |
| else if (exampleRow[5] && exampleRow[5].startsWith("t2i_")) newInternalTask = "t2i"; |
| else if (exampleRow[5] && exampleRow[5].startsWith("video_edit_")) newInternalTask = "video_edit"; |
| else if (exampleRow[5] && exampleRow[5].startsWith("image_edit_")) newInternalTask = "image_edit"; |
| else if (exampleRow[5] && exampleRow[5].startsWith("x2t_video_")) newInternalTask = "x2t_video"; |
| else if (exampleRow[5] && exampleRow[5].startsWith("x2t_image_")) newInternalTask = "x2t_image"; |
| |
| if (newInternalTask !== activeTask) { |
| const opt = Array.from(taskSelectDropdown.options).find(o => o.value === newInternalTask); |
| const taskLabel = opt ? opt.text : newInternalTask; |
| switchTask(newInternalTask, taskLabel); |
| } else { |
| sidebarControlsPane.classList.add("highlight-pulse"); |
| setTimeout(() => sidebarControlsPane.classList.remove("highlight-pulse"), 1200); |
| } |
| |
| |
| promptInput.value = exampleRow[0] || ""; |
| |
| |
| clearSelectedFile(); |
| const hasVideo = exampleRow[2]; |
| const hasImage = exampleRow[4]; |
| |
| if (hasVideo && (activeTask === "video_edit" || activeTask === "x2t_video")) { |
| const filename = hasVideo.split("/").pop(); |
| |
| |
| previewFilename.textContent = filename; |
| previewFilesize.textContent = "Example Asset"; |
| renderThumbnail(previewMediaWrapper, hasVideo, true); |
| |
| |
| centerPreviewName.textContent = filename; |
| centerPreviewSize.textContent = "Example Asset"; |
| renderThumbnail(centerPreviewThumbnail, hasVideo, true); |
| |
| dragDropZone.style.display = "none"; |
| filePreviewCard.style.display = "flex"; |
| centerMediaPreview.style.display = "flex"; |
| |
| uploadedFileReference = hasVideo; |
| } else if (hasImage && (activeTask === "image_edit" || activeTask === "x2t_image")) { |
| const filename = hasImage.split("/").pop(); |
| |
| |
| previewFilename.textContent = filename; |
| previewFilesize.textContent = "Example Asset"; |
| renderThumbnail(previewMediaWrapper, hasImage, false); |
| |
| |
| centerPreviewName.textContent = filename; |
| centerPreviewSize.textContent = "Example Asset"; |
| renderThumbnail(centerPreviewThumbnail, hasImage, false); |
| |
| dragDropZone.style.display = "none"; |
| filePreviewCard.style.display = "flex"; |
| centerMediaPreview.style.display = "flex"; |
| |
| uploadedFileReference = hasImage; |
| } |
| |
| |
| |
| |
| |
| scrollToMain(); |
| } |
| |
| |
| function renderQuickExamples(task) { |
| const quickExamplesWrapper = document.getElementById("quick-examples-wrapper"); |
| const quickExamplesBox = document.getElementById("quick-examples-box"); |
| if (!quickExamplesWrapper) return; |
| |
| let cases = []; |
| if (task === "t2v") { |
| cases = appConfig.VIDEO_GENERATION_EXAMPLES; |
| } else if (task === "video_edit") { |
| cases = appConfig.VIDEO_EDIT_EXAMPLES; |
| } else if (task === "x2t_video") { |
| cases = appConfig.VIDEO_UNDERSTANDING_EXAMPLES; |
| } else if (task === "t2i") { |
| cases = appConfig.IMAGE_GENERATION_EXAMPLES; |
| } else if (task === "image_edit") { |
| cases = appConfig.IMAGE_EDIT_EXAMPLES; |
| } else if (task === "x2t_image") { |
| cases = appConfig.IMAGE_UNDERSTANDING_EXAMPLES; |
| } |
| |
| if (cases && cases.length > 0) { |
| quickExamplesBox.style.display = "flex"; |
| quickExamplesWrapper.innerHTML = ""; |
| |
| |
| const maxExamples = Math.min(4, cases.length); |
| for (let i = 0; i < maxExamples; i++) { |
| const row = cases[i]; |
| const promptText = row[0]; |
| if (!promptText) continue; |
| |
| const pill = document.createElement("button"); |
| pill.className = "quick-example-pill"; |
| |
| |
| const isVideoType = (task === "t2v" || task === "video_edit" || task === "x2t_video"); |
| let mediaUrl = isVideoType ? resolveMediaUrl(row[1] || row[2]) : resolveMediaUrl(row[3] || row[4]); |
| |
| if (mediaUrl) { |
| if (isVideoType) { |
| const thumbVideo = document.createElement("video"); |
| thumbVideo.src = mediaUrl; |
| thumbVideo.className = "quick-example-thumb"; |
| thumbVideo.muted = true; |
| thumbVideo.playsInline = true; |
| |
| |
| pill.addEventListener("mouseenter", () => { |
| thumbVideo.play().catch(() => {}); |
| }); |
| pill.addEventListener("mouseleave", () => { |
| thumbVideo.pause(); |
| thumbVideo.currentTime = 0.5; |
| }); |
| |
| pill.appendChild(thumbVideo); |
| thumbVideo.addEventListener("loadeddata", () => { |
| thumbVideo.currentTime = 0.5; |
| }); |
| } else { |
| const thumbImg = document.createElement("img"); |
| thumbImg.src = mediaUrl; |
| thumbImg.className = "quick-example-thumb"; |
| pill.appendChild(thumbImg); |
| } |
| } else { |
| const fallbackIcon = document.createElement("div"); |
| fallbackIcon.className = "quick-example-thumb-icon"; |
| fallbackIcon.innerHTML = '<i data-lucide="sparkles" style="width:10px;height:10px;"></i>'; |
| pill.appendChild(fallbackIcon); |
| } |
| |
| |
| const textSpan = document.createElement("span"); |
| textSpan.className = "quick-example-text"; |
| textSpan.textContent = promptText; |
| textSpan.title = promptText; |
| pill.appendChild(textSpan); |
| |
| pill.addEventListener("click", () => { |
| populateExample(row); |
| }); |
| |
| quickExamplesWrapper.appendChild(pill); |
| } |
| lucide.createIcons(); |
| } else { |
| quickExamplesBox.style.display = "none"; |
| } |
| } |
| |
| |
| function resolveMediaUrl(filePath) { |
| if (!filePath || filePath.trim() === "") return null; |
| if (filePath.startsWith("http")) return filePath; |
| return "/api/file?path=" + encodeURIComponent(filePath); |
| } |
| |
| |
| |
| |
| function calculateDimensions(aspectRatio, isImage) { |
| let width = 640; |
| let height = 352; |
| |
| if (isImage) { |
| const map = { |
| "21:9": [1168, 496], |
| "16:9": [1024, 576], |
| "3:2": [944, 624], |
| "4:3": [880, 672], |
| "1:1": [768, 768], |
| "3:4": [672, 880], |
| "2:3": [624, 944], |
| "9:16": [576, 1024] |
| }; |
| if (map[aspectRatio]) { |
| width = map[aspectRatio][0]; |
| height = map[aspectRatio][1]; |
| } |
| } else { |
| const res = resControl.value || "video_360p"; |
| const map360p = { |
| "21:9": [672, 288], |
| "16:9": [640, 352], |
| "3:2": [528, 352], |
| "4:3": [560, 416], |
| "1:1": [480, 480], |
| "3:4": [416, 560], |
| "2:3": [352, 528], |
| "9:16": [352, 640] |
| }; |
| const map480p = { |
| "21:9": [976, 416], |
| "16:9": [848, 480], |
| "3:2": [784, 528], |
| "4:3": [736, 560], |
| "1:1": [640, 640], |
| "3:4": [560, 736], |
| "2:3": [528, 784], |
| "9:16": [480, 848] |
| }; |
| const map = res === "video_480p" ? map480p : map360p; |
| if (map[aspectRatio]) { |
| width = map[aspectRatio][0]; |
| height = map[aspectRatio][1]; |
| } |
| } |
| return { width, height }; |
| } |
| |
| |
| function startTimer() { |
| clearInterval(timerInterval); |
| timerStart = Date.now(); |
| statusElapsedTimer.textContent = "0.0s"; |
| |
| timerInterval = setInterval(() => { |
| const elapsed = (Date.now() - timerStart) / 1000; |
| statusElapsedTimer.textContent = `${elapsed.toFixed(1)}s`; |
| }, 100); |
| } |
| |
| function stopTimer() { |
| clearInterval(timerInterval); |
| } |
| |
| |
| async function triggerGeneration() { |
| if (!gradioClient) { |
| alert("Gradio Client is not fully initialized. Please reload the page."); |
| return; |
| } |
| |
| const prompt = promptInput.value.trim(); |
| if (!prompt) { |
| alert("Please enter a prompt, instruction or question."); |
| promptInput.focus(); |
| return; |
| } |
| |
| const isImage = activeTask === "t2i" || activeTask === "image_edit" || activeTask === "x2t_image"; |
| const isVideo = activeTask === "t2v" || activeTask === "video_edit" || activeTask === "x2t_video"; |
| const isUnderstanding = activeTask === "x2t_video" || activeTask === "x2t_image"; |
| const isEdit = activeTask === "image_edit" || activeTask === "video_edit"; |
| const isT2V = activeTask === "t2v"; |
| |
| |
| if ((isEdit || isUnderstanding) && !selectedFile && !uploadedFileReference) { |
| alert(isVideo ? "Please upload an input video." : "Please upload an input image."); |
| return; |
| } |
| |
| |
| if (sidebarControlsPane.classList.contains("open")) { |
| sidebarControlsPane.classList.remove("open"); |
| } |
| |
| |
| generateTrigger.disabled = true; |
| generateTrigger.innerHTML = '<span class="spinner" style="width:16px;height:16px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:8px;"></span><span>Processing...</span>'; |
| |
| |
| statusCard.style.display = "flex"; |
| statusTitleText.textContent = "Connecting..."; |
| statusDescText.textContent = "Initializing connection to Gradio's queuing engine"; |
| startTimer(); |
| |
| |
| |
| |
| try { |
| |
| let finalVideoInput = null; |
| let finalImageInput = null; |
| |
| if (selectedFile) { |
| statusTitleText.textContent = "Uploading file..."; |
| statusDescText.textContent = `Uploading ${selectedFile.name} to server`; |
| |
| const fileRef = await handle_file(selectedFile); |
| |
| if (isVideo) finalVideoInput = fileRef; |
| else finalImageInput = fileRef; |
| } else if (uploadedFileReference) { |
| const fileRef = { path: uploadedFileReference }; |
| if (isVideo) finalVideoInput = fileRef; |
| else finalImageInput = fileRef; |
| } |
| |
| |
| const aspectRatio = arControl.value || "16:9"; |
| const dims = calculateDimensions(aspectRatio, isImage); |
| |
| |
| let systemPrompt = null; |
| if (activeTask === "x2t_video") systemPrompt = appConfig.V2T_QA_SYSTEM_PROMPT; |
| if (activeTask === "x2t_image") systemPrompt = appConfig.I2T_QA_SYSTEM_PROMPT; |
| |
| |
| let frames = 61; |
| if (isT2V) { |
| const secs = parseInt(durationControl.value) || 5; |
| frames = 12 * secs + 1; |
| } |
| |
| |
| const payload = [ |
| activeTask, |
| prompt, |
| systemPrompt, |
| finalVideoInput, |
| finalImageInput, |
| dims.height, |
| dims.width, |
| frames, |
| parseInt(seedControl.value) || 42, |
| resControl.value || "video_480p", |
| parseInt(stepsControl.value) || 30, |
| parseFloat(shiftControl.value) || 3.5, |
| parseFloat(cfgControl.value) || 4.0, |
| rifeControl.value === "Yes" |
| ]; |
| |
| console.log("Submitting payload to /run_generation API:", payload); |
| |
| statusTitleText.textContent = "Queued Request..."; |
| statusDescText.textContent = "Waiting for concurrent GPU allocation slot"; |
| |
| |
| activeJob = gradioClient.submit("/run_generation", payload); |
| |
| let result = null; |
| for await (const message of activeJob) { |
| if (message.type === "status") { |
| console.log("Queue Job update received:", message); |
| const stage = message.status; |
| if (stage === "pending") { |
| const pos = message.position !== undefined ? message.position + 1 : 1; |
| const size = message.size || pos; |
| statusTitleText.textContent = `In Queue (Position ${pos}/${size})`; |
| statusDescText.textContent = "Request is locked in Gradio's concurrency manager"; |
| } else if (stage === "iterating" || stage === "generating") { |
| statusTitleText.textContent = "Generating outputs..."; |
| statusDescText.textContent = "Lance is executing generation in ZeroGPU environment"; |
| } |
| } else if (message.type === "data") { |
| result = message; |
| } |
| } |
| |
| stopTimer(); |
| statusCard.style.display = "none"; |
| |
| if (result) { |
| console.log("Generation finished successfully. Result:", result); |
| displayResult(result); |
| } else { |
| throw new Error("No data returned from job"); |
| } |
| |
| } catch (err) { |
| stopTimer(); |
| statusCard.style.display = "none"; |
| console.error("Generation failed:", err); |
| alert(`Error during generation: ${err.message || err}`); |
| } finally { |
| |
| generateTrigger.disabled = false; |
| generateTrigger.innerHTML = '<i data-lucide="rocket"></i><span>Generate Task</span>'; |
| lucide.createIcons(); |
| activeJob = null; |
| } |
| } |
| |
| |
| function displayResult(result) { |
| console.log("displayResult called with:", JSON.stringify(result)); |
| if (!result || !result.data) { |
| console.warn("displayResult: No result or result.data, resetting viewer"); |
| resetOutputViewer(); |
| return; |
| } |
| |
| |
| |
| |
| let rawData = result.data; |
| if (Array.isArray(rawData)) rawData = rawData[0]; |
| console.log("rawData after unwrap:", rawData); |
| |
| let parsed; |
| try { |
| if (typeof rawData === "string") { |
| parsed = JSON.parse(rawData); |
| } else if (typeof rawData === "object" && rawData !== null) { |
| parsed = rawData; |
| } else { |
| console.warn("displayResult: Unexpected rawData type:", typeof rawData); |
| resetOutputViewer(); |
| return; |
| } |
| } catch (e) { |
| console.error("displayResult: Failed to parse JSON:", e, rawData); |
| resetOutputViewer(); |
| return; |
| } |
| |
| console.log("displayResult parsed:", parsed); |
| |
| const videoPath = parsed.video || ""; |
| const imagePath = parsed.image || ""; |
| const textData = parsed.text || ""; |
| const statusMsg = parsed.status || ""; |
| |
| const videoUrl = resolveMediaUrl(videoPath); |
| const imageUrl = resolveMediaUrl(imagePath); |
| console.log("Resolved URLs:", { videoUrl, imageUrl, textData, statusMsg }); |
| |
| |
| if (statusMsg && !videoUrl && !imageUrl && !textData) { |
| alert(`Inference Status: ${statusMsg}`); |
| return; |
| } |
| |
| |
| if (videoUrl) { |
| |
| bgFallback.classList.remove("active"); |
| bgImageOutput.classList.remove("active"); |
| bgImageOutput.src = ""; |
| bgLightboxTrigger.style.display = "none"; |
| |
| bgVideoOutput.src = videoUrl; |
| bgVideoOutput.className = "fs-bg-element fs-bg-video ambient-blur active"; |
| bgVideoOutput.muted = true; |
| bgVideoOutput.play(); |
| |
| |
| centeredMediaViewport.classList.add("active"); |
| centeredImageOutput.classList.remove("active"); |
| centeredImageOutput.src = ""; |
| |
| centeredVideoOutput.src = videoUrl; |
| centeredVideoOutput.classList.add("active"); |
| centeredVideoOutput.muted = videoMuted; |
| centeredVideoOutput.play(); |
| |
| bgVolumeToggle.style.display = "flex"; |
| bgDownloadLink.href = videoUrl; |
| outputActionsCard.style.display = "flex"; |
| outputTextContainer.style.display = "none"; |
| outputTextContainer.classList.remove("centered-large"); |
| |
| enterOutputMode(); |
| console.log("Video output displayed:", videoUrl); |
| } |
| |
| else if (imageUrl) { |
| |
| bgFallback.classList.remove("active"); |
| bgVideoOutput.classList.remove("active"); |
| bgVideoOutput.removeAttribute("src"); |
| try { bgVideoOutput.load(); } catch (e) {} |
| bgVolumeToggle.style.display = "none"; |
| |
| bgImageOutput.src = imageUrl; |
| bgImageOutput.className = "fs-bg-element ambient-blur active"; |
| bgLightboxTrigger.style.display = "flex"; |
| |
| |
| centeredMediaViewport.classList.add("active"); |
| centeredVideoOutput.classList.remove("active"); |
| centeredVideoOutput.removeAttribute("src"); |
| try { centeredVideoOutput.load(); } catch (e) {} |
| |
| centeredImageOutput.src = imageUrl; |
| centeredImageOutput.classList.add("active"); |
| |
| bgDownloadLink.href = imageUrl; |
| outputActionsCard.style.display = "flex"; |
| outputTextContainer.style.display = "none"; |
| outputTextContainer.classList.remove("centered-large"); |
| |
| enterOutputMode(); |
| console.log("Image output displayed:", imageUrl); |
| } |
| |
| else if (textData) { |
| |
| bgFallback.classList.add("active"); |
| bgImageOutput.classList.remove("active"); |
| bgImageOutput.src = ""; |
| bgVideoOutput.classList.remove("active"); |
| bgVideoOutput.removeAttribute("src"); |
| try { bgVideoOutput.load(); } catch (e) {} |
| |
| centeredMediaViewport.classList.remove("active"); |
| centeredImageOutput.classList.remove("active"); |
| centeredImageOutput.src = ""; |
| centeredVideoOutput.classList.remove("active"); |
| centeredVideoOutput.removeAttribute("src"); |
| |
| outputTextContent.textContent = textData; |
| outputTextContainer.classList.add("centered-large"); |
| outputTextContainer.style.display = "block"; |
| |
| outputActionsCard.style.display = "none"; |
| bgLightboxTrigger.style.display = "none"; |
| bgVolumeToggle.style.display = "none"; |
| |
| enterOutputMode(); |
| console.log("Text output displayed centered:", textData); |
| } else { |
| console.warn("displayResult: No usable output found, resetting"); |
| resetOutputViewer(); |
| } |
| } |
| |
| |
| function resetOutputViewer() { |
| |
| bgFallback.classList.add("active"); |
| |
| bgImageOutput.classList.remove("active"); |
| bgImageOutput.src = ""; |
| bgImageOutput.className = "fs-bg-element"; |
| bgLightboxTrigger.style.display = "none"; |
| |
| bgVideoOutput.classList.remove("active"); |
| bgVideoOutput.removeAttribute("src"); |
| bgVideoOutput.className = "fs-bg-element fs-bg-video"; |
| try { bgVideoOutput.load(); } catch (e) {} |
| bgVolumeToggle.style.display = "none"; |
| |
| |
| centeredMediaViewport.classList.remove("active"); |
| centeredImageOutput.classList.remove("active"); |
| centeredImageOutput.src = ""; |
| centeredVideoOutput.classList.remove("active"); |
| centeredVideoOutput.removeAttribute("src"); |
| try { centeredVideoOutput.load(); } catch (e) {} |
| |
| |
| outputActionsCard.style.display = "none"; |
| bgDownloadLink.href = ""; |
| |
| outputTextContainer.style.display = "none"; |
| outputTextContainer.classList.remove("centered-large"); |
| outputTextContent.textContent = ""; |
| |
| exitOutputMode(); |
| lucide.createIcons(); |
| } |
| |
| |
| function enterOutputMode() { |
| const centerPanel = document.querySelector(".viewport-center-trigger"); |
| const bottomPanel = document.querySelector(".viewport-bottom"); |
| if (centerPanel) centerPanel.classList.add("output-mode"); |
| if (bottomPanel) bottomPanel.classList.add("output-hidden"); |
| } |
| |
| function exitOutputMode() { |
| const centerPanel = document.querySelector(".viewport-center-trigger"); |
| const bottomPanel = document.querySelector(".viewport-bottom"); |
| if (centerPanel) centerPanel.classList.remove("output-mode"); |
| if (bottomPanel) bottomPanel.classList.remove("output-hidden"); |
| } |
| </script> |
| </body> |
| </html> |
|
|