diff --git a/frontend/src/components/layout/GitHubStarBanner.tsx b/frontend/src/components/layout/GitHubStarBanner.tsx index b921791a..3e19b3bc 100644 --- a/frontend/src/components/layout/GitHubStarBanner.tsx +++ b/frontend/src/components/layout/GitHubStarBanner.tsx @@ -6,10 +6,17 @@ const GITHUB_URL = 'https://github.com/davidmonterocrespo24/velxio'; interface Props { onClose: () => void; + // Fired when the user clicks through to the repo (distinct from a plain + // dismiss) — lets the caller never prompt that user again. + onStarClick: () => void; + // 1 = first ask; 2 = follow-up ask for someone who dismissed the first one. + round?: 1 | 2; } -export const GitHubStarBanner: React.FC = ({ onClose }) => { +export const GitHubStarBanner: React.FC = ({ onClose, onStarClick, round = 1 }) => { const { t } = useTranslation(); + const titleKey = round === 2 ? 'starBanner.title2' : 'starBanner.title'; + const bodyKey = round === 2 ? 'starBanner.body2' : 'starBanner.body'; return (
@@ -19,14 +26,14 @@ export const GitHubStarBanner: React.FC = ({ onClose }) => {
- {t('starBanner.title')} -

{t('starBanner.body')}

+ {t(titleKey)} +

{t(bodyKey)}

⭐ {t('starBanner.cta')} diff --git a/frontend/src/i18n/locales/de/common.json b/frontend/src/i18n/locales/de/common.json index 898133c1..88e4aa86 100644 --- a/frontend/src/i18n/locales/de/common.json +++ b/frontend/src/i18n/locales/de/common.json @@ -373,6 +373,8 @@ "ariaLabel": "Velxio auf GitHub mit Stern markieren", "title": "Gefällt Ihnen Velxio?", "body": "Ein ⭐ auf GitHub hilft mehr Entwicklern, das Projekt zu entdecken – es dauert nur 2 Sekunden!", + "title2": "Ein kurzer Gefallen?", + "body2": "Velxio ist kostenlos und Open Source. Ein Stern auf GitHub ist der einfachste Weg, das Projekt zu unterstützen und mehr Menschen zu helfen, es zu finden.", "cta": "Auf GitHub mit Stern markieren", "dismiss": "Schließen" }, diff --git a/frontend/src/i18n/locales/en/common.json b/frontend/src/i18n/locales/en/common.json index 6c26e8f4..2740317f 100644 --- a/frontend/src/i18n/locales/en/common.json +++ b/frontend/src/i18n/locales/en/common.json @@ -373,6 +373,8 @@ "ariaLabel": "Star Velxio on GitHub", "title": "Enjoying Velxio?", "body": "A ⭐ on GitHub helps more developers discover the project — it only takes 2 seconds!", + "title2": "One quick favor?", + "body2": "Velxio is free and open-source. A star on GitHub is the simplest way to support it and help more people find the project.", "cta": "Star on GitHub", "dismiss": "Dismiss" }, diff --git a/frontend/src/i18n/locales/es/common.json b/frontend/src/i18n/locales/es/common.json index 4f965e32..5e083391 100644 --- a/frontend/src/i18n/locales/es/common.json +++ b/frontend/src/i18n/locales/es/common.json @@ -373,6 +373,8 @@ "ariaLabel": "Marcar Velxio con estrella en GitHub", "title": "¿Disfrutando de Velxio?", "body": "¡Una ⭐ en GitHub ayuda a que más desarrolladores descubran el proyecto — solo toma 2 segundos!", + "title2": "¿Un favor rápido?", + "body2": "Velxio es gratis y de código abierto. Una estrella en GitHub es la forma más simple de apoyarlo y ayudar a que más gente lo descubra.", "cta": "Marcar con estrella en GitHub", "dismiss": "Descartar" }, diff --git a/frontend/src/i18n/locales/fr/common.json b/frontend/src/i18n/locales/fr/common.json index 5193c5c4..3f00595f 100644 --- a/frontend/src/i18n/locales/fr/common.json +++ b/frontend/src/i18n/locales/fr/common.json @@ -373,6 +373,8 @@ "ariaLabel": "Mettre une étoile à Velxio sur GitHub", "title": "Vous appréciez Velxio ?", "body": "Une ⭐ sur GitHub aide plus de développeurs à découvrir le projet — cela ne prend que 2 secondes !", + "title2": "Un petit service ?", + "body2": "Velxio est gratuit et open source. Une étoile sur GitHub est le moyen le plus simple de le soutenir et d'aider davantage de personnes à le découvrir.", "cta": "Mettre une étoile sur GitHub", "dismiss": "Ignorer" }, diff --git a/frontend/src/i18n/locales/it/common.json b/frontend/src/i18n/locales/it/common.json index 6692ec0d..4b37eb40 100644 --- a/frontend/src/i18n/locales/it/common.json +++ b/frontend/src/i18n/locales/it/common.json @@ -373,6 +373,8 @@ "ariaLabel": "Metti una stella a Velxio su GitHub", "title": "Ti piace Velxio?", "body": "Una ⭐ su GitHub aiuta più sviluppatori a scoprire il progetto — ci vogliono solo 2 secondi!", + "title2": "Un piccolo favore?", + "body2": "Velxio è gratuito e open source. Una stella su GitHub è il modo più semplice per sostenerlo e aiutare più persone a scoprire il progetto.", "cta": "Metti stella su GitHub", "dismiss": "Ignora" }, diff --git a/frontend/src/i18n/locales/ja/common.json b/frontend/src/i18n/locales/ja/common.json index 1f050478..6a513e48 100644 --- a/frontend/src/i18n/locales/ja/common.json +++ b/frontend/src/i18n/locales/ja/common.json @@ -373,6 +373,8 @@ "ariaLabel": "GitHubでVelxioにスターを付ける", "title": "Velxioを気に入っていますか?", "body": "GitHubで⭐を付けると、より多くの開発者がこのプロジェクトを発見できます—たった2秒で完了します!", + "title2": "ちょっとしたお願いです", + "body2": "Velxio は無料のオープンソースです。GitHub でスターを付けることが、プロジェクトを応援し、より多くの人に見つけてもらう一番簡単な方法です。", "cta": "GitHubでスターを付ける", "dismiss": "閉じる" }, diff --git a/frontend/src/i18n/locales/pt-br/common.json b/frontend/src/i18n/locales/pt-br/common.json index b47a6585..5058379d 100644 --- a/frontend/src/i18n/locales/pt-br/common.json +++ b/frontend/src/i18n/locales/pt-br/common.json @@ -373,6 +373,8 @@ "ariaLabel": "Favoritar Velxio no GitHub", "title": "Gostando do Velxio?", "body": "Uma ⭐ no GitHub ajuda mais desenvolvedores a descobrirem o projeto — leva apenas 2 segundos!", + "title2": "Um favor rápido?", + "body2": "O Velxio é gratuito e de código aberto. Uma estrela no GitHub é a forma mais simples de apoiá-lo e ajudar mais pessoas a descobrirem o projeto.", "cta": "Favoritar no GitHub", "dismiss": "Dispensar" }, diff --git a/frontend/src/i18n/locales/ru/common.json b/frontend/src/i18n/locales/ru/common.json index a517b209..94777c6b 100644 --- a/frontend/src/i18n/locales/ru/common.json +++ b/frontend/src/i18n/locales/ru/common.json @@ -373,6 +373,8 @@ "ariaLabel": "Поставьте звезду Velxio на GitHub", "title": "Нравится Velxio?", "body": "Звезда на GitHub помогает большему числу разработчиков узнать о проекте — это занимает всего 2 секунды!", + "title2": "Небольшая просьба", + "body2": "Velxio — бесплатный проект с открытым исходным кодом. Звезда на GitHub — самый простой способ поддержать его и помочь большему числу людей узнать о проекте.", "cta": "Поставить звезду на GitHub", "dismiss": "Закрыть" }, diff --git a/frontend/src/i18n/locales/zh-cn/common.json b/frontend/src/i18n/locales/zh-cn/common.json index 31557ade..d63db6f6 100644 --- a/frontend/src/i18n/locales/zh-cn/common.json +++ b/frontend/src/i18n/locales/zh-cn/common.json @@ -373,6 +373,8 @@ "ariaLabel": "在 GitHub 上为 Velxio 加星", "title": "喜欢 Velxio 吗?", "body": "在 GitHub 上点个 ⭐ 可以帮助更多开发者发现这个项目——只需 2 秒钟!", + "title2": "帮个小忙?", + "body2": "Velxio 是免费的开源项目。在 GitHub 上点个 star 是支持它、帮助更多人发现它最简单的方式。", "cta": "在 GitHub 上加星", "dismiss": "忽略" }, diff --git a/frontend/src/pages/EditorPage.tsx b/frontend/src/pages/EditorPage.tsx index ccab47d4..df65f8a7 100644 --- a/frontend/src/pages/EditorPage.tsx +++ b/frontend/src/pages/EditorPage.tsx @@ -90,6 +90,7 @@ export const EditorPage: React.FC = () => { const setCompileLogs = useCompileLogsStore((s) => s.setLogs); const [bottomPanelHeight, setBottomPanelHeight] = useState(BOTTOM_PANEL_DEFAULT); const [showStarBanner, setShowStarBanner] = useState(false); + const [starRound, setStarRound] = useState<1 | 2>(1); // ── Electrical simulation (one-time mount) ──────────────────────────────── // `startSimulation()` is the single entry point: it constructs the @@ -100,14 +101,29 @@ export const EditorPage: React.FC = () => { return startSimulation(); }, []); - // ── GitHub star prompt (show once: 2nd visit OR after 3 min) ────────────── + // ── GitHub star prompt (show twice at most: 2nd visit OR after 3 min) ────── + // Three localStorage flags drive this: + // velxio_star_prompted → dismissed the first ask + // velxio_star_prompted_v2 → dismissed the follow-up ask (stop forever) + // velxio_star_clicked → clicked through to the repo (stop forever) + // Anyone who dismissed the first ask WITHOUT clicking through gets one + // follow-up (round 2) with a stronger message; clicking the repo link at + // any time opts them out permanently. useEffect(() => { const STAR_KEY = 'velxio_star_prompted'; + const STAR_KEY_V2 = 'velxio_star_prompted_v2'; + const STAR_CLICKED_KEY = 'velxio_star_clicked'; const VISITS_KEY = 'velxio_editor_visits'; const FIRST_VISIT_KEY = 'velxio_editor_first_visit'; const THREE_MIN = 3 * 60 * 1000; - if (localStorage.getItem(STAR_KEY)) return; + // Never bother people who already starred or already saw the follow-up. + if (localStorage.getItem(STAR_CLICKED_KEY)) return; + if (localStorage.getItem(STAR_KEY_V2)) return; + + // Round 2 = they dismissed the first ask (without clicking through). + const round = localStorage.getItem(STAR_KEY) ? 2 : 1; + setStarRound(round); // Increment visit counter const visits = parseInt(localStorage.getItem(VISITS_KEY) ?? '0', 10) + 1; @@ -129,13 +145,26 @@ export const EditorPage: React.FC = () => { const elapsed = Date.now() - firstVisit; const delay = Math.max(0, THREE_MIN - elapsed); const timer = setTimeout(() => { - if (!localStorage.getItem(STAR_KEY)) setShowStarBanner(true); + if (!localStorage.getItem(STAR_CLICKED_KEY) && !localStorage.getItem(STAR_KEY_V2)) { + setShowStarBanner(true); + } }, delay); return () => clearTimeout(timer); }, []); const handleDismissStarBanner = () => { - localStorage.setItem('velxio_star_prompted', '1'); + // First dismiss → mark round 1; second dismiss → mark round 2 (stop forever). + if (localStorage.getItem('velxio_star_prompted')) { + localStorage.setItem('velxio_star_prompted_v2', '1'); + } else { + localStorage.setItem('velxio_star_prompted', '1'); + } + setShowStarBanner(false); + }; + + const handleStarClick = () => { + // They went to the repo — opt them out of any further prompts. + localStorage.setItem('velxio_star_clicked', '1'); setShowStarBanner(false); }; const [explorerOpen, setExplorerOpen] = useState(true); @@ -642,7 +671,13 @@ export const EditorPage: React.FC = () => {
- {showStarBanner && } + {showStarBanner && ( + + )} {/* Slot reserved for the private pro overlay (e.g. agent chat panel). Self-hosted builds without an overlay see nothing here. */}