1<!DOCTYPE html><html lang="en" dir="ltr" class="bg-ink-950 text-zinc-100"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><meta name="theme-color" content="#07070b"><link rel="icon" type="image/svg+xml" href="/favicon.svg"><link rel="canonical" href="https://www.ai-geminiomni.com/video-studio/"><title>Gemini Omni Video Studio · AI text-to-video & image-to-video</title><meta name="description" content="Generate AI videos in the Gemini Omni Video Studio: text-to-video and image-to-video with first/last frame or reference images, 720P/1080P/4K, 16:9 and 9:16, live credit estimates and task history."><meta name="generator" content="Astro v5.18.1"><meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1"><meta name="google-adsense-account" content="ca-pub-1916766675410622"><link rel="alternate" hreflang="en" href="https://www.ai-geminiomni.com/video-studio/"><link rel="alternate" hreflang="es" href="https://www.ai-geminiomni.com/es/video-studio/"><link rel="alternate" hreflang="it" href="https://www.ai-geminiomni.com/it/video-studio/"><link rel="alternate" hreflang="hi" href="https://www.ai-geminiomni.com/hi/video-studio/"><link rel="alternate" hreflang="ar" href="https://www.ai-geminiomni.com/ar/video-studio/"><link rel="alternate" hreflang="zh-CN" href="https://www.ai-geminiomni.com/zh/video-studio/"><link rel="alternate" hreflang="ja" href="https://www.ai-geminiomni.com/ja/video-studio/"><link rel="alternate" hreflang="ko" href="https://www.ai-geminiomni.com/ko/video-studio/"><link rel="alternate" hreflang="fr" href="https://www.ai-geminiomni.com/fr/video-studio/"><link rel="alternate" hreflang="de" href="https://www.ai-geminiomni.com/de/video-studio/"><link rel="alternate" hreflang="nl" href="https://www.ai-geminiomni.com/nl/video-studio/"><link rel="alternate" hreflang="pl" href="https://www.ai-geminiomni.com/pl/video-studio/"><link rel="alternate" hreflang="pt-BR" href="https://www.ai-geminiomni.com/pt-BR/video-studio/"><link rel="alternate" hreflang="ru" href="https://www.ai-geminiomni.com/ru/video-studio/"><link rel="alternate" hreflang="tr" href="https://www.ai-geminiomni.com/tr/video-studio/"><link rel="alternate" hreflang="id" href="https://www.ai-geminiomni.com/id/video-studio/"><link rel="alternate" hreflang="vi" href="https://www.ai-geminiomni.com/vi/video-studio/"><link rel="alternate" hreflang="th" href="https://www.ai-geminiomni.com/th/video-studio/"><link rel="alternate" hreflang="ms" href="https://www.ai-geminiomni.com/ms/video-studio/"><link rel="alternate" hreflang="tl" href="https://www.ai-geminiomni.com/tl/video-studio/"><link rel="alternate" hreflang="bn" href="https://www.ai-geminiomni.com/bn/video-studio/"><link rel="alternate" hreflang="ta" href="https://www.ai-geminiomni.com/ta/video-studio/"><link rel="alternate" hreflang="te" href="https://www.ai-geminiomni.com/te/video-studio/"><link rel="alternate" hreflang="mr" href="https://www.ai-geminiomni.com/mr/video-studio/"><link rel="alternate" hreflang="he" href="https://www.ai-geminiomni.com/he/video-studio/"><link rel="alternate" hreflang="fa" href="https://www.ai-geminiomni.com/fa/video-studio/"><link rel="alternate" hreflang="ur" href="https://www.ai-geminiomni.com/ur/video-studio/"><link rel="alternate" hreflang="x-default" href="https://www.ai-geminiomni.com/video-studio/"><meta property="og:type" content="website"><meta property="og:site_name" content="Gemini Omni Info Hub"><meta property="og:title" content="Gemini Omni Video Studio · AI text-to-video & image-to-video"><meta property="og:description" content="Generate AI videos in the Gemini Omni Video Studio: text-to-video and image-to-video with first/last frame or reference images, 720P/1080P/4K, 16:9 and 9:16, live credit estimates and task history."><meta property="og:url" content="https://www.ai-geminiomni.com/video-studio/"><meta property="og:image" content="https://www.ai-geminiomni.com/og.svg"><meta property="og:locale" content="en"><meta property="og:locale:alternate" content="es"><meta property="og:locale:alternate" content="it"><meta property="og:locale:alternate" content="hi"><meta property="og:locale:alternate" content="ar"><meta property="og:locale:alternate" content="zh-CN"><meta property="og:locale:alternate" content="ja"><meta property="og:locale:alternate" content="ko"><meta property="og:locale:alternate" content="fr"><meta property="og:locale:alternate" content="de"><meta property="og:locale:alternate" content="nl"><meta property="og:locale:alternate" content="pl"><meta property="og:locale:alternate" content="pt-BR"><meta property="og:locale:alternate" content="ru"><meta property="og:locale:alternate" content="tr"><meta property="og:locale:alternate" content="id"><meta property="og:locale:alternate" content="vi"><meta property="og:locale:alternate" content="th"><meta property="og:locale:alternate" content="ms"><meta property="og:locale:alternate" content="tl"><meta property="og:locale:alternate" content="bn"><meta property="og:locale:alternate" content="ta"><meta property="og:locale:alternate" content="te"><meta property="og:locale:alternate" content="mr"><meta property="og:locale:alternate" content="he"><meta property="og:locale:alternate" content="fa"><meta property="og:locale:alternate" content="ur"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="Gemini Omni Video Studio · AI text-to-vi
1deo & image-to-video"><meta name="twitter:description" content="Generate AI videos in the Gemini Omni Video Studio: text-to-video and image-to-video with first/last frame or reference images, 720P/1080P/4K, 16:9 and 9:16, live credit estimates and task history."><meta name="twitter:image" content="https://www.ai-geminiomni.com/og.svg">
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebApplication","name":"Video Studio","description":"Generate AI videos in the Gemini Omni Video Studio: text-to-video and image-to-video with first/last frame or reference images, 720P/1080P/4K, 16:9 and 9:16, live credit estimates and task history.","url":"https://www.ai-geminiomni.com/video-studio/","inLanguage":"en","applicationCategory":"MultimediaApplication","applicationSubCategory":"AI video generator","operatingSystem":"Web","browserRequirements":"Requires JavaScript and a modern browser","isAccessibleForFree":true,"offers":{"@type":"Offer","price":"0","priceCurrency":"USD"},"featureList":["Text-to-video generation","Image-to-video with first/last frame or reference images","720P, 1080P and 4K output","16:9 landscape and 9:16 portrait","Live credit estimates and task history"],"audience":{"@type":"Audience","audienceType":"Video creators, marketers, prompt engineers"},"publisher":{"@type":"Organization","name":"Gemini Omni Info Hub","url":"https://www.ai-geminiomni.com/"}}</script>
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://www.ai-geminiomni.com/"},{"@type":"ListItem","position":2,"name":"Video Studio","item":"https://www.ai-geminiomni.com/video-studio/"}]}</script>
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"FAQPage","inLanguage":"en","mainEntity":[{"@type":"Question","name":"How does Video Studio generate clips?","acceptedAnswer":{"@type":"Answer","text":"You submit a prompt (and optional first/last-frame or reference images). We create a generation job, poll until it finishes, then show a preview you can download."}},{"@type":"Question","name":"How are credits charged?","acceptedAnswer":{"@type":"Answer","text":"Cost is estimated from resolution and duration before you submit. The billed amount appears after the job is created; failed jobs are refunded automatically."}},{"@type":"Question","name":"Can I leave the page while a video is generating?","acceptedAnswer":{"@type":"Answer","text":"Polling pauses when the tab is hidden and resumes when you come back. Sign-in is required; an in-progress job is restored for the same account after a refresh."}}]}</script>
vendor: 63 bytes, line 1
1<script async src="https://www.googletagmanager.com/gtag/js?id=
1G-R7WH0BH713
vendor: 11 bytes, line 1
1"></script>
1<script>(function(){const gaId = "G-R7WH0BH713"; 2 3 window.dataLayer = window.dataLayer || []; 4 function gtag(){ dataLayer.push(arguments); } 5 gtag('js', new Date()); 6 gtag('config', gaId, { anonymize_ip: true }); 7 })();</script>
7<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1916766675410622" crossorigin="anonymous"></script>
7<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link rel="preconnect" href="https://accounts.google.com"><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&family=JetBrains+Mono:wght@400;500&family=Noto+Sans+Arabic:wght@400;500;600;700&family=Noto+Sans+Hebrew:wght@400;500;600;700&family=Noto+Sans+Thai:wght@400;500;600;700&display=swap"><link rel="stylesheet" href="/_astro/_slug_.CcK_mi-L.css"> 8<style>.vs-mode-tab[data-astro-cid-zu7mr32p][aria-selected=true],.vs-img-submode[data-astro-cid-zu7mr32p][aria-selected=true]{background-color:#8b5cf633;--tw-text-opacity: 1;color:rgb(237 233 254 / var(--tw-text-opacity, 1))}.vs-bottom-tab[data-astro-cid-zu7mr32p][aria-selected=true]{border-bottom-width:2px;--tw-border-opacity: 1;border-color:rgb(167 139 250 / var(--tw-border-opacity, 1));--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))} 9</style></head> <body class="relative overflow-x-hidden font-sans antialiased"> <aside id="locale-banner" class="locale-banner" role="region" aria-label="Language recommendation banner" data-current-lang="en" data-default-lang="en" data-lang-labels="{"zh":"䏿","en":"English","ja":"æ¥æ¬èª","ko":"íêµì´","es":"Español","fr":"Français","de":"Deutsch","it":"Italiano","nl":"Nederlands","pl":"Polski","pt-BR":"Português","ru":"Ð ÑÑÑкий","tr":"Türkçe","id":"Bahasa Indonesia","vi":"Tiếng Viá»t","th":"à¹à¸à¸¢","ms":"Bahasa Melayu","tl":"Filipino","hi":"हिनà¥à¤¦à¥","bn":"বাà¦à¦²à¦¾","ta":"தமிழà¯","te":"à°¤à±à°²à±à°à±","mr":"मराठà¥","ar":"Ø§ÙØ¹Ø±Ø¨ÙØ©","he":"×¢×ר×ת","fa":"ÙØ§Ø±Ø³Û","ur":"اردÙ"}" data-lang-urls="{"en":"/video-studio/","es":"/es/video-studio/","it":"/it/video-studio/","hi":"/hi/video-studio/","ar":"/ar/video-studio/","zh":"/zh/video-studio/","ja":"/ja/video-studio/","ko":"/ko/video-studio/","fr":"/fr/video-studio/","de":"/de/video-studio/","nl":"/nl/video-studio/","pl":"/pl/video-studio/","pt-BR":"/pt-BR/video-studio/","ru":"/ru/video-studio/","tr":"/tr/video-studio/","id":"/id/video-studio/","vi":"/vi/video-studio/","th":"/th/video-studio/","ms":"/ms/video-studio/","tl":"/tl/video-studio/","bn":"/bn/video-studio/","ta":"/ta/video-studio/","te":"/te/video-studio/","mr":"/mr/video-studio/","he":"/he/video-studio/","fa":"/fa/video-studio/","ur":"/ur/video-studio/"}" data-banner-copy="{"en":{"prompt":"Choose another language to browse content adapted for your browser language preference.","continue":"Continue","close":"Close suggestion banner","selectLabel":"Select preferred language","regionLabel":"Language recommendation banner"},"zh":{"prompt":"éæ©å¦ä¸è¯è¨ä»¥æµè§éç¨äºä½ æµè§å¨è¯è¨å好çå 容ã","continue":"ç»§ç»","close":"å ³éè¯è¨å»ºè®®æ¨ªå¹ ","selectLabel":"éæ©é¦éè¯è¨","regionLabel":"è¯è¨æ¨èæ¨ªå¹ "},"ja":{"prompt":"å¥ã®è¨èªãé¸ã¶ã¨ããã©ã¦ã¶ã®è¨èªè¨å®ã«åãããã³ã³ãã³ãã表示ã§ãã¾ãã","continue":"ç¶ãã","close":"è¨èªææ¡ããã¼ãéãã","selectLabel":"叿ã®è¨èªã鏿","regionLabel":"è¨èªããããããã¼"},"ko":{"prompt":"ë¤ë¥¸ ì¸ì´ë¥¼ ì ííë©´ ë¸ë¼ì°ì ì¸ì´ ì¤ì ì ë§ë ì½í ì¸ ë¥¼ ë³¼ ì ììµëë¤.","continue":"ê³ì","close":"ì¸ì´ ì ì ë°°ë ë«ê¸°","selectLabel":"ìíë ì¸ì´ ì í","regionLabel":"ì¸ì´ ì¶ì² ë°°ë"},"es":{"prompt":"Elija otro idioma para ver contenido adaptado a la preferencia de idioma de su navegador.","continue":"Continuar","close":"Cerrar banner de sugerencia de idioma","selectLabel":"Seleccionar idioma preferido","regionLabel":"Banner de recomendación de idioma"},"fr":{"prompt":"Choisissez une autre langue pour afficher le contenu adapté aux préférences linguistiques de votre navigateur.","continue":"Continuer","close":"Fermer la bannière de suggestion de langue","selectLabel":"Sélectionner la langue préférée","regionLabel":"Bannière de recommandation de langue"},"de":{"prompt":"Wählen Sie eine andere Sprache, um Inhalte entsprechend Ihrer Browser-Spracheinstellung anzuzeigen.","continue":"Weiter","close":"Sprachvorschlagsbanner schlieÃen","selectLabel":"Bevorzugte Sprache auswählen","regionLabel":"Sprachempfehlungsbanner"},"it":{"prompt":"Scegli un'altra lingua per visualizzare contenuti adattati alle preferenze linguistiche del browser.","continue":"Continua","close":"Chiudi banner suggerimento lingua","selectLabel":"Seleziona lingua preferita","regionLabel":"Banner raccomandazione lingua"},"nl":{"prompt":"Kies een andere taal om content te bekijken die is afgestemd op de taalvoorkeur van je browser.","continue":"Doorgaan","close":"Sluit taalsuggestiebanner","selectLabel":"Selecteer voorkeurstaal","regionLabel":"Taalaanbevelingsbanner"},"pl":{"prompt":"Wybierz inny jÄzyk, aby przeglÄ daÄ treÅci dopasowane do preferencji jÄzykowych przeglÄ darki.","continue":"Kontynuuj","close":"Zamknij baner sugestii jÄzyka","selectLabel":"Wybierz preferowany jÄzyk","regionLabel":"Baner rekomendacji jÄzyka"},"pt-BR":{"prompt":"Escolha outro idioma para ver conteúdo adaptado à preferência de idioma do seu navegador.","continue":"Continuar","close":"Fechar banner de sugestão de idioma","selectLabel":"Selecionar idioma preferido","regionLabel":"Banner de recomendação de idioma"},"ru":{"prompt":"ÐÑбеÑиÑе дÑÑгой ÑзÑк, ÑÑÐ¾Ð±Ñ Ð¿ÑоÑмаÑÑиваÑÑ ÐºÐ¾Ð½ÑенÑ, адапÑиÑованнÑй к ÑзÑковÑм наÑÑÑойкам ваÑего бÑаÑзеÑа.","continue":"ÐÑодолжиÑÑ","close":"ÐакÑÑÑÑ Ð±Ð°Ð½Ð½ÐµÑ Ð¿ÑÐµÐ´Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÑзÑка","selectLabel":"ÐÑбеÑиÑе пÑедпоÑиÑаемÑй ÑзÑк","regionLabel":"ÐÐ°Ð½Ð½ÐµÑ ÑекомендаÑии ÑзÑка"},"tr":{"prompt":"Tarayıcı dil tercihinize uygun içeriÄi görmek için baÅka bir dil seçin.","continue":"Devam","close":"Dil önerisi baÅlıÄını kapat","selectLabel":"Tercih edilen dili seçin","regionLabel":"Dil önerisi baÅlıÄı"},"id":{"prompt":"Pilih bahasa lain untuk melihat konten yang disesuaikan dengan preferensi bahasa browser Anda.","continue":"Lanjutkan","close":"Tutup banner saran bahasa","selectLabel":"Pilih bahasa pilihan","regionLabel":"Banner rekomendasi bahasa"},"vi":{"prompt":"Chá»n ngôn ngữ khác Äá» xem ná»i dung phù hợp vá»i tùy chá»n ngôn ngữ trình duyá»t cá»§a bạn.","continue":"Tiếp tục","close":"Äóng banner gợi ý ngôn ngữ","selectLabel":"Chá»n ngôn ngữ ưa thÃch","regionLabel":"Banner Äá» xuất ngôn ngữ"},"th":{"prompt":"à¹à¸¥à¸·à¸à¸à¸ าษาà¸à¸·à¹à¸à¹à¸à¸·à¹à¸à¸à¸¹à¹à¸à¸·à¹à¸à¸«à¸²à¸à¸µà¹à¸à¸£à¸±à¸à¹à¸«à¹à¹à¸à¹à¸²à¸à¸±à¸à¸à¸²à¸£à¸à¸±à¹à¸à¸à¹à¸²à¸ าษาà¸à¸à¸à¹à¸à¸£à¸²à¸§à¹à¹à¸à¸à¸£à¹à¸à¸à¸à¸à¸¸à¸","continue":"à¸à¸³à¹à¸à¸´à¸à¸à¸²à¸£à¸à¹à¸","close":"à¸à¸´à¸à¹à¸à¸à¹à¸à¸à¸£à¹à¹à¸à¸°à¸à¸³à¸ าษา","selectLabel":"à¹à¸¥à¸·à¸à¸à¸ าษาà¸à¸µà¹à¸à¹à¸à¸à¸à¸²à¸£","regionLabel":"à¹à¸à¸à¹à¸à¸à¸£à¹à¹à¸à¸°à¸à¸³à¸ าษา"},"ms":{"prompt":"Pilih bahasa lain untuk melihat kandungan yang disesuaikan dengan pilihan bahasa pelayar anda.","continue":"Teruskan","close":"Tutup banner cadangan bahasa","selectLabel":"Pilih bahasa pilihan","regionLabel":"Banner cadangan bahasa"},"tl":{"prompt":"Pumili ng ibang wika upang makita ang content na naaayon sa preference ng wika ng browser mo.","continue":"Magpatuloy","close":"Isara ang banner ng mungkahi sa wika","selectLabel":"Piliin ang gustong wika","regionLabel":"Banner ng rekomendasyon sa wika"},"bn":{"prompt":"à¦à¦ªà¦¨à¦¾à¦° বà§à¦°à¦¾à¦à¦à¦¾à¦°à§à¦° à¦à¦¾à¦·à¦¾à¦° পà¦à¦¨à§à¦¦ ঠনà§à¦¯à¦¾à¦¯à¦¼à§ à¦à¦¨à¦à§à¦¨à§à¦ দà§à¦à¦¤à§ ঠনà§à¦¯ à¦à¦¾à¦·à¦¾ বà§à¦à§ নিন।","continue":"à¦à¦¾à¦²à¦¿à¦¯à¦¼à§ যান","close":"à¦à¦¾à¦·à¦¾ পরামরà§à¦¶ বà§à¦¯à¦¾à¦¨à¦¾à¦° বনà§à¦§ à¦à¦°à§à¦¨","selectLabel":"পà¦à¦¨à§à¦¦à§à¦° à¦à¦¾à¦·à¦¾ নিরà§à¦¬à¦¾à¦à¦¨ à¦à¦°à§à¦¨","regionLabel":"à¦à¦¾à¦·à¦¾ সà§à¦ªà¦¾à¦°à¦¿à¦¶ বà§à¦¯à¦¾à¦¨à¦¾à¦°"},"ta":{"prompt":"à®à®à¯à®à®³à¯ à®à®²à®¾à®µà®¿ à®®à¯à®´à®¿ விரà¯à®ªà¯à®ª
9தà¯à®¤à®¿à®±à¯à®à¯ à®à®±à¯à®ª à®à®³à¯à®³à®à®à¯à®à®¤à¯à®¤à¯à®ªà¯ பாரà¯à®à¯à® வà¯à®±à¯ à®®à¯à®´à®¿à®¯à¯à®¤à¯ தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®à¯à®à®µà¯à®®à¯.","continue":"தà¯à®à®°à®µà¯à®®à¯","close":"à®®à¯à®´à®¿ பரிநà¯à®¤à¯à®°à¯ பà¯à®©à®°à¯ à®®à¯à®à®µà¯à®®à¯","selectLabel":"விரà¯à®ªà¯à®ª à®®à¯à®´à®¿à®¯à¯à®¤à¯ தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®à¯à®à®µà¯à®®à¯","regionLabel":"à®®à¯à®´à®¿ பரிநà¯à®¤à¯à®°à¯ பà¯à®©à®°à¯"},"te":{"prompt":"మౠబà±à°°à±à°à°°à± à°à°¾à°·à°¾ à°ªà±à°°à°¾à°§à°¾à°¨à±à°¯à°¤à°à± à°
à°¨à±à°à±à°£à°à°à°¾ à°à°à°à±à°à°à±âనౠà°à±à°¡à°à°¾à°¨à°¿à°à°¿ మరà±à° à°à°¾à°·à°¨à± à°à°à°à±à°à±à°à°¡à°¿.","continue":"à°à±à°¨à°¸à°¾à°à°¿à°à°à±","close":"à°à°¾à°· à°¸à±à°à°¨ à°¬à±à°¯à°¾à°¨à°°à±âనౠమà±à°¸à°¿à°µà±à°¯à°¿","selectLabel":"à°à°·à±à°à°®à±à°¨ à°à°¾à°·à°¨à± à°à°à°à±à°à±à°à°¡à°¿","regionLabel":"à°à°¾à°· సిఫారà±à°¸à± à°¬à±à°¯à°¾à°¨à°°à±"},"mr":{"prompt":"तà¥à¤®à¤à¥à¤¯à¤¾ बà¥à¤°à¤¾à¤à¤à¤°à¤à¥à¤¯à¤¾ à¤à¤¾à¤·à¤¾ पà¥à¤°à¤¾à¤§à¤¾à¤¨à¥à¤¯à¤¾à¤¨à¥à¤¸à¤¾à¤° सामà¤à¥à¤°à¥ पाहणà¥à¤¯à¤¾à¤¸à¤¾à¤ ॠदà¥à¤¸à¤°à¥ à¤à¤¾à¤·à¤¾ निवडा.","continue":"पà¥à¤¢à¥ à¤à¤¾","close":"à¤à¤¾à¤·à¤¾ सà¥à¤à¤¨à¤¾ बà¥
नर बà¤à¤¦ à¤à¤°à¤¾","selectLabel":"पसà¤à¤¤à¥à¤à¥ à¤à¤¾à¤·à¤¾ निवडा","regionLabel":"à¤à¤¾à¤·à¤¾ शिफारस बà¥
नर"},"ar":{"prompt":"اختر ÙØºØ© Ø£Ø®Ø±Ù ÙØ¹Ø±Ø¶ Ù
ØØªÙÙ ÙØªÙÙÙ Ù
ع ØªÙØ¶ÙÙØ§Øª ÙØºØ© Ù
ØªØµÙØÙ.","continue":"Ù
تابعة","close":"Ø¥ØºÙØ§Ù Ø´Ø±ÙØ· Ø§ÙØªØ±Ø§Ø اÙÙØºØ©","selectLabel":"اختر اÙÙØºØ© اÙÙ
ÙØ¶ÙØ©","regionLabel":"Ø´Ø±ÙØ· ØªÙØµÙØ© اÙÙØºØ©"},"he":{"prompt":"××ר ×©×¤× ××רת ××× ×צפ×ת ×ת××× ×××ת×× ×××¢×פת ××©×¤× ×©× ××פ××¤× ×©××.","continue":"××ש×","close":"ס××ר ×ת ××× ×¨ ×צעת ×שפ×","selectLabel":"××ר ×©×¤× ×××¢×פת","regionLabel":"××× ×¨ ×××צת שפ×"},"fa":{"prompt":"زبا٠دÛÚ¯Ø±Û Ø±Ø§ Ø§ÙØªØ®Ø§Ø¨ Ú©ÙÛØ¯ تا Ù
ØØªÙاÛÛ Ù
ØªÙØ§Ø³Ø¨ با ØªØ±Ø¬ÛØ زبا٠Ù
Ø±ÙØ±Ú¯Ø± Ø®ÙØ¯ ببÛÙÛØ¯.","continue":"اداÙ
Ù","close":"Ø¨Ø³ØªÙ Ø¨ÙØ± Ù¾ÛØ´ÙÙØ§Ø¯ زباÙ","selectLabel":"Ø§ÙØªØ®Ø§Ø¨ Ø²Ø¨Ø§Ù ØªØ±Ø¬ÛØÛ","regionLabel":"Ø¨ÙØ± Ù¾ÛØ´ÙÙØ§Ø¯ زباÙ"},"hi":{"prompt":"à¤
पनॠबà¥à¤°à¤¾à¤à¤à¤¼à¤° à¤à¤¾à¤·à¤¾ पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤à¤¤à¤¾ à¤à¥ à¤
नà¥à¤¸à¤¾à¤° सामà¤à¥à¤°à¥ दà¥à¤à¤¨à¥ à¤à¥ लिठदà¥à¤¸à¤°à¥ à¤à¤¾à¤·à¤¾ à¤à¥à¤¨à¥à¤à¥¤","continue":"à¤à¤¾à¤°à¥ रà¤à¥à¤","close":"à¤à¤¾à¤·à¤¾ सà¥à¤à¤¾à¤µ बà¥à¤¨à¤° बà¤à¤¦ à¤à¤°à¥à¤","selectLabel":"पसà¤à¤¦à¥à¤¦à¤¾ à¤à¤¾à¤·à¤¾ à¤à¥à¤¨à¥à¤","regionLabel":"à¤à¤¾à¤·à¤¾ à¤
नà¥à¤¶à¤à¤¸à¤¾ बà¥à¤¨à¤°"},"ur":{"prompt":"اپÙÛ Ø¨Ø±Ø§Ø¤Ø²Ø± Ø²Ø¨Ø§Ù Ú©Û ØªØ±Ø¬ÛØ Ú©Û Ù
طاب٠Ù
ÙØ§Ø¯ دÛÚ©Ú¾ÙÛ Ú©Û ÙÛÛ Ø¯ÙØ³Ø±Û زبا٠Ù
ÙØªØ®Ø¨ کرÛÚºÛ","continue":"Ø¬Ø§Ø±Û Ø±Ú©Ú¾ÛÚº","close":"Ø²Ø¨Ø§Ù Ú©Û ØªØ¬ÙÛØ² بÛÙØ± Ø¨ÙØ¯ کرÛÚº","selectLabel":"Ù¾Ø³ÙØ¯ÛØ¯Û Ø²Ø¨Ø§Ù Ù
ÙØªØ®Ø¨ کرÛÚº","regionLabel":"Ø²Ø¨Ø§Ù Ú©Û Ø³ÙØ§Ø±Ø´ بÛÙØ±"}}" style="display: none;" data-astro-cid-xujs2ijk> <div class="locale-banner-inner" data-astro-cid-xujs2ijk> <div class="locale-banner-text" data-astro-cid-xujs2ijk> <span id="locale-banner-message" data-astro-cid-xujs2ijk>Choose another language to browse content adapted for your browser language preference.</span> </div>
9 <div class="locale-banner-controls" data-astro-cid-xujs2ijk> <div class="locale-banner-actions" data-astro-cid-xujs2ijk> <div class="locale-banner-select-wrapper" data-astro-cid-xujs2ijk> <svg class="locale-banner-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true" data-astro-cid-xujs2ijk> <polyline points="20 6 9 17 4 12" data-astro-cid-xujs2ijk></polyline> </svg> <select id="locale-banner-select" class="locale-banner-select" aria-label="Select preferred language" data-astro-cid-xujs2ijk> <option value="en" data-astro-cid-xujs2ijk>English</option><option value="es" selected data-astro-cid-xujs2ijk>Español</option><option value="it" selected data-astro-cid-xujs2ijk>Italiano</option><option value="hi" selected data-astro-cid-xujs2ijk>हिनà¥à¤¦à¥</option><option value="ar" selected data-astro-cid-xujs2ijk>Ø§ÙØ¹Ø±Ø¨ÙØ©</option><option value="zh" selected data-astro-cid-xujs2ijk>䏿</option><option value="ja" selected data-astro-cid-xujs2ijk>æ¥æ¬èª</option><option value="ko" selected data-astro-cid-xujs2ijk>íêµì´</option><option value="fr" selected data-astro-cid-xujs2ijk>Français</option><option value="de" selected data-astro-cid-xujs2ijk>Deutsch</option><option value="nl" selected data-astro-cid-xujs2ijk>Nederlands</option><option value="pl" selected data-astro-cid-xujs2ijk>Polski</option><option value="pt-BR" selected data-astro-cid-xujs2ijk>Português</option><option value="ru" selected data-astro-cid-xujs2ijk>Ð ÑÑÑкий</option><option value="tr" selected data-astro-cid-xujs2ijk>Türkçe</option><option value="id" selected data-astro-cid-xujs2ijk>Bahasa Indonesia</option><option value="vi" selected data-astro-cid-xujs2ijk>Tiếng Viá»t</option><option value="th" selected data-astro-cid-xujs2ijk>à¹à¸à¸¢</option><option value="ms" selected data-astro-cid-xujs2ijk>Bahasa Melayu</option><option value="tl" selected data-astro-cid-xujs2ijk>Filipino</option><option value="bn" selected data-astro-cid-xujs2ijk>বাà¦à¦²à¦¾</option><option value="ta" selected data-astro-cid-xujs2ijk>தமிழà¯</option><option value="te" selected data-astro-cid-xujs2ijk>à°¤à±à°²à±à°à±</option><option value="mr" selected data-astro-cid-xujs2ijk>मराठà¥</option><option value="he" selected data-astro-cid-xujs2ijk>×¢×ר×ת</option><option value="fa" selected data-astro-cid-xujs2ijk>ÙØ§Ø±Ø³Û</option><option value="ur" selected data-astro-cid-xujs2ijk>اردÙ</option> </select> <svg class="locale-banner-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true" data-astro-cid-xujs2ijk> <polyline points="6 9 12 15 18 9" data-astro-cid-xujs2ijk></polyline> </svg> </div> <a id="locale-banner-continue" href="#" class="locale-banner-button" data-astro-cid-xujs2ijk> Continue </a> </div> <button id="locale-banner-close" class="locale-banner-close" aria-label="Close suggestion banner" type="button" data-astro-cid-xujs2ijk> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true" data-astro-cid-xujs2ijk> <line x1="18" y1="6" x2="6" y2="18" data-astro-cid-xujs2ijk></line> <line x1="6" y1="6" x2="18" y2="18" data-astro-cid-xujs2ijk></line> </svg> </button> </div> </div> </aside>
9<script> 10 (() => { 11 try { 12 const banner = document.getElementById('locale-banner'); 13 if (!banner) return; 14 15 const DISMISSED_KEY = 'apple_style_lang_banner_dismissed'; 16 if (sessionStorage.getItem(DISMISSED_KEY) === '1') return; 17 18 const currentLang = banner.getAttribute('data-current-lang') || 'en'; 19 const langUrls = JSON.parse(banner.getAttribute('data-lang-urls') || '{}'); 20 const bannerCopy = JSON.parse(banner.getAttribute('data-banner-copy') || '{}'); 21 const supportedLangs = Object.keys(langUrls); 22 23 const alias = { pt: 'pt-BR', zh: 'zh', iw: 'he', in: 'id', fil: 'tl', tl: 'tl' }; 24 25 function match(raw) { 26 raw = (raw || '').toLowerCase(); 27 if (!raw) return ''; 28 for (let i = 0; i < supportedLangs.length; i++) { 29 if (supportedLangs[i].toLowerCase() === raw) return supportedLangs[i]; 30 } 31 const base = raw.split('-')[0]; 32 if (alias[base]) return alias[base]; 33 for (let j = 0; j < supportedLangs.length; j++) { 34 const s = supportedLangs[j].toLowerCase(); 35 if (s === base || s.split('-')[0] === base) return supportedLangs[j]; 36 } 37 return ''; 38 } 39 40 const prefs = 41 navigator.languages && navigator.languages.length 42 ? navigator.languages 43 : [navigator.language || 'en']; 44 45 let detectedLang = ''; 46 for (let k = 0; k < prefs.length && !detectedLang; k++) { 47 detectedLang = match(prefs[k]); 48 } 49 if (!detectedLang) detectedLang = 'en'; 50 51 if (detectedLang === currentLang) return; 52 53 const select = document.getElementById('locale-banner-select'); 54 const continueBtn = document.getElementById('locale-banner-continue'); 55 const closeBtn = document.getElementById('locale-banner-close'); 56 const msg = document.getElementById('locale-banner-message'); 57 58 function applyBannerCopy(lang) { 59 const copy = bannerCopy[lang] || bannerCopy.en || {}; 60 if (msg) msg.textContent = copy.prompt || ''; 61 if (continueBtn) continueBtn.textContent = copy.continue || ''; 62 if (select) select.setAttribute('aria-label', copy.selectLabel || ''); 63 if (closeBtn) closeBtn.setAttribute('aria-label', copy.close || ''); 64 banner.setAttribute('aria-label', copy.regionLabel || ''); 65 } 66 67 if (select) select.value = detectedLang; 68 applyBannerCopy(detectedLang); 69 70 function updateContinueTarget() { 71 if (!select || !continueBtn) return; 72 const target = select.value; 73 continueBtn.setAttribute('href', langUrls[target] || '/'); 74 applyBannerCopy(target); 75 } 76 77 if (select) select.addEventListener('change', updateContinueTarget); 78 79 if (closeBtn) { 80 closeBtn.addEventListener('click', () => { 81 banner.style.display = 'none'; 82 sessionStorage.setItem(DISMISSED_KEY, '1'); 83 }); 84 } 85 86 updateContinueTarget(); 87 banner.style.display = 'block'; 88 } catch (e) {} 89 })(); 90</script>
90 <div aria-hidden="true" class="pointer-events-none fixed inset-0 -z-10 mask-fade-b"> <div class="absolute -top-40 left-1/2 h-[600px] w-[1200px] -translate-x-1/2 rounded-full bg-violet-500/20 blur-3xl"></div> <div class="absolute right-[-200px] top-40 h-[500px] w-[500px] rounded-full bg-cyan-400/10 blur-3xl"></div> <div class="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/15 to-transparent"></div> </div> <header class="sticky top-0 z-40 border-b border-white/5 bg-ink-950/70 backdrop-blur-md"> <div class="container-x flex h-16 items-center justify-between gap-4"> <a href="/" class="flex shrink-0 items-center gap-2.5 font-display text-base font-semibold tracking-tight text-white"> <span class="grid h-7 w-7 shrink-0 place-items-center rounded-lg bg-brand-gradient text-ink-950 shadow-glow"> <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="currentColor" d="M12 2l1.7 7.3L21 11l-7.3 1.7L12 20l-1.7-7.3L3 11l7.3-1.7L12 2z"></path> </svg> </span> <span class="whitespace-nowrap">Gemini Omni</span> </a> <nav aria-label="Primary" class="hidden lg:block"> <ul class="flex items-center gap-1 text-sm text-zinc-300"> <li> <a href="/" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Home </a> </li><li> <a href="/#showcase" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Demos </a> </li><li> <a href="/#features" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Features </a> </li><li> <a href="/#free" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Free vs paid </a> </li><li> <a href="/prompt-generator/" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Prompt builder </a> </li><li> <a href="/video-studio/" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Video Studio </a> </li><li> <a href="/pricing/" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Pricing </a> </li><li> <a href="/blog/" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> Articles </a> </li><li> <a href="/#faq" class="whitespace-nowrap rounded-full px-3 py-1.5 transition-colors hover:bg-white/[0.06] hover:text-white"> FAQ </a> </li> </ul> </nav> <div class="flex shrink-0 items-center gap-2"> <div data-auth class="relative flex items-center gap-2"> <button type="button" data-open-login data-auth-signed-out class="chip cursor-pointer hover:border-violet-400/40 hover:text-white"> Sign in </button> <a href="/pricing/" data-auth-points hidden aria-label="Available credits" title="Credits" class="hidden items-center gap-1.5 rounded-full border border-violet-400/30 bg-violet-500/10 px-3 py-1 text-xs font-semibold text-violet-100 transition-colors hover:border-violet-400/60 hover:bg-violet-500/15 hover:text-white"> <svg viewBox="0 0 24 24" class="h-3.5 w-3.5" aria-hidden="true"> <path fill="currentColor" d="M13 2L3 14h7l-1 8 10-12h-7l1-8z"></path> </svg> <span data-auth-points-value class="tabular-nums">0</span> </a> <button type="button" data-auth-signed-in hidden aria-haspopup="menu" aria-expanded="false" class="flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.04] py-1 pl-1 pr-3 text-sm transition-colors hover:border-violet-400/40"> <span data-auth-avatar class="grid h-7 w-7 place-items-center overflow-hidden rounded-full bg-brand-gradient text-xs font-semibold uppercase text-ink-950"></span> <span data-auth-name class="hidden max-w-[8rem] truncate text-zinc-200 sm:inline"></span> <svg viewBox="0 0 24 24" class="hidden h-3 w-3 opacity-60 sm:inline" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6"></path> </svg> </button> <div data-auth-menu hidden role="menu" class="absolute right-0 top-full z-50 mt-2 w-64 max-w-[calc(100vw-1rem)] origin-top-right rounded-2xl border border-white/10 bg-ink-900/95 p-3 shadow-xl backdrop-blur"> <div class="flex items-center gap-3 border-b border-white/5 px-2 pb-3"> <span data-auth-avatar-lg class="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-full bg-brand-gradient text-sm font-semibold uppercase text-ink-950"></span> <div class="min-w-0"> <p data-auth-name-full class="truncate text-sm font-semibold text-white"></p> <p data-auth-email class="truncate text-xs text-zinc-400"></p> </div> </div> <button type="button" data-auth-logout class="mt-3 flex w-full items-center justify-center gap-2 rounded-xl border border-rose-400/30 bg-rose-500/10 px-3 py-2 text-sm font-semibold text-rose-200 transition-colors hover:bg-rose-500/15"> <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M15 12H4m0 0l4-4m-4 4l4 4M9 4h7a2 2 0 012 2v12a2 2 0 01-2 2H9"></path> </svg> Log out </button> </div> </div>
90<script>(function(){const API_BASE = "https://www.gushanai.com/api"; 91const APP_KEY = "gemini_omni_4561"; 92const HTML_LANG = "en"; 93 94 (() => { 95 if (typeof window === 'undefined') return; 96 97 const AUTH_TOKEN_KEY = 'auth_token'; 98 const USER_INFO_KEY = 'user_info'; 99 const AUTH_CHANGED_EVENT = 'gemini-omni:auth-changed'; 100 const REFRESH_USER_EVENT = 'gemini-omni:refresh-user'; 101 const SESSION_REFRESHED_KEY = 'gemini-omni:auth-refreshed'; 102 103 const root = document.querySelector('[data-auth]'); 104 if (!root) return; 105 106 const signedOutEl = root.querySelector('[data-auth-signed-out]'); 107 const signedInEl = root.querySelector('[data-auth-signed-in]'); 108 const menuEl = root.querySelector('[data-auth-menu]'); 109 const avatarEl = root.querySelector('[data-auth-avatar]'); 110 const avatarLgEl = root.querySelector('[data-auth-avatar-lg]'); 111 const nameEl = root.querySelector('[data-auth-name]'); 112 const nameFullEl = root.querySelector('[data-auth-name-full]'); 113 const emailEl = root.querySelector('[data-auth-email]'); 114 const logoutBtn = root.querySelector('[data-auth-logout]'); 115 const pointsEl = root.querySelector('[data-auth-points]'); 116 const pointsValueEl = root.querySelector('[data-auth-points-value]'); 117 118 let pointsFormatter; 119 try { 120 pointsFormatter = new Intl.NumberFormat(HTML_LANG); 121 } catch { 122 pointsFormatter = new Intl.NumberFormat('en'); 123 } 124 125 const getToken = () => window.localStorage.getItem(AUTH_TOKEN_KEY); 126 const getUser = () => { 127 const raw = window.localStorage.getItem(USER_INFO_KEY); 128 if (!raw) return null; 129 try { 130 return JSON.parse(raw); 131 } catch { 132 return null; 133 } 134 }; 135 136 const initials = (user) => { 137 const src = (user?.nickName || user?.email || '').trim(); 138 if (!src) return '?'; 139 const ch = src.codePointAt(0); 140 return ch ? String.fromCodePoint(ch).toUpperCase() : '?'; 141 }; 142 143 const renderAvatar = (el, user) => { 144 if (!el) return; 145 el.innerHTML = ''; 146 if (user?.avatarUrl) { 147 const img = document.createElement('img'); 148 img.src = user.avatarUrl; 149 img.alt = user.nickName || user.email || ''; 150 img.referrerPolicy = 'no-referrer'; 151 img.className = 'h-full w-full object-cover'; 152 el.appendChild(img); 153 } else { 154 el.textContent = initials(user); 155 } 156 }; 157 158 const closeMenu = () => { 159 menuEl?.setAttribute('hidden', ''); 160 signedInEl?.setAttribute('aria-expanded', 'false'); 161 if (menuEl) menuEl.style.right = ''; 162 }; 163 164 const adjustMenuPosition = () => { 165 if (!menuEl) return; 166 menuEl.style.right = ''; 167 const rect = menuEl.getBoundingClientRect(); 168 const minMargin = 8; 169 if (rect.left < minMargin) { 170 const shift = minMargin - rect.left; 171 menuEl.style.right = `-${shift}px`; 172 } else if (rect.right > window.innerWidth - minMargin) { 173 const overflow = rect.right - (window.innerWidth - minMargin); 174 menuEl.style.right = `${overflow}px`; 175 } 176 }; 177 178 const openMenu = () => { 179 menuEl?.removeAttribute('hidden'); 180 signedInEl?.setAttribute('aria-expanded', 'true'); 181 adjustMenuPosition(); 182 }; 183 184 const renderPoints = (user) => { 185 if (!pointsEl) return; 186 const total = Number(user?.wallet?.points?.totalBalance); 187 if (Number.isFinite(total) && total >= 0) { 188 if (pointsValueEl) pointsValueEl.textContent = pointsFormatter.format(total); 189 pointsEl.removeAttribute('hidden'); 190 pointsEl.classList.remove('hidden'); 191 pointsEl.classList.add('inline-flex'); 192 } else { 193 pointsEl.setAttribute('hidden', ''); 194 pointsEl.classList.add('hidden'); 195 pointsEl.classList.remove('inline-flex'); 196 } 197 }; 198 199 const render = () => { 200 const token = getToken(); 201 const user = getUser(); 202 if (token && user) { 203 signedOutEl?.setAttribute('hidden', ''); 204 signedInEl?.removeAttribute('hidden'); 205 renderAvatar(avatarEl, user); 206 renderAvatar(avatarLgEl, user); 207 const display = user.nickName || user.email || ''; 208 if (nameEl) nameEl.textContent = display; 209 if (nameFullEl) nameFullEl.textContent = user.nickName || user.email || ''; 210 if (emailEl) emailEl.textContent = user.email || ''; 211 renderPoints(user); 212 } else { 213 signedInEl?.setAttribute('hidden', ''); 214 signedOutEl?.removeAttribute('hidden'); 215 renderPoints(null); 216 closeMenu(); 217 } 218 }; 219 220 const apiRequest = async (path, body) => { 221 const headers = { 'Content-Type': 'application/json', 'x-app-key': APP_KEY }; 222 const token = getToken(); 223 if (token) headers.Authorization = token; 224 const res = await fetch(`${API_BASE}${path}`, { 225 method: 'POST', 226 headers, 227 body: JSON.stringify(body || {}), 228 }); 229 let env;
230 try { 231 env = await res.json(); 232 } catch { 233 env = { code: res.status, message: `HTTP ${res.status}` }; 234 } 235 if (!env || (env.code !== 200 && env.code !== 0)) { 236 const err = new Error(env?.message || `HTTP ${res.status}`); 237 err.code = env?.code ?? res.status; 238 err.status = res.status; 239 throw err; 240 } 241 return env.data; 242 }; 243 244 const clearAuth = () => { 245 window.localStorage.removeItem(AUTH_TOKEN_KEY); 246 window.localStorage.removeItem(USER_INFO_KEY); 247 try { 248 window.sessionStorage.removeItem(SESSION_REFRESHED_KEY); 249 } catch {} 250 window.dispatchEvent(new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token: null, user: null } })); 251 }; 252 253 const refreshIfNeeded = async () => { 254 const token = getToken(); 255 if (!token) return; 256 let alreadyRefreshed = false; 257 try { 258 alreadyRefreshed = window.sessionStorage.getItem(SESSION_REFRESHED_KEY) === '1'; 259 } catch {} 260 if (alreadyRefreshed) return; 261 try { 262 const refreshData = await apiRequest('/auth/refresh', {}); 263 if (refreshData?.token) { 264 window.localStorage.setItem(AUTH_TOKEN_KEY, refreshData.token); 265 } 266 const user = await apiRequest('/mine/info', {}); 267 if (user) { 268 window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(user)); 269 } 270 try { 271 window.sessionStorage.setItem(SESSION_REFRESHED_KEY, '1'); 272 } catch {} 273 window.dispatchEvent( 274 new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token: getToken(), user } }) 275 ); 276 } catch (err) { 277 const status = err && (err.status || err.code); 278 if (status === 401 || status === 403) { 279 clearAuth(); 280 } 281 } 282 }; 283 284 signedInEl?.addEventListener('click', (e) => { 285 e.stopPropagation(); 286 if (menuEl?.hasAttribute('hidden')) openMenu(); 287 else closeMenu(); 288 }); 289 290 logoutBtn?.addEventListener('click', (e) => { 291 e.stopPropagation(); 292 clearAuth(); 293 closeMenu(); 294 }); 295 296 document.addEventListener('click', (e) => { 297 if (!root.contains(e.target)) closeMenu(); 298 }); 299 300 document.addEventListener('keydown', (e) => { 301 if (e.key === 'Escape') closeMenu(); 302 }); 303 304 window.addEventListener(AUTH_CHANGED_EVENT, render); 305 306 window.addEventListener(REFRESH_USER_EVENT, async () => { 307 if (!getToken()) return; 308 try { 309 const user = await apiRequest('/mine/info', {}); 310 if (user) { 311 window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(user)); 312 window.dispatchEvent( 313 new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token: getToken(), user } }) 314 ); 315 } 316 } catch (err) { 317 const status = err && (err.status || err.code); 318 if (status === 401 || status === 403) { 319 clearAuth(); 320 } 321 } 322 }); 323 324 render(); 325 refreshIfNeeded(); 326 })(); 327})();</script>
327 <div class="relative" data-lang-switcher> <button type="button" class="chip hover:border-violet-400/40 hover:text-white" aria-label="Switch language" aria-haspopup="listbox" aria-expanded="false" data-lang-toggle> <svg viewBox="0 0 24 24" class="h-3.5 w-3.5" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" d="M12 3v18M3 12h18M5.5 7.5c4 3 9 3 13 0M5.5 16.5c4-3 9-3 13 0"></path> </svg> <span class="hidden sm:inline">English</span> <svg viewBox="0 0 24 24" class="h-3 w-3 opacity-60" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6"></path> </svg> </button> <div class="absolute end-0 z-50 mt-2 hidden max-h-[min(60vh,420px)] w-48 origin-top-right overflow-y-auto overscroll-contain rtl:origin-top-left rounded-2xl border border-white/10 bg-ink-900/95 p-1.5 shadow-xl backdrop-blur" role="listbox" data-lang-menu> <a href="/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors bg-white/[0.06] text-white" role="option" aria-selected="true" hreflang="en"> <span>English</span> <svg viewBox="0 0 24 24" class="h-3.5 w-3.5 text-violet-300" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M5 12l5 5 9-11"></path> </svg> </a><a href="/es/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="es"> <span>Español</span> </a><a href="/it/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="it"> <span>Italiano</span> </a><a href="/hi/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="hi"> <span>हिनà¥à¤¦à¥</span> </a><a href="/ar/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="ar"> <span>Ø§ÙØ¹Ø±Ø¨ÙØ©</span> </a><a href="/zh/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="zh"> <span>䏿</span> </a><a href="/ja/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="ja"> <span>æ¥æ¬èª</span> </a><a href="/ko/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="ko"> <span>íêµì´</span> </a><a href="/fr/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="fr"> <span>Français</span> </a><a href="/de/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="de"> <span>Deutsch</span> </a><a href="/nl/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="nl"> <span>Nederlands</span> </a><a href="/pl/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="pl"> <span>Polski</span> </a><a href="/pt-BR/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="pt-BR"> <span>Português</span> </a><a href="/ru/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="ru"> <span>Ð ÑÑÑкий</span> </a><a href="/tr/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="tr"> <span>Türkçe</span> </a><a href="/id/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="id"> <span>Bahasa Indonesia</span> </a><a href="/vi/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="vi"> <span>Tiếng Viá»t</span> </a><a href="/th/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="th"> <span>à¹à¸à¸¢</span> </a>
327<a href="/ms/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="ms"> <span>Bahasa Melayu</span> </a><a href="/tl/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="tl"> <span>Filipino</span> </a><a href="/bn/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="bn"> <span>বাà¦à¦²à¦¾</span> </a><a href="/ta/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="ta"> <span>தமிழà¯</span> </a><a href="/te/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="te"> <span>à°¤à±à°²à±à°à±</span> </a><a href="/mr/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="mr"> <span>मराठà¥</span> </a><a href="/he/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="he"> <span>×¢×ר×ת</span> </a><a href="/fa/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="fa"> <span>ÙØ§Ø±Ø³Û</span> </a><a href="/ur/video-studio/" class="flex items-center justify-between rounded-xl px-3 py-2 text-sm transition-colors text-zinc-300 hover:bg-white/[0.04] hover:text-white" role="option" aria-selected="false" hreflang="ur"> <span>اردÙ</span> </a> </div> </div> <button type="button" class="grid h-9 w-9 place-items-center rounded-full border border-white/10 bg-white/[0.04] text-zinc-200 transition-colors hover:border-violet-400/40 hover:text-white lg:hidden" aria-label="Menu" aria-expanded="false" aria-controls="mobile-menu" data-mobile-toggle> <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" d="M4 7h16M4 12h16M4 17h16"></path> </svg> </button> </div> </div> <div id="mobile-menu" class="lg:hidden hidden border-t border-white/5 bg-ink-950/95 backdrop-blur"> <ul class="container-x flex flex-col gap-1 py-3 text-sm text-zinc-300"> <li> <a href="/" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Home </a> </li><li> <a href="/#showcase" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Demos </a> </li><li> <a href="/#features" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Features </a> </li><li> <a href="/#specs" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Specs </a> </li><li> <a href="/#comparison" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Compare </a> </li><li> <a href="/#free" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Free vs paid </a> </li><li> <a href="/prompt-generator/" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Prompt builder </a> </li><li> <a href="/video-studio/" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Video Studio </a> </li><li> <a href="/pricing/" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Pricing </a> </li><li> <a href="/blog/" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> Articles </a> </li><li> <a href="/#faq" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link> FAQ </a> </li><li> <a href="/#sources" class="block rounded-lg px-3 py-2 transition-colors hover:bg-white/[0.06] hover:text-white" data-mobile-link>
327 Sources </a> </li> </ul> </div> </header>
327<script> 328 (() => { 329 const toggle = document.querySelector('[data-mobile-toggle]'); 330 const menu = document.getElementById('mobile-menu'); 331 if (toggle && menu) { 332 const close = () => { menu.classList.add('hidden'); toggle.setAttribute('aria-expanded', 'false'); }; 333 const open = () => { menu.classList.remove('hidden'); toggle.setAttribute('aria-expanded', 'true'); }; 334 toggle.addEventListener('click', () => menu.classList.contains('hidden') ? open() : close()); 335 menu.querySelectorAll('[data-mobile-link]').forEach((el) => el.addEventListener('click', close)); 336 } 337 338 const switcher = document.querySelector('[data-lang-switcher]'); 339 if (switcher) { 340 const langToggle = switcher.querySelector('[data-lang-toggle]'); 341 const langMenu = switcher.querySelector('[data-lang-menu]'); 342 const closeLang = () => { langMenu.classList.add('hidden'); langToggle.setAttribute('aria-expanded', 'false'); }; 343 const openLang = () => { langMenu.classList.remove('hidden'); langToggle.setAttribute('aria-expanded', 'true'); }; 344 langToggle.addEventListener('click', (e) => { 345 e.stopPropagation(); 346 langMenu.classList.contains('hidden') ? openLang() : closeLang(); 347 }); 348 document.addEventListener('click', (e) => { 349 if (!switcher.contains(e.target)) closeLang(); 350 }); 351 document.addEventListener('keydown', (e) => { 352 if (e.key === 'Escape') closeLang(); 353 }); 354 } 355 })(); 356</script>
356 <main> <div data-vs-root data-vs-signed-in="false" class="group/vs pb-16 pt-10 sm:pt-14 data-[vs-signed-in=true]:pt-6 sm:data-[vs-signed-in=true]:pt-8" data-astro-cid-zu7mr32p> <div class="container-x" data-astro-cid-zu7mr32p> <nav aria-label="Breadcrumb" class="mb-5 flex flex-wrap items-center gap-2 text-xs text-zinc-500 group-data-[vs-signed-in=true]/vs:mb-3" data-astro-cid-zu7mr32p> <a class="hover:text-zinc-200" href="/" data-astro-cid-zu7mr32p>Home</a> <span aria-hidden="true" data-astro-cid-zu7mr32p>/</span> <span class="text-zinc-300" data-astro-cid-zu7mr32p>Video Studio</span> </nav> <header class="reveal mb-8 max-w-3xl group-data-[vs-signed-in=true]/vs:mb-5" data-astro-cid-zu7mr32p> <span class="chip mb-4 border-violet-400/30 text-violet-200 group-data-[vs-signed-in=true]/vs:hidden" data-astro-cid-zu7mr32p>Tool · Video generation</span> <h1 class="font-display text-3xl font-semibold tracking-tight text-white sm:text-4xl group-data-[vs-signed-in=true]/vs:text-2xl group-data-[vs-signed-in=true]/vs:sm:text-3xl" data-astro-cid-zu7mr32p> <span class="text-gradient" data-astro-cid-zu7mr32p>Video Studio</span> </h1> <p class="mt-3 text-sm leading-relaxed text-zinc-400 sm:text-base group-data-[vs-signed-in=true]/vs:text-sm" data-astro-cid-zu7mr32p>Text-to-video or image-to-video with resolution, aspect ratio and duration controls plus live credit estimates.</p> </header> <div class="grid gap-6 lg:grid-cols-[1fr_1fr] lg:gap-8" data-astro-cid-zu7mr32p> <!-- Left: Controls --> <div class="flex flex-col gap-5 rounded-3xl border border-white/10 bg-white/[0.03] p-4 backdrop-blur-sm sm:p-6" data-vs-controls data-astro-cid-zu7mr32p> <!-- Mode tabs --> <div class="flex gap-1 rounded-2xl border border-white/10 bg-ink-950/40 p-1" role="tablist" data-astro-cid-zu7mr32p> <button type="button" role="tab" aria-selected="true" data-vs-mode="text" class="vs-mode-tab flex-1 rounded-xl px-3 py-2 text-sm font-semibold transition-colors aria-selected:bg-violet-500/20 aria-selected:text-violet-100 text-zinc-400 hover:text-zinc-200" data-astro-cid-zu7mr32p> Text to video </button> <button type="button" role="tab" aria-selected="false" data-vs-mode="image" class="vs-mode-tab flex-1 rounded-xl px-3 py-2 text-sm font-semibold transition-colors aria-selected:bg-violet-500/20 aria-selected:text-violet-100 text-zinc-400 hover:text-zinc-200" data-astro-cid-zu7mr32p> Image to video </button> </div> <!-- Image upload (image mode only) --> <div data-vs-image-panel hidden class="flex flex-col gap-3" data-astro-cid-zu7mr32p> <div class="flex gap-1 rounded-xl border border-white/10 bg-ink-950/40 p-1" data-astro-cid-zu7mr32p> <button type="button" data-vs-image-submode="frames" class="vs-img-submode flex-1 rounded-lg px-2 py-1.5 text-xs font-semibold transition-colors aria-selected:bg-white/10 aria-selected:text-white text-zinc-400" aria-selected="true" data-astro-cid-zu7mr32p> First frame / Last frame </button> <button type="button" data-vs-image-submode="reference" class="vs-img-submode flex-1 rounded-lg px-2 py-1.5 text-xs font-semibold transition-colors aria-selected:bg-white/10 aria-selected:text-white text-zinc-400" aria-selected="false" data-astro-cid-zu7mr32p> Reference </button> </div> <div data-vs-frames-panel class="flex flex-col gap-3" data-astro-cid-zu7mr32p> <p class="text-xs text-zinc-500" data-astro-cid-zu7mr32p>Upload a first frame (required), optional last frame. Frames and reference images are mutually exclusive.</p> <div class="grid grid-cols-2 gap-3" data-astro-cid-zu7mr32p> <div data-vs-slot="first_frame" class="vs-image-slot" data-astro-cid-zu7mr32p> <label class="flex cursor-pointer flex-col items-center gap-2 rounded-2xl border border-dashed border-white/15 bg-ink-950/50 px-3 py-5 transition-colors hover:border-violet-400/40" data-astro-cid-zu7mr32p> <span class="text-xs font-semibold text-zinc-300" data-astro-cid-zu7mr32p>First frame</span> <span data-vs-slot-preview class="hidden h-20 w-full overflow-hidden rounded-lg bg-black/40" data-astro-cid-zu7mr32p> <img alt="" class="h-full w-full object-cover" data-astro-cid-zu7mr32p> </span> <span data-vs-slot-placeholder data-vs-slot-placeholder-label="Upload image" class="text-[11px] text-zinc-500" data-astro-cid-zu7mr32p>Upload image</span> <input type="file" accept="image/*" class="hidden" data-vs-file-input data-astro-cid-zu7mr32p> </label> <button type="button" data-vs-slot-remove hidden class="mt-1 text-[11px] text-rose-300 hover:text-rose-200" data-astro-cid-zu7mr32p> Remove </button> </div> <div data-vs-slot="last_frame" class="vs-image-slot" data-astro-cid-zu7mr32p> <label class="flex cursor-pointer flex-col items-center gap-2 rounded-2xl border border-dashed border-white/15 bg-ink-950/50 px-3 py-5 transition-colors hover:border-violet-400/40" data-astro-cid-zu7mr32p> <span class="text-xs font-semibold text-zinc-300" data-astro-cid-zu7mr32p>Last frame</span> <span data-vs-slot-preview class="hidden h-20 w-full overflow-hidden rounded-lg bg-black/40" data-astro-cid-zu7mr32p> <img alt="" class="h-full w-full object-cover" data-astro-cid-zu7mr32p> </span> <span data-vs-slot-placeholder data-vs-slot-placeholder-label="Upload image" class="text-[11px] text-zinc-500" data-astro-cid-zu7mr32p>Upload image</span> <input type="file" accept="image/*" class="hidden" data-vs-file-input data-astro-cid-zu7mr32p> </label> <button type="button" data-vs-slot-remove hidden class="mt-1 text-[11px] text-rose-300 hover:text-rose-200" data-astro-cid-zu7mr32p> Remove </button> </div> </div> </div> <div data-vs-reference-panel hidden class="flex flex-col gap-3" data-astro-cid-zu7mr32p> <p class="text-xs text-zinc-500" data-astro-cid-zu7mr32p>Up to 3 reference images. Cannot combine with first/last frame mode.</p> <div class="grid grid-cols-3 gap-2" data-vs-ref-grid data-astro-cid-zu7mr32p> <div data-vs-ref-slot="0" class="vs-ref-slot" data-astro-cid-zu7mr32p> <label class="flex aspect-square cursor-pointer flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-white/15 bg-ink-950/50 p-2 transition-colors hover:border-violet-400/40" data-astro-cid-zu7mr32p> <span data-vs-slot-preview class="hidden h-full w-full overflow-hidden rounded-lg" data-astro-cid-zu7mr32p> <img alt="" class="h-full w-full object-cover" data-astro-cid-zu7mr32p> </span> <span data-vs-slot-placeholder data-vs-slot-placeholder-label="Upload image" class="text-[10px] text-zinc-500" data-astro-cid-zu7mr32p>Upload image</span> <input type="file" accept="image/*" class="hidden" data-vs-file-input data-astro-cid-zu7mr32p> </label> <button type="button" data-vs-slot-remove hidden class="mt-0.5 w-full text-center text-[10px] text-rose-300 hover:text-rose-200" data-astro-cid-zu7mr32p> Remove </button> </div><div data-vs-ref-slot="1" class="vs-ref-slot" data-astro-cid-zu7mr32p> <label class="flex aspect-square cursor-pointer flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-white/15 bg-ink-950/50 p-2 transition-colors hover:border-violet-400/40" data-astro-cid-zu7mr32p> <span data-vs-slot-preview class="hidden h-full w-full overflow-hidden rounded-lg" data-astro-cid-zu7mr32p> <img alt="" class="h-full w-full object-cover" data-astro-cid-zu7mr32p> </span> <span data-vs-slot-placeholder data-vs-slot-placeholder-label="Upload image" class="text-[10px] text-zinc-500" data-astro-cid-zu7mr32p>Upload image</span> <input type="file" accept="image/*" class="hidden" data-vs-file-input data-astro-cid-zu7mr32p> </label> <button type="button" data-vs-slot-remove hidden class="mt-0.5 w-full text-center text-[10px] text-rose-300 hover:text-rose-200" data-astro-cid-zu7mr32p> Remove </button> </div><div data-vs-ref-slot="2" class="vs-ref-slot" data-astro-cid-zu7mr32p> <label class="flex aspect-square cursor-pointer flex-col items-center justify-center gap-1 rounded-xl border border-dashed border-white/15 bg-ink-950/50 p-2 transition-colors hover:border-violet-400/40" data-astro-cid-zu7mr32p> <span data-vs-slot-preview class="hidden h-full w-full overflow-hidden rounded-lg" data-astro-cid-zu7mr32p> <img alt="" class="h-full w-full object-cover" data-astro-cid-zu7mr32p> </span> <span data-vs-slot-placeholder data-vs-slot-placeholder-label="Upload image" class="text-[10px] text-zinc-500" data-astro-cid-zu7mr32p>Upload image</span> <input type="file" accept="image/*" class="hidden" data-vs-file-input data-astro-cid-zu7mr32p> </label> <button type="button" data-vs-slot-remove hidden class="mt-0.5 w-full text-center text-[10px] text-rose-300 hover:text-rose-200" data-astro-cid-zu7mr32p> Remove </button> </div> </div> </div> </div> <!-- Prompt --> <div class="flex flex-col gap-2" data-astro-cid-zu7mr32p> <label for="vs-prompt" class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400" data-astro-cid-zu7mr32p> Video prompt </label> <textarea id="vs-prompt" data-vs-prompt rows="6" maxlength="8000" placeholder="Describe the scene, camera motion, lighting, style and moodâ¦" class="min-h-[8rem] w-full resize-y rounded-2xl border border-white/10 bg-ink-950/60 px-4 py-3 text-sm leading-relaxed text-white outline-none ring-2 ring-transparent transition-all placeholder:text-zinc-500 focus:border-violet-400/40 focu
356s:ring-violet-400/20" data-astro-cid-zu7mr32p></textarea> <div class="flex flex-wrap items-start justify-between gap-2" data-astro-cid-zu7mr32p> <p class="text-xs text-zinc-500" data-astro-cid-zu7mr32p>Up to 8000 characters. More detail usually means more stable output.</p> <span data-vs-counter class="shrink-0 font-mono text-[11px] tabular-nums text-zinc-500" data-astro-cid-zu7mr32p>0 / 8000</span> </div> </div> <!-- Examples --> <div data-astro-cid-zu7mr32p> <p class="mb-2 text-xs font-semibold text-zinc-400" data-astro-cid-zu7mr32p>Quick examples</p> <div class="flex flex-wrap gap-2" data-astro-cid-zu7mr32p> <button type="button" data-vs-example data-vs-example-prompt="Premium skincare on a marble surface, soft side light, slow push-in on product details, shallow depth of field, cinematic 4K ad look." class="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-zinc-300 transition-colors hover:border-violet-400/40 hover:text-white" data-astro-cid-zu7mr32p> Product showcase </button><button type="button" data-vs-example data-vs-example-prompt="Morning mist rising in a mountain valley, slow drone fly-through the clouds, golden sunlight through trees, epic documentary style." class="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-zinc-300 transition-colors hover:border-violet-400/40 hover:text-white" data-astro-cid-zu7mr32p> Nature landscape </button><button type="button" data-vs-example data-vs-example-prompt="Fashion creator walking through a city street, tracking shot, natural light, shallow DOF, vertical social-media pacing and energy." class="rounded-full border border-white/10 bg-white/[0.04] px-3 py-1.5 text-xs text-zinc-300 transition-colors hover:border-violet-400/40 hover:text-white" data-astro-cid-zu7mr32p> Portrait vertical </button> </div> </div> <!-- Aspect ratio --> <div class="flex flex-col gap-2" data-astro-cid-zu7mr32p> <span class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400" data-astro-cid-zu7mr32p>Aspect ratio</span> <div class="flex gap-2" data-astro-cid-zu7mr32p> <button type="button" data-vs-ratio="16:9" aria-pressed="true" class="vs-ratio-btn flex-1 rounded-xl border border-violet-400/40 bg-violet-500/10 px-3 py-2 text-sm font-semibold text-violet-100 transition-colors" data-astro-cid-zu7mr32p> 16:9 landscape </button> <button type="button" data-vs-ratio="9:16" aria-pressed="false" class="vs-ratio-btn flex-1 rounded-xl border border-white/10 bg-white/[0.04] px-3 py-2 text-sm font-semibold text-zinc-300 transition-colors hover:border-violet-400/40" data-astro-cid-zu7mr32p> 9:16 portrait </button> </div> </div> <!-- Resolution --> <div class="flex flex-col gap-2" data-astro-cid-zu7mr32p> <span class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400" data-astro-cid-zu7mr32p>Resolution</span> <div class="flex gap-2" data-astro-cid-zu7mr32p> <button type="button" data-vs-resolution="720P" aria-pressed="true" class="vs-res-btn flex-1 rounded-xl border px-3 py-2 text-sm font-semibold transition-colors border-violet-400/40 bg-violet-500/10 text-violet-100" data-astro-cid-zu7mr32p> 720P </button><button type="button" data-vs-resolution="1080P" aria-pressed="false" class="vs-res-btn flex-1 rounded-xl border px-3 py-2 text-sm font-semibold transition-colors border-white/10 bg-white/[0.04] text-zinc-300 hover:border-violet-400/40" data-astro-cid-zu7mr32p> 1080P </button><button type="button" data-vs-resolution="4K" aria-pressed="false" class="vs-res-btn flex-1 rounded-xl border px-3 py-2 text-sm font-semibold transition-colors border-white/10 bg-white/[0.04] text-zinc-300 hover:border-violet-400/40" data-astro-cid-zu7mr32p> 4K </button> </div> </div> <!-- Duration --> <div class="flex flex-col gap-2" data-astro-cid-zu7mr32p> <div class="flex items-center justify-between" data-astro-cid-zu7mr32p> <span class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400" data-astro-cid-zu7mr32p>Duration</span> <span data-vs-duration-label class="text-sm font-semibold tabular-nums text-violet-200" data-astro-cid-zu7mr32p> 5s </span> </div> <input type="range" data-vs-duration min="3" max="10" step="1" value="5" class="w-full accent-violet-400" data-astro-cid-zu7mr32p> <div class="flex justify-between text-[10px] text-zinc-600" data-astro-cid-zu7mr32p> <span data-astro-cid-zu7mr32p>3s</span><span data-astro-cid-zu7mr32p>4s</span><span data-astro-cid-zu7mr32p>5s</span><span data-astro-cid-zu7mr32p>6s</span><span data-astro-cid-zu7mr32p>7s</span><span data-astro-cid-zu7mr32p>8s</span><span data-astro-cid-zu7mr32p>9s</span><span data-astro-cid-zu7mr32p>10s</span> </div> </div> <!-- Cost --> <div class="rounded-2xl border border-white/10 bg-ink-950/50 px-4 py-3" data-astro-cid-zu7mr32p> <div class="flex items-center justify-between text-sm" data-astro-cid-zu7mr32p> <span class="text-zinc-400" data-astro-cid-zu7mr32p>Estimated cost</span> <span data-vs-cost-estimate class="font-semibold tabular-nums text-violet-200" data-astro-cid-zu7mr32p> 50 credits </span> </div> <p data-vs-cost-rate class="mt-1 text-[11px] text-zinc-500" data-astro-cid-zu7mr32p> 10 credits/sec </p> <p data-vs-cost-actual hidden class="mt-1 text-[11px] text-zinc-400" data-astro-cid-zu7mr32p></p> <div class="mt-2 flex items-center justify-between border-t border-white/5 pt-2 text-sm" data-astro-cid-zu7mr32p> <span class="text-zinc-400" data-astro-cid-zu7mr32p>Balance</span> <span data-vs-balance class="font-semibold tabular-nums text-zinc-200" data-astro-cid-zu7mr32p>â</span> </div> </div> <p data-vs-error class="hidden text-sm text-rose-300" data-astro-cid-zu7mr32p></p> <button type="button" data-vs-generate class="btn-primary w-full justify-center py-3 text-base font-semibold disabled:cursor-not-allowed disabled:opacity-60" data-astro-cid-zu7mr32p> <span data-vs-generate-label data-astro-cid-zu7mr32p>Generate video</span> </button> </div> <!-- Right: Monitor -->
356 <div class="flex flex-col gap-4 rounded-3xl border border-white/10 bg-white/[0.03] p-4 backdrop-blur-sm sm:p-6" data-vs-monitor data-astro-cid-zu7mr32p> <h2 class="font-display text-base font-semibold text-white" data-astro-cid-zu7mr32p>Generation monitor</h2> <div data-vs-monitor-empty class="flex flex-1 flex-col items-center justify-center gap-3 rounded-2xl border border-dashed border-white/10 bg-ink-950/40 px-6 py-16 text-center" data-astro-cid-zu7mr32p> <svg viewBox="0 0 24 24" class="h-10 w-10 text-zinc-600" aria-hidden="true" data-astro-cid-zu7mr32p> <path fill="none" stroke="currentColor" stroke-width="1.5" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" data-astro-cid-zu7mr32p></path> </svg> <p class="text-sm text-zinc-500" data-astro-cid-zu7mr32p>Task progress and preview appear here after you submit</p> </div> <div data-vs-monitor-active hidden class="flex flex-col gap-4" data-astro-cid-zu7mr32p> <div class="flex flex-wrap items-center gap-2" data-astro-cid-zu7mr32p> <span data-vs-status-badge class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold" data-astro-cid-zu7mr32p></span> <span data-vs-poll-indicator class="hidden text-xs text-zinc-500" data-astro-cid-zu7mr32p></span> </div> <dl class="grid gap-2 text-xs text-zinc-400" data-astro-cid-zu7mr32p> <div class="flex justify-between gap-4" data-astro-cid-zu7mr32p> <dt data-astro-cid-zu7mr32p>Task ID</dt> <dd data-vs-task-id class="truncate font-mono text-zinc-300" data-astro-cid-zu7mr32p></dd> </div> <div class="flex justify-between gap-4" data-astro-cid-zu7mr32p> <dt data-astro-cid-zu7mr32p>Elapsed</dt> <dd data-vs-elapsed class="tabular-nums text-zinc-300" data-astro-cid-zu7mr32p>0:00</dd> </div> <div class="flex justify-between gap-4" data-astro-cid-zu7mr32p> <dt data-astro-cid-zu7mr32p>Est. remaining</dt> <dd data-vs-eta class="tabular-nums text-zinc-300" data-astro-cid-zu7mr32p>â</dd> </div> </dl> <div data-vs-progress-wrap hidden class="h-1.5 overflow-hidden rounded-full bg-white/10" data-astro-cid-zu7mr32p> <div data-vs-progress-bar class="h-full rounded-full bg-violet-400 transition-all duration-500" style="width: 0%" data-astro-cid-zu7mr32p></div> </div> <div data-vs-video-wrap hidden class="overflow-hidden rounded-2xl border border-white/10 bg-black" data-astro-cid-zu7mr32p> <video data-vs-video controls playsinline class="aspect-video w-full data-[portrait=true]:aspect-[9/16]" data-astro-cid-zu7mr32p></video> </div> <p data-vs-failed-hint hidden class="rounded-xl border border-rose-400/30 bg-rose-500/10 px-4 py-3 text-sm text-rose-200" data-astro-cid-zu7mr32p></p> <button type="button" data-vs-retry hidden class="btn-ghost inline-flex w-full justify-center" data-astro-cid-zu7mr32p> Retry </button> <button type="button" data-vs-download hidden class="btn-ghost inline-flex w-full justify-center gap-2" data-astro-cid-zu7mr32p> <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true" data-astro-cid-zu7mr32p> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M12 3v12m0 0l4-4m-4 4l-4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2" data-astro-cid-zu7mr32p></path> </svg> <span data-vs-download-label data-astro-cid-zu7mr32p>Download MP4</span> </button> </div> </div> </div> <!-- Bottom tabs --> <div class="mt-10" data-astro-cid-zu7mr32p> <div class="flex gap-1 border-b border-white/10" role="tablist" data-astro-cid-zu7mr32p> <button type="button" role="tab" aria-selected="true" data-vs-bottom-tab="history" class="vs-bottom-tab px-4 py-2.5 text-sm font-semibold transition-colors aria-selected:border-b-2 aria-selected:border-violet-400 aria-selected:text-white text-zinc-400 hover:text-zinc-200" data-astro-cid-zu7mr32p> History </button> <button type="button" role="tab" aria-selected="false" data-vs-bottom-tab="ledger" class="vs-bottom-tab px-4 py-2.5 text-sm font-semibold transition-colors aria-selected:border-b-2 aria-selected:border-violet-400 aria-selected:text-white text-zinc-400 hover:text-zinc-200" data-astro-cid-zu7mr32p> Credit ledger </button> </div> <div data-vs-panel-history class="pt-6" data-astro-cid-zu7mr32p> <div data-vs-history-list class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3" data-astro-cid-zu7mr32p></div> <p data-vs-history-empty hidden class="py-8 text-center text-sm text-zinc-500" data-astro-cid-zu7mr32p>No generation history yet</p> <div data-vs-history-pager hidden class="mt-6 flex items-center justify-center gap-3" data-astro-cid-zu7mr32p> <button type="button" data-vs-history-prev class="btn-ghost text-sm" data-astro-cid-zu7mr32p>Previous</button> <span data-vs-history-page-info class="text-xs text-zinc-400" data-astro-cid-zu7mr32p></span> <button type="button" data-vs-history-next class="btn-ghost text-sm" data-astro-cid-zu7mr32p>Next</button> </div> </div> <div data-vs-panel-ledger hidden class="pt-6" data-astro-cid-zu7mr32p> <div class="overflow-x-auto rounded-2xl border border-white/10" data-astro-cid-zu7mr32p> <table class="w-full min-w-[480px] text-left text-sm" data-astro-cid-zu7mr32p> <thead class="border-b border-white/10 bg-white/[0.03] text-xs uppercase tracking-wider text-zinc-500" data-astro-cid-zu7mr32p> <tr data-astro-cid-zu7mr32p> <th class="px-4 py-3" data-astro-cid-zu7mr32p>Time</th> <th class="px-4 py-3" data-astro-cid-zu7mr32p>Type</th> <th class="px-4 py-3" data-astro-cid-zu7mr32p>Change</th> <th class="px-4 py-3" data-astro-cid-zu7mr32p>Balance</th> </tr> </thead> <tbody data-vs-ledger-body class="divide-y divide-white/5 text-zinc-300" data-astro-cid-zu7mr32p></tbody> </table> </div> <p data-vs-ledger-empty hidden class="py-8 text-center text-sm text-zinc-500" data-astro-cid-zu7mr32p>No credit transactions yet</p> <div data-vs-ledger-pager hidden class="mt-6 flex items-center justify-center gap-3" data-astro-cid-zu7mr32p> <button type="button" data-vs-ledger-prev class="btn-ghost text-sm" data-astro-cid-zu7mr32p>Previous</button> <span data-vs-ledger-page-info class="text-xs text-zinc-400" data-astro-cid-zu7mr32p></span> <button type="button" data-vs-ledger-next class="btn-ghost text-sm" data-astro-cid-zu7mr32p>Next</button> </div> </div> </div> </div> </div>
356<script type="application/json" id="vs-i18n-data">{"loginRequired":"Sign in before generating a video","emptyPrompt":"Enter a video prompt first","genericError":"Something went wrong. Please try again.","insufficient":"Not enough credits â please top up","needFirst":"Image-to-video requires a first frame upload","uploadError":"Image upload failed. Please try again.","maxReference":"Reference limit reached (3 images)","failedHint":"Generation failed. Credits were refunded automatically.","canceledHint":"Generation was canceled.","timeout":"Timed out. Check history later for the final status.","historyEmpty":"No generation history yet","ledgerEmpty":"No credit transactions yet","monitorEmpty":"Task progress and preview appear here after you submit","polling":"Pollingâ¦","paused":"Tab hidden â polling paused","retry":"Retry","costPoints":"{n} credits","costRate":"{rate} credits/sec","costActual":"Billed: {n} credits","durationSec":"{n}s","historyCost":"{n} credits used","historyPage":"Page {page} of {total}","ledgerPage":"Page {page} of {total}","statusQueued":"Queued","statusProcessing":"Processing","statusSuccess":"Completed","statusFailed":"Failed","statusCanceled":"Canceled","generating":"Submittingâ¦","generate":"Generate video","historyView":"View","historyDownload":"Download","historyMonitor":"Monitor","imageUploading":"Uploadingâ¦","imageUpload":"Upload image","needReference":"Reference mode needs at least one image","errorType":"Please upload an image file","errorSize":"Image must be 10 MB or smaller","openVideo":"Open video","download":"Download MP4"}</script>
356
356<script type="module" src="/_astro/VideoStudio.astro_astro_type_script_index_0_lang.D6vDC_mI.js"></script>
356 </main> <footer class="mt-32 border-t border-white/5"> <div class="container-x grid gap-10 py-12 lg:grid-cols-[1.6fr_1fr_1fr]"> <div> <a href="/" class="inline-flex items-center gap-2.5 font-display text-base font-semibold text-white"> <span class="grid h-7 w-7 place-items-center rounded-lg bg-brand-gradient text-ink-950 shadow-glow"> <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="currentColor" d="M12 2l1.7 7.3L21 11l-7.3 1.7L12 20l-1.7-7.3L3 11l7.3-1.7L12 2z"></path> </svg> </span> <span>Gemini Omni</span> </a> <p class="mt-4 max-w-md text-sm leading-relaxed text-zinc-500">Unofficial info hub. Content is aggregated from public reporting and community leaks. Gemini, Omni and related marks belong to Google.</p> <p class="mt-4 text-sm text-zinc-400"> <span class="text-zinc-500">Contact:</span> <a class="ml-1 text-white hover:underline" href="mailto:[email protected]">[email protected]</a> </p> </div> <div> <h2 class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400">Quick links</h2> <ul class="mt-4 grid grid-cols-2 gap-y-2 text-sm text-zinc-400"> <li><a class="hover:text-white" href="/#showcase">Demos</a></li> <li><a class="hover:text-white" href="/#features">Features</a></li> <li><a class="hover:text-white" href="/#specs">Specs</a></li> <li><a class="hover:text-white" href="/#architecture">Architecture</a></li> <li><a class="hover:text-white" href="/#cases">Use cases</a></li> <li><a class="hover:text-white" href="/#comparison">Compare</a></li> <li><a class="hover:text-white" href="/#free">Free vs paid</a></li> <li><a class="hover:text-white" href="/#timeline">Timeline</a></li> <li><a class="hover:text-white" href="/prompt-generator/">Prompt builder</a></li> <li><a class="hover:text-white" href="/video-studio/">Video Studio</a></li> <li><a class="hover:text-white" href="/pricing/">Pricing</a></li> <li><a class="hover:text-white" href="/blog/">Articles</a></li> <li><a class="hover:text-white" href="/#faq">FAQ</a></li> <li><a class="hover:text-white" href="/#sources">Sources</a></li> </ul> </div> <div> <h2 class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400">Language</h2> <ul class="mt-4 grid max-h-72 grid-cols-2 gap-y-2 overflow-y-auto overscroll-contain pr-2 text-sm text-zinc-400 sm:grid-cols-3 sm:max-h-64"> <li> <a class="hover:text-white text-white font-medium" href="/video-studio" hreflang="en"> English </a> </li><li> <a class="hover:text-white " href="/es/video-studio" hreflang="es"> Español </a> </li><li> <a class="hover:text-white " href="/it/video-studio" hreflang="it"> Italiano </a> </li><li> <a class="hover:text-white " href="/hi/video-studio" hreflang="hi"> हिनà¥à¤¦à¥ </a> </li><li> <a class="hover:text-white " href="/ar/video-studio" hreflang="ar"> Ø§ÙØ¹Ø±Ø¨ÙØ© </a> </li><li> <a class="hover:text-white " href="/zh/video-studio" hreflang="zh"> 䏿 </a> </li><li> <a class="hover:text-white " href="/ja/video-studio" hreflang="ja"> æ¥æ¬èª </a> </li><li> <a class="hover:text-white " href="/ko/video-studio" hreflang="ko"> íêµì´ </a> </li><li> <a class="hover:text-white " href="/fr/video-studio" hreflang="fr"> Français </a> </li><li> <a class="hover:text-white " href="/de/video-studio" hreflang="de"> Deutsch </a> </li><li> <a class="hover:text-white " href="/nl/video-studio" hreflang="nl"> Nederlands </a> </li><li> <a class="hover:text-white " href="/pl/video-studio" hreflang="pl"> Polski </a> </li><li> <a class="hover:text-white " href="/pt-BR/video-studio" hreflang="pt-BR"> Português </a> </li><li> <a class="hover:text-white " href="/ru/video-studio" hreflang="ru"> Ð ÑÑÑкий </a> </li><li> <a class="hover:text-white " href="/tr/video-studio" hreflang="tr"> Türkçe </a> </li><li> <a class="hover:text-white " href="/id/video-studio" hreflang="id"> Bahasa Indonesia </a> </li><li> <a class="hover:text-white " href="/vi/video-studio" hreflang="vi"> Tiếng Viá»t </a> </li><li> <a class="hover:text-white " href="/th/video-studio" hreflang="th"> à¹à¸à¸¢ </a> </li><li>
356 <a class="hover:text-white " href="/ms/video-studio" hreflang="ms"> Bahasa Melayu </a> </li><li> <a class="hover:text-white " href="/tl/video-studio" hreflang="tl"> Filipino </a> </li><li> <a class="hover:text-white " href="/bn/video-studio" hreflang="bn"> বাà¦à¦²à¦¾ </a> </li><li> <a class="hover:text-white " href="/ta/video-studio" hreflang="ta"> தமிழ௠</a> </li><li> <a class="hover:text-white " href="/te/video-studio" hreflang="te"> à°¤à±à°²à±à°à± </a> </li><li> <a class="hover:text-white " href="/mr/video-studio" hreflang="mr"> मराठॠ</a> </li><li> <a class="hover:text-white " href="/he/video-studio" hreflang="he"> ×¢×ר×ת </a> </li><li> <a class="hover:text-white " href="/fa/video-studio" hreflang="fa"> ÙØ§Ø±Ø³Û </a> </li><li> <a class="hover:text-white " href="/ur/video-studio" hreflang="ur"> ارد٠</a> </li> </ul> </div> </div> <div class="border-t border-white/5"> <div class="container-x flex flex-col gap-3 py-5 text-xs text-zinc-500 sm:flex-row sm:items-center sm:justify-between"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4"> <p>© 2026 Gemini Omni Info Hub</p> <nav class="flex flex-wrap gap-x-4 gap-y-1" aria-label="Legal"> <a class="hover:text-white" href="/terms/">Terms of Service</a> <a class="hover:text-white" href="/privacy/">Privacy Policy</a> </nav> </div> <p>Built with Astro · Tailwind</p> </div> </div> </footer> <dialog id="login-modal" class="hidden w-[min(440px,calc(100vw-24px))] rounded-3xl border border-white/10 bg-ink-900/95 p-0 text-zinc-100 shadow-glow backdrop:bg-ink-950/70 backdrop:backdrop-blur-md open:flex open:flex-col" aria-labelledby="login-modal-title"> <div class="relative max-h-[min(720px,90vh)] overflow-y-auto px-7 pb-8 pt-9 sm:px-9"> <button type="button" data-close aria-label="Close" class="absolute right-3 top-3 grid h-9 w-9 place-items-center rounded-full border border-white/10 bg-white/[0.04] text-zinc-300 transition-colors hover:border-violet-400/40 hover:text-white"> <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" d="M6 6l12 12M18 6L6 18"></path> </svg> </button> <div data-state="form" class="flex flex-col gap-6"> <header class="flex flex-col items-center text-center"> <span class="mb-4 grid h-14 w-14 place-items-center rounded-2xl bg-brand-gradient shadow-glow"> <img src="/favicon.svg" alt="Gemini Omni" class="h-9 w-9" width="36" height="36"> </span> <h2 id="login-modal-title" class="font-display text-2xl font-semibold tracking-tight text-white"> Be first when Gemini Omni opens up </h2> <p class="mt-2 max-w-xs text-sm leading-relaxed text-zinc-400"> Sign in to unlock the prompt builder and credits. Omni 1.1 Flash is production-ready for developers (gemini-omni-1.1-flash). </p> </header> <div class="flex flex-col gap-3"> <div id="gsi-button-container" data-locale="en" data-google-failed="Google sign-in failed. Please try again." class="flex min-h-[44px] items-center justify-center"></div> </div> <div class="relative"> <div class="absolute inset-0 flex items-center" aria-hidden="true"> <div class="w-full border-t border-white/10"></div> </div> <div class="relative flex justify-center"> <span class="bg-ink-900/95 px-3 text-xs uppercase tracking-[0.25em] text-zinc-500"> or </span> </div> </div> <div data-step="email" class="flex flex-col gap-3"> <label class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400" for="login-email"> Email </label> <input id="login-email" type="email" autocomplete="email" inputmode="email" placeholder="[email protected]" class="w-full rounded-xl border border-white/10 bg-white/[0.04] px-4 py-3 text-sm text-white outline-none ring-2 ring-transparent transition-all placeholder:text-zinc-500 focus:border-violet-400/40 focus:ring-violet-400/20"> <button type="button" data-action="send-code" data-invalid-email="Please enter a valid email address." data-send-failed="Failed to send code. Please try again." class="btn-primary w-full justify-center"> <span data-label-idle>Send code</span> <span data-label-loading hidden class="flex items-center gap-2"> <span class="inline-block h-3.5 w-3.5 animate-spin rounded-full border-2 border-white/70 border-t-transparent"></span> Sending⦠</span> </button> </div> <div data-step="code" hidden class="flex flex-col gap-3"> <div class="flex items-center justify-between"> <label class="text-xs font-semibold uppercase tracking-[0.18em] text-zinc-400" for="login-code">
356 Verification code </label> <button type="button" data-action="change-email" class="text-xs font-semibold text-violet-300 transition-colors hover:text-violet-200"> Change email </button> </div> <p class="rounded-xl border border-white/5 bg-white/[0.03] px-3 py-2 text-xs text-zinc-400"> <span>We sent a 6-digit code to</span> <span data-code-email class="ml-1 font-semibold text-zinc-100"></span> </p> <input id="login-code" type="text" inputmode="numeric" autocomplete="one-time-code" maxlength="6" placeholder="000000" class="w-full rounded-xl border border-white/10 bg-white/[0.04] px-4 py-4 text-center text-2xl font-semibold tracking-[0.6em] text-white outline-none ring-2 ring-transparent transition-all placeholder:text-zinc-600 focus:border-violet-400/40 focus:ring-violet-400/20"> <button type="button" data-action="verify" data-verify-failed="Invalid or expired code." class="btn-primary w-full justify-center"> <span data-label-idle>Verify & sign in</span> <span data-label-loading hidden class="flex items-center gap-2"> <span class="inline-block h-3.5 w-3.5 animate-spin rounded-full border-2 border-white/70 border-t-transparent"></span> Verifying⦠</span> </button> <div class="text-center text-xs text-zinc-400"> <button type="button" data-action="resend" data-template="Resend in {n}s" class="font-semibold text-violet-300 transition-colors hover:text-violet-200 disabled:cursor-not-allowed disabled:text-zinc-500 disabled:hover:text-zinc-500"> <span data-resend-idle>Resend code</span> <span data-resend-countdown hidden></span> </button> </div> </div> <p data-error hidden class="rounded-xl border border-rose-500/30 bg-rose-500/10 px-3 py-2 text-sm text-rose-200"></p> <p class="text-center text-xs leading-relaxed text-zinc-500"> By signing in, you agree to our <a href="/terms/" class="text-zinc-300 underline decoration-zinc-600 underline-offset-2 hover:text-white">Terms of Service</a> and <a href="/privacy/" class="text-zinc-300 underline decoration-zinc-600 underline-offset-2 hover:text-white">Privacy Policy</a> </p> </div> <div data-state="success" hidden class="flex flex-col items-center gap-5 py-4 text-center"> <span class="grid h-16 w-16 place-items-center rounded-full bg-emerald-500/15 text-emerald-300 ring-1 ring-emerald-400/40"> <svg viewBox="0 0 24 24" class="h-8 w-8" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" d="M5 12l4.5 4.5L19 7"></path> </svg> </span> <h2 class="font-display text-2xl font-semibold tracking-tight text-white"> You're signed in </h2> <p class="max-w-xs text-sm leading-relaxed text-zinc-400"> You can close this and keep exploring. Try the prompt builder when you are ready. </p> <p data-success-user class="text-sm font-semibold text-zinc-200"></p> <div class="flex w-full flex-col items-stretch gap-2 sm:flex-row sm:justify-center sm:gap-3"> <a href="/prompt-generator/" class="btn-primary justify-center"> Open the prompt builder <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"></path> </svg> </a> <button type="button" data-close class="btn-ghost justify-center"> Close </button> </div> </div> </div> </dialog>
356<script>(function(){const API_BASE = "https://www.gushanai.com/api"; 357const APP_KEY = "gemini_omni_4561"; 358const GOOGLE_CLIENT_ID = "463343176094-ptfq906flrtlronsfq8c4omvui6dtck2.apps.googleusercontent.com"; 359const HTML_LANG = "en"; 360 361 (() => { 362 if (typeof window === 'undefined') return; 363 const dialog = document.getElementById('login-modal'); 364 if (!(dialog instanceof HTMLDialogElement)) return; 365 366 const AUTH_TOKEN_KEY = 'auth_token'; 367 const USER_INFO_KEY = 'user_info'; 368 const AUTH_CHANGED_EVENT = 'gemini-omni:auth-changed'; 369 370 const formRoot = dialog.querySelector('[data-state="form"]'); 371 const successRoot = dialog.querySelector('[data-state="success"]'); 372 const errorEl = dialog.querySelector('[data-error]'); 373 const stepEmail = dialog.querySelector('[data-step="email"]'); 374 const stepCode = dialog.querySelector('[data-step="code"]'); 375 const emailInput = dialog.querySelector('#login-email'); 376 const codeInput = dialog.querySelector('#login-code'); 377 const codeEmailLabel = dialog.querySelector('[data-code-email]'); 378 const successUserEl = dialog.querySelector('[data-success-user]'); 379 const gsiContainer = dialog.querySelector('#gsi-button-container'); 380 381 const sendBtn = dialog.querySelector('[data-action="send-code"]'); 382 const verifyBtn = dialog.querySelector('[data-action="verify"]'); 383 const resendBtn = dialog.querySelector('[data-action="resend"]'); 384 const changeEmailBtn = dialog.querySelector('[data-action="change-email"]'); 385 386 let currentEmail = ''; 387 let countdown = 0; 388 let countdownTimer = null; 389 let gsiLoaded = false; 390 let gsiInitialized = false; 391 392 const isAuthed = () => !!window.localStorage.getItem(AUTH_TOKEN_KEY); 393 394 const showError = (msg) => { 395 if (!errorEl) return; 396 if (!msg) { 397 errorEl.textContent = ''; 398 errorEl.setAttribute('hidden', ''); 399 return; 400 } 401 errorEl.textContent = msg; 402 errorEl.removeAttribute('hidden'); 403 }; 404 405 const setBtnLoading = (btn, loading) => { 406 if (!btn) return; 407 btn.toggleAttribute('disabled', loading); 408 const idle = btn.querySelector('[data-label-idle]'); 409 const busy = btn.querySelector('[data-label-loading]'); 410 if (idle) idle.toggleAttribute('hidden', loading); 411 if (busy) busy.toggleAttribute('hidden', !loading); 412 }; 413 414 const switchStep = (step) => { 415 if (step === 'code') { 416 stepEmail?.setAttribute('hidden', ''); 417 stepCode?.removeAttribute('hidden'); 418 setTimeout(() => codeInput?.focus(), 30); 419 } else { 420 stepCode?.setAttribute('hidden', ''); 421 stepEmail?.removeAttribute('hidden'); 422 setTimeout(() => emailInput?.focus(), 30); 423 } 424 }; 425 426 const stopCountdown = () => { 427 if (countdownTimer) { 428 clearInterval(countdownTimer); 429 countdownTimer = null; 430 } 431 }; 432 433 const updateResendUI = () => { 434 if (!resendBtn) return; 435 const idle = resendBtn.querySelector('[data-resend-idle]'); 436 const cd = resendBtn.querySelector('[data-resend-countdown]'); 437 if (countdown > 0) { 438 resendBtn.setAttribute('disabled', ''); 439 idle?.setAttribute('hidden', ''); 440 if (cd) { 441 cd.removeAttribute('hidden'); 442 cd.textContent = (resendBtn.dataset.template || '{n}s').replace('{n}', String(countdown)); 443 } 444 } else { 445 resendBtn.removeAttribute('disabled'); 446 idle?.removeAttribute('hidden'); 447 cd?.setAttribute('hidden', ''); 448 } 449 }; 450 451 const startCountdown = (seconds) => { 452 stopCountdown(); 453 countdown = seconds; 454 updateResendUI(); 455 countdownTimer = window.setInterval(() => { 456 countdown -= 1; 457 if (countdown <= 0) { 458 countdown = 0; 459 stopCountdown(); 460 } 461 updateResendUI(); 462 }, 1000); 463 }; 464 465 const showSuccess = (user) => { 466 formRoot?.setAttribute('hidden', ''); 467 successRoot?.removeAttribute('hidden'); 468 if (successUserEl) { 469 const name = user?.nickName || user?.email || ''; 470 successUserEl.textContent = name; 471 successUserEl.toggleAttribute('hidden', !name); 472 } 473 }; 474 475 const resetForm = () => { 476 currentEmail = ''; 477 stopCountdown(); 478 countdown = 0; 479 updateResendUI(); 480 if (emailInput) emailInput.value = ''; 481 if (codeInput) codeInput.value = ''; 482 showError(''); 483 switchStep('email'); 484 formRoot?.removeAttribute('hidden'); 485 successRoot?.setAttribute('hidden', ''); 486 }; 487 488 const openDialog = () => { 489 showError(''); 490 if (isAuthed()) { 491 const raw = window.localStorage.getItem(USER_INFO_KEY); 492 let user = null; 493 try { user = raw ? JSON.parse(raw) : null; } catch {} 494 formRoot?.setAttribute('hidden', ''); 495 successRoot?.removeAttribute('hidden'); 496 if (successUserEl) { 497 const name = user?.nickName || user?.email || ''; 498 successUserEl.textContent = name; 499 successUserEl.toggleAttribute('hidden', !name); 500 } 501 } else { 502 resetForm(); 503 } 504 if (!dialog.open) { 505 try { dialog.showModal(); } catch { dialog.setAttribute('open', ''); } 506 } 507 ensureGoogleButton(); 508 }; 509 510 const closeDialog = () => { 511 if (dialog.open) { 512 try { dialog.close(); } catch { dialog.removeAttribute('open'); } 513 } 514 }; 515 516 const apiRequest = async (path, body) => { 517 const headers = { 'Content-Type': 'application/json', 'x-app-key': APP_KEY }; 518 const token = window.localStorage.getItem(AUTH_TOKEN_KEY); 519 if (token) headers.Authorization = token; 520 const res = await fetch(`${API_BASE}${path}`, { 521 method: 'POST', 522 headers, 523 body: JSON.stringify(body || {}), 524 }); 525 let env;
526 try { env = await res.json(); } catch { env = { code: res.status, message: `HTTP ${res.status}` }; } 527 if (!env || (env.code !== 200 && env.code !== 0)) { 528 const err = new Error(env?.message || `HTTP ${res.status}`); 529 err.code = env?.code ?? res.status; 530 throw err; 531 } 532 return env.data; 533 }; 534 535 const finishLogin = (token, user) => { 536 window.localStorage.setItem(AUTH_TOKEN_KEY, token); 537 if (user) window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(user)); 538 window.dispatchEvent(new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token, user } })); 539 showSuccess(user); 540 }; 541 542 const handleGoogleCredential = async (response) => { 543 const idToken = response?.credential; 544 if (!idToken) return; 545 showError(''); 546 try { 547 const data = await apiRequest('/auth/google/web', { idToken }); 548 finishLogin(data.token, data.user || null); 549 try { 550 const fresh = await apiRequest('/mine/info', {}); 551 window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(fresh)); 552 if (successUserEl) { 553 const name = fresh?.nickName || fresh?.email || ''; 554 successUserEl.textContent = name; 555 successUserEl.toggleAttribute('hidden', !name); 556 } 557 } catch {} 558 } catch (err) { 559 showError(err?.message || gsiContainer?.dataset.googleFailed || 'Google sign-in failed'); 560 } 561 }; 562 563 const initGoogle = () => { 564 if (gsiInitialized || !gsiContainer) return; 565 const g = window.google; 566 if (!g?.accounts?.id) return; 567 g.accounts.id.initialize({ 568 client_id: GOOGLE_CLIENT_ID, 569 callback: handleGoogleCredential, 570 ux_mode: 'popup', 571 auto_select: false, 572 }); 573 gsiContainer.innerHTML = ''; 574 g.accounts.id.renderButton(gsiContainer, { 575 theme: 'filled_black', 576 size: 'large', 577 shape: 'pill', 578 text: 'signin_with', 579 logo_alignment: 'left', 580 width: gsiContainer.clientWidth || 320, 581 locale: HTML_LANG, 582 }); 583 gsiInitialized = true; 584 }; 585 586 const ensureGoogleButton = () => { 587 if (!gsiContainer || gsiInitialized) return; 588 if (gsiLoaded) { 589 initGoogle(); 590 return; 591 } 592 const existing = document.querySelector('script[data-gsi-loader]'); 593 if (existing) { 594 existing.addEventListener('load', initGoogle, { once: true }); 595 gsiLoaded = true; 596 return; 597 } 598 const s = document.createElement('script'); 599 s.src = 'https://accounts.google.com/gsi/client'; 600 s.async = true; 601 s.defer = true; 602 s.dataset.gsiLoader = '1'; 603 s.onload = () => { gsiLoaded = true; initGoogle(); }; 604 document.head.appendChild(s); 605 }; 606 607 const isValidEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v); 608 609 sendBtn?.addEventListener('click', async () => { 610 const email = (emailInput?.value || '').trim(); 611 showError(''); 612 if (!email || !isValidEmail(email)) { 613 showError(sendBtn.dataset.invalidEmail || 'Invalid email'); 614 return; 615 } 616 setBtnLoading(sendBtn, true);
617 try { 618 await apiRequest('/auth/email/send-code', { email }); 619 currentEmail = email; 620 if (codeEmailLabel) codeEmailLabel.textContent = email; 621 switchStep('code'); 622 startCountdown(60); 623 } catch (err) { 624 showError(err?.message || sendBtn.dataset.sendFailed || 'Failed to send code'); 625 } finally { 626 setBtnLoading(sendBtn, false); 627 } 628 }); 629 630 verifyBtn?.addEventListener('click', async () => { 631 const code = (codeInput?.value || '').replace(/\D/g, ''); 632 showError(''); 633 if (code.length !== 6) { 634 showError(verifyBtn.dataset.verifyFailed || 'Invalid code'); 635 return; 636 } 637 setBtnLoading(verifyBtn, true); 638 try { 639 const data = await apiRequest('/auth/email/verify', { email: currentEmail, code }); 640 finishLogin(data.token, data.user || null); 641 try { 642 const fresh = await apiRequest('/mine/info', {}); 643 window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(fresh)); 644 if (successUserEl) { 645 const name = fresh?.nickName || fresh?.email || ''; 646 successUserEl.textContent = name; 647 successUserEl.toggleAttribute('hidden', !name); 648 } 649 } catch {} 650 } catch (err) { 651 showError(err?.message || verifyBtn.dataset.verifyFailed || 'Verification failed'); 652 } finally { 653 setBtnLoading(verifyBtn, false); 654 } 655 }); 656 657 resendBtn?.addEventListener('click', async () => { 658 if (countdown > 0 || !currentEmail) return; 659 showError(''); 660 try { 661 await apiRequest('/auth/email/send-code', { email: currentEmail }); 662 startCountdown(60); 663 } catch (err) { 664 showError(err?.message || sendBtn?.dataset.sendFailed || 'Failed to send code'); 665 } 666 }); 667 668 changeEmailBtn?.addEventListener('click', () => { 669 showError(''); 670 stopCountdown(); 671 countdown = 0; 672 updateResendUI(); 673 if (codeInput) codeInput.value = ''; 674 switchStep('email'); 675 }); 676 677 codeInput?.addEventListener('input', (e) => { 678 const target = e.target; 679 if (target instanceof HTMLInputElement) { 680 target.value = target.value.replace(/\D/g, '').slice(0, 6); 681 } 682 }); 683 684 emailInput?.addEventListener('keydown', (e) => { 685 if (e.key === 'Enter') { 686 e.preventDefault(); 687 sendBtn?.click(); 688 } 689 }); 690 691 codeInput?.addEventListener('keydown', (e) => { 692 if (e.key === 'Enter') { 693 e.preventDefault(); 694 verifyBtn?.click(); 695 } 696 }); 697 698 dialog.addEventListener('click', (e) => { 699 const target = e.target; 700 if (target instanceof Element && target.closest('[data-close]')) { 701 e.preventDefault(); 702 closeDialog(); 703 return; 704 } 705 if (target === dialog) { 706 closeDialog(); 707 } 708 }); 709 710 dialog.addEventListener('cancel', (e) => { 711 e.preventDefault(); 712 closeDialog(); 713 }); 714 715 window.addEventListener('open-login', openDialog); 716 717 document.addEventListener('click', (e) => { 718 const target = e.target; 719 if (!(target instanceof Element)) return; 720 const trigger = target.closest('[data-open-login]'); 721 if (trigger) { 722 e.preventDefault(); 723 openDialog(); 724 } 725 }); 726 })(); 727})();</script>
727 <dialog id="recharge-modal" class="hidden w-[min(440px,calc(100vw-24px))] rounded-3xl border border-white/10 bg-ink-900/95 p-0 text-zinc-100 shadow-glow backdrop:bg-ink-950/70 backdrop:backdrop-blur-md open:flex open:flex-col" aria-labelledby="recharge-modal-title"> <div class="relative px-7 pb-8 pt-9 sm:px-9"> <button type="button" data-close aria-label="Not now" class="absolute right-3 top-3 grid h-9 w-9 place-items-center rounded-full border border-white/10 bg-white/[0.04] text-zinc-300 transition-colors hover:border-violet-400/40 hover:text-white"> <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" d="M6 6l12 12M18 6L6 18"></path> </svg> </button> <header class="flex flex-col items-center text-center"> <span class="mb-4 grid h-14 w-14 place-items-center rounded-2xl bg-gradient-to-br from-amber-400/30 via-rose-400/20 to-violet-500/30 ring-1 ring-amber-400/40"> <svg viewBox="0 0 24 24" class="h-7 w-7 text-amber-300" aria-hidden="true"> <path fill="currentColor" d="M13 2L3 14h7l-1 8 10-12h-7l1-8z"></path> </svg> </span> <h2 id="recharge-modal-title" class="font-display text-2xl font-semibold tracking-tight text-white"> You are out of credits </h2> <p class="mt-2 max-w-xs text-sm leading-relaxed text-zinc-400"> Top up your Gemini Omni balance to keep generating prompts and adaptations. </p> </header> <div class="mt-6 flex flex-col gap-3 sm:flex-row"> <button type="button" data-close class="btn-ghost w-full justify-center"> Not now </button> <a href="/pricing/" class="btn-primary w-full justify-center" data-recharge-cta> Go to pricing <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"></path> </svg> </a> </div> </div> </dialog>
727<script> 728 (() => { 729 if (typeof window === 'undefined') return; 730 const dialog = document.getElementById('recharge-modal'); 731 if (!(dialog instanceof HTMLDialogElement)) return; 732 733 const openDialog = () => { 734 if (!dialog.open) { 735 try { 736 dialog.showModal(); 737 } catch { 738 dialog.setAttribute('open', ''); 739 } 740 } 741 }; 742 743 const closeDialog = () => { 744 if (dialog.open) { 745 try { 746 dialog.close(); 747 } catch { 748 dialog.removeAttribute('open'); 749 } 750 } 751 }; 752 753 dialog.addEventListener('click', (e) => { 754 const target = e.target; 755 if (target instanceof Element && target.closest('[data-close]')) { 756 e.preventDefault(); 757 closeDialog(); 758 return; 759 } 760 if (target === dialog) closeDialog(); 761 }); 762 763 dialog.addEventListener('cancel', (e) => { 764 e.preventDefault(); 765 closeDialog(); 766 }); 767 768 window.addEventListener('gemini-omni:open-recharge', openDialog); 769 })(); 770</script>
770
770<script> 771 (() => { 772 if (typeof window === 'undefined') return; 773 const observer = new IntersectionObserver( 774 (entries) => { 775 for (const entry of entries) { 776 if (entry.isIntersecting) { 777 entry.target.classList.add('is-visible'); 778 observer.unobserve(entry.target); 779 } 780 } 781 }, 782 { threshold: 0.12, rootMargin: '0px 0px -40px 0px' } 783 ); 784 const onReady = () => { 785 document.querySelectorAll('.reveal').forEach((el) => observer.observe(el)); 786 }; 787 if (document.readyState === 'loading') { 788 document.addEventListener('DOMContentLoaded', onReady); 789 } else { 790 onReady(); 791 } 792 })(); 793 </script>
793 </body> </html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.