Initial commit: PassMaster PWA MVP
This commit is contained in:
94
app/globals.css
Normal file
94
app/globals.css
Normal file
@@ -0,0 +1,94 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 222.2 84% 4.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 222.2 84% 4.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 222.2 84% 4.9%;
|
||||
--primary: 221.2 83.2% 53.3%;
|
||||
--primary-foreground: 210 40% 98%;
|
||||
--secondary: 210 40% 96%;
|
||||
--secondary-foreground: 222.2 84% 4.9%;
|
||||
--muted: 210 40% 96%;
|
||||
--muted-foreground: 215.4 16.3% 46.9%;
|
||||
--accent: 210 40% 96%;
|
||||
--accent-foreground: 222.2 84% 4.9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 214.3 31.8% 91.4%;
|
||||
--input: 214.3 31.8% 91.4%;
|
||||
--ring: 221.2 83.2% 53.3%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--foreground: 210 40% 98%;
|
||||
--card: 222.2 84% 4.9%;
|
||||
--card-foreground: 210 40% 98%;
|
||||
--popover: 222.2 84% 4.9%;
|
||||
--popover-foreground: 210 40% 98%;
|
||||
--primary: 217.2 91.2% 59.8%;
|
||||
--primary-foreground: 222.2 84% 4.9%;
|
||||
--secondary: 217.2 32.6% 17.5%;
|
||||
--secondary-foreground: 210 40% 98%;
|
||||
--muted: 217.2 32.6% 17.5%;
|
||||
--muted-foreground: 215 20.2% 65.1%;
|
||||
--accent: 217.2 32.6% 17.5%;
|
||||
--accent-foreground: 210 40% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 210 40% 98%;
|
||||
--border: 217.2 32.6% 17.5%;
|
||||
--input: 217.2 32.6% 17.5%;
|
||||
--ring: 224.3 76.3% 94.1%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.btn-primary {
|
||||
@apply bg-primary-600 hover:bg-primary-700 text-white font-medium py-3 px-6 rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
@apply bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-900 dark:text-gray-100 font-medium py-3 px-6 rounded-lg transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
@apply w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-transparent transition-colors duration-200;
|
||||
}
|
||||
|
||||
.strength-meter {
|
||||
@apply h-2 rounded-full transition-all duration-300;
|
||||
}
|
||||
|
||||
.strength-weak {
|
||||
@apply bg-red-500;
|
||||
}
|
||||
|
||||
.strength-ok {
|
||||
@apply bg-yellow-500;
|
||||
}
|
||||
|
||||
.strength-strong {
|
||||
@apply bg-blue-500;
|
||||
}
|
||||
|
||||
.strength-excellent {
|
||||
@apply bg-green-500;
|
||||
}
|
||||
}
|
||||
150
app/layout.tsx
Normal file
150
app/layout.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import type { Metadata } from 'next'
|
||||
import './globals.css'
|
||||
import { ThemeProvider } from '@/components/theme-provider'
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Footer } from '@/components/layout/Footer'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'PassMaster – Free Offline Secure Password Generator (Open Source)',
|
||||
description: 'Generate ultra-secure passwords instantly, offline with client-side encryption. 100% open-source, private, and free.',
|
||||
keywords: ['password generator', 'secure passwords', 'offline password generator', 'open source', 'privacy', 'security'],
|
||||
authors: [{ name: 'PassMaster' }],
|
||||
creator: 'PassMaster',
|
||||
publisher: 'PassMaster',
|
||||
formatDetection: {
|
||||
email: false,
|
||||
address: false,
|
||||
telephone: false,
|
||||
},
|
||||
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'https://passmaster.app'),
|
||||
alternates: {
|
||||
canonical: '/',
|
||||
},
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: '/icons/icon-192.png', sizes: '192x192', type: 'image/png' },
|
||||
{ url: '/icons/icon-512.png', sizes: '512x512', type: 'image/png' },
|
||||
],
|
||||
shortcut: '/icons/icon-192.png',
|
||||
apple: '/icons/icon-192.png',
|
||||
},
|
||||
manifest: '/manifest.json',
|
||||
openGraph: {
|
||||
title: 'PassMaster – Free Offline Secure Password Generator (Open Source)',
|
||||
description: 'Generate ultra-secure passwords instantly, offline with client-side encryption. 100% open-source, private, and free.',
|
||||
url: '/',
|
||||
siteName: 'PassMaster',
|
||||
images: [
|
||||
{
|
||||
url: '/og-image.png',
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: 'PassMaster - Secure Password Generator',
|
||||
},
|
||||
],
|
||||
locale: 'en_US',
|
||||
type: 'website',
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
title: 'PassMaster – Free Offline Secure Password Generator (Open Source)',
|
||||
description: 'Generate ultra-secure passwords instantly, offline with client-side encryption. 100% open-source, private, and free.',
|
||||
images: ['/og-image.png'],
|
||||
},
|
||||
robots: {
|
||||
index: true,
|
||||
follow: true,
|
||||
googleBot: {
|
||||
index: true,
|
||||
follow: true,
|
||||
'max-video-preview': -1,
|
||||
'max-image-preview': 'large',
|
||||
'max-snippet': -1,
|
||||
},
|
||||
},
|
||||
verification: {
|
||||
google: 'your-google-verification-code',
|
||||
},
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<head>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="theme-color" content="#3b82f6" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
{/* Service Worker Registration */}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(function(registration) {
|
||||
console.log('SW registered: ', registration);
|
||||
})
|
||||
.catch(function(registrationError) {
|
||||
console.log('SW registration failed: ', registrationError);
|
||||
});
|
||||
});
|
||||
}
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* JSON-LD Schema */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify({
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "PassMaster",
|
||||
"applicationCategory": "SecurityApplication",
|
||||
"operatingSystem": "Web",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"isAccessibleForFree": true,
|
||||
"url": process.env.NEXT_PUBLIC_SITE_URL || "https://passmaster.app",
|
||||
"description": "Generate ultra-secure passwords instantly, offline with client-side encryption. 100% open-source, private, and free.",
|
||||
"author": {
|
||||
"@type": "Organization",
|
||||
"name": "PassMaster"
|
||||
},
|
||||
"softwareVersion": "1.0.0",
|
||||
"downloadUrl": process.env.NEXT_PUBLIC_SITE_URL || "https://passmaster.app",
|
||||
"installUrl": process.env.NEXT_PUBLIC_SITE_URL || "https://passmaster.app"
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</head>
|
||||
<body className="font-sans">
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<div className="min-h-screen flex flex-col bg-gray-50 dark:bg-gray-900">
|
||||
<Header />
|
||||
<main className="flex-1">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
291
app/offline-test/page.tsx
Normal file
291
app/offline-test/page.tsx
Normal file
@@ -0,0 +1,291 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
Wifi,
|
||||
WifiOff,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
RefreshCw,
|
||||
ArrowLeft
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function OfflineTestPage() {
|
||||
const [isOnline, setIsOnline] = useState(true)
|
||||
const [serviceWorkerStatus, setServiceWorkerStatus] = useState<string>('checking')
|
||||
const [cacheStatus, setCacheStatus] = useState<string>('checking')
|
||||
|
||||
useEffect(() => {
|
||||
// Check online status
|
||||
const updateOnlineStatus = () => {
|
||||
setIsOnline(navigator.onLine)
|
||||
}
|
||||
|
||||
window.addEventListener('online', updateOnlineStatus)
|
||||
window.addEventListener('offline', updateOnlineStatus)
|
||||
updateOnlineStatus()
|
||||
|
||||
// Check service worker status
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
if (registration.active) {
|
||||
setServiceWorkerStatus('active')
|
||||
} else {
|
||||
setServiceWorkerStatus('inactive')
|
||||
}
|
||||
}).catch(() => {
|
||||
setServiceWorkerStatus('error')
|
||||
})
|
||||
} else {
|
||||
setServiceWorkerStatus('not-supported')
|
||||
}
|
||||
|
||||
// Check cache status
|
||||
if ('caches' in window) {
|
||||
caches.open('passmaster-v1.0.0').then((cache) => {
|
||||
cache.keys().then((keys) => {
|
||||
if (keys.length > 0) {
|
||||
setCacheStatus('cached')
|
||||
} else {
|
||||
setCacheStatus('empty')
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
setCacheStatus('error')
|
||||
})
|
||||
} else {
|
||||
setCacheStatus('not-supported')
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', updateOnlineStatus)
|
||||
window.removeEventListener('offline', updateOnlineStatus)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
case 'cached':
|
||||
return <CheckCircle className="h-5 w-5 text-green-500" />
|
||||
case 'inactive':
|
||||
case 'empty':
|
||||
case 'error':
|
||||
return <XCircle className="h-5 w-5 text-red-500" />
|
||||
case 'checking':
|
||||
return <RefreshCw className="h-5 w-5 text-yellow-500 animate-spin" />
|
||||
default:
|
||||
return <XCircle className="h-5 w-5 text-gray-500" />
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
case 'active':
|
||||
return 'Service Worker Active'
|
||||
case 'inactive':
|
||||
return 'Service Worker Inactive'
|
||||
case 'error':
|
||||
return 'Service Worker Error'
|
||||
case 'not-supported':
|
||||
return 'Service Worker Not Supported'
|
||||
case 'cached':
|
||||
return 'Resources Cached'
|
||||
case 'empty':
|
||||
return 'Cache Empty'
|
||||
case 'checking':
|
||||
return 'Checking...'
|
||||
default:
|
||||
return 'Unknown Status'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{/* Header */}
|
||||
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5 mr-2" />
|
||||
Back to PassMaster
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
{/* Hero Section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className={`p-4 rounded-full ${isOnline ? 'bg-green-100 dark:bg-green-900/20' : 'bg-red-100 dark:bg-red-900/20'}`}>
|
||||
{isOnline ? (
|
||||
<Wifi className="h-12 w-12 text-green-600" />
|
||||
) : (
|
||||
<WifiOff className="h-12 w-12 text-red-600" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Offline Functionality Test
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300">
|
||||
Test your PWA's offline capabilities and service worker status.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Status Cards */}
|
||||
<div className="grid md:grid-cols-2 gap-8 mb-12">
|
||||
{/* Connection Status */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className={`bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border ${
|
||||
isOnline ? 'border-green-200 dark:border-green-800' : 'border-red-200 dark:border-red-800'
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Connection Status
|
||||
</h3>
|
||||
{isOnline ? (
|
||||
<Wifi className="h-6 w-6 text-green-500" />
|
||||
) : (
|
||||
<WifiOff className="h-6 w-6 text-red-500" />
|
||||
)}
|
||||
</div>
|
||||
<p className={`text-sm ${isOnline ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}`}>
|
||||
{isOnline ? 'You are currently online' : 'You are currently offline'}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Service Worker Status */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Service Worker
|
||||
</h3>
|
||||
{getStatusIcon(serviceWorkerStatus)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{getStatusText(serviceWorkerStatus)}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Cache Status */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.3 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Cache Status
|
||||
</h3>
|
||||
{getStatusIcon(cacheStatus)}
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{getStatusText(cacheStatus)}
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* PWA Status */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.4 }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
PWA Status
|
||||
</h3>
|
||||
<CheckCircle className="h-6 w-6 text-green-500" />
|
||||
</div>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{isOnline && serviceWorkerStatus === 'active' && cacheStatus === 'cached'
|
||||
? 'Ready for offline use'
|
||||
: 'Some features may not work offline'}
|
||||
</p>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Instructions */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.5 }}
|
||||
className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-8"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
How to Test Offline Functionality
|
||||
</h2>
|
||||
<div className="space-y-4 text-gray-600 dark:text-gray-300">
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
|
||||
1
|
||||
</div>
|
||||
<p>Make sure you're online and the service worker is active (green checkmark above)</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
|
||||
2
|
||||
</div>
|
||||
<p>Navigate to the main page and let it fully load</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
|
||||
3
|
||||
</div>
|
||||
<p>Disconnect your internet connection (turn off WiFi or unplug ethernet)</p>
|
||||
</div>
|
||||
<div className="flex items-start space-x-3">
|
||||
<div className="flex-shrink-0 w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-bold">
|
||||
4
|
||||
</div>
|
||||
<p>Try navigating back to the main page - it should still work offline!</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.6 }}
|
||||
className="mt-8 flex flex-col sm:flex-row gap-4 justify-center"
|
||||
>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Go to Main Page
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="inline-flex items-center justify-center px-6 py-3 border border-gray-300 dark:border-gray-600 text-base font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
Refresh Page
|
||||
</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
203
app/page.tsx
Normal file
203
app/page.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
Shield,
|
||||
Lock,
|
||||
Zap,
|
||||
Globe,
|
||||
ArrowUp,
|
||||
Key
|
||||
} from 'lucide-react'
|
||||
import { PasswordGenerator } from '@/components/PasswordGenerator'
|
||||
import { FAQ } from '@/components/FAQ'
|
||||
import { FloatingCTA } from '@/components/FloatingCTA'
|
||||
|
||||
export default function HomePage() {
|
||||
const [showScrollTop, setShowScrollTop] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setShowScrollTop(window.scrollY > 400)
|
||||
}
|
||||
|
||||
window.addEventListener('scroll', handleScroll)
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [])
|
||||
|
||||
const scrollToTop = () => {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
const features = [
|
||||
{
|
||||
icon: Lock,
|
||||
title: "End-to-End Client-Side Encryption",
|
||||
description: "Your passwords are generated locally in your browser. Nothing is ever sent to our servers."
|
||||
},
|
||||
{
|
||||
icon: Zap,
|
||||
title: "Works Offline (PWA)",
|
||||
description: "Install as an app and generate passwords even without an internet connection."
|
||||
},
|
||||
{
|
||||
icon: Globe,
|
||||
title: "100% Open Source",
|
||||
description: "Transparent code that you can audit, modify, and contribute to on GitHub."
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Hero Section */}
|
||||
<section className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="p-4 bg-primary-100 dark:bg-primary-900/20 rounded-full">
|
||||
<Shield className="h-12 w-12 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
Free Offline Secure Password Generator
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto leading-relaxed">
|
||||
Generate strong, unique passwords in seconds — fully client-side, private, and open-source.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Primary CTA */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.2 }}
|
||||
className="mb-12"
|
||||
>
|
||||
<a
|
||||
href="#generator"
|
||||
className="btn-primary text-lg px-8 py-4 inline-flex items-center space-x-2"
|
||||
>
|
||||
<Key className="h-5 w-5" />
|
||||
<span>Generate Password</span>
|
||||
</a>
|
||||
</motion.div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Features Section */}
|
||||
<section className="py-16 px-4 sm:px-6 lg:px-8 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Why PassMaster is Safer
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
|
||||
Built with privacy and security as the foundation, not an afterthought.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
whileHover={{ y: -5 }}
|
||||
className="card text-center group cursor-pointer"
|
||||
>
|
||||
<div className="flex justify-center mb-4">
|
||||
<div className="p-3 bg-primary-100 dark:bg-primary-900/20 rounded-full group-hover:bg-primary-200 dark:group-hover:bg-primary-900/40 transition-colors duration-200">
|
||||
<feature.icon className="h-8 w-8 text-primary-600" />
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{feature.description}
|
||||
</p>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Password Generator Section */}
|
||||
<section id="generator" className="py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Generate Your Strong Password
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">
|
||||
Customize your password settings and generate secure passwords instantly.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<PasswordGenerator />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section id="faq" className="py-16 px-4 sm:px-6 lg:px-8 bg-gray-50 dark:bg-gray-800">
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">
|
||||
Everything you need to know about PassMaster and password security.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<FAQ />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Floating CTA */}
|
||||
<FloatingCTA />
|
||||
|
||||
{/* Scroll to Top Button */}
|
||||
{showScrollTop && (
|
||||
<motion.button
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-6 right-6 z-50 p-3 bg-primary-600 hover:bg-primary-700 text-white rounded-full shadow-lg transition-colors duration-200"
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0 }}
|
||||
whileHover={{ scale: 1.1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
aria-label="Scroll to top"
|
||||
>
|
||||
<ArrowUp className="h-5 w-5" />
|
||||
</motion.button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
280
app/privacy/page.tsx
Normal file
280
app/privacy/page.tsx
Normal file
@@ -0,0 +1,280 @@
|
||||
"use client"
|
||||
|
||||
import { motion } from 'framer-motion'
|
||||
import {
|
||||
Shield,
|
||||
Lock,
|
||||
Eye,
|
||||
Server,
|
||||
FileText,
|
||||
CheckCircle,
|
||||
ArrowLeft
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function PrivacyPage() {
|
||||
const privacyFeatures = [
|
||||
{
|
||||
icon: Lock,
|
||||
title: "Client-Side Only",
|
||||
description: "All password generation happens locally in your browser. No data is ever sent to our servers."
|
||||
},
|
||||
{
|
||||
icon: Eye,
|
||||
title: "No Tracking",
|
||||
description: "We don't use cookies, analytics, or any tracking mechanisms. Your privacy is guaranteed."
|
||||
},
|
||||
{
|
||||
icon: Server,
|
||||
title: "No Server Storage",
|
||||
description: "We don't store any passwords, user data, or personal information on our servers."
|
||||
},
|
||||
{
|
||||
icon: Shield,
|
||||
title: "Open Source",
|
||||
description: "All code is publicly available and auditable. You can verify our privacy claims."
|
||||
}
|
||||
]
|
||||
|
||||
const dataPractices = [
|
||||
{
|
||||
title: "What We Don't Collect",
|
||||
items: [
|
||||
"Passwords or generated content",
|
||||
"Personal information",
|
||||
"IP addresses",
|
||||
"Browser history",
|
||||
"Usage analytics",
|
||||
"Device information"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "What We Don't Store",
|
||||
items: [
|
||||
"User accounts",
|
||||
"Password history",
|
||||
"Settings or preferences",
|
||||
"Session data",
|
||||
"Cookies or local storage",
|
||||
"Any personal data"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "What We Don't Share",
|
||||
items: [
|
||||
"No third-party services",
|
||||
"No advertising networks",
|
||||
"No analytics providers",
|
||||
"No data brokers",
|
||||
"No government requests",
|
||||
"No commercial use"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{/* Header */}
|
||||
<div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
|
||||
>
|
||||
<ArrowLeft className="h-5 w-5 mr-2" />
|
||||
Back to PassMaster
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
{/* Hero Section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
className="text-center mb-16"
|
||||
>
|
||||
<div className="flex justify-center mb-6">
|
||||
<div className="p-4 bg-blue-100 dark:bg-blue-900/20 rounded-full">
|
||||
<Shield className="h-12 w-12 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
Privacy Policy
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
|
||||
Your privacy is our top priority. PassMaster is designed with privacy-first principles.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Privacy Features */}
|
||||
<section className="mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Privacy-First Design
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">
|
||||
Every aspect of PassMaster is built to protect your privacy.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-8">
|
||||
{privacyFeatures.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-start space-x-4">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="p-3 bg-blue-100 dark:bg-blue-900/20 rounded-lg">
|
||||
<feature.icon className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
|
||||
{feature.title}
|
||||
</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Data Practices */}
|
||||
<section className="mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Our Data Practices
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">
|
||||
Transparency about how we handle (or don't handle) your data.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{dataPractices.map((practice, index) => (
|
||||
<motion.div
|
||||
key={practice.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-6 shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
{practice.title}
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{practice.items.map((item, itemIndex) => (
|
||||
<li key={itemIndex} className="flex items-start space-x-3">
|
||||
<CheckCircle className="h-5 w-5 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span className="text-gray-600 dark:text-gray-300">{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Technical Details */}
|
||||
<section className="mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="flex items-center mb-6">
|
||||
<FileText className="h-8 w-8 text-blue-600 mr-3" />
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Technical Implementation
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none">
|
||||
<h3 className="text-lg font-semibold mb-4">How PassMaster Works</h3>
|
||||
<ul className="space-y-3 text-gray-600 dark:text-gray-300">
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle className="h-5 w-5 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span><strong>Local Processing:</strong> All password generation happens in your browser using JavaScript</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle className="h-5 w-5 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span><strong>No Network Requests:</strong> The app works completely offline after initial load</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle className="h-5 w-5 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span><strong>Open Source:</strong> All code is publicly available on GitHub for verification</span>
|
||||
</li>
|
||||
<li className="flex items-start space-x-3">
|
||||
<CheckCircle className="h-5 w-5 text-green-500 mt-0.5 flex-shrink-0" />
|
||||
<span><strong>No Dependencies:</strong> We don't use external services or third-party libraries that could track you</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Contact */}
|
||||
<section className="text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-8"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Questions About Privacy?
|
||||
</h2>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-6">
|
||||
We're committed to transparency. If you have any questions about our privacy practices,
|
||||
please review our source code or contact us.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a
|
||||
href="https://github.com/your-repo/passmaster"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
View Source Code
|
||||
</a>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-flex items-center justify-center px-6 py-3 border border-gray-300 dark:border-gray-600 text-base font-medium rounded-md text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
Back to Generator
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user