This commit is contained in:
knuthtimo-lab 2025-09-05 16:19:03 +02:00
parent fc62302f97
commit 30a41125d7
18 changed files with 958 additions and 84 deletions

View File

@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(npm run dev:*)",
"Bash(lsof:*)",
"Bash(npm install)"
],
"deny": [],
"ask": []
}
}

View File

@ -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

View File

@ -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>

View File

@ -5,6 +5,9 @@ const nextConfig = {
images: {
domains: [],
},
env: {
NEXT_PUBLIC_ADSENSE_CLIENT: process.env.NEXT_PUBLIC_ADSENSE_CLIENT,
},
};
module.exports = nextConfig;

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

View File

@ -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",

BIN
public/og-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 KiB

View File

@ -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>

View File

@ -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,17 +175,32 @@ export default function HomePage() {
</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="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}
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" />
@ -142,7 +213,21 @@ export default function HomePage() {
{feature.description}
</p>
</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>

View File

@ -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

26
src/app/test/page.tsx Normal file
View File

@ -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>
)
}

135
src/components/AdSense.tsx Normal file
View File

@ -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}`}
/>
)
}

View File

@ -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>
)}
</>
)
}

View File

@ -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 {

View File

@ -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}
/>
)
}

View File

@ -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,17 +34,24 @@ export function PWAInstallPrompt() {
const handleInstall = async () => {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
try {
// Show the install prompt
deferredPrompt.prompt();
if (outcome === 'accepted') {
console.log('User accepted the install prompt');
} else {
console.log('User dismissed the install prompt');
// Wait for the user to respond to the prompt
const { outcome } = await deferredPrompt.userChoice;
if (outcome === 'accepted') {
console.log('User accepted the install prompt');
} else {
console.log('User dismissed the install prompt');
}
} catch (error) {
console.error('Error showing install prompt:', error);
} finally {
setDeferredPrompt(null);
setShowPrompt(false);
}
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>

View File

@ -81,6 +81,7 @@ export function PasswordGenerator() {
const newPassword = generatePassword(options)
setPassword(newPassword)
setCopied(false)
}
const handleCopy = async () => {