diff --git a/app/layout.tsx b/app/layout.tsx
index a092d4fe..abd33a83 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -10,6 +10,8 @@ import { Sidebar } from '@/components/sidebar'
import { Analytics } from "@vercel/analytics/next"
import { SpeedInsights } from "@vercel/speed-insights/next"
import { Toaster } from '@/components/ui/sonner'
+import { IosInstallPrompt } from '@/components/pwa/ios-install-prompt'
+import { SwRegistration } from '@/components/pwa/sw-registration'
import { MapToggleProvider } from '@/components/map-toggle-context'
import { ProfileToggleProvider } from '@/components/profile-toggle-context'
import { CalendarToggleProvider } from '@/components/calendar-toggle-context'
@@ -36,6 +38,15 @@ export const metadata: Metadata = {
metadataBase: new URL('https://www.qcx.world'),
title,
description,
+ appleWebApp: {
+ capable: true,
+ statusBarStyle: 'default',
+ title: 'QCX'
+ },
+ icons: {
+ icon: '/icon-192.png',
+ apple: '/icon-192.png'
+ },
openGraph: {
title,
description
@@ -87,6 +98,8 @@ export default function RootLayout({
+
+
diff --git a/app/manifest.ts b/app/manifest.ts
new file mode 100644
index 00000000..0d5d5914
--- /dev/null
+++ b/app/manifest.ts
@@ -0,0 +1,34 @@
+import type { MetadataRoute } from 'next'
+
+export default function manifest(): MetadataRoute.Manifest {
+ return {
+ name: 'QCX - AI-powered language to Maps',
+ short_name: 'QCX',
+ description: 'A minimalistic AI-powered tool that transforms language into interactive maps.',
+ start_url: '/?utm_source=pwa',
+ display: 'standalone',
+ background_color: '#171717',
+ theme_color: '#171717',
+ icons: [
+ {
+ src: '/icon-192.png',
+ sizes: '192x192',
+ type: 'image/png',
+ purpose: 'any'
+ },
+ {
+ src: '/icon-512.png',
+ sizes: '512x512',
+ type: 'image/png',
+ purpose: 'any'
+ },
+ {
+ src: '/icon-maskable.png',
+ sizes: '1024x1024',
+ type: 'image/png',
+ purpose: 'maskable'
+ }
+ ],
+ categories: ['search', 'ai', 'productivity', 'maps']
+ }
+}
diff --git a/components/pwa/ios-install-prompt.tsx b/components/pwa/ios-install-prompt.tsx
new file mode 100644
index 00000000..b56deeb8
--- /dev/null
+++ b/components/pwa/ios-install-prompt.tsx
@@ -0,0 +1,73 @@
+'use client'
+
+import React, { useEffect, useState } from 'react'
+import { X, Share } from 'lucide-react'
+
+export function IosInstallPrompt() {
+ const [showPrompt, setShowPrompt] = useState(false)
+
+ useEffect(() => {
+ if (typeof window === 'undefined') return
+
+ // Detect iOS
+ const userAgent = window.navigator.userAgent
+ const isIosDevice = /iPad|iPhone|iPod/.test(userAgent)
+
+ // Detect if already in standalone mode (installed)
+ const isInStandaloneMode =
+ (window.navigator as any).standalone ||
+ window.matchMedia('(display-mode: standalone)').matches
+
+ // Check if prompt was dismissed recently
+ const isPromptDismissed = localStorage.getItem('iosPwaPromptDismissed')
+
+ if (isIosDevice && !isInStandaloneMode && !isPromptDismissed) {
+ setShowPrompt(true)
+ }
+ }, [])
+
+ const handleDismiss = () => {
+ setShowPrompt(false)
+ localStorage.setItem('iosPwaPromptDismissed', 'true')
+ }
+
+ if (!showPrompt) return null
+
+ return (
+
+
+
+
+
+
+
+
+
Install QCX
+
+ Add this app to your home screen for a better experience.
+
+
+
+
+
+
Tap the Share icon
+
+
+
+
then scroll down and select
+
"Add to Home Screen"
+
+
+
+ )
+}
diff --git a/components/pwa/sw-registration.tsx b/components/pwa/sw-registration.tsx
new file mode 100644
index 00000000..42fa6e34
--- /dev/null
+++ b/components/pwa/sw-registration.tsx
@@ -0,0 +1,22 @@
+'use client'
+
+import { useEffect } from 'react'
+
+export function SwRegistration() {
+ useEffect(() => {
+ if (typeof window !== 'undefined' && 'serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker
+ .register('/sw.js')
+ .then((registration) => {
+ console.log('SW registered: ', registration)
+ })
+ .catch((registrationError) => {
+ console.log('SW registration failed: ', registrationError)
+ })
+ })
+ }
+ }, [])
+
+ return null
+}
diff --git a/public/icon-192.png b/public/icon-192.png
new file mode 100644
index 00000000..d1216935
Binary files /dev/null and b/public/icon-192.png differ
diff --git a/public/icon-512.png b/public/icon-512.png
new file mode 100644
index 00000000..d1216935
Binary files /dev/null and b/public/icon-512.png differ
diff --git a/public/icon-maskable.png b/public/icon-maskable.png
new file mode 100644
index 00000000..d1216935
Binary files /dev/null and b/public/icon-maskable.png differ
diff --git a/public/sw.js b/public/sw.js
new file mode 100644
index 00000000..1d663e5f
--- /dev/null
+++ b/public/sw.js
@@ -0,0 +1,14 @@
+// public/sw.js
+// Basic service worker to satisfy PWA install criteria
+self.addEventListener('install', (event) => {
+ self.skipWaiting();
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(clients.claim());
+});
+
+self.addEventListener('fetch', (event) => {
+ // A fetch handler is required for the PWA to be installable on Chrome.
+ // This is a "no-op" fetch handler.
+});