PageSourceSearch

https://www.ai-geminiomni.com/video-studio/

html ai-geminiomni.com collected 2026-10-04 13:25:21 UTC 106,208 bytes, 793 lines download raw bytes

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 &amp; 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 &#38; 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 &#38; 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="{&#34;zh&#34;:&#34;中文&#34;,&#34;en&#34;:&#34;English&#34;,&#34;ja&#34;:&#34;日本語&#34;,&#34;ko&#34;:&#34;한국어&#34;,&#34;es&#34;:&#34;Español&#34;,&#34;fr&#34;:&#34;Français&#34;,&#34;de&#34;:&#34;Deutsch&#34;,&#34;it&#34;:&#34;Italiano&#34;,&#34;nl&#34;:&#34;Nederlands&#34;,&#34;pl&#34;:&#34;Polski&#34;,&#34;pt-BR&#34;:&#34;Português&#34;,&#34;ru&#34;:&#34;Русский&#34;,&#34;tr&#34;:&#34;Türkçe&#34;,&#34;id&#34;:&#34;Bahasa Indonesia&#34;,&#34;vi&#34;:&#34;Tiếng Việt&#34;,&#34;th&#34;:&#34;ไทย&#34;,&#34;ms&#34;:&#34;Bahasa Melayu&#34;,&#34;tl&#34;:&#34;Filipino&#34;,&#34;hi&#34;:&#34;हिन्दी&#34;,&#34;bn&#34;:&#34;বাংলা&#34;,&#34;ta&#34;:&#34;தமிழ்&#34;,&#34;te&#34;:&#34;తెలుగు&#34;,&#34;mr&#34;:&#34;मराठी&#34;,&#34;ar&#34;:&#34;العربية&#34;,&#34;he&#34;:&#34;עברית&#34;,&#34;fa&#34;:&#34;فارسی&#34;,&#34;ur&#34;:&#34;اردو&#34;}" data-lang-urls="{&#34;en&#34;:&#34;/video-studio/&#34;,&#34;es&#34;:&#34;/es/video-studio/&#34;,&#34;it&#34;:&#34;/it/video-studio/&#34;,&#34;hi&#34;:&#34;/hi/video-studio/&#34;,&#34;ar&#34;:&#34;/ar/video-studio/&#34;,&#34;zh&#34;:&#34;/zh/video-studio/&#34;,&#34;ja&#34;:&#34;/ja/video-studio/&#34;,&#34;ko&#34;:&#34;/ko/video-studio/&#34;,&#34;fr&#34;:&#34;/fr/video-studio/&#34;,&#34;de&#34;:&#34;/de/video-studio/&#34;,&#34;nl&#34;:&#34;/nl/video-studio/&#34;,&#34;pl&#34;:&#34;/pl/video-studio/&#34;,&#34;pt-BR&#34;:&#34;/pt-BR/video-studio/&#34;,&#34;ru&#34;:&#34;/ru/video-studio/&#34;,&#34;tr&#34;:&#34;/tr/video-studio/&#34;,&#34;id&#34;:&#34;/id/video-studio/&#34;,&#34;vi&#34;:&#34;/vi/video-studio/&#34;,&#34;th&#34;:&#34;/th/video-studio/&#34;,&#34;ms&#34;:&#34;/ms/video-studio/&#34;,&#34;tl&#34;:&#34;/tl/video-studio/&#34;,&#34;bn&#34;:&#34;/bn/video-studio/&#34;,&#34;ta&#34;:&#34;/ta/video-studio/&#34;,&#34;te&#34;:&#34;/te/video-studio/&#34;,&#34;mr&#34;:&#34;/mr/video-studio/&#34;,&#34;he&#34;:&#34;/he/video-studio/&#34;,&#34;fa&#34;:&#34;/fa/video-studio/&#34;,&#34;ur&#34;:&#34;/ur/video-studio/&#34;}" data-banner-copy="{&#34;en&#34;:{&#34;prompt&#34;:&#34;Choose another language to browse content adapted for your browser language preference.&#34;,&#34;continue&#34;:&#34;Continue&#34;,&#34;close&#34;:&#34;Close suggestion banner&#34;,&#34;selectLabel&#34;:&#34;Select preferred language&#34;,&#34;regionLabel&#34;:&#34;Language recommendation banner&#34;},&#34;zh&#34;:{&#34;prompt&#34;:&#34;选择另一语言以浏览适用于你浏览器语言偏好的内容。&#34;,&#34;continue&#34;:&#34;继续&#34;,&#34;close&#34;:&#34;关闭语言建议横幅&#34;,&#34;selectLabel&#34;:&#34;选择首选语言&#34;,&#34;regionLabel&#34;:&#34;语言推荐横幅&#34;},&#34;ja&#34;:{&#34;prompt&#34;:&#34;別の言語を選ぶと、ブラウザの言語設定に合わせたコンテンツを表示できます。&#34;,&#34;continue&#34;:&#34;続ける&#34;,&#34;close&#34;:&#34;言語提案バナーを閉じる&#34;,&#34;selectLabel&#34;:&#34;希望の言語を選択&#34;,&#34;regionLabel&#34;:&#34;言語おすすめバナー&#34;},&#34;ko&#34;:{&#34;prompt&#34;:&#34;다른 언어를 선택하면 브라우저 언어 설정에 맞는 콘텐츠를 볼 수 있습니다.&#34;,&#34;continue&#34;:&#34;계속&#34;,&#34;close&#34;:&#34;언어 제안 배너 닫기&#34;,&#34;selectLabel&#34;:&#34;원하는 언어 선택&#34;,&#34;regionLabel&#34;:&#34;언어 추천 배너&#34;},&#34;es&#34;:{&#34;prompt&#34;:&#34;Elija otro idioma para ver contenido adaptado a la preferencia de idioma de su navegador.&#34;,&#34;continue&#34;:&#34;Continuar&#34;,&#34;close&#34;:&#34;Cerrar banner de sugerencia de idioma&#34;,&#34;selectLabel&#34;:&#34;Seleccionar idioma preferido&#34;,&#34;regionLabel&#34;:&#34;Banner de recomendación de idioma&#34;},&#34;fr&#34;:{&#34;prompt&#34;:&#34;Choisissez une autre langue pour afficher le contenu adapté aux préférences linguistiques de votre navigateur.&#34;,&#34;continue&#34;:&#34;Continuer&#34;,&#34;close&#34;:&#34;Fermer la bannière de suggestion de langue&#34;,&#34;selectLabel&#34;:&#34;Sélectionner la langue préférée&#34;,&#34;regionLabel&#34;:&#34;Bannière de recommandation de langue&#34;},&#34;de&#34;:{&#34;prompt&#34;:&#34;Wählen Sie eine andere Sprache, um Inhalte entsprechend Ihrer Browser-Spracheinstellung anzuzeigen.&#34;,&#34;continue&#34;:&#34;Weiter&#34;,&#34;close&#34;:&#34;Sprachvorschlagsbanner schließen&#34;,&#34;selectLabel&#34;:&#34;Bevorzugte Sprache auswählen&#34;,&#34;regionLabel&#34;:&#34;Sprachempfehlungsbanner&#34;},&#34;it&#34;:{&#34;prompt&#34;:&#34;Scegli un'altra lingua per visualizzare contenuti adattati alle preferenze linguistiche del browser.&#34;,&#34;continue&#34;:&#34;Continua&#34;,&#34;close&#34;:&#34;Chiudi banner suggerimento lingua&#34;,&#34;selectLabel&#34;:&#34;Seleziona lingua preferita&#34;,&#34;regionLabel&#34;:&#34;Banner raccomandazione lingua&#34;},&#34;nl&#34;:{&#34;prompt&#34;:&#34;Kies een andere taal om content te bekijken die is afgestemd op de taalvoorkeur van je browser.&#34;,&#34;continue&#34;:&#34;Doorgaan&#34;,&#34;close&#34;:&#34;Sluit taalsuggestiebanner&#34;,&#34;selectLabel&#34;:&#34;Selecteer voorkeurstaal&#34;,&#34;regionLabel&#34;:&#34;Taalaanbevelingsbanner&#34;},&#34;pl&#34;:{&#34;prompt&#34;:&#34;Wybierz inny język, aby przeglądać treści dopasowane do preferencji językowych przeglądarki.&#34;,&#34;continue&#34;:&#34;Kontynuuj&#34;,&#34;close&#34;:&#34;Zamknij baner sugestii języka&#34;,&#34;selectLabel&#34;:&#34;Wybierz preferowany język&#34;,&#34;regionLabel&#34;:&#34;Baner rekomendacji języka&#34;},&#34;pt-BR&#34;:{&#34;prompt&#34;:&#34;Escolha outro idioma para ver conteúdo adaptado à preferência de idioma do seu navegador.&#34;,&#34;continue&#34;:&#34;Continuar&#34;,&#34;close&#34;:&#34;Fechar banner de sugestão de idioma&#34;,&#34;selectLabel&#34;:&#34;Selecionar idioma preferido&#34;,&#34;regionLabel&#34;:&#34;Banner de recomendação de idioma&#34;},&#34;ru&#34;:{&#34;prompt&#34;:&#34;Выберите другой язык, чтобы просматривать контент, адаптированный к языковым настройкам вашего браузера.&#34;,&#34;continue&#34;:&#34;Продолжить&#34;,&#34;close&#34;:&#34;Закрыть баннер предложения языка&#34;,&#34;selectLabel&#34;:&#34;Выберите предпочитаемый язык&#34;,&#34;regionLabel&#34;:&#34;Баннер рекомендации языка&#34;},&#34;tr&#34;:{&#34;prompt&#34;:&#34;Tarayıcı dil tercihinize uygun içeriği görmek için başka bir dil seçin.&#34;,&#34;continue&#34;:&#34;Devam&#34;,&#34;close&#34;:&#34;Dil önerisi başlığını kapat&#34;,&#34;selectLabel&#34;:&#34;Tercih edilen dili seçin&#34;,&#34;regionLabel&#34;:&#34;Dil önerisi başlığı&#34;},&#34;id&#34;:{&#34;prompt&#34;:&#34;Pilih bahasa lain untuk melihat konten yang disesuaikan dengan preferensi bahasa browser Anda.&#34;,&#34;continue&#34;:&#34;Lanjutkan&#34;,&#34;close&#34;:&#34;Tutup banner saran bahasa&#34;,&#34;selectLabel&#34;:&#34;Pilih bahasa pilihan&#34;,&#34;regionLabel&#34;:&#34;Banner rekomendasi bahasa&#34;},&#34;vi&#34;:{&#34;prompt&#34;:&#34;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.&#34;,&#34;continue&#34;:&#34;Tiếp tục&#34;,&#34;close&#34;:&#34;Đóng banner gợi ý ngôn ngữ&#34;,&#34;selectLabel&#34;:&#34;Chọn ngôn ngữ ưa thích&#34;,&#34;regionLabel&#34;:&#34;Banner đề xuất ngôn ngữ&#34;},&#34;th&#34;:{&#34;prompt&#34;:&#34;เลือกภาษาอื่นเพื่อดูเนื้อหาที่ปรับให้เข้ากับการตั้งค่าภาษาของเบราว์เซอร์ของคุณ&#34;,&#34;continue&#34;:&#34;ดำเนินการต่อ&#34;,&#34;close&#34;:&#34;ปิดแบนเนอร์แนะนำภาษา&#34;,&#34;selectLabel&#34;:&#34;เลือกภาษาที่ต้องการ&#34;,&#34;regionLabel&#34;:&#34;แบนเนอร์แนะนำภาษา&#34;},&#34;ms&#34;:{&#34;prompt&#34;:&#34;Pilih bahasa lain untuk melihat kandungan yang disesuaikan dengan pilihan bahasa pelayar anda.&#34;,&#34;continue&#34;:&#34;Teruskan&#34;,&#34;close&#34;:&#34;Tutup banner cadangan bahasa&#34;,&#34;selectLabel&#34;:&#34;Pilih bahasa pilihan&#34;,&#34;regionLabel&#34;:&#34;Banner cadangan bahasa&#34;},&#34;tl&#34;:{&#34;prompt&#34;:&#34;Pumili ng ibang wika upang makita ang content na naaayon sa preference ng wika ng browser mo.&#34;,&#34;continue&#34;:&#34;Magpatuloy&#34;,&#34;close&#34;:&#34;Isara ang banner ng mungkahi sa wika&#34;,&#34;selectLabel&#34;:&#34;Piliin ang gustong wika&#34;,&#34;regionLabel&#34;:&#34;Banner ng rekomendasyon sa wika&#34;},&#34;bn&#34;:{&#34;prompt&#34;:&#34;আপনার ব্রাউজারের ভাষার পছন্দ অনুযায়ী কনটেন্ট দেখতে অন্য ভাষা বেছে নিন।&#34;,&#34;continue&#34;:&#34;চালিয়ে যান&#34;,&#34;close&#34;:&#34;ভাষা পরামর্শ ব্যানার বন্ধ করুন&#34;,&#34;selectLabel&#34;:&#34;পছন্দের ভাষা নির্বাচন করুন&#34;,&#34;regionLabel&#34;:&#34;ভাষা সুপারিশ ব্যানার&#34;},&#34;ta&#34;:{&#34;prompt&#34;:&#34;உங்கள் உலாவி மொழி விருப்ப
9த்திற்கு ஏற்ப உள்ளடக்கத்தைப் பார்க்க வேறு மொழியைத் தேர்ந்தெடுக்கவும்.&#34;,&#34;continue&#34;:&#34;தொடரவும்&#34;,&#34;close&#34;:&#34;மொழி பரிந்துரை பேனரை மூடவும்&#34;,&#34;selectLabel&#34;:&#34;விருப்ப மொழியைத் தேர்ந்தெடுக்கவும்&#34;,&#34;regionLabel&#34;:&#34;மொழி பரிந்துரை பேனர்&#34;},&#34;te&#34;:{&#34;prompt&#34;:&#34;మీ బ్రౌజర్ భాషా ప్రాధాన్యతకు అనుగుణంగా కంటెంట్‌ను చూడటానికి మరొక భాషను ఎంచుకోండి.&#34;,&#34;continue&#34;:&#34;కొనసాగించు&#34;,&#34;close&#34;:&#34;భాష సూచన బ్యానర్‌ను మూసివేయి&#34;,&#34;selectLabel&#34;:&#34;ఇష్టమైన భాషను ఎంచుకోండి&#34;,&#34;regionLabel&#34;:&#34;భాష సిఫార్సు బ్యానర్&#34;},&#34;mr&#34;:{&#34;prompt&#34;:&#34;तुमच्या ब्राउझरच्या भाषा प्राधान्यानुसार सामग्री पाहण्यासाठी दुसरी भाषा निवडा.&#34;,&#34;continue&#34;:&#34;पुढे जा&#34;,&#34;close&#34;:&#34;भाषा सूचना बॅनर बंद करा&#34;,&#34;selectLabel&#34;:&#34;पसंतीची भाषा निवडा&#34;,&#34;regionLabel&#34;:&#34;भाषा शिफारस बॅनर&#34;},&#34;ar&#34;:{&#34;prompt&#34;:&#34;اختر لغة أخرى لعرض محتوى يتكيف مع تفضيلات لغة متصفحك.&#34;,&#34;continue&#34;:&#34;متابعة&#34;,&#34;close&#34;:&#34;إغلاق شريط اقتراح اللغة&#34;,&#34;selectLabel&#34;:&#34;اختر اللغة المفضلة&#34;,&#34;regionLabel&#34;:&#34;شريط توصية اللغة&#34;},&#34;he&#34;:{&#34;prompt&#34;:&#34;בחר שפה אחרת כדי לצפות בתוכן המותאם להעדפת השפה של הדפדפן שלך.&#34;,&#34;continue&#34;:&#34;המשך&#34;,&#34;close&#34;:&#34;סגור את באנר הצעת השפה&#34;,&#34;selectLabel&#34;:&#34;בחר שפה מועדפת&#34;,&#34;regionLabel&#34;:&#34;באנר המלצת שפה&#34;},&#34;fa&#34;:{&#34;prompt&#34;:&#34;زبان دیگری را انتخاب کنید تا محتوایی متناسب با ترجیح زبان مرورگر خود ببینید.&#34;,&#34;continue&#34;:&#34;ادامه&#34;,&#34;close&#34;:&#34;بستن بنر پیشنهاد زبان&#34;,&#34;selectLabel&#34;:&#34;انتخاب زبان ترجیحی&#34;,&#34;regionLabel&#34;:&#34;بنر پیشنهاد زبان&#34;},&#34;hi&#34;:{&#34;prompt&#34;:&#34;अपनी ब्राउज़र भाषा प्राथमिकता के अनुसार सामग्री देखने के लिए दूसरी भाषा चुनें।&#34;,&#34;continue&#34;:&#34;जारी रखें&#34;,&#34;close&#34;:&#34;भाषा सुझाव बैनर बंद करें&#34;,&#34;selectLabel&#34;:&#34;पसंदीदा भाषा चुनें&#34;,&#34;regionLabel&#34;:&#34;भाषा अनुशंसा बैनर&#34;},&#34;ur&#34;:{&#34;prompt&#34;:&#34;اپنی براؤزر زبان کی ترجیح کے مطابق مواد دیکھنے کے لیے دوسری زبان منتخب کریں۔&#34;,&#34;continue&#34;:&#34;جاری رکھیں&#34;,&#34;close&#34;:&#34;زبان کی تجویز بینر بند کریں&#34;,&#34;selectLabel&#34;:&#34;پسندیدہ زبان منتخب کریں&#34;,&#34;regionLabel&#34;:&#34;زبان کی سفارش بینر&#34;}}" 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 &amp; 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&#39;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.