diff --git a/packages/app/src/components/github-star-modal.tsx b/packages/app/src/components/github-star-modal.tsx
new file mode 100644
index 0000000..36c985a
--- /dev/null
+++ b/packages/app/src/components/github-star-modal.tsx
@@ -0,0 +1,126 @@
+'use client';
+
+import { track } from '@/lib/analytics';
+import { Star } from 'lucide-react';
+import { useCallback, useEffect, useState } from 'react';
+
+import { GITHUB_OWNER, GITHUB_REPO } from '@semianalysisai/inferencex-constants';
+import { Button } from '@/components/ui/button';
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog';
+
+const GITHUB_REPO_URL = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}`;
+export const STORAGE_KEY = 'inferencex-star-modal-dismissed';
+export const STARRED_KEY = 'inferencex-star-modal-starred';
+export const DISMISS_DURATION_MS = 7 * 24 * 60 * 60 * 1000; // 1 week
+
+let sessionDismissed = false;
+
+export function shouldShowModal(): boolean {
+ if (sessionDismissed) return false;
+ try {
+ if (localStorage.getItem(STARRED_KEY)) return false;
+ const value = localStorage.getItem(STORAGE_KEY);
+ if (!value) return true;
+ const dismissedAt = Number(value);
+ if (Number.isNaN(dismissedAt)) return true;
+ return Date.now() - dismissedAt >= DISMISS_DURATION_MS;
+ } catch {
+ return false;
+ }
+}
+
+export function saveDismissTimestamp(): void {
+ try {
+ localStorage.setItem(STORAGE_KEY, String(Date.now()));
+ } catch {
+ // localStorage unavailable
+ }
+}
+
+export function saveStarred(): void {
+ try {
+ localStorage.setItem(STARRED_KEY, '1');
+ } catch {
+ // localStorage unavailable
+ }
+}
+
+export function GitHubStarModal() {
+ const [open, setOpen] = useState(false);
+ const [ready, setReady] = useState(false);
+
+ useEffect(() => {
+ if (shouldShowModal()) {
+ setOpen(true);
+ track('github_star_modal_shown');
+ }
+ setReady(true);
+ }, []);
+
+ const handleDismiss = useCallback(() => {
+ setOpen(false);
+ sessionDismissed = true;
+ saveDismissTimestamp();
+ track('github_star_modal_dismissed');
+ }, []);
+
+ const handleStar = useCallback(() => {
+ window.open(GITHUB_REPO_URL, '_blank', 'noopener,noreferrer');
+ setOpen(false);
+ sessionDismissed = true;
+ saveStarred();
+ track('github_star_modal_starred');
+ }, []);
+
+ return (
+ <>
+ {ready && }
+
+ >
+ );
+}
diff --git a/packages/app/src/components/page-content.tsx b/packages/app/src/components/page-content.tsx
index b119962..5688bd6 100644
--- a/packages/app/src/components/page-content.tsx
+++ b/packages/app/src/components/page-content.tsx
@@ -10,6 +10,7 @@ import { GpuSpecsContent } from '@/components/gpu-specs/gpu-specs-content';
import GpuMetricsDisplay from '@/components/gpu-power/GpuPowerDisplay';
import HistoricalTrendsDisplay from '@/components/trends/HistoricalTrendsDisplay';
import { ExportNudge } from '@/components/export-nudge';
+import { GitHubStarModal } from '@/components/github-star-modal';
import { StarNudge } from '@/components/star-nudge';
import { InferenceProvider } from '@/components/inference/InferenceContext';
import InferenceChartDisplay from '@/components/inference/ui/ChartDisplay';
@@ -244,6 +245,7 @@ function ChartTabs({ initialTab }: { initialTab: string }) {
export function PageContent({ initialTab = 'inference' }: { initialTab?: string }) {
return (
<>
+