Initial commit: PassMaster PWA MVP

This commit is contained in:
2025-08-26 11:49:01 +02:00
commit 0623e2e29f
56 changed files with 14200 additions and 0 deletions

94
app/globals.css Normal file
View 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
View 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
View 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
View 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
View 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>
)
}