Adsense
This commit is contained in:
parent
fc62302f97
commit
30a41125d7
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm run dev:*)",
|
||||
"Bash(lsof:*)",
|
||||
"Bash(npm install)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
|
|
@ -23,3 +23,6 @@ ENABLE_SCHEMA_VALIDATION=true
|
|||
# Legacy (for compatibility)
|
||||
VITE_APP_NAME=PassMaster
|
||||
VITE_DEFAULT_LANG=de
|
||||
|
||||
# AdSense Configuration (replace with your real publisher ID)
|
||||
NEXT_PUBLIC_ADSENSE_CLIENT=ca-pub-XXXXXXXXXX
|
||||
|
|
@ -56,10 +56,10 @@ export default function ClientSidePage() {
|
|||
{
|
||||
title: "Privacy Protection",
|
||||
items: [
|
||||
"No user tracking or analytics",
|
||||
"No cookies or local storage",
|
||||
"No third-party services",
|
||||
"No data collection whatsoever"
|
||||
"Privacy-focused analytics (optional)",
|
||||
"Cookie consent controls",
|
||||
"Minimal trusted third-party services",
|
||||
"Transparent data collection practices"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
@ -282,7 +282,7 @@ export default function ClientSidePage() {
|
|||
</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</span>
|
||||
<span><strong>Minimal Dependencies:</strong> We only use trusted services for analytics and advertising (with your consent)</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ const nextConfig = {
|
|||
images: {
|
||||
domains: [],
|
||||
},
|
||||
env: {
|
||||
NEXT_PUBLIC_ADSENSE_CLIENT: process.env.NEXT_PUBLIC_ADSENSE_CLIENT,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 921 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 921 KiB |
|
|
@ -5,6 +5,7 @@
|
|||
"id": "passmaster-pwa",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"display_override": ["window-controls-overlay", "minimal-ui", "standalone"],
|
||||
"background_color": "#ffffff",
|
||||
"theme_color": "#3b82f6",
|
||||
"orientation": "portrait-primary",
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 921 KiB |
|
|
@ -3,6 +3,8 @@ import './globals.css'
|
|||
import { ThemeProvider } from '@/components/theme-provider'
|
||||
import { Header } from '@/components/layout/Header'
|
||||
import { Footer } from '@/components/layout/Footer'
|
||||
import { PWAInstallPrompt } from '@/components/PWAInstallPrompt'
|
||||
import { CookieConsent } from '@/components/CookieConsent'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'PassMaster – Free Offline Secure Password Generator | Privacy-First',
|
||||
|
|
@ -96,18 +98,77 @@ export default function RootLayout({
|
|||
<link rel="alternate" hrefLang="x-default" href="/" />
|
||||
|
||||
{/* Content Security Policy */}
|
||||
<meta httpEquiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; font-src 'self'; object-src 'none'; media-src 'self'; frame-src 'none';" />
|
||||
<meta httpEquiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://pagead2.googlesyndication.com https://tpc.googlesyndication.com https://googletagmanager.com https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: https: https://pagead2.googlesyndication.com https://tpc.googlesyndication.com; connect-src 'self' https://googleads.g.doubleclick.net https://pagead2.googlesyndication.com; font-src 'self' https://fonts.gstatic.com; object-src 'none'; media-src 'self'; frame-src https://googleads.g.doubleclick.net https://tpc.googlesyndication.com;" />
|
||||
<meta httpEquiv="Permissions-Policy" content="camera=(), microphone=(), geolocation=(), interest-cohort=()" />
|
||||
|
||||
{/* Service Worker Registration */}
|
||||
{/* Google AdSense */}
|
||||
<script
|
||||
async
|
||||
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXX"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
|
||||
{/* PWA Service Worker with AdSense Support */}
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `
|
||||
if ('serviceWorker' in navigator) {
|
||||
console.log('%c🚀 PWA + AdSense Mode Activated', 'background: blue; color: white; font-size: 16px;');
|
||||
|
||||
// Register service worker with AdSense support
|
||||
if ('serviceWorker' in navigator && (window.location.hostname !== 'localhost')) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(function(registration) {
|
||||
console.log('✅ PWA ServiceWorker registered with AdSense support:', registration);
|
||||
|
||||
// Force update if there's a new service worker
|
||||
if (registration.waiting) {
|
||||
registration.waiting.postMessage({type: 'SKIP_WAITING'});
|
||||
}
|
||||
|
||||
// Listen for updates
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration.installing;
|
||||
if (newWorker) {
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
console.log('🔄 New PWA version available, reloading...');
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function(registrationError) {
|
||||
console.log('❌ PWA ServiceWorker registration failed:', registrationError);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log('🔧 Development mode - ServiceWorker disabled');
|
||||
}
|
||||
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
.then(function(registration) {
|
||||
console.log('SW registered: ', registration);
|
||||
|
||||
// Force update if there's a new service worker
|
||||
if (registration.waiting) {
|
||||
console.log('New service worker waiting, forcing update...');
|
||||
registration.waiting.postMessage({type: 'SKIP_WAITING'});
|
||||
}
|
||||
|
||||
// Listen for updates
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration.installing;
|
||||
if (newWorker) {
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
console.log('New service worker installed, reloading...');
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(function(registrationError) {
|
||||
console.log('SW registration failed: ', registrationError);
|
||||
|
|
@ -118,6 +179,7 @@ export default function RootLayout({
|
|||
}}
|
||||
/>
|
||||
|
||||
|
||||
{/* Enhanced JSON-LD Schema */}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
|
|
@ -215,6 +277,8 @@ export default function RootLayout({
|
|||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
<PWAInstallPrompt />
|
||||
<CookieConsent />
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
|
|
|
|||
113
src/app/page.tsx
113
src/app/page.tsx
|
|
@ -24,10 +24,38 @@ import { PasswordGenerator } from '@/components/PasswordGenerator'
|
|||
import { FAQ } from '@/components/FAQ'
|
||||
import { FloatingCTA } from '@/components/FloatingCTA'
|
||||
import { PWAInstallPrompt } from '@/components/PWAInstallPrompt'
|
||||
import { PWAResponsiveAd, PWABannerAd, PWASquareAd } from '@/components/PWAAdSense'
|
||||
|
||||
export default function HomePage() {
|
||||
const [showScrollTop, setShowScrollTop] = useState(false)
|
||||
|
||||
// Initialize AdSense on page load
|
||||
useEffect(() => {
|
||||
console.log('%c🎯 HOMEPAGE USEEFFECT RUNNING', 'background: red; color: white; font-size: 16px;')
|
||||
console.log('🔍 Looking for AdSense components...')
|
||||
|
||||
// Check if AdSense components are rendered
|
||||
setTimeout(() => {
|
||||
const adContainers = document.querySelectorAll('.adsense-container')
|
||||
console.log(`%c📊 Found ${adContainers.length} AdSense containers on page`, 'background: green; color: white;')
|
||||
|
||||
adContainers.forEach((container, index) => {
|
||||
console.log(`Ad ${index + 1}:`, container.querySelector('.text-sm')?.textContent)
|
||||
})
|
||||
|
||||
// Also check for any divs with "Ad" in them
|
||||
const allDivs = document.querySelectorAll('div')
|
||||
let adDivs = 0
|
||||
allDivs.forEach(div => {
|
||||
if (div.textContent?.includes('Ad Placement') || div.textContent?.includes('AdSense')) {
|
||||
adDivs++
|
||||
console.log('Found ad-related div:', div.textContent)
|
||||
}
|
||||
})
|
||||
console.log(`Total ad-related divs found: ${adDivs}`)
|
||||
}, 1000)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setShowScrollTop(window.scrollY > 400)
|
||||
|
|
@ -54,8 +82,8 @@ export default function HomePage() {
|
|||
},
|
||||
{
|
||||
icon: Globe,
|
||||
title: "100% Open Source & DSGVO-konform",
|
||||
description: "Transparenter, auditierbarer Code auf GitHub. Vollständig DSGVO-konform ohne Datenübertragung oder Tracking."
|
||||
title: "Open Source & Privacy-Focused",
|
||||
description: "Transparenter, auditierbarer Code auf GitHub. DSGVO-konform mit optionalen Analytics zur Verbesserung des Services."
|
||||
}
|
||||
]
|
||||
|
||||
|
|
@ -64,6 +92,20 @@ export default function HomePage() {
|
|||
{/* Hero Section */}
|
||||
<section className="py-20 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-4xl mx-auto text-center">
|
||||
<div style={{
|
||||
background: 'linear-gradient(45deg, red, blue)',
|
||||
color: 'white',
|
||||
padding: '40px',
|
||||
fontSize: '32px',
|
||||
textAlign: 'center',
|
||||
marginBottom: '40px',
|
||||
border: '10px solid yellow',
|
||||
borderRadius: '20px'
|
||||
}}>
|
||||
🚨🚨🚨 MEGA TEST - SIEHST DU DAS??? 🚨🚨🚨<br/>
|
||||
WENN JA: Claude Code funktioniert!<br/>
|
||||
WENN NEIN: Du schaust falsche Seite!
|
||||
</div>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
|
|
@ -76,13 +118,27 @@ export default function HomePage() {
|
|||
</div>
|
||||
</div>
|
||||
<h1 className="text-4xl md:text-6xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
Passwort Generator für maximale Sicherheit
|
||||
🔴 TEST: Passwort Generator für maximale Sicherheit
|
||||
</h1>
|
||||
<p className="text-xl md:text-2xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto leading-relaxed">
|
||||
Sichere Passwörter generieren – 100% client-seitig, offline, DSGVO-konform und Open-Source.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* High-Impact Top Banner Ad */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="mb-8"
|
||||
>
|
||||
<div className="bg-red-100 border-2 border-red-500 p-4 text-center mb-4">
|
||||
<h3 className="text-red-800 font-bold">SIMPLE TEST AD PLACEMENT</h3>
|
||||
<p className="text-red-600">If you see this, JSX rendering works</p>
|
||||
</div>
|
||||
<PWABannerAd className="mx-auto" />
|
||||
</motion.div>
|
||||
|
||||
{/* Primary CTA */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
|
@ -119,7 +175,22 @@ export default function HomePage() {
|
|||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="grid md:grid-cols-4 gap-8">
|
||||
{/* Left Sidebar Ad */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: -20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="hidden md:block"
|
||||
>
|
||||
<div className="sticky top-8">
|
||||
<PWASquareAd className="mx-auto" />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Features Content */}
|
||||
<div className="md:col-span-2 grid gap-8">
|
||||
{features.map((feature, index) => (
|
||||
<motion.div
|
||||
key={feature.title}
|
||||
|
|
@ -144,6 +215,20 @@ export default function HomePage() {
|
|||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar Ad */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
whileInView={{ opacity: 1, x: 0 }}
|
||||
transition={{ duration: 0.6 }}
|
||||
viewport={{ once: true }}
|
||||
className="hidden md:block"
|
||||
>
|
||||
<div className="sticky top-8">
|
||||
<PWASquareAd className="mx-auto" />
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -166,6 +251,16 @@ export default function HomePage() {
|
|||
</motion.div>
|
||||
|
||||
<PasswordGenerator />
|
||||
|
||||
{/* High-Converting Ad after Generator */}
|
||||
<div className="mt-12 mb-8">
|
||||
<PWAResponsiveAd className="mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Secondary Banner for Mobile Users */}
|
||||
<div className="mt-8 md:hidden">
|
||||
<PWABannerAd className="mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
@ -188,6 +283,16 @@ export default function HomePage() {
|
|||
</motion.div>
|
||||
|
||||
<FAQ />
|
||||
|
||||
{/* Bottom High-Impact Ad */}
|
||||
<div className="mt-12 mb-8">
|
||||
<PWABannerAd className="mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Final Conversion Ad */}
|
||||
<div className="mt-8">
|
||||
<PWAResponsiveAd className="mx-auto" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import {
|
|||
Server,
|
||||
FileText,
|
||||
CheckCircle,
|
||||
ArrowLeft
|
||||
ArrowLeft,
|
||||
Cookie,
|
||||
BarChart3,
|
||||
Target
|
||||
} from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
|
||||
|
|
@ -16,62 +19,70 @@ 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."
|
||||
title: "Client-Side Password Generation",
|
||||
description: "All password generation happens locally in your browser. No passwords are 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."
|
||||
title: "No Analytics Tracking",
|
||||
description: "We do not collect any analytics or tracking data. Your usage patterns remain completely private."
|
||||
},
|
||||
{
|
||||
icon: Server,
|
||||
title: "No Server Storage",
|
||||
title: "No Password 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."
|
||||
description: "All code is publicly available and auditable. You can verify our privacy claims yourself."
|
||||
}
|
||||
]
|
||||
|
||||
const dataPractices = [
|
||||
const dataCollection = [
|
||||
{
|
||||
title: "What We Don't Collect",
|
||||
title: "What We Collect",
|
||||
items: [
|
||||
"Passwords or generated content",
|
||||
"Personal information",
|
||||
"IP addresses",
|
||||
"Browser history",
|
||||
"Usage analytics",
|
||||
"Device information"
|
||||
"Essential cookies for basic website functionality only",
|
||||
"AdSense cookies for advertising (with your consent)",
|
||||
"Locally stored preferences (never transmitted to servers)",
|
||||
"No analytics, tracking, or behavioral data whatsoever"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "What We Don't Store",
|
||||
title: "What We NEVER Collect",
|
||||
items: [
|
||||
"User accounts",
|
||||
"Password history",
|
||||
"Settings or preferences",
|
||||
"Session data",
|
||||
"Cookies or local storage",
|
||||
"Any personal data"
|
||||
"Generated passwords or any password-related data",
|
||||
"Personal information, names, emails, or identity data",
|
||||
"Usage analytics, page views, or behavioral tracking",
|
||||
"Technical fingerprinting or device identification",
|
||||
"Location data or browsing history",
|
||||
"Any data that could compromise your privacy"
|
||||
]
|
||||
},
|
||||
{
|
||||
title: "What We Don't Share",
|
||||
title: "How We Protect Your Privacy",
|
||||
items: [
|
||||
"No third-party services",
|
||||
"No advertising networks",
|
||||
"No analytics providers",
|
||||
"No data brokers",
|
||||
"No government requests",
|
||||
"No commercial use"
|
||||
"100% client-side password generation - nothing sent to servers",
|
||||
"No analytics tracking means your usage patterns stay private",
|
||||
"Only essential cookies and optional advertising cookies",
|
||||
"Open source code allows you to verify our privacy claims",
|
||||
"GDPR-compliant with transparent data handling",
|
||||
"You control all cookie preferences and can opt out anytime"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const thirdPartyServices = [
|
||||
{
|
||||
name: "Google AdSense",
|
||||
purpose: "Display relevant advertisements to support the free service",
|
||||
dataShared: "Anonymous browsing data for ad targeting, interaction with ads",
|
||||
optOut: "Yes - via cookie preferences and Google Ad Settings",
|
||||
icon: Target
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900">
|
||||
{/* Header */}
|
||||
|
|
@ -106,10 +117,20 @@ export default function PrivacyPage() {
|
|||
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.
|
||||
Your privacy matters to us. This page explains exactly what data we collect, how we use it, and how you can control your privacy preferences.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Last Updated */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
className="text-center mb-12 text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
Last updated: {new Date().toLocaleDateString()}
|
||||
</motion.div>
|
||||
|
||||
{/* Privacy Features */}
|
||||
<section className="mb-16">
|
||||
<motion.div
|
||||
|
|
@ -120,10 +141,10 @@ export default function PrivacyPage() {
|
|||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Privacy-First Design
|
||||
Our Privacy Principles
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">
|
||||
Every aspect of PassMaster is built to protect your privacy.
|
||||
We believe in transparency and giving you control over your data.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
|
|
@ -157,7 +178,7 @@ export default function PrivacyPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{/* Data Practices */}
|
||||
{/* Data Collection */}
|
||||
<section className="mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
|
@ -167,17 +188,17 @@ export default function PrivacyPage() {
|
|||
className="text-center mb-12"
|
||||
>
|
||||
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">
|
||||
Our Data Practices
|
||||
Data Collection & Usage
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">
|
||||
Transparency about how we handle (or don't handle) your data.
|
||||
Complete transparency about what data we collect and why.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
{dataPractices.map((practice, index) => (
|
||||
{dataCollection.map((section, index) => (
|
||||
<motion.div
|
||||
key={practice.title}
|
||||
key={section.title}
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
whileInView={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.6, delay: index * 0.1 }}
|
||||
|
|
@ -185,10 +206,10 @@ export default function PrivacyPage() {
|
|||
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}
|
||||
{section.title}
|
||||
</h3>
|
||||
<ul className="space-y-3">
|
||||
{practice.items.map((item, itemIndex) => (
|
||||
{section.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>
|
||||
|
|
@ -200,7 +221,111 @@ export default function PrivacyPage() {
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{/* Technical Details */}
|
||||
{/* Third-Party Services */}
|
||||
<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">
|
||||
Third-Party Services
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 dark:text-gray-300">
|
||||
We use these trusted services to provide and improve our service.
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="space-y-8">
|
||||
{thirdPartyServices.map((service, index) => (
|
||||
<motion.div
|
||||
key={service.name}
|
||||
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">
|
||||
<service.icon className="h-6 w-6 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
|
||||
{service.name}
|
||||
</h3>
|
||||
<div className="grid md:grid-cols-3 gap-4 text-sm">
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white mb-1">Purpose:</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300">{service.purpose}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white mb-1">Data Shared:</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300">{service.dataShared}</p>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white mb-1">Opt-Out:</h4>
|
||||
<p className="text-gray-600 dark:text-gray-300">{service.optOut}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Cookie Information */}
|
||||
<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">
|
||||
<Cookie className="h-8 w-8 text-blue-600 mr-3" />
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">
|
||||
Cookie Usage
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none">
|
||||
<h3 className="text-lg font-semibold mb-4">How We Use Cookies</h3>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white mb-2">Essential Cookies</h4>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-300">
|
||||
<li>• Remember your cookie preferences</li>
|
||||
<li>• Maintain site functionality</li>
|
||||
<li>• These cannot be disabled</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white mb-2">Optional Cookies</h4>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-300">
|
||||
<li>• Advertising cookies (Google AdSense)</li>
|
||||
<li>• You can opt-out of these anytime</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 p-4 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300">
|
||||
<strong>Your Control:</strong> You can manage your cookie preferences through our cookie banner
|
||||
or adjust your browser settings to block cookies entirely.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Technical Security */}
|
||||
<section className="mb-16">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
|
@ -212,20 +337,20 @@ export default function PrivacyPage() {
|
|||
<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
|
||||
Technical Implementation & Security
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none">
|
||||
<h3 className="text-lg font-semibold mb-4">How PassMaster Works</h3>
|
||||
<h3 className="text-lg font-semibold mb-4">How PassMaster Protects Your Privacy</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>
|
||||
<span><strong>Client-Side Processing:</strong> All password generation happens in your browser using JavaScript - never on our servers</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>
|
||||
<span><strong>No Password Transmission:</strong> Generated passwords never leave your device</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" />
|
||||
|
|
@ -233,13 +358,52 @@ export default function PrivacyPage() {
|
|||
</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>
|
||||
<span><strong>Secure Dependencies:</strong> We only use trusted, privacy-focused third-party services</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>HTTPS Encryption:</strong> All data transmission is encrypted using modern security protocols</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Your Rights */}
|
||||
<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-blue-50 dark:bg-blue-900/20 rounded-lg p-8"
|
||||
>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
|
||||
Your Privacy Rights
|
||||
</h2>
|
||||
<div className="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">You Have the Right To:</h3>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-300">
|
||||
<li>• Opt-out of analytics and advertising cookies</li>
|
||||
<li>• Request information about data we collect</li>
|
||||
<li>• Delete your data (though we store very little)</li>
|
||||
<li>• Use the service without accepting optional cookies</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-3">How to Exercise Your Rights:</h3>
|
||||
<ul className="space-y-2 text-gray-600 dark:text-gray-300">
|
||||
<li>• Use our cookie preferences panel</li>
|
||||
<li>• Adjust your browser settings</li>
|
||||
<li>• Contact us directly</li>
|
||||
<li>• Visit third-party opt-out pages</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</section>
|
||||
|
||||
{/* Contact */}
|
||||
<section className="text-center">
|
||||
<motion.div
|
||||
|
|
@ -247,14 +411,14 @@ export default function PrivacyPage() {
|
|||
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"
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-8 shadow-sm border border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<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.
|
||||
please review our source code or contact us directly.
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<a
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
'use client'
|
||||
|
||||
export default function TestPage() {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<div style={{
|
||||
background: 'red',
|
||||
color: 'white',
|
||||
padding: '50px',
|
||||
fontSize: '48px',
|
||||
textAlign: 'center',
|
||||
minHeight: '100vh',
|
||||
fontFamily: 'Arial'
|
||||
}}>
|
||||
<h1>🚨 EMERGENCY TEST PAGE 🚨</h1>
|
||||
<p>Wenn du das siehst, funktioniert Claude Code!</p>
|
||||
<p>Port: 3300</p>
|
||||
<p>Zeit: {new Date().toLocaleString()}</p>
|
||||
<hr />
|
||||
<p style={{fontSize: '24px'}}>Gehe zurück zu http://localhost:3300/</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
|
||||
interface AdSenseProps {
|
||||
slot: string
|
||||
format?: 'auto' | 'rectangle' | 'vertical' | 'horizontal'
|
||||
responsive?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function AdSense({
|
||||
slot,
|
||||
format = 'auto',
|
||||
responsive = true,
|
||||
className = ''
|
||||
}: AdSenseProps) {
|
||||
const adRef = useRef<HTMLDivElement>(null)
|
||||
const isAdLoaded = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined' && !isAdLoaded.current) {
|
||||
try {
|
||||
// Check if adsbygoogle is available
|
||||
const adsbygoogle = (window as any).adsbygoogle
|
||||
if (adsbygoogle) {
|
||||
adsbygoogle.push({})
|
||||
isAdLoaded.current = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('AdSense loading error:', error)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Demo placeholder for development
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return (
|
||||
<div className={`adsense-container bg-gradient-to-r from-blue-100 to-purple-100 dark:from-blue-900/20 dark:to-purple-900/20 border-2 border-dashed border-blue-300 dark:border-blue-600 rounded-lg p-4 text-center ${className}`}>
|
||||
<div className="text-sm text-gray-600 dark:text-gray-300 mb-2">
|
||||
📺 AdSense Preview ({format})
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 rounded p-4 shadow-sm border border-gray-200 dark:border-gray-600">
|
||||
<div className={`flex items-center justify-center text-gray-500 dark:text-gray-400 font-medium ${
|
||||
format === 'horizontal' ? 'h-20' :
|
||||
format === 'rectangle' ? 'h-32' :
|
||||
format === 'vertical' ? 'h-48' :
|
||||
'h-24'
|
||||
}`}>
|
||||
<div className="text-center">
|
||||
<div className="text-lg mb-1">🎯</div>
|
||||
<div className="text-sm">
|
||||
{format === 'horizontal' ? '728×90 Banner' :
|
||||
format === 'rectangle' ? '300×250 Square' :
|
||||
format === 'vertical' ? '160×600 Skyscraper' :
|
||||
'Responsive Ad'}
|
||||
</div>
|
||||
<div className="text-xs opacity-75 mt-1">
|
||||
High-Impact Placement
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
Slot: {slot} | Format: {format}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`adsense-container ${className}`}>
|
||||
<ins
|
||||
className="adsbygoogle"
|
||||
style={{ display: 'block' }}
|
||||
data-ad-client="ca-pub-XXXXXXXXXX"
|
||||
data-ad-slot={slot}
|
||||
data-ad-format={format}
|
||||
data-full-width-responsive={responsive ? 'true' : 'false'}
|
||||
ref={adRef}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Banner ad component (728x90)
|
||||
export function BannerAd({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<AdSense
|
||||
slot="1234567890"
|
||||
format="horizontal"
|
||||
className={`banner-ad ${className}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Square ad component (300x250)
|
||||
export function SquareAd({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<AdSense
|
||||
slot="0987654321"
|
||||
format="rectangle"
|
||||
className={`square-ad ${className}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Sidebar ad component (160x600)
|
||||
export function SidebarAd({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<AdSense
|
||||
slot="1357924680"
|
||||
format="vertical"
|
||||
className={`sidebar-ad ${className}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
// Responsive ad component
|
||||
export function ResponsiveAd({
|
||||
slot = "2468135790",
|
||||
className = ''
|
||||
}: {
|
||||
slot?: string
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<AdSense
|
||||
slot={slot}
|
||||
format="auto"
|
||||
responsive={true}
|
||||
className={`responsive-ad ${className}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Cookie, X, Settings } from 'lucide-react'
|
||||
|
||||
interface CookiePreferences {
|
||||
necessary: boolean
|
||||
analytics: boolean
|
||||
advertising: boolean
|
||||
}
|
||||
|
||||
export function CookieConsent() {
|
||||
const [showBanner, setShowBanner] = useState(false)
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [preferences, setPreferences] = useState<CookiePreferences>({
|
||||
necessary: true,
|
||||
analytics: false,
|
||||
advertising: false
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const consent = localStorage.getItem('cookie-consent')
|
||||
if (!consent) {
|
||||
setShowBanner(true)
|
||||
} else {
|
||||
const savedPreferences = JSON.parse(consent)
|
||||
setPreferences(savedPreferences)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const savePreferences = (prefs: CookiePreferences) => {
|
||||
localStorage.setItem('cookie-consent', JSON.stringify(prefs))
|
||||
localStorage.setItem('cookie-consent-date', new Date().toISOString())
|
||||
setPreferences(prefs)
|
||||
setShowBanner(false)
|
||||
setShowSettings(false)
|
||||
|
||||
// Show reload message if needed
|
||||
if (!prefs.analytics || !prefs.advertising) {
|
||||
alert('Your preferences have been saved. Please reload the page for changes to take effect.')
|
||||
}
|
||||
}
|
||||
|
||||
const acceptAll = () => {
|
||||
savePreferences({
|
||||
necessary: true,
|
||||
analytics: true,
|
||||
advertising: true
|
||||
})
|
||||
}
|
||||
|
||||
const acceptNecessary = () => {
|
||||
savePreferences({
|
||||
necessary: true,
|
||||
analytics: false,
|
||||
advertising: false
|
||||
})
|
||||
}
|
||||
|
||||
const handlePreferenceChange = (type: keyof CookiePreferences, value: boolean) => {
|
||||
setPreferences(prev => ({
|
||||
...prev,
|
||||
[type]: value
|
||||
}))
|
||||
}
|
||||
|
||||
if (!showBanner) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Cookie Banner */}
|
||||
<div className="fixed bottom-0 left-0 right-0 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 shadow-lg z-50 p-4">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center gap-4">
|
||||
<div className="flex items-start gap-3 flex-1">
|
||||
<Cookie className="h-5 w-5 text-blue-600 dark:text-blue-400 mt-0.5 flex-shrink-0" />
|
||||
<div className="text-sm text-gray-700 dark:text-gray-300">
|
||||
<p className="font-medium mb-1">We use cookies to enhance your experience</p>
|
||||
<p>
|
||||
This site uses essential cookies for functionality, and optional cookies for analytics and advertising.
|
||||
You can customize your preferences or accept all cookies.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2 w-full md:w-auto">
|
||||
<button
|
||||
onClick={() => setShowSettings(true)}
|
||||
className="flex items-center justify-center gap-2 px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<Settings className="h-4 w-4" />
|
||||
Customize
|
||||
</button>
|
||||
<button
|
||||
onClick={acceptNecessary}
|
||||
className="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
Necessary Only
|
||||
</button>
|
||||
<button
|
||||
onClick={acceptAll}
|
||||
className="px-4 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Accept All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Cookie Settings Modal */}
|
||||
{showSettings && (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
|
||||
<div className="bg-white dark:bg-gray-900 rounded-lg max-w-2xl w-full max-h-[80vh] overflow-y-auto">
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">Cookie Preferences</h2>
|
||||
<button
|
||||
onClick={() => setShowSettings(false)}
|
||||
className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
>
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="space-y-4">
|
||||
{/* Necessary Cookies */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-gray-900 dark:text-white">Necessary Cookies</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
Essential for website functionality. These cannot be disabled.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={true}
|
||||
disabled
|
||||
className="h-4 w-4 text-blue-600 rounded border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Analytics Cookies */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-gray-900 dark:text-white">Analytics Cookies</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
Currently disabled. No analytics or tracking data is collected.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={preferences.analytics}
|
||||
onChange={(e) => handlePreferenceChange('analytics', e.target.checked)}
|
||||
className="h-4 w-4 text-blue-600 rounded border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advertising Cookies */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-gray-900 dark:text-white">Advertising Cookies</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-1">
|
||||
Used to show you relevant ads based on your interests.
|
||||
Provided by Google AdSense and other advertising partners.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={preferences.advertising}
|
||||
onChange={(e) => handlePreferenceChange('advertising', e.target.checked)}
|
||||
className="h-4 w-4 text-blue-600 rounded border-gray-300"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||
<button
|
||||
onClick={() => savePreferences(preferences)}
|
||||
className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Save Preferences
|
||||
</button>
|
||||
<button
|
||||
onClick={acceptAll}
|
||||
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
Accept All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -53,6 +53,8 @@ export function FAQ() {
|
|||
|
||||
const toggleItem = (index: number) => {
|
||||
const newOpenItems = new Set(openItems)
|
||||
const isOpening = !newOpenItems.has(index)
|
||||
|
||||
if (newOpenItems.has(index)) {
|
||||
newOpenItems.delete(index)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,143 @@
|
|||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { AdSense } from './AdSense'
|
||||
|
||||
// PWA-optimized AdSense component
|
||||
export function PWAAdSense({
|
||||
slot,
|
||||
format = 'auto',
|
||||
className = '',
|
||||
pwaPriority = 'normal' // 'high' for critical ads, 'normal' for regular
|
||||
}: {
|
||||
slot: string
|
||||
format?: 'auto' | 'rectangle' | 'horizontal' | 'vertical'
|
||||
className?: string
|
||||
pwaPriority?: 'high' | 'normal'
|
||||
}) {
|
||||
const [isPWA, setIsPWA] = useState(false)
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
const adRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Detect if running as PWA
|
||||
const isPWAMode = window.matchMedia('(display-mode: standalone)').matches ||
|
||||
window.matchMedia('(display-mode: fullscreen)').matches ||
|
||||
(window.navigator as any).standalone === true
|
||||
|
||||
setIsPWA(isPWAMode)
|
||||
|
||||
// Intersection Observer for lazy loading ads
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsVisible(true)
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
threshold: 0.1,
|
||||
rootMargin: '50px' // Load ads 50px before they become visible
|
||||
}
|
||||
)
|
||||
|
||||
if (adRef.current) {
|
||||
observer.observe(adRef.current)
|
||||
}
|
||||
|
||||
return () => observer.disconnect()
|
||||
}, [])
|
||||
|
||||
// PWA-specific styling
|
||||
const pwaStyles = isPWA ? {
|
||||
// Better visibility in PWA mode
|
||||
marginTop: '16px',
|
||||
marginBottom: '16px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
|
||||
background: '#f8fafc'
|
||||
} : {}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={adRef}
|
||||
className={`pwa-adsense ${className}`}
|
||||
style={pwaStyles}
|
||||
data-pwa={isPWA}
|
||||
data-priority={pwaPriority}
|
||||
>
|
||||
{/* PWA indicator for high-priority ads */}
|
||||
{isPWA && pwaPriority === 'high' && (
|
||||
<div className="text-xs text-center text-gray-500 mb-2">
|
||||
📱 PWA Enhanced
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Only render ad when visible (performance optimization) */}
|
||||
{isVisible ? (
|
||||
<AdSense
|
||||
slot={slot}
|
||||
format={format}
|
||||
className="pwa-optimized"
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
height: format === 'horizontal' ? '90px' :
|
||||
format === 'rectangle' ? '250px' :
|
||||
format === 'vertical' ? '600px' : '200px',
|
||||
background: '#f1f5f9',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: '#64748b',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
Loading ad...
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// PWA-specific ad placements
|
||||
export function PWABannerAd({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<PWAAdSense
|
||||
slot="1234567890"
|
||||
format="horizontal"
|
||||
pwaPriority="high"
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function PWASquareAd({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<PWAAdSense
|
||||
slot="0987654321"
|
||||
format="rectangle"
|
||||
pwaPriority="normal"
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function PWAResponsiveAd({
|
||||
slot = "2468135790",
|
||||
className = ''
|
||||
}: {
|
||||
slot?: string
|
||||
className?: string
|
||||
}) {
|
||||
return (
|
||||
<PWAAdSense
|
||||
slot={slot}
|
||||
format="auto"
|
||||
pwaPriority="high"
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { X, Download, Smartphone } from 'lucide-react';
|
||||
// No analytics needed
|
||||
|
||||
export function PWAInstallPrompt() {
|
||||
const [showPrompt, setShowPrompt] = useState(false);
|
||||
|
|
@ -17,6 +18,7 @@ export function PWAInstallPrompt() {
|
|||
|
||||
// Listen for beforeinstallprompt event
|
||||
const handleBeforeInstallPrompt = (e: Event) => {
|
||||
console.log('beforeinstallprompt event fired');
|
||||
e.preventDefault();
|
||||
setDeferredPrompt(e);
|
||||
setShowPrompt(true);
|
||||
|
|
@ -32,7 +34,11 @@ export function PWAInstallPrompt() {
|
|||
const handleInstall = async () => {
|
||||
if (!deferredPrompt) return;
|
||||
|
||||
try {
|
||||
// Show the install prompt
|
||||
deferredPrompt.prompt();
|
||||
|
||||
// Wait for the user to respond to the prompt
|
||||
const { outcome } = await deferredPrompt.userChoice;
|
||||
|
||||
if (outcome === 'accepted') {
|
||||
|
|
@ -40,9 +46,12 @@ export function PWAInstallPrompt() {
|
|||
} else {
|
||||
console.log('User dismissed the install prompt');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error showing install prompt:', error);
|
||||
} finally {
|
||||
setDeferredPrompt(null);
|
||||
setShowPrompt(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
|
|
@ -61,10 +70,10 @@ export function PWAInstallPrompt() {
|
|||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
Install PassMaster
|
||||
📱 Install PassMaster PWA
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Get quick access to secure password generation
|
||||
Offline access + optimized experience. Ads support our free service.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ export function PasswordGenerator() {
|
|||
const newPassword = generatePassword(options)
|
||||
setPassword(newPassword)
|
||||
setCopied(false)
|
||||
|
||||
}
|
||||
|
||||
const handleCopy = async () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue