PageSourceSearch

https://www.ai-geminiomni.com/pricing/

html ai-geminiomni.com collected 2026-10-04 13:25:18 UTC 96,889 bytes, 1,058 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/pricing/"><title>Gemini Omni credits &amp; pricing</title><meta name="description" content="Top up Gemini Omni credits to generate adapted prompts, scripts and creative briefs. Pick a pack — Starter, Standard or Professional — and pay securely via card."><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/pricing/"><link rel="alternate" hreflang="es" href="https://www.ai-geminiomni.com/es/pricing/"><link rel="alternate" hreflang="it" href="https://www.ai-geminiomni.com/it/pricing/"><link rel="alternate" hreflang="hi" href="https://www.ai-geminiomni.com/hi/pricing/"><link rel="alternate" hreflang="ar" href="https://www.ai-geminiomni.com/ar/pricing/"><link rel="alternate" hreflang="zh-CN" href="https://www.ai-geminiomni.com/zh/pricing/"><link rel="alternate" hreflang="ja" href="https://www.ai-geminiomni.com/ja/pricing/"><link rel="alternate" hreflang="ko" href="https://www.ai-geminiomni.com/ko/pricing/"><link rel="alternate" hreflang="fr" href="https://www.ai-geminiomni.com/fr/pricing/"><link rel="alternate" hreflang="de" href="https://www.ai-geminiomni.com/de/pricing/"><link rel="alternate" hreflang="nl" href="https://www.ai-geminiomni.com/nl/pricing/"><link rel="alternate" hreflang="pl" href="https://www.ai-geminiomni.com/pl/pricing/"><link rel="alternate" hreflang="pt-BR" href="https://www.ai-geminiomni.com/pt-BR/pricing/"><link rel="alternate" hreflang="ru" href="https://www.ai-geminiomni.com/ru/pricing/"><link rel="alternate" hreflang="tr" href="https://www.ai-geminiomni.com/tr/pricing/"><link rel="alternate" hreflang="id" href="https://www.ai-geminiomni.com/id/pricing/"><link rel="alternate" hreflang="vi" href="https://www.ai-geminiomni.com/vi/pricing/"><link rel="alternate" hreflang="th" href="https://www.ai-geminiomni.com/th/pricing/"><link rel="alternate" hreflang="ms" href="https://www.ai-geminiomni.com/ms/pricing/"><link rel="alternate" hreflang="tl" href="https://www.ai-geminiomni.com/tl/pricing/"><link rel="alternate" hreflang="bn" href="https://www.ai-geminiomni.com/bn/pricing/"><link rel="alternate" hreflang="ta" href="https://www.ai-geminiomni.com/ta/pricing/"><link rel="alternate" hreflang="te" href="https://www.ai-geminiomni.com/te/pricing/"><link rel="alternate" hreflang="mr" href="https://www.ai-geminiomni.com/mr/pricing/"><link rel="alternate" hreflang="he" href="https://www.ai-geminiomni.com/he/pricing/"><link rel="alternate" hreflang="fa" href="https://www.ai-geminiomni.com/fa/pricing/"><link rel="alternate" hreflang="ur" href="https://www.ai-geminiomni.com/ur/pricing/"><link rel="alternate" hreflang="x-default" href="https://www.ai-geminiomni.com/pricing/"><meta property="og:type" content="website"><meta property="og:site_name" content="Gemini Omni Info Hub"><meta property="og:title" content="Gemini Omni credits &#38; pricing"><meta property="og:description" content="Top up Gemini Omni credits to generate adapted prompts, scripts and creative briefs. Pick a pack — Starter, Standard or Professional — and pay securely via card."><meta property="og:url" content="https://www.ai-geminiomni.com/pricing/"><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 credits &#38; pricing"><meta name="twitter:description" content="Top up Gemini Omni credits to generate adapted prompts, scripts and creative briefs. Pick a pack — Starter, Standard or Professional — and pay securely via card."><meta name="twitter:image" content="https://www.ai-geminiomni.com/og.svg">
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","name":"Gemini Omni Info Hub","url":"https://www.ai-geminiomni.com/","inLanguage":"en","description":"Top up Gemini Omni credits to generate adapted prompts, scripts and creative briefs. Pick a pack — Starter, Standard or Professional — and pay securely via card.","potentialAction":{"@type":"SearchAction","target":"https://www.ai-geminiomni.com/?q={search_term_string}","query-input":"required name=search_term_string"}}</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"></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;/pricing/&#34;,&#34;es&#34;:&#34;/es/pricing/&#34;,&#34;it&#34;:&#34;/it/pricing/&#34;,&#34;hi&#34;:&#34;/hi/pricing/&#34;,&#34;ar&#34;:&#34;/ar/pricing/&#34;,&#34;zh&#34;:&#34;/zh/pricing/&#34;,&#34;ja&#34;:&#34;/ja/pricing/&#34;,&#34;ko&#34;:&#34;/ko/pricing/&#34;,&#34;fr&#34;:&#34;/fr/pricing/&#34;,&#34;de&#34;:&#34;/de/pricing/&#34;,&#34;nl&#34;:&#34;/nl/pricing/&#34;,&#34;pl&#34;:&#34;/pl/pricing/&#34;,&#34;pt-BR&#34;:&#34;/pt-BR/pricing/&#34;,&#34;ru&#34;:&#34;/ru/pricing/&#34;,&#34;tr&#34;:&#34;/tr/pricing/&#34;,&#34;id&#34;:&#34;/id/pricing/&#34;,&#34;vi&#34;:&#34;/vi/pricing/&#34;,&#34;th&#34;:&#34;/th/pricing/&#34;,&#34;ms&#34;:&#34;/ms/pricing/&#34;,&#34;tl&#34;:&#34;/tl/pricing/&#34;,&#34;bn&#34;:&#34;/bn/pricing/&#34;,&#34;ta&#34;:&#34;/ta/pricing/&#34;,&#34;te&#34;:&#34;/te/pricing/&#34;,&#34;mr&#34;:&#34;/mr/pricing/&#34;,&#34;he&#34;:&#34;/he/pricing/&#34;,&#34;fa&#34;:&#34;/fa/pricing/&#34;,&#34;ur&#34;:&#34;/ur/pricing/&#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;உங்கள் உலாவி மொழி விருப்ப
7த்திற்கு ஏற்ப உள்ளடக்கத்தைப் பார்க்க வேறு மொழியைத் தேர்ந்தெடுக்கவும்.&#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>
7 <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> 
7<script>
8  (() => {
9    try {
10      const banner = document.getElementById('locale-banner');
11      if (!banner) return;
12
13      const DISMISSED_KEY = 'apple_style_lang_banner_dismissed';
14      if (sessionStorage.getItem(DISMISSED_KEY) === '1') return;
15
16      const currentLang = banner.getAttribute('data-current-lang') || 'en';
17      const langUrls = JSON.parse(banner.getAttribute('data-lang-urls') || '{}');
18      const bannerCopy = JSON.parse(banner.getAttribute('data-banner-copy') || '{}');
19      const supportedLangs = Object.keys(langUrls);
20
21      const alias = { pt: 'pt-BR', zh: 'zh', iw: 'he', in: 'id', fil: 'tl', tl: 'tl' };
22
23      function match(raw) {
24        raw = (raw || '').toLowerCase();
25        if (!raw) return '';
26        for (let i = 0; i < supportedLangs.length; i++) {
27          if (supportedLangs[i].toLowerCase() === raw) return supportedLangs[i];
28        }
29        const base = raw.split('-')[0];
30        if (alias[base]) return alias[base];
31        for (let j = 0; j < supportedLangs.length; j++) {
32          const s = supportedLangs[j].toLowerCase();
33          if (s === base || s.split('-')[0] === base) return supportedLangs[j];
34        }
35        return '';
36      }
37
38      const prefs =
39        navigator.languages && navigator.languages.length
40          ? navigator.languages
41          : [navigator.language || 'en'];
42
43      let detectedLang = '';
44      for (let k = 0; k < prefs.length && !detectedLang; k++) {
45        detectedLang = match(prefs[k]);
46      }
47      if (!detectedLang) detectedLang = 'en';
48
49      if (detectedLang === currentLang) return;
50
51      const select = document.getElementById('locale-banner-select');
52      const continueBtn = document.getElementById('locale-banner-continue');
53      const closeBtn = document.getElementById('locale-banner-close');
54      const msg = document.getElementById('locale-banner-message');
55
56      function applyBannerCopy(lang) {
57        const copy = bannerCopy[lang] || bannerCopy.en || {};
58        if (msg) msg.textContent = copy.prompt || '';
59        if (continueBtn) continueBtn.textContent = copy.continue || '';
60        if (select) select.setAttribute('aria-label', copy.selectLabel || '');
61        if (closeBtn) closeBtn.setAttribute('aria-label', copy.close || '');
62        banner.setAttribute('aria-label', copy.regionLabel || '');
63      }
64
65      if (select) select.value = detectedLang;
66      applyBannerCopy(detectedLang);
67
68      function updateContinueTarget() {
69        if (!select || !continueBtn) return;
70        const target = select.value;
71        continueBtn.setAttribute('href', langUrls[target] || '/');
72        applyBannerCopy(target);
73      }
74
75      if (select) select.addEventListener('change', updateContinueTarget);
76
77      if (closeBtn) {
78        closeBtn.addEventListener('click', () => {
79          banner.style.display = 'none';
80          sessionStorage.setItem(DISMISSED_KEY, '1');
81        });
82      }
83
84      updateContinueTarget();
85      banner.style.display = 'block';
86    } catch (e) {}
87  })();
88</script>
88  <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> 
88<script>(function(){const API_BASE = "https://www.gushanai.com/api";
89const APP_KEY = "gemini_omni_4561";
90const HTML_LANG = "en";
91
92  (() => {
93    if (typeof window === 'undefined') return;
94
95    const AUTH_TOKEN_KEY = 'auth_token';
96    const USER_INFO_KEY = 'user_info';
97    const AUTH_CHANGED_EVENT = 'gemini-omni:auth-changed';
98    const REFRESH_USER_EVENT = 'gemini-omni:refresh-user';
99    const SESSION_REFRESHED_KEY = 'gemini-omni:auth-refreshed';
100
101    const root = document.querySelector('[data-auth]');
102    if (!root) return;
103
104    const signedOutEl = root.querySelector('[data-auth-signed-out]');
105    const signedInEl = root.querySelector('[data-auth-signed-in]');
106    const menuEl = root.querySelector('[data-auth-menu]');
107    const avatarEl = root.querySelector('[data-auth-avatar]');
108    const avatarLgEl = root.querySelector('[data-auth-avatar-lg]');
109    const nameEl = root.querySelector('[data-auth-name]');
110    const nameFullEl = root.querySelector('[data-auth-name-full]');
111    const emailEl = root.querySelector('[data-auth-email]');
112    const logoutBtn = root.querySelector('[data-auth-logout]');
113    const pointsEl = root.querySelector('[data-auth-points]');
114    const pointsValueEl = root.querySelector('[data-auth-points-value]');
115
116    let pointsFormatter;
117    try {
118      pointsFormatter = new Intl.NumberFormat(HTML_LANG);
119    } catch {
120      pointsFormatter = new Intl.NumberFormat('en');
121    }
122
123    const getToken = () => window.localStorage.getItem(AUTH_TOKEN_KEY);
124    const getUser = () => {
125      const raw = window.localStorage.getItem(USER_INFO_KEY);
126      if (!raw) return null;
127      try {
128        return JSON.parse(raw);
129      } catch {
130        return null;
131      }
132    };
133
134    const initials = (user) => {
135      const src = (user?.nickName || user?.email || '').trim();
136      if (!src) return '?';
137      const ch = src.codePointAt(0);
138      return ch ? String.fromCodePoint(ch).toUpperCase() : '?';
139    };
140
141    const renderAvatar = (el, user) => {
142      if (!el) return;
143      el.innerHTML = '';
144      if (user?.avatarUrl) {
145        const img = document.createElement('img');
146        img.src = user.avatarUrl;
147        img.alt = user.nickName || user.email || '';
148        img.referrerPolicy = 'no-referrer';
149        img.className = 'h-full w-full object-cover';
150        el.appendChild(img);
151      } else {
152        el.textContent = initials(user);
153      }
154    };
155
156    const closeMenu = () => {
157      menuEl?.setAttribute('hidden', '');
158      signedInEl?.setAttribute('aria-expanded', 'false');
159      if (menuEl) menuEl.style.right = '';
160    };
161
162    const adjustMenuPosition = () => {
163      if (!menuEl) return;
164      menuEl.style.right = '';
165      const rect = menuEl.getBoundingClientRect();
166      const minMargin = 8;
167      if (rect.left < minMargin) {
168        const shift = minMargin - rect.left;
169        menuEl.style.right = `-${shift}px`;
170      } else if (rect.right > window.innerWidth - minMargin) {
171        const overflow = rect.right - (window.innerWidth - minMargin);
172        menuEl.style.right = `${overflow}px`;
173      }
174    };
175
176    const openMenu = () => {
177      menuEl?.removeAttribute('hidden');
178      signedInEl?.setAttribute('aria-expanded', 'true');
179      adjustMenuPosition();
180    };
181
182    const renderPoints = (user) => {
183      if (!pointsEl) return;
184      const total = Number(user?.wallet?.points?.totalBalance);
185      if (Number.isFinite(total) && total >= 0) {
186        if (pointsValueEl) pointsValueEl.textContent = pointsFormatter.format(total);
187        pointsEl.removeAttribute('hidden');
188        pointsEl.classList.remove('hidden');
189        pointsEl.classList.add('inline-flex');
190      } else {
191        pointsEl.setAttribute('hidden', '');
192        pointsEl.classList.add('hidden');
193        pointsEl.classList.remove('inline-flex');
194      }
195    };
196
197    const render = () => {
198      const token = getToken();
199      const user = getUser();
200      if (token && user) {
201        signedOutEl?.setAttribute('hidden', '');
202        signedInEl?.removeAttribute('hidden');
203        renderAvatar(avatarEl, user);
204        renderAvatar(avatarLgEl, user);
205        const display = user.nickName || user.email || '';
206        if (nameEl) nameEl.textContent = display;
207        if (nameFullEl) nameFullEl.textContent = user.nickName || user.email || '';
208        if (emailEl) emailEl.textContent = user.email || '';
209        renderPoints(user);
210      } else {
211        signedInEl?.setAttribute('hidden', '');
212        signedOutEl?.removeAttribute('hidden');
213        renderPoints(null);
214        closeMenu();
215      }
216    };
217
218    const apiRequest = async (path, body) => {
219      const headers = { 'Content-Type': 'application/json', 'x-app-key': APP_KEY };
220      const token = getToken();
221      if (token) headers.Authorization = token;
222      const res = await fetch(`${API_BASE}${path}`, {
223        method: 'POST',
224        headers,
225        body: JSON.stringify(body || {}),
226      });
227      let env;
228      try {
229        env = await res.json();
230      } catch {
231        env = { code: res.status, message: `HTTP ${res.status}` };
232      }
233      if (!env || (env.code !== 200 && env.code !== 0)) {
234        const err = new Error(env?.message || `HTTP ${res.status}`);
235        err.code = env?.code ?? res.status;
236        err.status = res.status;
237        throw err;
238      }
239      return env.data;
240    };
241
242    const clearAuth = () => {
243      window.localStorage.removeItem(AUTH_TOKEN_KEY);
244      window.localStorage.removeItem(USER_INFO_KEY);
245      try {
246        window.sessionStorage.removeItem(SESSION_REFRESHED_KEY);
247      } catch {}
248      window.dispatchEvent(new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token: null, user: null } }));
249    };
250
251    const refreshIfNeeded = async () => {
252      const token = getToken();
253      if (!token) return;
254      let alreadyRefreshed = false;
255      try {
256        alreadyRefreshed = window.sessionStorage.getItem(SESSION_REFRESHED_KEY) === '1';
257      } catch {}
258      if (alreadyRefreshed) return;
259      try {
260        const refreshData = await apiRequest('/auth/refresh', {});
261        if (refreshData?.token) {
262          window.localStorage.setItem(AUTH_TOKEN_KEY, refreshData.token);
263        }
264        const user = await apiRequest('/mine/info', {});
265        if (user) {
266          window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(user));
267        }
268        try {
269          window.sessionStorage.setItem(SESSION_REFRESHED_KEY, '1');
270        } catch {}
271        window.dispatchEvent(
272          new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token: getToken(), user } })
273        );
274      } catch (err) {
275        const status = err && (err.status || err.code);
276        if (status === 401 || status === 403) {
277          clearAuth();
278        }
279      }
280    };
281
282    signedInEl?.addEventListener('click', (e) => {
283      e.stopPropagation();
284      if (menuEl?.hasAttribute('hidden')) openMenu();
285      else closeMenu();
286    });
287
288    logoutBtn?.addEventListener('click', (e) => {
289      e.stopPropagation();
290      clearAuth();
291      closeMenu();
292    });
293
294    document.addEventListener('click', (e) => {
295      if (!root.contains(e.target)) closeMenu();
296    });
297
298    document.addEventListener('keydown', (e) => {
299      if (e.key === 'Escape') closeMenu();
300    });
301
302    window.addEventListener(AUTH_CHANGED_EVENT, render);
303
304    window.addEventListener(REFRESH_USER_EVENT, async () => {
305      if (!getToken()) return;
306      try {
307        const user = await apiRequest('/mine/info', {});
308        if (user) {
309          window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(user));
310          window.dispatchEvent(
311            new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token: getToken(), user } })
312          );
313        }
314      } catch (err) {
315        const status = err && (err.status || err.code);
316        if (status === 401 || status === 403) {
317          clearAuth();
318        }
319      }
320    });
321
322    render();
323    refreshIfNeeded();
324  })();
325})();</script>
325 <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="/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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>
325<a href="/ms/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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/pricing/" 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>
325 Sources </a> </li> </ul> </div> </header> 
325<script>
326  (() => {
327    const toggle = document.querySelector('[data-mobile-toggle]');
328    const menu = document.getElementById('mobile-menu');
329    if (toggle && menu) {
330      const close = () => { menu.classList.add('hidden'); toggle.setAttribute('aria-expanded', 'false'); };
331      const open = () => { menu.classList.remove('hidden'); toggle.setAttribute('aria-expanded', 'true'); };
332      toggle.addEventListener('click', () => menu.classList.contains('hidden') ? open() : close());
333      menu.querySelectorAll('[data-mobile-link]').forEach((el) => el.addEventListener('click', close));
334    }
335
336    const switcher = document.querySelector('[data-lang-switcher]');
337    if (switcher) {
338      const langToggle = switcher.querySelector('[data-lang-toggle]');
339      const langMenu = switcher.querySelector('[data-lang-menu]');
340      const closeLang = () => { langMenu.classList.add('hidden'); langToggle.setAttribute('aria-expanded', 'false'); };
341      const openLang = () => { langMenu.classList.remove('hidden'); langToggle.setAttribute('aria-expanded', 'true'); };
342      langToggle.addEventListener('click', (e) => {
343        e.stopPropagation();
344        langMenu.classList.contains('hidden') ? openLang() : closeLang();
345      });
346      document.addEventListener('click', (e) => {
347        if (!switcher.contains(e.target)) closeLang();
348      });
349      document.addEventListener('keydown', (e) => {
350        if (e.key === 'Escape') closeLang();
351      });
352    }
353  })();
354</script>
354 <main>  <section class="relative overflow-hidden pt-16 pb-10 sm:pt-20 sm:pb-12"> <div class="container-x"> <div class="reveal mx-auto max-w-2xl text-center"> <span class="chip border-violet-400/30 text-violet-200">Credits &amp; top-ups</span> <h1 class="mt-4 font-display text-3xl font-semibold leading-[1.1] tracking-tight text-white sm:text-5xl"> <span class="text-gradient">Fuel your Gemini Omni workflow</span> </h1> <p class="mt-4 text-sm leading-relaxed text-zinc-400 sm:text-base"> Credits power the prompt generator and every AI adaptation. Pick the pack that matches your cadence; bigger packs ship lower per-credit cost. </p> </div> </div> </section> <section id="pricing-list" class="pb-14 sm:pb-20" data-pricing-root data-locale="en" data-error-load="Could not load credit packs. Please refresh and try again." data-error-order="Could not start checkout. Please try again." data-error-network="Network error. Please check your connection." data-error-login="Sign in to purchase credits." data-label-credits="credits" data-label-cta="Buy now" data-label-processing="Opening checkout…" data-label-popular="Most popular" data-label-best-value="Best value"> <div class="container-x"> <div data-pricing-loading class="grid place-items-center py-20"> <span class="inline-block h-10 w-10 animate-spin rounded-full border-4 border-violet-400/30 border-t-violet-400"></span> </div> <div data-pricing-error hidden class="mx-auto max-w-md rounded-2xl border border-rose-500/30 bg-rose-500/10 px-5 py-4 text-center text-sm text-rose-200"></div> <div data-pricing-grid hidden class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"></div> </div> </section> <template id="pricing-card-template"> <article data-pricing-card class="relative flex flex-col rounded-3xl border border-white/10 bg-white/[0.03] p-7 transition-colors hover:border-violet-400/40"> <span data-tag hidden class="absolute -top-3 left-1/2 -translate-x-1/2 rounded-full bg-brand-gradient px-3 py-1 text-[11px] font-semibold uppercase tracking-wide text-ink-950 shadow-glow"></span> <h3 data-title class="font-display text-lg font-semibold text-white"></h3> <p data-credits class="mt-2 text-sm text-zinc-400"></p> <div class="mt-5 flex items-end gap-1.5"> <span data-price class="font-display text-4xl font-bold text-white"></span> <span data-currency class="pb-1 text-sm text-zinc-400"></span> </div> <p class="mt-1 flex items-center gap-2 text-xs"> <span data-discount hidden class="rounded-md bg-amber-400/15 px-2 py-0.5 font-semibold text-amber-200"></span> <span data-original class="text-zinc-500 line-through" hidden></span> </p> <ul data-features class="mt-6 flex flex-col gap-2 text-sm text-zinc-300"></ul> <button type="button" data-buy class="btn-primary mt-7 w-full justify-center"> <span data-buy-idle></span> <span data-buy-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> <span data-buy-loading-label></span> </span> </button> </article> </template> 
354<script>(function(){const API_BASE = "https://www.gushanai.com/api";
355const APP_KEY = "gemini_omni_4561";
356const HTML_LANG = "en";
357
358  (() => {
359    if (typeof window === 'undefined') return;
360    const root = document.querySelector('[data-pricing-root]');
361    if (!root) return;
362    const loadingEl = root.querySelector('[data-pricing-loading]');
363    const errorEl = root.querySelector('[data-pricing-error]');
364    const gridEl = root.querySelector('[data-pricing-grid]');
365    const tpl = document.getElementById('pricing-card-template');
366    if (!loadingEl || !errorEl || !gridEl || !(tpl instanceof HTMLTemplateElement)) return;
367
368    const AUTH_TOKEN_KEY = 'auth_token';
369    const AUTH_CHANGED_EVENT = 'gemini-omni:auth-changed';
370
371    const labels = {
372      credits: root.dataset.labelCredits || 'credits',
373      cta: root.dataset.labelCta || 'Buy now',
374      processing: root.dataset.labelProcessing || 'Processing…',
375      popular: root.dataset.labelPopular || 'Most popular',
376      bestValue: root.dataset.labelBestValue || 'Best value',
377    };
378    const errors = {
379      load: root.dataset.errorLoad || 'Failed to load products.',
380      order: root.dataset.errorOrder || 'Failed to create order.',
381      network: root.dataset.errorNetwork || 'Network error, try again.',
382      login: root.dataset.errorLogin || 'Sign in to continue.',
383    };
384
385    let numberFormatter;
386    try {
387      numberFormatter = new Intl.NumberFormat(HTML_LANG);
388    } catch {
389      numberFormatter = new Intl.NumberFormat('en');
390    }
391
392    const getToken = () => {
393      try {
394        return window.localStorage.getItem(AUTH_TOKEN_KEY);
395      } catch {
396        return null;
397      }
398    };
399
400    const showError = (msg) => {
401      loadingEl.setAttribute('hidden', '');
402      gridEl.setAttribute('hidden', '');
403      errorEl.textContent = msg;
404      errorEl.removeAttribute('hidden');
405    };
406
407    const clearError = () => {
408      errorEl.setAttribute('hidden', '');
409      errorEl.textContent = '';
410    };
411
412    const apiRequest = async (path, body) => {
413      const headers = { 'Content-Type': 'application/json', 'x-app-key': APP_KEY };
414      const token = getToken();
415      if (token) headers.Authorization = token;
416      const htmlLang = document.documentElement.lang || HTML_LANG;
417      if (htmlLang) headers['x-locale'] = htmlLang;
418      let res;
419      try {
420        res = await fetch(`${API_BASE}${path}`, {
421          method: 'POST',
422          headers,
423          body: JSON.stringify(body || {}),
424        });
425      } catch (err) {
426        const e = new Error((err && err.message) || errors.network);
427        e.code = -1;
428        throw e;
429      }
430      let env;
431      try {
432        env = await res.json();
433      } catch {
434        env = { code: res.status, message: `HTTP ${res.status}` };
435      }
436      if (!env || (env.code !== 200 && env.code !== 0)) {
437        const e = new Error(env?.message || `HTTP ${res.status}`);
438        e.code = env?.code ?? res.status;
439        e.status = res.status;
440        throw e;
441      }
442      return env.data;
443    };
444
445    const formatPrice = (value, currency) => {
446      const isUSD = (currency || '').toUpperCase() === 'USD';
447      const sym = isUSD ? '$' : '';
448      return `${sym}${(value || 0).toFixed(2)}`;
449    };
450
451    const getDiscountPercent = (index, total) => {
452      if (total === 1) return 45;
453      const min = 30;
454      const max = 60;
455      const step = (max - min) / (total - 1);
456      return Math.round(min + step * index);
457    };
458
459    const renderCards = (items) => {
460      gridEl.innerHTML = '';
461      const sorted = [...items].sort((a, b) => (a.sort ?? 0) - (b.sort ?? 0));
462      sorted.forEach((product, idx) => {
463        const frag = tpl.content.cloneNode(true);
464        const card = frag.querySelector('[data-pricing-card]');
465        const tag = frag.querySelector('[data-tag]');
466        const titleEl = frag.querySelector('[data-title]');
467        const creditsEl = frag.querySelector('[data-credits]');
468        const priceEl = frag.querySelector('[data-price]');
469        const currencyEl = frag.querySelector('[data-currency]');
470        const discountEl = frag.querySelector('[data-discount]');
471        const originalEl = frag.querySelector('[data-original]');
472        const featuresEl = frag.querySelector('[data-features]');
473        const buyBtn = frag.querySelector('[data-buy]');
474        const buyIdle = frag.querySelector('[data-buy-idle]');
475        const buyLoadingLabel = frag.querySelector('[data-buy-loading-label]');
476
477        const tags = (product.extra_data && product.extra_data.tags) || [];
478        const isPopular = tags.includes('popular') || tags.includes('best_value');
479        if (isPopular) {
480          card.classList.add('border-violet-400/60', 'bg-violet-500/10');
481          tag.textContent = tags.includes('best_value') ? labels.bestValue : labels.popular;
482          tag.removeAttribute('hidden');
483        }
484
485        titleEl.textContent = product.title || '';
486        const credits = numberFormatter.format(product.credits || 0);
487        creditsEl.textContent = `${credits} ${labels.credits}`;
488
489        priceEl.textContent = formatPrice(product.price_hint, product.currency_hint);
490        const currencyText = (product.currency_hint || '').toUpperCase() === 'USD'
491          ? ''
492          : product.currency_hint || '';
493        if (currencyText) currencyEl.textContent = currencyText;
494
495        const discountPct = getDiscountPercent(idx, sorted.length);
496        if (discountPct > 0) {
497          const original = (product.price_hint || 0) / (1 - discountPct / 100);
498          discountEl.textContent = `-${discountPct}%`;
499          discountEl.removeAttribute('hidden');
500          originalEl.textContent = formatPrice(original, product.currency_hint);
501          originalEl.removeAttribute('hidden');
502        }
503
504        const features = (product.description || '').split('\n').map((s) => s.trim()).filter(Boolean);
505        features.forEach((line) => {
506          const li = document.createElement('li');
507          li.className = 'flex items-start gap-2';
508          li.innerHTML = `
509            <svg viewBox="0 0 24 24" class="mt-0.5 h-4 w-4 shrink-0 text-violet-300" aria-hidden="true">
510              <path fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M5 12l4.5 4.5L19 7"/>
511            </svg>
512            <span></span>`;
513          li.querySelector('span').textContent = line;
514          featuresEl.appendChild(li);
515        });
516
517        buyIdle.textContent = labels.cta;
518        buyLoadingLabel.textContent = labels.processing;
519
520        const setBuyLoading = (loading) => {
521          const loadingEl = buyBtn.querySelector('[data-buy-loading]');
522          if (loading) {
523            buyBtn.setAttribute('disabled', '');
524            buyIdle.setAttribute('hidden', '');
525            loadingEl?.removeAttribute('hidden');
526          } else {
527            buyBtn.removeAttribute('disabled');
528            buyIdle.removeAttribute('hidden');
529            loadingEl?.setAttribute('hidden', '');
530          }
531        };
532
533        buyBtn.addEventListener('click', async () => {
534          if (!getToken()) {
535            window.dispatchEvent(new CustomEvent('open-login'));
536            showError(errors.login);
537            return;
538          }
539          clearError();
540          setBuyLoading(true);
541          try {
542            const data = await apiRequest('/payments/order', {
543              platform: 'web',
544              productId: product.product_id,
545            });
546            const url = data && data.checkout && data.checkout.checkoutUrl;
547            if (url) {
548              window.location.href = url;
549            } else {
550              throw new Error(errors.order);
551            }
552          } catch (err) {
553            const status = err && (err.status || err.code);
554            if (status === 401 || status === 403) {
555              window.dispatchEvent(new CustomEvent('open-login'));
556              showError(errors.login);
557            } else {
558              showError((err && err.message) || errors.order);
559            }
560            setBuyLoading(false);
561          }
562        });
563
564        gridEl.appendChild(frag);
565      });
566    };
567
568    const loadProducts = async () => {
569      clearError();
570      loadingEl.removeAttribute('hidden');
571      gridEl.setAttribute('hidden', '');
572      if (!getToken()) {
573        loadingEl.setAttribute('hidden', '');
574        showError(errors.login);
575        window.dispatchEvent(new CustomEvent('open-login'));
576        return;
577      }
578      try {
579        const data = await apiRequest('/payments/products', { platform: 'web' });
580        const items = (data && data.items) || [];
581        if (!items.length) {
582          showError(errors.load);
583          return;
584        }
585        renderCards(items);
586        loadingEl.setAttribute('hidden', '');
587        gridEl.removeAttribute('hidden');
588      } catch (err) {
589        const status = err && (err.status || err.code);
590        if (status === 401 || status === 403) {
591          showError(errors.login);
592          window.dispatchEvent(new CustomEvent('open-login'));
593        } else {
594          showError((err && err.message) || errors.load);
595        }
596        loadingEl.setAttribute('hidden', '');
597      }
598    };
599
600    window.addEventListener(AUTH_CHANGED_EVENT, (ev) => {
601      const detail = ev && ev.detail;
602      if (detail && detail.token) {
603        loadProducts();
604      }
605    });
606
607    const resetAllBuyButtons = () => {
608      gridEl.querySelectorAll('[data-buy]').forEach((btn) => {
609        btn.removeAttribute('disabled');
610        btn.querySelector('[data-buy-idle]')?.removeAttribute('hidden');
611        btn.querySelector('[data-buy-loading]')?.setAttribute('hidden', '');
612      });
613    };
614
615    window.addEventListener('pageshow', (event) => {
616      if (event.persisted) resetAllBuyButtons();
617    });
618
619    loadProducts();
620  })();
621})();</script>
621 <section class="pb-24" aria-labelledby="pricing-faq-title"> <div class="container-x"> <div class="reveal mx-auto max-w-3xl"> <h2 id="pricing-faq-title" class="section-title">Pricing FAQ</h2> <div class="mt-8 space-y-4"> <details class="group rounded-2xl border border-white/10 bg-white/[0.03] p-5 transition-colors hover:border-violet-400/40"> <summary class="flex cursor-pointer list-none items-center justify-between gap-4 text-base font-semibold text-zinc-100"> <span>How do credits work?</span> <svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0 text-zinc-400 transition-transform group-open:rotate-180" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6"></path> </svg> </summary> <p class="mt-3 text-sm leading-relaxed text-zinc-400">Each AI generation in the prompt builder consumes a small amount of credits. Your balance is shown in the top-right when signed in.</p> </details><details class="group rounded-2xl border border-white/10 bg-white/[0.03] p-5 transition-colors hover:border-violet-400/40"> <summary class="flex cursor-pointer list-none items-center justify-between gap-4 text-base font-semibold text-zinc-100"> <span>Do credits expire?</span> <svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0 text-zinc-400 transition-transform group-open:rotate-180" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6"></path> </svg> </summary> <p class="mt-3 text-sm leading-relaxed text-zinc-400">No. Credits you purchase do not expire and stack with any free balance from promotions.</p> </details><details class="group rounded-2xl border border-white/10 bg-white/[0.03] p-5 transition-colors hover:border-violet-400/40"> <summary class="flex cursor-pointer list-none items-center justify-between gap-4 text-base font-semibold text-zinc-100"> <span>Which payment methods are supported?</span> <svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0 text-zinc-400 transition-transform group-open:rotate-180" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6"></path> </svg> </summary> <p class="mt-3 text-sm leading-relaxed text-zinc-400">Major credit and debit cards are supported via our payment processor. After payment, we redirect back to verify the order automatically.</p> </details><details class="group rounded-2xl border border-white/10 bg-white/[0.03] p-5 transition-colors hover:border-violet-400/40"> <summary class="flex cursor-pointer list-none items-center justify-between gap-4 text-base font-semibold text-zinc-100"> <span>Can I get a refund?</span> <svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0 text-zinc-400 transition-transform group-open:rotate-180" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6"></path> </svg> </summary> <p class="mt-3 text-sm leading-relaxed text-zinc-400">Unused credit packs can be refunded within 7 days of purchase. Reach out to support with your order id and we will process it.</p> </details><details class="group rounded-2xl border border-white/10 bg-white/[0.03] p-5 transition-colors hover:border-violet-400/40"> <summary class="flex cursor-pointer list-none items-center justify-between gap-4 text-base font-semibold text-zinc-100"> <span>Will I get an invoice?</span> <svg viewBox="0 0 24 24" class="h-4 w-4 shrink-0 text-zinc-400 transition-transform group-open:rotate-180" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6"></path> </svg> </summary> <p class="mt-3 text-sm leading-relaxed text-zinc-400">Yes. Email support after the order completes and we will send a tax-compliant invoice for your purchase.</p> </details> </div> </div> </div> </section>  </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">
621 <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="/pricing" hreflang="en"> English </a> </li><li> <a class="hover:text-white " href="/es/pricing" hreflang="es"> Español </a> </li><li> <a class="hover:text-white " href="/it/pricing" hreflang="it"> Italiano </a> </li><li> <a class="hover:text-white " href="/hi/pricing" hreflang="hi"> हिन्दी </a> </li><li> <a class="hover:text-white " href="/ar/pricing" hreflang="ar"> العربية </a> </li><li> <a class="hover:text-white " href="/zh/pricing" hreflang="zh"> 中文 </a> </li><li> <a class="hover:text-white " href="/ja/pricing" hreflang="ja"> 日本語 </a> </li><li> <a class="hover:text-white " href="/ko/pricing" hreflang="ko"> 한국어 </a> </li><li> <a class="hover:text-white " href="/fr/pricing" hreflang="fr"> Français </a> </li><li> <a class="hover:text-white " href="/de/pricing" hreflang="de"> Deutsch </a> </li><li> <a class="hover:text-white " href="/nl/pricing" hreflang="nl"> Nederlands </a> </li><li> <a class="hover:text-white " href="/pl/pricing" hreflang="pl"> Polski </a> </li><li> <a class="hover:text-white " href="/pt-BR/pricing" hreflang="pt-BR"> Português </a> </li><li> <a class="hover:text-white " href="/ru/pricing" hreflang="ru"> Русский </a> </li><li> <a class="hover:text-white " href="/tr/pricing" hreflang="tr"> Türkçe </a> </li><li> <a class="hover:text-white " href="/id/pricing" hreflang="id"> Bahasa Indonesia </a> </li><li> <a class="hover:text-white " href="/vi/pricing" hreflang="vi"> Tiếng Việt </a> </li><li> <a class="hover:text-white " href="/th/pricing" hreflang="th"> ไทย </a> </li><li> <a class="hover:text-white " href="/ms/pricing" hreflang="ms"> Bahasa Melayu </a> </li><li> <a class="hover:text-white " href="/tl/pricing" hreflang="tl"> Filipino </a> </li><li> <a class="hover:text-white " href="/bn/pricing" hreflang="bn"> বাংলা </a> </li><li> <a class="hover:text-white " href="/ta/pricing" hreflang="ta"> தமிழ் </a> </li><li> <a class="hover:text-white " href="/te/pricing" hreflang="te"> తెలుగు </a> </li><li> <a class="hover:text-white " href="/mr/pricing" hreflang="mr"> मराठी </a> </li><li> <a class="hover:text-white " href="/he/pricing" hreflang="he"> עברית </a> </li><li> <a class="hover:text-white " href="/fa/pricing" hreflang="fa"> فارسی </a> </li><li> <a class="hover:text-white " href="/ur/pricing" 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 focu
621s: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"> 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> 
621<script>(function(){const API_BASE = "https://www.gushanai.com/api";
622const APP_KEY = "gemini_omni_4561";
623const GOOGLE_CLIENT_ID = "463343176094-ptfq906flrtlronsfq8c4omvui6dtck2.apps.googleusercontent.com";
624const HTML_LANG = "en";
625
626  (() => {
627    if (typeof window === 'undefined') return;
628    const dialog = document.getElementById('login-modal');
629    if (!(dialog instanceof HTMLDialogElement)) return;
630
631    const AUTH_TOKEN_KEY = 'auth_token';
632    const USER_INFO_KEY = 'user_info';
633    const AUTH_CHANGED_EVENT = 'gemini-omni:auth-changed';
634
635    const formRoot = dialog.querySelector('[data-state="form"]');
636    const successRoot = dialog.querySelector('[data-state="success"]');
637    const errorEl = dialog.querySelector('[data-error]');
638    const stepEmail = dialog.querySelector('[data-step="email"]');
639    const stepCode = dialog.querySelector('[data-step="code"]');
640    const emailInput = dialog.querySelector('#login-email');
641    const codeInput = dialog.querySelector('#login-code');
642    const codeEmailLabel = dialog.querySelector('[data-code-email]');
643    const successUserEl = dialog.querySelector('[data-success-user]');
644    const gsiContainer = dialog.querySelector('#gsi-button-container');
645
646    const sendBtn = dialog.querySelector('[data-action="send-code"]');
647    const verifyBtn = dialog.querySelector('[data-action="verify"]');
648    const resendBtn = dialog.querySelector('[data-action="resend"]');
649    const changeEmailBtn = dialog.querySelector('[data-action="change-email"]');
650
651    let currentEmail = '';
652    let countdown = 0;
653    let countdownTimer = null;
654    let gsiLoaded = false;
655    let gsiInitialized = false;
656
657    const isAuthed = () => !!window.localStorage.getItem(AUTH_TOKEN_KEY);
658
659    const showError = (msg) => {
660      if (!errorEl) return;
661      if (!msg) {
662        errorEl.textContent = '';
663        errorEl.setAttribute('hidden', '');
664        return;
665      }
666      errorEl.textContent = msg;
667      errorEl.removeAttribute('hidden');
668    };
669
670    const setBtnLoading = (btn, loading) => {
671      if (!btn) return;
672      btn.toggleAttribute('disabled', loading);
673      const idle = btn.querySelector('[data-label-idle]');
674      const busy = btn.querySelector('[data-label-loading]');
675      if (idle) idle.toggleAttribute('hidden', loading);
676      if (busy) busy.toggleAttribute('hidden', !loading);
677    };
678
679    const switchStep = (step) => {
680      if (step === 'code') {
681        stepEmail?.setAttribute('hidden', '');
682        stepCode?.removeAttribute('hidden');
683        setTimeout(() => codeInput?.focus(), 30);
684      } else {
685        stepCode?.setAttribute('hidden', '');
686        stepEmail?.removeAttribute('hidden');
687        setTimeout(() => emailInput?.focus(), 30);
688      }
689    };
690
691    const stopCountdown = () => {
692      if (countdownTimer) {
693        clearInterval(countdownTimer);
694        countdownTimer = null;
695      }
696    };
697
698    const updateResendUI = () => {
699      if (!resendBtn) return;
700      const idle = resendBtn.querySelector('[data-resend-idle]');
701      const cd = resendBtn.querySelector('[data-resend-countdown]');
702      if (countdown > 0) {
703        resendBtn.setAttribute('disabled', '');
704        idle?.setAttribute('hidden', '');
705        if (cd) {
706          cd.removeAttribute('hidden');
707          cd.textContent = (resendBtn.dataset.template || '{n}s').replace('{n}', String(countdown));
708        }
709      } else {
710        resendBtn.removeAttribute('disabled');
711        idle?.removeAttribute('hidden');
712        cd?.setAttribute('hidden', '');
713      }
714    };
715
716    const startCountdown = (seconds) => {
717      stopCountdown();
718      countdown = seconds;
719      updateResendUI();
720      countdownTimer = window.setInterval(() => {
721        countdown -= 1;
722        if (countdown <= 0) {
723          countdown = 0;
724          stopCountdown();
725        }
726        updateResendUI();
727      }, 1000);
728    };
729
730    const showSuccess = (user) => {
731      formRoot?.setAttribute('hidden', '');
732      successRoot?.removeAttribute('hidden');
733      if (successUserEl) {
734        const name = user?.nickName || user?.email || '';
735        successUserEl.textContent = name;
736        successUserEl.toggleAttribute('hidden', !name);
737      }
738    };
739
740    const resetForm = () => {
741      currentEmail = '';
742      stopCountdown();
743      countdown = 0;
744      updateResendUI();
745      if (emailInput) emailInput.value = '';
746      if (codeInput) codeInput.value = '';
747      showError('');
748      switchStep('email');
749      formRoot?.removeAttribute('hidden');
750      successRoot?.setAttribute('hidden', '');
751    };
752
753    const openDialog = () => {
754      showError('');
755      if (isAuthed()) {
756        const raw = window.localStorage.getItem(USER_INFO_KEY);
757        let user = null;
758        try { user = raw ? JSON.parse(raw) : null; } catch {}
759        formRoot?.setAttribute('hidden', '');
760        successRoot?.removeAttribute('hidden');
761        if (successUserEl) {
762          const name = user?.nickName || user?.email || '';
763          successUserEl.textContent = name;
764          successUserEl.toggleAttribute('hidden', !name);
765        }
766      } else {
767        resetForm();
768      }
769      if (!dialog.open) {
770        try { dialog.showModal(); } catch { dialog.setAttribute('open', ''); }
771      }
772      ensureGoogleButton();
773    };
774
775    const closeDialog = () => {
776      if (dialog.open) {
777        try { dialog.close(); } catch { dialog.removeAttribute('open'); }
778      }
779    };
780
781    const apiRequest = async (path, body) => {
782      const headers = { 'Content-Type': 'application/json', 'x-app-key': APP_KEY };
783      const token = window.localStorage.getItem(AUTH_TOKEN_KEY);
784      if (token) headers.Authorization = token;
785      const res = await fetch(`${API_BASE}${path}`, {
786        method: 'POST',
787        headers,
788        body: JSON.stringify(body || {}),
789      });
790      let env;
791      try { env = await res.json(); } catch { env = { code: res.status, message: `HTTP ${res.status}` }; }
792      if (!env || (env.code !== 200 && env.code !== 0)) {
793        const err = new Error(env?.message || `HTTP ${res.status}`);
794        err.code = env?.code ?? res.status;
795        throw err;
796      }
797      return env.data;
798    };
799
800    const finishLogin = (token, user) => {
801      window.localStorage.setItem(AUTH_TOKEN_KEY, token);
802      if (user) window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(user));
803      window.dispatchEvent(new CustomEvent(AUTH_CHANGED_EVENT, { detail: { token, user } }));
804      showSuccess(user);
805    };
806
807    const handleGoogleCredential = async (response) => {
808      const idToken = response?.credential;
809      if (!idToken) return;
810      showError('');
811      try {
812        const data = await apiRequest('/auth/google/web', { idToken });
813        finishLogin(data.token, data.user || null);
814        try {
815          const fresh = await apiRequest('/mine/info', {});
816          window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(fresh));
817          if (successUserEl) {
818            const name = fresh?.nickName || fresh?.email || '';
819            successUserEl.textContent = name;
820            successUserEl.toggleAttribute('hidden', !name);
821          }
822        } catch {}
823      } catch (err) {
824        showError(err?.message || gsiContainer?.dataset.googleFailed || 'Google sign-in failed');
825      }
826    };
827
828    const initGoogle = () => {
829      if (gsiInitialized || !gsiContainer) return;
830      const g = window.google;
831      if (!g?.accounts?.id) return;
832      g.accounts.id.initialize({
833        client_id: GOOGLE_CLIENT_ID,
834        callback: handleGoogleCredential,
835        ux_mode: 'popup',
836        auto_select: false,
837      });
838      gsiContainer.innerHTML = '';
839      g.accounts.id.renderButton(gsiContainer, {
840        theme: 'filled_black',
841        size: 'large',
842        shape: 'pill',
843        text: 'signin_with',
844        logo_alignment: 'left',
845        width: gsiContainer.clientWidth || 320,
846        locale: HTML_LANG,
847      });
848      gsiInitialized = true;
849    };
850
851    const ensureGoogleButton = () => {
852      if (!gsiContainer || gsiInitialized) return;
853      if (gsiLoaded) {
854        initGoogle();
855        return;
856      }
857      const existing = document.querySelector('script[data-gsi-loader]');
858      if (existing) {
859        existing.addEventListener('load', initGoogle, { once: true });
860        gsiLoaded = true;
861        return;
862      }
863      const s = document.createElement('script');
864      s.src = 'https://accounts.google.com/gsi/client';
865      s.async = true;
866      s.defer = true;
867      s.dataset.gsiLoader = '1';
868      s.onload = () => { gsiLoaded = true; initGoogle(); };
869      document.head.appendChild(s);
870    };
871
872    const isValidEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
873
874    sendBtn?.addEventListener('click', async () => {
875      const email = (emailInput?.value || '').trim();
876      showError('');
877      if (!email || !isValidEmail(email)) {
878        showError(sendBtn.dataset.invalidEmail || 'Invalid email');
879        return;
880      }
881      setBtnLoading(sendBtn, true);
882      try {
883        await apiRequest('/auth/email/send-code', { email });
884        currentEmail = email;
885        if (codeEmailLabel) codeEmailLabel.textContent = email;
886        switchStep('code');
887        startCountdown(60);
888      } catch (err) {
889        showError(err?.message || sendBtn.dataset.sendFailed || 'Failed to send code');
890      } finally {
891        setBtnLoading(sendBtn, false);
892      }
893    });
894
895    verifyBtn?.addEventListener('click', async () => {
896      const code = (codeInput?.value || '').replace(/\D/g, '');
897      showError('');
898      if (code.length !== 6) {
899        showError(verifyBtn.dataset.verifyFailed || 'Invalid code');
900        return;
901      }
902      setBtnLoading(verifyBtn, true);
903      try {
904        const data = await apiRequest('/auth/email/verify', { email: currentEmail, code });
905        finishLogin(data.token, data.user || null);
906        try {
907          const fresh = await apiRequest('/mine/info', {});
908          window.localStorage.setItem(USER_INFO_KEY, JSON.stringify(fresh));
909          if (successUserEl) {
910            const name = fresh?.nickName || fresh?.email || '';
911            successUserEl.textContent = name;
912            successUserEl.toggleAttribute('hidden', !name);
913          }
914        } catch {}
915      } catch (err) {
916        showError(err?.message || verifyBtn.dataset.verifyFailed || 'Verification failed');
917      } finally {
918        setBtnLoading(verifyBtn, false);
919      }
920    });
921
922    resendBtn?.addEventListener('click', async () => {
923      if (countdown > 0 || !currentEmail) return;
924      showError('');
925      try {
926        await apiRequest('/auth/email/send-code', { email: currentEmail });
927        startCountdown(60);
928      } catch (err) {
929        showError(err?.message || sendBtn?.dataset.sendFailed || 'Failed to send code');
930      }
931    });
932
933    changeEmailBtn?.addEventListener('click', () => {
934      showError('');
935      stopCountdown();
936      countdown = 0;
937      updateResendUI();
938      if (codeInput) codeInput.value = '';
939      switchStep('email');
940    });
941
942    codeInput?.addEventListener('input', (e) => {
943      const target = e.target;
944      if (target instanceof HTMLInputElement) {
945        target.value = target.value.replace(/\D/g, '').slice(0, 6);
946      }
947    });
948
949    emailInput?.addEventListener('keydown', (e) => {
950      if (e.key === 'Enter') {
951        e.preventDefault();
952        sendBtn?.click();
953      }
954    });
955
956    codeInput?.addEventListener('keydown', (e) => {
957      if (e.key === 'Enter') {
958        e.preventDefault();
959        verifyBtn?.click();
960      }
961    });
962
963    dialog.addEventListener('click', (e) => {
964      const target = e.target;
965      if (target instanceof Element && target.closest('[data-close]')) {
966        e.preventDefault();
967        closeDialog();
968        return;
969      }
970      if (target === dialog) {
971        closeDialog();
972      }
973    });
974
975    dialog.addEventListener('cancel', (e) => {
976      e.preventDefault();
977      closeDialog();
978    });
979
980    window.addEventListener('open-login', openDialog);
981
982    document.addEventListener('click', (e) => {
983      const target = e.target;
984      if (!(target instanceof Element)) return;
985      const trigger = target.closest('[data-open-login]');
986      if (trigger) {
987        e.preventDefault();
988        openDialog();
989      }
990    });
991  })();
992})();</script>
992 <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> 
992<script>
993  (() => {
994    if (typeof window === 'undefined') return;
995    const dialog = document.getElementById('recharge-modal');
996    if (!(dialog instanceof HTMLDialogElement)) return;
997
998    const openDialog = () => {
999      if (!dialog.open) {
1000        try {
1001          dialog.showModal();
1002        } catch {
1003          dialog.setAttribute('open', '');
1004        }
1005      }
1006    };
1007
1008    const closeDialog = () => {
1009      if (dialog.open) {
1010        try {
1011          dialog.close();
1012        } catch {
1013          dialog.removeAttribute('open');
1014        }
1015      }
1016    };
1017
1018    dialog.addEventListener('click', (e) => {
1019      const target = e.target;
1020      if (target instanceof Element && target.closest('[data-close]')) {
1021        e.preventDefault();
1022        closeDialog();
1023        return;
1024      }
1025      if (target === dialog) closeDialog();
1026    });
1027
1028    dialog.addEventListener('cancel', (e) => {
1029      e.preventDefault();
1030      closeDialog();
1031    });
1032
1033    window.addEventListener('gemini-omni:open-recharge', openDialog);
1034  })();
1035</script>
1035 
1035<script>
1036      (() => {
1037        if (typeof window === 'undefined') return;
1038        const observer = new IntersectionObserver(
1039          (entries) => {
1040            for (const entry of entries) {
1041              if (entry.isIntersecting) {
1042                entry.target.classList.add('is-visible');
1043                observer.unobserve(entry.target);
1044              }
1045            }
1046          },
1047          { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }
1048        );
1049        const onReady = () => {
1050          document.querySelectorAll('.reveal').forEach((el) => observer.observe(el));
1051        };
1052        if (document.readyState === 'loading') {
1053          document.addEventListener('DOMContentLoaded', onReady);
1054        } else {
1055          onReady();
1056        }
1057      })();
1058    </script>
1058 </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.