diff --git a/app/layout.tsx b/app/layout.tsx
index 48057f0..27c760b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -4,6 +4,7 @@ import { Inter } from 'next/font/google'
import { LaunchGate } from '@/components/LaunchGate'
import { ServiceWorkerRegistrar } from '@/components/ServiceWorkerRegistrar'
import { PwaMetaTags } from '@/components/PwaMetaTags'
+import { InstallBanner } from '@/components/InstallBanner'
import './globals.css'
const inter = Inter({
@@ -58,6 +59,7 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
+
{children}
{/* Domain is encoded in the proxy script URL - no data-domain attribute needed */}
{process.env.NODE_ENV === 'production' && (
diff --git a/components/InstallBanner.tsx b/components/InstallBanner.tsx
new file mode 100644
index 0000000..b5b1f3e
--- /dev/null
+++ b/components/InstallBanner.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+import { useState, useRef, useEffect } from 'react'
+
+interface BeforeInstallPromptEvent extends Event {
+ prompt: () => Promise
+ userChoice: Promise<{ outcome: 'accepted' | 'dismissed' }>
+}
+
+export function InstallBanner() {
+ const [canInstall, setCanInstall] = useState(false)
+ const [dismissed, setDismissed] = useState(false)
+ const installEventRef = useRef(null)
+
+ useEffect(() => {
+ const handleBeforeInstall = (e: Event) => {
+ e.preventDefault()
+ installEventRef.current = e as BeforeInstallPromptEvent
+ setCanInstall(true)
+ }
+ const handleInstalled = () => {
+ setCanInstall(false)
+ }
+ window.addEventListener('beforeinstallprompt', handleBeforeInstall)
+ window.addEventListener('appinstalled', handleInstalled)
+ return () => {
+ window.removeEventListener('beforeinstallprompt', handleBeforeInstall)
+ window.removeEventListener('appinstalled', handleInstalled)
+ installEventRef.current = null
+ setCanInstall(false)
+ }
+ }, [])
+
+ const handleInstall = async () => {
+ const e = installEventRef.current
+ if (!e) return
+ await e.prompt()
+ const { outcome } = await e.userChoice
+ if (outcome === 'accepted') setCanInstall(false)
+ }
+
+ if (!canInstall || dismissed) return null
+
+ return (
+
+
Install Multicorn Learn
+
+
+
+
+
+ )
+}
diff --git a/components/PwaMetaTags.tsx b/components/PwaMetaTags.tsx
index 5a7ebc7..2eb81fe 100644
--- a/components/PwaMetaTags.tsx
+++ b/components/PwaMetaTags.tsx
@@ -4,13 +4,13 @@ import { useEffect } from 'react'
/**
* Injects iOS PWA meta tags. Next.js Metadata API does not output
- * apple-mobile-web-app-capable in a way that enables Safari splash screens,
+ * mobile-web-app-capable in a way that enables Safari splash screens,
* so we inject these on the client.
*/
export function PwaMetaTags() {
useEffect(() => {
const tags: [string, Record][] = [
- ['meta', { name: 'apple-mobile-web-app-capable', content: 'yes' }],
+ ['meta', { name: 'mobile-web-app-capable', content: 'yes' }],
['meta', { name: 'apple-mobile-web-app-status-bar-style', content: 'default' }],
['meta', { name: 'apple-mobile-web-app-title', content: 'Learn' }],
]
diff --git a/public/learn/apple-touch-icon.png b/public/learn/apple-touch-icon.png
index 92a8ec3..8c4d8e1 100644
Binary files a/public/learn/apple-touch-icon.png and b/public/learn/apple-touch-icon.png differ
diff --git a/public/learn/favicon-192x192.png b/public/learn/favicon-192x192.png
index 8f64ef9..f3ee545 100644
Binary files a/public/learn/favicon-192x192.png and b/public/learn/favicon-192x192.png differ
diff --git a/public/learn/favicon-512x512.png b/public/learn/favicon-512x512.png
index ebfdbf4..535099a 100644
Binary files a/public/learn/favicon-512x512.png and b/public/learn/favicon-512x512.png differ