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 & 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 & 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 & 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="{"zh":"䏿","en":"English","ja":"æ¥æ¬èª","ko":"íêµì´","es":"Español","fr":"Français","de":"Deutsch","it":"Italiano","nl":"Nederlands","pl":"Polski","pt-BR":"Português","ru":"Ð ÑÑÑкий","tr":"Türkçe","id":"Bahasa Indonesia","vi":"Tiếng Viá»t","th":"à¹à¸à¸¢","ms":"Bahasa Melayu","tl":"Filipino","hi":"हिनà¥à¤¦à¥","bn":"বাà¦à¦²à¦¾","ta":"தமிழà¯","te":"à°¤à±à°²à±à°à±","mr":"मराठà¥","ar":"Ø§ÙØ¹Ø±Ø¨ÙØ©","he":"×¢×ר×ת","fa":"ÙØ§Ø±Ø³Û","ur":"اردÙ"}" data-lang-urls="{"en":"/pricing/","es":"/es/pricing/","it":"/it/pricing/","hi":"/hi/pricing/","ar":"/ar/pricing/","zh":"/zh/pricing/","ja":"/ja/pricing/","ko":"/ko/pricing/","fr":"/fr/pricing/","de":"/de/pricing/","nl":"/nl/pricing/","pl":"/pl/pricing/","pt-BR":"/pt-BR/pricing/","ru":"/ru/pricing/","tr":"/tr/pricing/","id":"/id/pricing/","vi":"/vi/pricing/","th":"/th/pricing/","ms":"/ms/pricing/","tl":"/tl/pricing/","bn":"/bn/pricing/","ta":"/ta/pricing/","te":"/te/pricing/","mr":"/mr/pricing/","he":"/he/pricing/","fa":"/fa/pricing/","ur":"/ur/pricing/"}" data-banner-copy="{"en":{"prompt":"Choose another language to browse content adapted for your browser language preference.","continue":"Continue","close":"Close suggestion banner","selectLabel":"Select preferred language","regionLabel":"Language recommendation banner"},"zh":{"prompt":"éæ©å¦ä¸è¯è¨ä»¥æµè§éç¨äºä½ æµè§å¨è¯è¨å好çå
容ã","continue":"ç»§ç»","close":"å
³éè¯è¨å»ºè®®æ¨ªå¹
","selectLabel":"éæ©é¦éè¯è¨","regionLabel":"è¯è¨æ¨è横å¹
"},"ja":{"prompt":"å¥ã®è¨èªãé¸ã¶ã¨ããã©ã¦ã¶ã®è¨èªè¨å®ã«åãããã³ã³ãã³ãã表示ã§ãã¾ãã","continue":"ç¶ãã","close":"è¨èªææ¡ããã¼ãéãã","selectLabel":"叿ã®è¨èªã鏿","regionLabel":"è¨èªããããããã¼"},"ko":{"prompt":"ë¤ë¥¸ ì¸ì´ë¥¼ ì ííë©´ ë¸ë¼ì°ì ì¸ì´ ì¤ì ì ë§ë ì½í
ì¸ ë¥¼ ë³¼ ì ììµëë¤.","continue":"ê³ì","close":"ì¸ì´ ì ì ë°°ë ë«ê¸°","selectLabel":"ìíë ì¸ì´ ì í","regionLabel":"ì¸ì´ ì¶ì² ë°°ë"},"es":{"prompt":"Elija otro idioma para ver contenido adaptado a la preferencia de idioma de su navegador.","continue":"Continuar","close":"Cerrar banner de sugerencia de idioma","selectLabel":"Seleccionar idioma preferido","regionLabel":"Banner de recomendación de idioma"},"fr":{"prompt":"Choisissez une autre langue pour afficher le contenu adapté aux préférences linguistiques de votre navigateur.","continue":"Continuer","close":"Fermer la bannière de suggestion de langue","selectLabel":"Sélectionner la langue préférée","regionLabel":"Bannière de recommandation de langue"},"de":{"prompt":"Wählen Sie eine andere Sprache, um Inhalte entsprechend Ihrer Browser-Spracheinstellung anzuzeigen.","continue":"Weiter","close":"Sprachvorschlagsbanner schlieÃen","selectLabel":"Bevorzugte Sprache auswählen","regionLabel":"Sprachempfehlungsbanner"},"it":{"prompt":"Scegli un'altra lingua per visualizzare contenuti adattati alle preferenze linguistiche del browser.","continue":"Continua","close":"Chiudi banner suggerimento lingua","selectLabel":"Seleziona lingua preferita","regionLabel":"Banner raccomandazione lingua"},"nl":{"prompt":"Kies een andere taal om content te bekijken die is afgestemd op de taalvoorkeur van je browser.","continue":"Doorgaan","close":"Sluit taalsuggestiebanner","selectLabel":"Selecteer voorkeurstaal","regionLabel":"Taalaanbevelingsbanner"},"pl":{"prompt":"Wybierz inny jÄzyk, aby przeglÄ
daÄ treÅci dopasowane do preferencji jÄzykowych przeglÄ
darki.","continue":"Kontynuuj","close":"Zamknij baner sugestii jÄzyka","selectLabel":"Wybierz preferowany jÄzyk","regionLabel":"Baner rekomendacji jÄzyka"},"pt-BR":{"prompt":"Escolha outro idioma para ver conteúdo adaptado à preferência de idioma do seu navegador.","continue":"Continuar","close":"Fechar banner de sugestão de idioma","selectLabel":"Selecionar idioma preferido","regionLabel":"Banner de recomendação de idioma"},"ru":{"prompt":"ÐÑбеÑиÑе дÑÑгой ÑзÑк, ÑÑÐ¾Ð±Ñ Ð¿ÑоÑмаÑÑиваÑÑ ÐºÐ¾Ð½ÑенÑ, адапÑиÑованнÑй к ÑзÑковÑм наÑÑÑойкам ваÑего бÑаÑзеÑа.","continue":"ÐÑодолжиÑÑ","close":"ÐакÑÑÑÑ Ð±Ð°Ð½Ð½ÐµÑ Ð¿ÑÐµÐ´Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÑзÑка","selectLabel":"ÐÑбеÑиÑе пÑедпоÑиÑаемÑй ÑзÑк","regionLabel":"ÐÐ°Ð½Ð½ÐµÑ ÑекомендаÑии ÑзÑка"},"tr":{"prompt":"Tarayıcı dil tercihinize uygun içeriÄi görmek için baÅka bir dil seçin.","continue":"Devam","close":"Dil önerisi baÅlıÄını kapat","selectLabel":"Tercih edilen dili seçin","regionLabel":"Dil önerisi baÅlıÄı"},"id":{"prompt":"Pilih bahasa lain untuk melihat konten yang disesuaikan dengan preferensi bahasa browser Anda.","continue":"Lanjutkan","close":"Tutup banner saran bahasa","selectLabel":"Pilih bahasa pilihan","regionLabel":"Banner rekomendasi bahasa"},"vi":{"prompt":"Chá»n ngôn ngữ khác Äá» xem ná»i dung phù hợp vá»i tùy chá»n ngôn ngữ trình duyá»t cá»§a bạn.","continue":"Tiếp tục","close":"Äóng banner gợi ý ngôn ngữ","selectLabel":"Chá»n ngôn ngữ ưa thÃch","regionLabel":"Banner Äá» xuất ngôn ngữ"},"th":{"prompt":"à¹à¸¥à¸·à¸à¸à¸ าษาà¸à¸·à¹à¸à¹à¸à¸·à¹à¸à¸à¸¹à¹à¸à¸·à¹à¸à¸«à¸²à¸à¸µà¹à¸à¸£à¸±à¸à¹à¸«à¹à¹à¸à¹à¸²à¸à¸±à¸à¸à¸²à¸£à¸à¸±à¹à¸à¸à¹à¸²à¸ าษาà¸à¸à¸à¹à¸à¸£à¸²à¸§à¹à¹à¸à¸à¸£à¹à¸à¸à¸à¸à¸¸à¸","continue":"à¸à¸³à¹à¸à¸´à¸à¸à¸²à¸£à¸à¹à¸","close":"à¸à¸´à¸à¹à¸à¸à¹à¸à¸à¸£à¹à¹à¸à¸°à¸à¸³à¸ าษา","selectLabel":"à¹à¸¥à¸·à¸à¸à¸ าษาà¸à¸µà¹à¸à¹à¸à¸à¸à¸²à¸£","regionLabel":"à¹à¸à¸à¹à¸à¸à¸£à¹à¹à¸à¸°à¸à¸³à¸ าษา"},"ms":{"prompt":"Pilih bahasa lain untuk melihat kandungan yang disesuaikan dengan pilihan bahasa pelayar anda.","continue":"Teruskan","close":"Tutup banner cadangan bahasa","selectLabel":"Pilih bahasa pilihan","regionLabel":"Banner cadangan bahasa"},"tl":{"prompt":"Pumili ng ibang wika upang makita ang content na naaayon sa preference ng wika ng browser mo.","continue":"Magpatuloy","close":"Isara ang banner ng mungkahi sa wika","selectLabel":"Piliin ang gustong wika","regionLabel":"Banner ng rekomendasyon sa wika"},"bn":{"prompt":"à¦à¦ªà¦¨à¦¾à¦° বà§à¦°à¦¾à¦à¦à¦¾à¦°à§à¦° à¦à¦¾à¦·à¦¾à¦° পà¦à¦¨à§à¦¦ à¦
নà§à¦¯à¦¾à¦¯à¦¼à§ à¦à¦¨à¦à§à¦¨à§à¦ দà§à¦à¦¤à§ à¦
নà§à¦¯ à¦à¦¾à¦·à¦¾ বà§à¦à§ নিন।","continue":"à¦à¦¾à¦²à¦¿à¦¯à¦¼à§ যান","close":"à¦à¦¾à¦·à¦¾ পরামরà§à¦¶ বà§à¦¯à¦¾à¦¨à¦¾à¦° বনà§à¦§ à¦à¦°à§à¦¨","selectLabel":"পà¦à¦¨à§à¦¦à§à¦° à¦à¦¾à¦·à¦¾ নিরà§à¦¬à¦¾à¦à¦¨ à¦à¦°à§à¦¨","regionLabel":"à¦à¦¾à¦·à¦¾ সà§à¦ªà¦¾à¦°à¦¿à¦¶ বà§à¦¯à¦¾à¦¨à¦¾à¦°"},"ta":{"prompt":"à®à®à¯à®à®³à¯ à®à®²à®¾à®µà®¿ à®®à¯à®´à®¿ விரà¯à®ªà¯à®ª
7தà¯à®¤à®¿à®±à¯à®à¯ à®à®±à¯à®ª à®à®³à¯à®³à®à®à¯à®à®¤à¯à®¤à¯à®ªà¯ பாரà¯à®à¯à® வà¯à®±à¯ à®®à¯à®´à®¿à®¯à¯à®¤à¯ தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®à¯à®à®µà¯à®®à¯.","continue":"தà¯à®à®°à®µà¯à®®à¯","close":"à®®à¯à®´à®¿ பரிநà¯à®¤à¯à®°à¯ பà¯à®©à®°à¯ à®®à¯à®à®µà¯à®®à¯","selectLabel":"விரà¯à®ªà¯à®ª à®®à¯à®´à®¿à®¯à¯à®¤à¯ தà¯à®°à¯à®¨à¯à®¤à¯à®à¯à®à¯à®à®µà¯à®®à¯","regionLabel":"à®®à¯à®´à®¿ பரிநà¯à®¤à¯à®°à¯ பà¯à®©à®°à¯"},"te":{"prompt":"మౠబà±à°°à±à°à°°à± à°à°¾à°·à°¾ à°ªà±à°°à°¾à°§à°¾à°¨à±à°¯à°¤à°à± à°
à°¨à±à°à±à°£à°à°à°¾ à°à°à°à±à°à°à±âనౠà°à±à°¡à°à°¾à°¨à°¿à°à°¿ మరà±à° à°à°¾à°·à°¨à± à°à°à°à±à°à±à°à°¡à°¿.","continue":"à°à±à°¨à°¸à°¾à°à°¿à°à°à±","close":"à°à°¾à°· à°¸à±à°à°¨ à°¬à±à°¯à°¾à°¨à°°à±âనౠమà±à°¸à°¿à°µà±à°¯à°¿","selectLabel":"à°à°·à±à°à°®à±à°¨ à°à°¾à°·à°¨à± à°à°à°à±à°à±à°à°¡à°¿","regionLabel":"à°à°¾à°· సిఫారà±à°¸à± à°¬à±à°¯à°¾à°¨à°°à±"},"mr":{"prompt":"तà¥à¤®à¤à¥à¤¯à¤¾ बà¥à¤°à¤¾à¤à¤à¤°à¤à¥à¤¯à¤¾ à¤à¤¾à¤·à¤¾ पà¥à¤°à¤¾à¤§à¤¾à¤¨à¥à¤¯à¤¾à¤¨à¥à¤¸à¤¾à¤° सामà¤à¥à¤°à¥ पाहणà¥à¤¯à¤¾à¤¸à¤¾à¤ ॠदà¥à¤¸à¤°à¥ à¤à¤¾à¤·à¤¾ निवडा.","continue":"पà¥à¤¢à¥ à¤à¤¾","close":"à¤à¤¾à¤·à¤¾ सà¥à¤à¤¨à¤¾ बà¥
नर बà¤à¤¦ à¤à¤°à¤¾","selectLabel":"पसà¤à¤¤à¥à¤à¥ à¤à¤¾à¤·à¤¾ निवडा","regionLabel":"à¤à¤¾à¤·à¤¾ शिफारस बà¥
नर"},"ar":{"prompt":"اختر ÙØºØ© Ø£Ø®Ø±Ù ÙØ¹Ø±Ø¶ Ù
ØØªÙÙ ÙØªÙÙÙ Ù
ع ØªÙØ¶ÙÙØ§Øª ÙØºØ© Ù
ØªØµÙØÙ.","continue":"Ù
تابعة","close":"Ø¥ØºÙØ§Ù Ø´Ø±ÙØ· Ø§ÙØªØ±Ø§Ø اÙÙØºØ©","selectLabel":"اختر اÙÙØºØ© اÙÙ
ÙØ¶ÙØ©","regionLabel":"Ø´Ø±ÙØ· ØªÙØµÙØ© اÙÙØºØ©"},"he":{"prompt":"××ר ×©×¤× ××רת ××× ×צפ×ת ×ת××× ×××ת×× ×××¢×פת ××©×¤× ×©× ××פ××¤× ×©××.","continue":"××ש×","close":"ס××ר ×ת ××× ×¨ ×צעת ×שפ×","selectLabel":"××ר ×©×¤× ×××¢×פת","regionLabel":"××× ×¨ ×××צת שפ×"},"fa":{"prompt":"زبا٠دÛÚ¯Ø±Û Ø±Ø§ Ø§ÙØªØ®Ø§Ø¨ Ú©ÙÛØ¯ تا Ù
ØØªÙاÛÛ Ù
ØªÙØ§Ø³Ø¨ با ØªØ±Ø¬ÛØ زبا٠Ù
Ø±ÙØ±Ú¯Ø± Ø®ÙØ¯ ببÛÙÛØ¯.","continue":"اداÙ
Ù","close":"Ø¨Ø³ØªÙ Ø¨ÙØ± Ù¾ÛØ´ÙÙØ§Ø¯ زباÙ","selectLabel":"Ø§ÙØªØ®Ø§Ø¨ Ø²Ø¨Ø§Ù ØªØ±Ø¬ÛØÛ","regionLabel":"Ø¨ÙØ± Ù¾ÛØ´ÙÙØ§Ø¯ زباÙ"},"hi":{"prompt":"à¤
पनॠबà¥à¤°à¤¾à¤à¤à¤¼à¤° à¤à¤¾à¤·à¤¾ पà¥à¤°à¤¾à¤¥à¤®à¤¿à¤à¤¤à¤¾ à¤à¥ à¤
नà¥à¤¸à¤¾à¤° सामà¤à¥à¤°à¥ दà¥à¤à¤¨à¥ à¤à¥ लिठदà¥à¤¸à¤°à¥ à¤à¤¾à¤·à¤¾ à¤à¥à¤¨à¥à¤à¥¤","continue":"à¤à¤¾à¤°à¥ रà¤à¥à¤","close":"à¤à¤¾à¤·à¤¾ सà¥à¤à¤¾à¤µ बà¥à¤¨à¤° बà¤à¤¦ à¤à¤°à¥à¤","selectLabel":"पसà¤à¤¦à¥à¤¦à¤¾ à¤à¤¾à¤·à¤¾ à¤à¥à¤¨à¥à¤","regionLabel":"à¤à¤¾à¤·à¤¾ à¤
नà¥à¤¶à¤à¤¸à¤¾ बà¥à¤¨à¤°"},"ur":{"prompt":"اپÙÛ Ø¨Ø±Ø§Ø¤Ø²Ø± Ø²Ø¨Ø§Ù Ú©Û ØªØ±Ø¬ÛØ Ú©Û Ù
طاب٠Ù
ÙØ§Ø¯ دÛÚ©Ú¾ÙÛ Ú©Û ÙÛÛ Ø¯ÙØ³Ø±Û زبا٠Ù
ÙØªØ®Ø¨ کرÛÚºÛ","continue":"Ø¬Ø§Ø±Û Ø±Ú©Ú¾ÛÚº","close":"Ø²Ø¨Ø§Ù Ú©Û ØªØ¬ÙÛØ² بÛÙØ± Ø¨ÙØ¯ کرÛÚº","selectLabel":"Ù¾Ø³ÙØ¯ÛØ¯Û Ø²Ø¨Ø§Ù Ù
ÙØªØ®Ø¨ کرÛÚº","regionLabel":"Ø²Ø¨Ø§Ù Ú©Û Ø³ÙØ§Ø±Ø´ بÛÙØ±"}}" style="display: none;" data-astro-cid-xujs2ijk> <div class="locale-banner-inner" data-astro-cid-xujs2ijk> <div class="locale-banner-text" data-astro-cid-xujs2ijk> <span id="locale-banner-message" data-astro-cid-xujs2ijk>Choose another language to browse content adapted for your browser language preference.</span> </div>
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 & 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 & sign in</span> <span data-label-loading hidden class="flex items-center gap-2"> <span class="inline-block h-3.5 w-3.5 animate-spin rounded-full border-2 border-white/70 border-t-transparent"></span> Verifying⦠</span> </button> <div class="text-center text-xs text-zinc-400"> <button type="button" data-action="resend" data-template="Resend in {n}s" class="font-semibold text-violet-300 transition-colors hover:text-violet-200 disabled:cursor-not-allowed disabled:text-zinc-500 disabled:hover:text-zinc-500"> <span data-resend-idle>Resend code</span> <span data-resend-countdown hidden></span> </button> </div> </div> <p data-error hidden class="rounded-xl border border-rose-500/30 bg-rose-500/10 px-3 py-2 text-sm text-rose-200"></p> <p class="text-center text-xs leading-relaxed text-zinc-500"> By signing in, you agree to our <a href="/terms/" class="text-zinc-300 underline decoration-zinc-600 underline-offset-2 hover:text-white">Terms of Service</a> and <a href="/privacy/" class="text-zinc-300 underline decoration-zinc-600 underline-offset-2 hover:text-white">Privacy Policy</a> </p> </div> <div data-state="success" hidden class="flex flex-col items-center gap-5 py-4 text-center"> <span class="grid h-16 w-16 place-items-center rounded-full bg-emerald-500/15 text-emerald-300 ring-1 ring-emerald-400/40"> <svg viewBox="0 0 24 24" class="h-8 w-8" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" d="M5 12l4.5 4.5L19 7"></path> </svg> </span> <h2 class="font-display text-2xl font-semibold tracking-tight text-white"> You're signed in </h2> <p class="max-w-xs text-sm leading-relaxed text-zinc-400"> You can close this and keep exploring. Try the prompt builder when you are ready. </p> <p data-success-user class="text-sm font-semibold text-zinc-200"></p> <div class="flex w-full flex-col items-stretch gap-2 sm:flex-row sm:justify-center sm:gap-3"> <a href="/prompt-generator/" class="btn-primary justify-center"> Open the prompt builder <svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true"> <path fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M13 6l6 6-6 6"></path> </svg> </a> <button type="button" data-close class="btn-ghost justify-center"> Close </button> </div> </div> </div> </dialog>
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.