first commit

This commit is contained in:
2026-06-21 11:46:31 +02:00
commit ca9a6b2882
3 changed files with 1510 additions and 0 deletions
+275
View File
@@ -0,0 +1,275 @@
document.addEventListener('DOMContentLoaded', () => {
// --- Mobile Menu Toggle ---
const menuToggle = document.querySelector('.menu-toggle');
const navLinks = document.querySelector('.nav-links');
if (menuToggle && navLinks) {
menuToggle.addEventListener('click', () => {
navLinks.classList.toggle('mobile-open');
const icon = menuToggle.querySelector('i');
if (icon) {
icon.classList.toggle('fa-bars');
icon.classList.toggle('fa-xmark');
}
});
}
// --- Accordion FAQ ---
const accordionHeaders = document.querySelectorAll('.accordion-header');
accordionHeaders.forEach(header => {
header.addEventListener('click', () => {
const item = header.parentElement;
const isActive = item.classList.contains('active');
// Close all items
document.querySelectorAll('.accordion-item').forEach(i => {
i.classList.remove('active');
const content = i.querySelector('.accordion-content');
if (content) content.style.maxHeight = null;
});
// Toggle active item
if (!isActive) {
item.classList.add('active');
const content = item.querySelector('.accordion-content');
if (content) {
content.style.maxHeight = content.scrollHeight + "px";
}
}
});
});
// --- Feedback Hub Logic ---
const feedbackForm = document.getElementById('feedback-form');
const feedbackList = document.getElementById('feedback-list');
const filterButtons = document.querySelectorAll('.filter-btn');
// Default Seed Data
const defaultFeedback = [
{
id: 'fb-1',
author: 'Markus T.',
type: 'feature',
title: 'QR-Code Download für Gäste',
text: 'Es wäre super cool, wenn nach der Aufnahme ein QR-Code angezeigt wird, über den sich die Gäste das Foto direkt auf das Handy laden können.',
upvotes: 42,
voted: false
},
{
id: 'fb-2',
author: 'Sabrina',
type: 'love',
title: 'Sticker-Editor ist genial!',
text: 'Unsere Hochzeitsgäste haben stundenlang Emojis und Eventtexte verschoben. Die Bedienung ist wirklich super intuitiv und einfach.',
upvotes: 28,
voted: false
},
{
id: 'fb-3',
author: 'EventTech GmbH',
type: 'bug',
title: 'Drucker-Timeout bei schlechtem WLAN',
text: 'Manchmal meldet die App einen Fehler, wenn der Drucker im lokalen Netzwerk kurz die Verbindung verliert. Ein automatischer Retry wäre hilfreich.',
upvotes: 15,
voted: false
}
];
// Load from LocalStorage or seed defaults
let feedbackData = JSON.parse(localStorage.getItem('schnappix_feedback'));
if (!feedbackData) {
feedbackData = defaultFeedback;
localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData));
}
// Upvote storage track (local to session/device)
let upvotedIds = JSON.parse(localStorage.getItem('schnappix_upvoted_ids')) || [];
const saveToLocalStorage = () => {
localStorage.setItem('schnappix_feedback', JSON.stringify(feedbackData));
localStorage.setItem('schnappix_upvoted_ids', JSON.stringify(upvotedIds));
};
const renderFeedback = (filter = 'all') => {
if (!feedbackList) return;
feedbackList.innerHTML = '';
// Sort by upvotes descending
const sortedData = [...feedbackData].sort((a, b) => b.upvotes - a.upvotes);
sortedData.forEach(item => {
if (filter !== 'all' && item.type !== filter) return;
const isVoted = upvotedIds.includes(item.id);
const card = document.createElement('div');
card.className = 'feedback-item glass';
let badgeIcon = '✨';
let categoryLabel = 'Feature';
if (item.type === 'bug') {
badgeIcon = '🐛';
categoryLabel = 'Bug';
} else if (item.type === 'love') {
badgeIcon = '💖';
categoryLabel = 'Lob';
}
card.innerHTML = `
<div class="fb-card-content">
<div class="fb-meta">
<span class="fb-tag ${item.type}">${badgeIcon} ${categoryLabel}</span>
<span class="fb-author">von ${escapeHtml(item.author)}</span>
</div>
<h4>${escapeHtml(item.title)}</h4>
<p>${escapeHtml(item.text)}</p>
</div>
<div class="fb-upvote-section">
<button class="upvote-btn ${isVoted ? 'voted' : ''}" data-id="${item.id}" aria-label="Abstimmen">
<i class="fa-solid fa-caret-up"></i>
<span>${item.upvotes}</span>
</button>
</div>
`;
feedbackList.appendChild(card);
});
setupUpvoteButtons();
};
const escapeHtml = (str) => {
return str.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};
const setupUpvoteButtons = () => {
const buttons = document.querySelectorAll('.upvote-btn');
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const id = btn.getAttribute('data-id');
const index = feedbackData.findIndex(item => item.id === id);
if (index !== -1) {
if (upvotedIds.includes(id)) {
// Undo upvote
feedbackData[index].upvotes--;
upvotedIds = upvotedIds.filter(vId => vId !== id);
} else {
// Upvote
feedbackData[index].upvotes++;
upvotedIds.push(id);
}
saveToLocalStorage();
renderFeedback(getActiveFilter());
}
});
});
};
const getActiveFilter = () => {
const activeBtn = document.querySelector('.filter-btn.active');
return activeBtn ? activeBtn.getAttribute('data-filter') : 'all';
};
// Filter switching
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderFeedback(btn.getAttribute('data-filter'));
});
});
// Form Submission
if (feedbackForm) {
feedbackForm.addEventListener('submit', (e) => {
e.preventDefault();
const authorInput = document.getElementById('fb-author');
const typeSelect = document.getElementById('fb-type');
const titleInput = document.getElementById('fb-title');
const textInput = document.getElementById('fb-text');
const newFb = {
id: 'fb-' + Date.now(),
author: authorInput.value,
type: typeSelect.value,
title: titleInput.value,
text: textInput.value,
upvotes: 1,
voted: true
};
feedbackData.push(newFb);
upvotedIds.push(newFb.id); // Auto-upvote own post
saveToLocalStorage();
// Reset form
feedbackForm.reset();
// Re-render
renderFeedback(getActiveFilter());
// Smooth scroll to list
feedbackList.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
});
}
// --- Modal Management for Impressum & Privacy ---
const modalTriggers = document.querySelectorAll('.legal-trigger');
const modals = document.querySelectorAll('.modal-overlay');
const closeButtons = document.querySelectorAll('.modal-close');
modalTriggers.forEach(trigger => {
trigger.addEventListener('click', (e) => {
e.preventDefault();
const targetId = trigger.getAttribute('href').substring(1);
const targetModal = document.getElementById(targetId);
if (targetModal) {
targetModal.classList.add('open');
document.body.style.overflow = 'hidden'; // Stop scrolling background
}
});
});
const closeModal = (modal) => {
modal.classList.remove('open');
// Only restore scroll if no other modals are open
const anyOpen = Array.from(modals).some(m => m.classList.contains('open'));
if (!anyOpen) {
document.body.style.overflow = '';
}
};
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
const modal = btn.closest('.modal-overlay');
if (modal) closeModal(modal);
});
});
modals.forEach(modal => {
// Close modal when clicking outside of the content block
modal.addEventListener('click', (e) => {
if (e.target === modal) {
closeModal(modal);
}
});
});
// Close modal on Escape key press
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
modals.forEach(modal => {
if (modal.classList.contains('open')) closeModal(modal);
});
}
});
// Initial render
renderFeedback();
});
+282
View File
@@ -0,0 +1,282 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schnappix - Die ultimative Photobooth App</title>
<meta name="description" content="Schnappix ist eine Tablet-optimierte Photobooth-App mit Kiosk-Modus, Direktdruck, Filtern, Stickern und anpassbaren Rahmen für dein perfektes Event.">
<link rel="stylesheet" href="style.css">
<!-- Google Fonts: Outfit for titles, Inter for body -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Outfit:wght@500;700;800&display=swap" rel="stylesheet">
<!-- FontAwesome for neon-style icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>
<body>
<!-- Header Navigation -->
<header class="main-header">
<div class="container nav-container">
<div class="logo">
<span class="logo-glow">Schnappix</span>
</div>
<nav class="nav-links">
<a href="#features">Features</a>
<a href="#faq">FAQ</a>
<a href="#feedback">Feedback Hub</a>
<a href="#legal-imprint" class="legal-trigger">Impressum</a>
</nav>
<button class="menu-toggle" aria-label="Menu öffnen">
<i class="fa-solid fa-bars"></i>
</button>
</div>
</header>
<!-- Hero Section -->
<section class="hero">
<div class="hero-bg-glow"></div>
<div class="container hero-container">
<div class="hero-content">
<div class="badge">🚀 Version 23.01.X</div>
<h1>Erstelle unvergessliche Momente mit <span class="text-gradient">Schnappix</span></h1>
<p class="hero-lead">Die professionelle, Tablet-optimierte Photobooth-App für dein Event. Kiosk-Modus, Direktdruck, kreative Sticker und maßgeschneiderte Rahmen vereint in einem atemberaubenden Synthwave-Design.</p>
<div class="hero-actions">
<a href="#features" class="btn btn-primary">Features entdecken</a>
<a href="#feedback" class="btn btn-secondary">Feedback abgeben</a>
</div>
</div>
<div class="hero-visual">
<div class="mockup-container">
<div class="tablet-mockup">
<div class="tablet-screen">
<div class="app-mockup-content">
<div class="app-header">
<i class="fa-solid fa-camera-retro app-logo-icon"></i>
<span class="app-title">Schnappix</span>
</div>
<div class="app-body">
<div class="pulse-ring"></div>
<div class="app-circle-btn">
<i class="fa-solid fa-camera"></i>
</div>
<p class="app-welcome">Zeit für ein Erinnerungsfoto!</p>
<p class="app-footer-text">Display tippen! • Lächeln! • Foto schnappen!</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section id="features" class="features-section">
<div class="container">
<h2 class="section-title">Geniale Features für <span class="text-gradient">dein Event</span></h2>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon"><i class="fa-solid fa-lock"></i></div>
<h3>Echter Kiosk-Modus</h3>
<p>Dank nativer Kotlin-Module sperrt die App das Tablet bombensicher für Gäste. Kein Verlassen der App ohne Admin-Code möglich.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fa-solid fa-print"></i></div>
<h3>Direkter Netzwerkdruck</h3>
<p>Integrierter IPP-Drucker-Support (z.B. Canon Selphy). Fotos werden direkt aus dem Vorschaubildschirm gedruckt.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fa-solid fa-wand-magic-sparkles"></i></div>
<h3>Sticker & Emojis</h3>
<p>Interaktiver Editor wie bei Social Media. Emojis, Datumsanzeige und Eventname frei verschieben, drehen und skalieren.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fa-solid fa-images"></i></div>
<h3>Serienbild & Collagen</h3>
<p>Nimm 1-5 Fotos hintereinander auf. Die App generiert automatisch eine wunderschöne Collage zum Drucken und Speichern.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fa-solid fa-image"></i></div>
<h3>Individuelle Rahmen</h3>
<p>Blende maßgeschneiderte, transparente PNG-Rahmen über die Aufnahmen. Perfekt für Geburtstage, Hochzeiten oder Firmenfeiern.</p>
</div>
<div class="feature-card">
<div class="feature-icon"><i class="fa-solid fa-plug"></i></div>
<h3>USB-Kamera Support</h3>
<p>Nutze entweder die Frontkamera deines Tablets oder schließe eine externe USB-Kamera an für überragende Bildqualität.</p>
</div>
</div>
</div>
</section>
<!-- FAQ Section -->
<section id="faq" class="faq-section">
<div class="container faq-container">
<h2 class="section-title">Häufig gestellte <span class="text-gradient">Fragen</span></h2>
<div class="accordion">
<div class="accordion-item">
<button class="accordion-header">
<span>Wie aktiviere ich den Kiosk-Modus auf dem Tablet?</span>
<i class="fa-solid fa-chevron-down"></i>
</button>
<div class="accordion-content">
<p>Damit der Kiosk-Modus (LockTask) ohne Bestätigungsdialoge funktioniert, muss die App als <strong>Device Owner</strong> festgelegt werden. Führe dazu folgenden ADB-Befehl aus:</p>
<code>adb shell dpm set-device-owner de.orfel.schnappix/de.orfel.schnappix.KioskDeviceAdminReceiver</code>
<p>Danach kannst du den Kiosk-Modus einfach im passwortgeschützten Admin-Bereich ein- und ausschalten.</p>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header">
<span>Wie füge ich eigene Fotorahmen hinzu?</span>
<i class="fa-solid fa-chevron-down"></i>
</button>
<div class="accordion-content">
<p>Erstelle ein transparentes PNG im Format <strong>1500 x 1000 px</strong> (3:2 Querformat, max. 500 KB) und lege es im Ordner <code>assets/frames/</code> als <code>frame_name.png</code> ab. Trage das Template anschließend in <code>src/data/frames.ts</code> ein und führe einen neuen Build aus.</p>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header">
<span>Welche Drucker werden unterstützt?</span>
<i class="fa-solid fa-chevron-down"></i>
</button>
<div class="accordion-content">
<p>Es werden alle Drucker unterstützt, die das <strong>IPP (Internet Printing Protocol)</strong> im lokalen Netzwerk unterstützen. Beliebte Fotodrucker wie der Canon CP1300 lassen sich direkt über ihre lokale IP-Adresse ansteuern.</p>
</div>
</div>
<div class="accordion-item">
<button class="accordion-header">
<span>Wo werden die Fotos gespeichert?</span>
<i class="fa-solid fa-chevron-down"></i>
</button>
<div class="accordion-content">
<p>Schnappix nutzt die moderne Android MediaStore API und speichert Collagen sowie Originale direkt in der Galerie deines Tablets unter den Ordnern:</p>
<ul>
<li><code>Schnappix - &lt;EventName&gt; - Collagen</code></li>
<li><code>Schnappix - &lt;EventName&gt; - Originale</code></li>
</ul>
</div>
</div>
</div>
</div>
</section>
<!-- Feedback Hub Section -->
<section id="feedback" class="feedback-section">
<div class="container">
<h2 class="section-title">Feedback & <span class="text-gradient">Ideen</span></h2>
<p class="section-subtitle">Deine Meinung hilft uns, Schnappix noch besser zu machen. Hinterlasse Feedback oder stimme für Ideen ab!</p>
<div class="feedback-grid">
<!-- Submit Form -->
<div class="feedback-form-card glass">
<h3>Neues Feedback einreichen</h3>
<form id="feedback-form">
<div class="form-group">
<label for="fb-author">Dein Name</label>
<input type="text" id="fb-author" placeholder="z.B. Alex" required>
</div>
<div class="form-group">
<label for="fb-type">Kategorie</label>
<select id="fb-type">
<option value="feature">✨ Wunsch-Feature</option>
<option value="bug">🐛 Fehler melden</option>
<option value="love">💖 Lob / Allgemein</option>
</select>
</div>
<div class="form-group">
<label for="fb-title">Titel</label>
<input type="text" id="fb-title" placeholder="Kurzer, prägnanter Titel" required>
</div>
<div class="form-group">
<label for="fb-text">Beschreibung</label>
<textarea id="fb-text" rows="4" placeholder="Beschreibe dein Feedback oder deine Idee..." required></textarea>
</div>
<button type="submit" class="btn btn-primary btn-block">Absenden</button>
</form>
</div>
<!-- Feedback List -->
<div class="feedback-list-container">
<div class="list-header">
<h3>Community Feedback</h3>
<div class="list-filters">
<button class="filter-btn active" data-filter="all">Alle</button>
<button class="filter-btn" data-filter="feature">Features</button>
<button class="filter-btn" data-filter="bug">Bugs</button>
</div>
</div>
<div id="feedback-list" class="feedback-list">
<!-- Dynamic items will load here -->
</div>
</div>
</div>
</div>
</section>
<!-- Footer & Legal triggers -->
<footer class="main-footer">
<div class="container footer-container">
<p>&copy; 2026 Schnappix. Alle Rechte vorbehalten.</p>
<div class="footer-links">
<a href="#legal-imprint" class="legal-trigger">Impressum</a>
<a href="#legal-privacy" class="legal-trigger">Datenschutzerklärung</a>
</div>
</div>
</footer>
<!-- Modals for German Legal Requirements -->
<div id="legal-imprint" class="modal-overlay">
<div class="modal-content glass">
<button class="modal-close" aria-label="Schließen">&times;</button>
<h2>Impressum</h2>
<div class="modal-scroll-area">
<h3>Angaben gemäß § 5 TMG</h3>
<p>Jannik [Nachname]<br>
[Straße und Hausnummer]<br>
[PLZ und Ort]</p>
<h3>Kontakt</h3>
<p>E-Mail: info@schnappix.de<br>
Telefon: [Telefonnummer - optional]</p>
<h3>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV</h3>
<p>Jannik [Nachname]<br>
[Adresse wie oben]</p>
<h3>Streitschlichtung</h3>
<p>Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit: <a href="https://ec.europa.eu/consumers/odr" target="_blank" rel="noopener">https://ec.europa.eu/consumers/odr</a>.<br>
Unsere E-Mail-Adresse finden Sie oben im Impressum. Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.</p>
</div>
</div>
</div>
<div id="legal-privacy" class="modal-overlay">
<div class="modal-content glass">
<button class="modal-close" aria-label="Schließen">&times;</button>
<h2>Datenschutzerklärung</h2>
<div class="modal-scroll-area">
<h3>1. Datenschutz auf einen Blick</h3>
<h4>Allgemeine Hinweise</h4>
<p>Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können.</p>
<h3>2. Datenerfassung auf unserer Website</h3>
<h4>Cookies & Local Storage</h4>
<p>Diese Webseite nutzt den lokalen Speicher (Local Storage) Ihres Browsers, um die von Ihnen geschriebenen Feedbacks sowie Ihre Stimmen (Upvotes) zu speichern. Dies geschieht ausschließlich auf Ihrem Endgerät und wird nicht an externe Server übertragen, es sei denn, Sie senden explizit ein Feedback ab.</p>
<h4>Server-Logfiles</h4>
<p>Der Provider der Seiten erhebt und speichert automatisch Informationen in sogenannten Server-Logfiles, die Ihr Browser automatisch an uns übermittelt. Dies sind Browsertyp, Betriebssystem, Referrer URL, Hostname des zugreifenden Rechners und Uhrzeit der Serveranfrage. Diese Daten sind nicht bestimmten Personen zuzuordnen.</p>
<h3>3. Feedback Hub</h3>
<p>Wenn Sie den Feedback Hub nutzen, werden Ihre Eingaben (Name, Titel, Text, Kategorie) gespeichert, um die Funktionalität der Feedback-Plattform bereitzustellen. Bitte geben Sie keine sensiblen personenbezogenen Daten im Freitextfeld an.</p>
</div>
</div>
</div>
<script src="app.js"></script>
</body>
</html>
+953
View File
@@ -0,0 +1,953 @@
/*
SCHNAPPIX Synthwave Neon CSS Stylesheet
Matching THEME from App:
- Deep background: #050510 (with linear gradients)
- Magenta glow (primary): #ff2bd6
- Cyan glow (accent): #28dfff
- Mid Purple: #a033ff
*/
:root {
--bg-color: #050510;
--surface-color: #0d0d1a;
--surface-secondary: #151528;
--primary-color: #ff2bd6;
--primary-dark: #cc22ab;
--accent-color: #28dfff;
--accent-dark: #1fb3cc;
--text-color: #ffffff;
--text-muted: #7a7a9e;
--border-color: rgba(40, 223, 255, 0.15);
--font-titles: 'Outfit', sans-serif;
--font-body: 'Inter', sans-serif;
--shadow-neon: 0 0 15px rgba(255, 43, 214, 0.35);
--shadow-cyan: 0 0 15px rgba(40, 223, 255, 0.35);
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Reset */
* {
margin: 0;
padding: 0;
box-box: border-box;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: var(--font-body);
line-height: 1.6;
overflow-x: hidden;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 24px;
}
/* Header */
.main-header {
background: rgba(5, 5, 16, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--border-color);
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
}
.nav-container {
height: 80px;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-family: var(--font-titles);
font-size: 28px;
font-weight: 800;
letter-spacing: 1px;
}
.logo-glow {
color: #fff;
text-shadow: 0 0 10px var(--primary-color), 0 0 20px var(--primary-color);
animation: logoGlowPulse 4s infinite alternate;
}
.nav-links {
display: flex;
gap: 32px;
}
.nav-links a {
color: var(--text-muted);
text-decoration: none;
font-weight: 500;
transition: var(--transition-smooth);
}
.nav-links a:hover {
color: var(--accent-color);
text-shadow: 0 0 8px rgba(40, 223, 255, 0.5);
}
.menu-toggle {
display: none;
background: none;
border: none;
color: var(--text-color);
font-size: 24px;
cursor: pointer;
}
/* Hero Section */
.hero {
padding: 160px 0 100px;
position: relative;
overflow: hidden;
}
.hero-bg-glow {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 600px;
height: 600px;
background: radial-gradient(circle, rgba(160, 51, 255, 0.15) 0%, rgba(5, 5, 16, 0) 70%);
z-index: 0;
pointer-events: none;
}
.hero-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 60px;
align-items: center;
position: relative;
z-index: 1;
}
.badge {
display: inline-block;
background: rgba(255, 43, 214, 0.1);
border: 1px solid var(--primary-color);
color: var(--primary-color);
padding: 6px 16px;
border-radius: 9999px;
font-size: 14px;
font-weight: 600;
margin-bottom: 24px;
text-shadow: 0 0 8px rgba(255, 43, 214, 0.3);
}
.hero-content h1 {
font-family: var(--font-titles);
font-size: 54px;
font-weight: 800;
line-height: 1.15;
margin-bottom: 24px;
}
.text-gradient {
background: linear-gradient(90deg, var(--primary-color), #a033ff, var(--accent-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
filter: drop-shadow(0 0 10px rgba(160, 51, 255, 0.3));
}
.hero-lead {
font-size: 18px;
color: var(--text-muted);
margin-bottom: 40px;
}
.hero-actions {
display: flex;
gap: 20px;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 14px 28px;
border-radius: 12px;
font-family: var(--font-body);
font-weight: 600;
text-decoration: none;
font-size: 16px;
cursor: pointer;
transition: var(--transition-smooth);
}
.btn-primary {
background: linear-gradient(90deg, var(--primary-color), #a033ff);
color: #fff;
border: none;
box-shadow: var(--shadow-neon);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 0 25px rgba(255, 43, 214, 0.6);
}
.btn-secondary {
background: rgba(21, 21, 40, 0.6);
border: 1px solid var(--border-color);
color: var(--text-color);
}
.btn-secondary:hover {
border-color: var(--accent-color);
box-shadow: var(--shadow-cyan);
transform: translateY(-2px);
}
.btn-block {
width: 100%;
}
/* Interactive Device Mockup styling */
.hero-visual {
display: flex;
justify-content: center;
align-items: center;
}
.mockup-container {
position: relative;
}
.tablet-mockup {
width: 440px;
height: 300px;
background: #181824;
border: 12px solid #000;
border-radius: 24px;
box-shadow: var(--shadow-neon), 0 20px 40px rgba(0,0,0,0.8);
position: relative;
overflow: hidden;
}
.tablet-screen {
width: 100%;
height: 100%;
background-color: #050510;
position: relative;
}
.app-mockup-content {
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
padding: 20px;
}
.app-header {
display: flex;
align-items: center;
gap: 8px;
}
.app-logo-icon {
color: var(--primary-color);
font-size: 20px;
filter: drop-shadow(0 0 5px var(--primary-color));
}
.app-title {
font-family: var(--font-titles);
font-weight: 700;
font-size: 18px;
letter-spacing: 0.5px;
}
.app-body {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.app-circle-btn {
width: 70px;
height: 70px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 28px;
cursor: pointer;
box-shadow: 0 0 15px rgba(160, 51, 255, 0.6);
position: relative;
z-index: 2;
transition: var(--transition-smooth);
}
.app-circle-btn:hover {
transform: scale(1.1);
box-shadow: 0 0 25px rgba(160, 51, 255, 0.9);
}
.pulse-ring {
position: absolute;
width: 70px;
height: 70px;
border-radius: 50%;
border: 2px solid var(--primary-color);
animation: ringPulse 2s infinite ease-out;
pointer-events: none;
z-index: 1;
}
.app-welcome {
margin-top: 16px;
font-size: 14px;
font-weight: 600;
}
.app-footer-text {
margin-top: 4px;
font-size: 10px;
color: var(--text-muted);
}
/* Features Grid */
.features-section {
padding: 100px 0;
background: linear-gradient(180deg, var(--bg-color) 0%, var(--surface-color) 100%);
border-top: 1px solid var(--border-color);
}
.section-title {
font-family: var(--font-titles);
font-size: 40px;
font-weight: 800;
text-align: center;
margin-bottom: 60px;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 30px;
}
.feature-card {
background: rgba(13, 13, 26, 0.65);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 40px 30px;
transition: var(--transition-smooth);
}
.feature-card:hover {
transform: translateY(-8px);
border-color: var(--primary-color);
box-shadow: var(--shadow-neon);
}
.feature-icon {
width: 60px;
height: 60px;
border-radius: 14px;
background: rgba(40, 223, 255, 0.1);
color: var(--accent-color);
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
margin-bottom: 24px;
border: 1px solid rgba(40, 223, 255, 0.2);
transition: var(--transition-smooth);
}
.feature-card:hover .feature-icon {
background: rgba(255, 43, 214, 0.15);
color: var(--primary-color);
border-color: rgba(255, 43, 214, 0.3);
box-shadow: 0 0 10px rgba(255, 43, 214, 0.2);
}
.feature-card h3 {
font-family: var(--font-titles);
font-size: 22px;
font-weight: 700;
margin-bottom: 12px;
}
.feature-card p {
color: var(--text-muted);
}
/* FAQ Section */
.faq-section {
padding: 100px 0;
background: var(--bg-color);
}
.faq-container {
max-width: 800px;
}
.accordion {
margin-top: 40px;
}
.accordion-item {
border: 1px solid var(--border-color);
border-radius: 12px;
margin-bottom: 16px;
background: var(--surface-color);
overflow: hidden;
transition: var(--transition-smooth);
}
.accordion-item:hover {
border-color: rgba(40, 223, 255, 0.35);
}
.accordion-header {
width: 100%;
padding: 20px 24px;
background: none;
border: none;
color: #fff;
font-family: var(--font-body);
font-size: 18px;
font-weight: 600;
text-align: left;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
}
.accordion-header i {
color: var(--accent-color);
transition: var(--transition-smooth);
}
.accordion-content {
max-height: 0;
overflow: hidden;
transition: max-height 0.35s ease-out;
padding: 0 24px;
}
.accordion-content p {
padding-bottom: 20px;
color: var(--text-muted);
}
.accordion-content code {
display: block;
background: #050510;
padding: 12px 16px;
border-radius: 6px;
font-family: monospace;
color: var(--accent-color);
margin-bottom: 16px;
word-break: break-all;
border-left: 3px solid var(--primary-color);
}
.accordion-content ul {
margin-left: 20px;
color: var(--text-muted);
padding-bottom: 20px;
}
/* Active State Accordion */
.accordion-item.active {
border-color: var(--accent-color);
box-shadow: 0 0 15px rgba(40, 223, 255, 0.15);
}
.accordion-item.active .accordion-header i {
transform: rotate(180deg);
}
.accordion-item.active .accordion-content {
max-height: 500px;
}
/* Feedback Section */
.feedback-section {
padding: 100px 0;
background: linear-gradient(180deg, var(--surface-color) 0%, var(--bg-color) 100%);
border-top: 1px solid var(--border-color);
}
.section-subtitle {
text-align: center;
color: var(--text-muted);
max-width: 600px;
margin: -40px auto 60px;
}
.feedback-grid {
display: grid;
grid-template-columns: 1fr 1.5fr;
gap: 40px;
align-items: start;
}
/* Glassmorphism panel style */
.glass {
background: rgba(13, 13, 26, 0.8);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(40, 223, 255, 0.15);
border-radius: 20px;
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
.feedback-form-card {
padding: 30px;
}
.feedback-form-card h3 {
font-family: var(--font-titles);
font-size: 22px;
margin-bottom: 24px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-muted);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 12px 16px;
background: rgba(5, 5, 16, 0.6);
border: 1px solid var(--border-color);
border-radius: 8px;
color: #fff;
font-family: var(--font-body);
font-size: 15px;
transition: var(--transition-smooth);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--accent-color);
box-shadow: 0 0 10px rgba(40, 223, 255, 0.3);
}
/* Feedback List Column */
.feedback-list-container {
display: flex;
flex-direction: column;
gap: 24px;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.list-header h3 {
font-family: var(--font-titles);
font-size: 22px;
}
.list-filters {
display: flex;
gap: 8px;
}
.filter-btn {
background: rgba(21, 21, 40, 0.6);
border: 1px solid var(--border-color);
color: var(--text-muted);
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: var(--transition-smooth);
}
.filter-btn.active,
.filter-btn:hover {
border-color: var(--primary-color);
color: #fff;
}
.feedback-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.feedback-item {
background: var(--surface-color);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
display: flex;
justify-content: space-between;
gap: 20px;
transition: var(--transition-smooth);
}
.feedback-item:hover {
border-color: rgba(255, 43, 214, 0.25);
box-shadow: 0 4px 20px rgba(255, 43, 214, 0.05);
}
.fb-card-content {
flex: 1;
}
.fb-meta {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 8px;
}
.fb-tag {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
padding: 3px 8px;
border-radius: 4px;
}
.fb-tag.feature {
background: rgba(40, 223, 255, 0.1);
color: var(--accent-color);
border: 1px solid rgba(40, 223, 255, 0.25);
}
.fb-tag.bug {
background: rgba(255, 68, 102, 0.1);
color: #ff4466;
border: 1px solid rgba(255, 68, 102, 0.25);
}
.fb-tag.love {
background: rgba(255, 43, 214, 0.1);
color: var(--primary-color);
border: 1px solid rgba(255, 43, 214, 0.25);
}
.fb-author {
font-size: 13px;
color: var(--text-muted);
}
.feedback-item h4 {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.feedback-item p {
color: var(--text-muted);
font-size: 14px;
}
.fb-upvote-section {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.upvote-btn {
background: rgba(21, 21, 40, 0.8);
border: 1px solid var(--border-color);
border-radius: 8px;
width: 48px;
height: 54px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: var(--text-muted);
cursor: pointer;
transition: var(--transition-smooth);
}
.upvote-btn i {
font-size: 14px;
margin-bottom: 4px;
}
.upvote-btn span {
font-size: 12px;
font-weight: 700;
}
.upvote-btn:hover,
.upvote-btn.voted {
border-color: var(--primary-color);
color: var(--primary-color);
box-shadow: 0 0 10px rgba(255, 43, 214, 0.2);
}
.upvote-btn.voted i {
transform: translateY(-2px);
}
/* Legal & Imprint Modals */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(5, 5, 16, 0.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 2000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.modal-overlay.open {
opacity: 1;
pointer-events: auto;
}
.modal-content {
width: 90%;
max-width: 650px;
max-height: 80vh;
padding: 40px;
position: relative;
display: flex;
flex-direction: column;
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-overlay.open .modal-content {
transform: scale(1);
}
.modal-close {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
color: var(--text-muted);
font-size: 32px;
cursor: pointer;
line-height: 1;
transition: var(--transition-smooth);
}
.modal-close:hover {
color: var(--primary-color);
}
.modal-content h2 {
font-family: var(--font-titles);
font-size: 28px;
margin-bottom: 24px;
border-bottom: 1px solid var(--border-color);
padding-bottom: 12px;
}
.modal-scroll-area {
overflow-y: auto;
flex: 1;
padding-right: 12px;
}
.modal-scroll-area::-webkit-scrollbar {
width: 6px;
}
.modal-scroll-area::-webkit-scrollbar-track {
background: rgba(5, 5, 16, 0.3);
}
.modal-scroll-area::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
.modal-scroll-area h3 {
margin: 20px 0 10px;
font-size: 18px;
}
.modal-scroll-area h4 {
margin: 15px 0 8px;
font-size: 16px;
color: var(--accent-color);
}
.modal-scroll-area p {
margin-bottom: 12px;
font-size: 14px;
color: var(--text-muted);
}
.modal-scroll-area a {
color: var(--accent-color);
}
/* Footer styling */
.main-footer {
border-top: 1px solid var(--border-color);
padding: 40px 0;
font-size: 14px;
color: var(--text-muted);
}
.footer-container {
display: flex;
justify-content: space-between;
align-items: center;
}
.footer-links {
display: flex;
gap: 24px;
}
.footer-links a {
color: var(--text-muted);
text-decoration: none;
transition: var(--transition-smooth);
}
.footer-links a:hover {
color: var(--primary-color);
}
/* Keyframes */
@keyframes logoGlowPulse {
0% {
text-shadow: 0 0 10px var(--primary-color), 0 0 20px var(--primary-color);
}
100% {
text-shadow: 0 0 15px var(--primary-color), 0 0 30px var(--primary-color), 0 0 40px var(--accent-color);
}
}
@keyframes ringPulse {
0% {
transform: scale(0.95);
opacity: 0.8;
}
100% {
transform: scale(1.6);
opacity: 0;
}
}
/* Responsiveness */
@media (max-width: 992px) {
.hero-container {
grid-template-columns: 1fr;
text-align: center;
gap: 40px;
}
.hero-actions {
justify-content: center;
}
.feedback-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.menu-toggle {
display: block;
}
.nav-links {
display: none;
position: absolute;
top: 80px;
left: 0;
width: 100%;
background: var(--bg-color);
border-bottom: 1px solid var(--border-color);
flex-direction: column;
padding: 24px;
gap: 20px;
box-shadow: 0 10px 20px rgba(0,0,0,0.5);
}
.nav-links.mobile-open {
display: flex;
}
.hero-content h1 {
font-size: 38px;
}
.tablet-mockup {
width: 320px;
height: 220px;
border-width: 8px;
}
.app-circle-btn {
width: 50px;
height: 50px;
font-size: 20px;
}
.pulse-ring {
width: 50px;
height: 50px;
}
.footer-container {
flex-direction: column;
gap: 20px;
text-align: center;
}
}