commit b82c1f65909e572eb9837be01b8cf2d0ffabd314 Author: claude-dev Date: Sun Apr 26 14:22:12 2026 +0200 Archiv: Letzter Stand der Webseite vor Promotion der Vorschau Vollstaendiger Snapshot des Live-Standes von /opt/v2-Docker/aegis-website/html am 2026-04-26, kurz bevor die Inhalte aus /vorschau/ in den Root verschoben und die alte Webseite ausgemustert wurde. Dient als historische Referenz; nicht fuer aktive Entwicklung gedacht. Co-Authored-By: Claude Opus 4.7 (1M context) diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..7b88c36 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,16 @@ +{ + "permissions": { + "allow": [ + "Bash(grep:*)", + "Bash(mkdir:*)", + "Bash(ls:*)", + "Bash(git lfs:*)", + "Bash(sudo apt-get:*)", + "Bash(sudo apt-get install:*)", + "Bash(git add:*)", + "Bash(rm:*)", + "Bash(hostname)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c4e6feb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Video files +# Large image files +*.mp4 filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab1a123 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Lagebild-Daten (werden per Cron-Sync vom Monitor regeneriert) +lagebild/data/ +lagen/*/data/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..ea11209 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,148 @@ +# CLAUDE.md - AegisSight Website + +RELATED_DOCS: + COOKIE_CONSENT_IMPLEMENTATION.md: "Cookie-Banner Implementierung, GDPR, Testing" + DATENSCHUTZ_ANALYTICS.md: "Textbausteine fuer Datenschutzerklaerung" + PROFESSIONAL_TOOLBOX_CONTENT.md: "Backup-HTML fuer entfernte Produktkarte" + VIDEO_UPLOAD_INSTRUCTIONS.md: "Anleitung fuer manuelle Video-Uploads (Git-Limit)" + +PROJECT: Website +STATUS: PRODUCTION +URL: https://aegis-sight.de +CONTAINER: aegis-website-nginx + +COMPANY: + name: AegisSight UG (haftungsbeschraenkt) + domain: aegis-sight.de + email: info@aegis-sight.de + +TECHNOLOGY: + type: Static Website + build_process: NONE + deployment: Docker/nginx + + frontend: + - HTML5 + - CSS3 (modular) + - JavaScript ES6+ + - SVG + + features: + - multi_language: [DE, EN] + - responsive: mobile-first + - video_backgrounds: true + - cookie_consent: GDPR-compliant + - security_headers: enabled + +STRUCTURE: + root_files: + - index.html: Hauptseite + - impressum.html: Impressum DE + - impressum-en.html: Impressum EN + - datenschutz.html: Datenschutz DE + - datenschutz-en.html: Datenschutz EN + - accountforger-video.html: Produkt-Demo + - robots.txt: SEO-Konfiguration + - cookie-consent.js: GDPR Cookie-System + - cookie-consent.css: Cookie-Banner Styles + + directories: + assets: + fonts: [Inter, Bebas Neue] + images: + icons: UI-Icons (SVG) + flags: Laenderflaggen + logos: "Logo+Schrift_Rechts.svg" + videos: "~300MB - Hero-Videos, AFv6.mp4" + + css: + - main.css: Kern-Styles + - animations-enhanced.css: Animationen + - mobile.css: Mobile Responsive + - fonts.css: Typografie + + js: + - main.js: Einstiegspunkt + - translations.js: Mehrsprachigkeit + - components.js: UI-Komponenten + - animations.js: Animationssystem + + docs: Rechtliche PDFs + + lagen: + iran-konflikt: + - index.html: Lagebild-Seite (Leaflet, Tabs, Timeline) + - lagebild.js: Datenladung, Rendering, Interaktionen + - lagebild.css: Dark-Theme Styling + - data/: current.json + Snapshots (sync alle 5min) + + vorschau: + - index.html: Produktseite AegisSight Monitor (Passwort-Gate) + css: + - style.css: Light-Mode Design (Navy/Gold Akzente) + js: + - app.js: Hero-Videos, 3D-Karussell, Leaflet-Karte, Live-Daten, Kontaktformular + +PAGES: + homepage: + sections: [Hero mit Video, Ueber uns, Loesungen, Kontakt] + legal: + - Impressum (DE/EN) + - Datenschutz (DE/EN) + product: + - AccountForger Video-Demo + lagen: + url_struktur: /lagen/{thema}/ + redirect: /lagebild/ -> 301 -> /lagen/iran-konflikt/ + aktiv: + - iran-konflikt: Live-Lagebild Irankonflikt (ehemals /lagebild/) + geplant: + - (2 weitere Lagen in Vorbereitung) + vorschau: + url: /vorschau/ + zweck: Produktseite AegisSight Monitor (ersetzt spaeter die Hauptseite) + auth: JavaScript SHA-256 Passwort-Gate (kein Benutzername) + design: Light-Mode, Navy/Gold Akzente, SVG-Wellen/Diagonale Divider + sections: [Hero mit Video (clip-path Chevron), Problem (dark), Workflow 3-Schritte, Live-Stats, 3D-Karussell mit Lagebild-Text, Leaflet-Karte (gekoppelt an Karussell), Faktenprüfung-Statement, Features (5 Cards zentriert), CTA, Unser Versprechen] + daten: Fetcht /lagen/iran-konflikt/data/summary.json (~116 KB) + karussell: 3D-Perspektive, 3 Cards (Iran live + 2 Platzhalter), Karte wechselt mit + karte: Leaflet mit Pulse-Markern, Dark Popups/Legende, gekoppelt an aktive Lage + kontaktformular: Popup-Modal (Name, Organisation, E-Mail, Nachricht) -> /api/contact -> SMTP + icons: monitor.svg + languages.svg (Lucide) hinzugefuegt + +DEVELOPMENT: + translations: js/translations.js + large_files: "assets/videos/ (~300MB)" + design: mobile-first responsive + +SERVICES: + contact-form: + script: /opt/v2-Docker/aegis-website/contact-form.py + service: aegis-contact.service + port: 127.0.0.1:8074 + nginx: /api/contact -> 127.0.0.1:8074 + zweck: Kontaktformular-Handler (SMTP an info@aegis-sight.de) + rate_limit: 3 Anfragen pro IP / 10 Min + +DEPLOYMENT: + container: aegis-website-nginx + server: nginx (static files) + ssl: enabled + security_headers: enabled + rate_limiting: configured + +CHANGE_LOG: + 2026-01-08: + - "Rebrand: IntelSight -> AegisSight" + - "Neues Logo: Logo+Schrift_Rechts.svg" + - "Email: info@aegis-sight.de" + - "Footer: Dynamisches Jahr, AGB entfernt" + +Last-Updated: 2026-04-06 + +RULES: + neue_html_seiten: + - "Jede neue HTML-Datei MUSS im folgende Favicon-Tags enthalten:" + - "" + - "" + - "Fuer Unterverzeichnisse relative Pfade anpassen, z.B. ../favicon.svg" diff --git a/COOKIE_CONSENT_IMPLEMENTATION.md b/COOKIE_CONSENT_IMPLEMENTATION.md new file mode 100644 index 0000000..29a8129 --- /dev/null +++ b/COOKIE_CONSENT_IMPLEMENTATION.md @@ -0,0 +1,535 @@ +# ANALYTICS IMPLEMENTATION - KOMPLETT-GUIDE + +**AegisSight UG** +**Datum:** 2026-03-20 +**Version:** 2.0 +**Status:** Implementation Complete + +--- + +# PHASE 1: BESTANDSAUFNAHME + +## Dienste & Tracking Katalog + +### **Dienste im Einsatz:** +- **AegisSight Analytics (Umami v3.0.3)** - Self-Hosted, Deutschland +- Keine Google Analytics +- Keine Facebook Pixel +- Keine Third-Party CDNs +- Keine Social Media Widgets + +### **Cookies:** + +**Keine.** Umami arbeitet vollständig cookieless. Es werden weder Session-Cookies noch Tracking-Cookies gesetzt. Die Identifikation erfolgt ohne Cookies und ohne Fingerprinting. + +| Eintrag | Zweck | Typ | Opt-In? | +|---------|-------|-----|---------| +| `analytics-consent` (LocalStorage) | Consent-Status | LocalStorage | Nein (technisch) | + +### **Tracking-Daten (anonymisiert):** +- Browser, OS, Device, Screen Resolution +- Land, Region (kein Stadtlevel, kein GeoIP-Lookup) +- Seiten, Referrer, Session-Dauer +- Traffic Source, UTM-Parameter + +### **Personenbezug:** Nein + +Umami speichert keine IP-Adressen und verwendet kein Fingerprinting. Alle Daten sind aggregiert und nicht auf einzelne Nutzer rückführbar. + +**Rechtsgrundlage:** Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse) - da cookieless und ohne Personenbezug ist kein Opt-In zwingend erforderlich. Der Consent-Banner wird dennoch aus Transparenzgründen eingesetzt. + +--- + +# PHASE 2: ENTSCHEIDUNG & DESIGN + +## **Entscheidung: CUSTOM BANNER** + +**Gründe:** +- Nur 1 Dienst - CMP wäre Overkill +- Volle Kontrolle, keine Abhängigkeiten +- Schneller (<5KB vs. 50-100KB für CMP) +- Kostenlos + +--- + +## **UI-Design: Slim Layer** + +### Banner (Primary): +``` +┌─────────────────────────────────────────┐ +│ Website-Analyse │ +│ │ +│ Text: Cookieless Analytics, Self- │ +│ Hosted in Deutschland, keine │ +│ Weitergabe an Dritte... │ +│ │ +│ [Details & Einstellungen] │ +│ [Alles akzeptieren] [Nur notwendige] │ +└─────────────────────────────────────────┘ +``` + +### Settings Modal: +``` +┌─────────────────────────────────────┐ +│ Analyse-Einstellungen [×] │ +├─────────────────────────────────────┤ +│ ☑ Notwendig (immer aktiv) │ +│ ☐ Statistik & Analyse │ +│ └─ AegisSight Analytics (Umami) │ +│ • Keine Cookies │ +│ • Keine IP-Speicherung │ +│ • Self-Hosted, Deutschland │ +│ │ +│ [Auswahl speichern] [Alle akz.] │ +└─────────────────────────────────────┘ +``` + +--- + +## **Texte (DE/EN)** + +**Deutsch:** +- Titel: "Website-Analyse" +- Text: "Wir nutzen cookielose Analyse-Software auf unserem eigenen Server in Deutschland. Es werden keine persönlichen Daten gespeichert und nichts an Dritte weitergegeben." +- Buttons: "Alles akzeptieren" / "Nur notwendige" + +**Englisch:** +- Title: "Website Analytics" +- Text: "We use cookieless analytics software on our own server in Germany. No personal data is stored and nothing is shared with third parties." +- Buttons: "Accept all" / "Only necessary" + +--- + +# PHASE 3: IMPLEMENTATIONSPLAN + +## **3.1 Gating-Pattern** + +``` +Page Load + ↓ +Check LocalStorage['analytics-consent'] + ↓ + ├─→ null → Show Banner + Block Tracking + ├─→ 'accepted' → Load Umami Script + └─→ 'rejected' → Block Tracking +``` + +**Ablauf:** Das Umami-Script `/analytics-und-so/datenblick.js` wird erst nach Zustimmung geladen. + +--- + +## **3.2 Script-Einbindung** + +### Umami Tracking-Script (wird dynamisch geladen bei Consent): + +```html + +``` + +### API-Endpoint: +``` +POST /analytics-und-so/api/erfassen +``` + +--- + +## **3.3 Storage-Schema** + +### LocalStorage Keys: + +```javascript +// Consent Status +"analytics-consent": { + value: "accepted" | "rejected", + expires: 1774051200000 // Timestamp (12 Monate) +} + +// Consent Details (Audit-Trail) +"analytics-consent-details": { + timestamp: "2026-03-20T12:00:00Z", + version: "2.0", + categories: { necessary: true, analytics: true }, + language: "de", + gpcSignal: false +} +``` + +--- + +## **3.4 Footer-Link (Persistent)** + +```html + +``` + +**Funktion:** Öffnet Banner erneut, ermöglicht Widerruf + +--- + +## **3.5 Re-Prompt Regeln** + +**Banner erneut zeigen bei:** +1. Consent abgelaufen (nach 12 Monaten) +2. Version-Change (z.B. 2.0 → 2.1) +3. User löscht LocalStorage +4. Klick auf "Analyse-Einstellungen" + +--- + +## **3.6 Global Privacy Control (GPC)** + +```javascript +if (navigator.globalPrivacyControl === true) { + // Auto-reject analytics + localStorage.setItem('analytics-consent', JSON.stringify({ + value: 'rejected', + expires: Date.now() + 365 * 24 * 60 * 60 * 1000 + })); +} +``` + +--- + +# PHASE 4: UMSETZUNG + TESTS + +## **4.1 Dateien** + +### **1. cookie-consent.css** +- Responsive Design (Mobile-First) +- Accessibility (Focus-Trap, ARIA, Keyboard) +- Animations (Fade-In, Slide-Up) +- Dark Mode kompatibel + +### **2. cookie-consent.js** +- Consent Management Logic +- GPC Detection +- LocalStorage mit Expiry +- Multilingual (DE/EN) +- Public API +- Version Control +- Dynamisches Laden von `/analytics-und-so/datenblick.js` + +### **3. DATENSCHUTZ_ANALYTICS.md** +- Fertige Datenschutzerklärung +- DSGVO-konforme Texte + +--- + +## **4.2 Features implementiert** + +- **Opt-In vor Tracking** (Gating) +- **Backdrop-Overlay** (verhindert Interaktion) +- **Two-Step Design** (Banner → Settings) +- **Consent-Versionierung** (Re-Prompt bei Updates) +- **GPC/DNT Support** (Auto-Reject) +- **LocalStorage mit Expiry** (12 Monate) +- **Audit-Trail** (Consent-Details) +- **Multilingual** (DE/EN) +- **Accessibility** (ARIA, Focus-Trap, Keyboard) +- **Responsive** (Mobile-optimiert) +- **Public API** (programmatischer Zugriff) +- **Cookieless Tracking** (keine Cookies nötig) + +--- + +## **4.3 Test-Checkliste** + +### **Funktional:** +- [ ] Banner erscheint beim ersten Besuch +- [ ] "Alles akzeptieren" lädt `/analytics-und-so/datenblick.js` +- [ ] "Nur notwendige" blockiert Script-Laden +- [ ] Re-Visit: Kein Banner (Consent gespeichert) +- [ ] Footer-Link öffnet Banner erneut +- [ ] Widerruf funktioniert (Accept → Reject) +- [ ] GPC-Signal wird erkannt und respektiert +- [ ] Version-Change löst Re-Prompt aus + +### **Script-Integration:** +- [ ] Script hat korrektes `data-website-id="598ef5fd-d2dc-4540-9e65-602889981dac"` +- [ ] Pageview wird an `/analytics-und-so/api/erfassen` gesendet +- [ ] Keine Cookies im Browser nach Tracking + +### **Accessibility & Responsive:** +- [ ] Keyboard-Navigation funktioniert (Tab, Enter, Esc) +- [ ] Screen Reader liest Banner korrekt vor +- [ ] Mobile: Buttons sind touchbar, kein horizontaler Scroll + +--- + +## **4.4 Browser-Kompatibilität** + +| Browser | Version | Status | +|---------|---------|--------| +| Chrome | 120+ | - | +| Firefox | 121+ | - | +| Safari | 17+ | - | +| Edge | 120+ | - | +| Chrome Mobile | 120+ | - | +| Safari iOS | 17+ | - | + +**Minimum Support:** ES6 (2015+), LocalStorage, Fetch API + +--- + +# PHASE 5: DATENSCHUTZERKLÄRUNG + +## **5.1 Textbausteine** + +**Datei:** `DATENSCHUTZ_ANALYTICS.md` + +**Enthält:** +- Art und Umfang der Datenverarbeitung +- Rechtsgrundlage (Art. 6 Abs. 1 lit. f DSGVO - berechtigtes Interesse) +- Hinweis auf cookieloses Tracking +- Keine IP-Speicherung, kein Fingerprinting +- Self-Hosted auf eigenem Server (Deutschland) +- Keine Datenübermittlung an Dritte +- Widerruf der Einwilligung +- GPC-Unterstützung +- Betroffenenrechte (Art. 15-21 DSGVO) + +--- + +## **5.2 Integration in Website** + +```html + +
+

4. Website-Analyse

+

Wir nutzen AegisSight Analytics (Umami), eine cookielose, datenschutzfreundliche + Analyse-Software. Diese wird auf unserem eigenen Server in Deutschland betrieben. + Es werden keine Cookies gesetzt, keine IP-Adressen gespeichert und kein + Fingerprinting eingesetzt. Die erhobenen Daten sind nicht auf einzelne Personen + rückführbar.

+
+``` + +--- + +## **5.3 Rechtliche Checkliste** + +- [x] Opt-In vor Tracking (Consent-Banner) +- [x] Widerruf ermöglichen (Footer-Link) +- [x] Datenschutzerklärung (vollständig) +- [x] Rechtsgrundlage benannt (Art. 6 I f DSGVO) +- [x] Hinweis cookieloses Tracking +- [x] Betroffenenrechte (Art. 15-21) +- [ ] Impressum vollständig (muss geprüft werden) +- [ ] Aufsichtsbehörde (muss eingefügt werden) + +--- + +# DEPLOYMENT-ANLEITUNG + +## **1. Dateien auf Server** + +``` +/var/www/html/ +├── cookie-consent.css +├── cookie-consent.js +└── cookie-consent-demo.html (optional) +``` + +## **2. Umami-Infrastruktur** + +``` +Analytics-System: Umami v3.0.3 (Node.js/Next.js) +Datenbank: PostgreSQL +Script-Pfad: /analytics-und-so/datenblick.js +API-Endpoint: /analytics-und-so/api/erfassen +Website-ID: 598ef5fd-d2dc-4540-9e65-602889981dac +``` + +--- + +## **3. In HTML-Seiten integrieren** + +```html + + + + + Ihre Seite + + + + + + + + + + + + + + +``` + +--- + +## **4. Container neu laden** + +```bash +docker exec aegis-website-nginx nginx -s reload +``` + +--- + +# WARTUNG & UPDATES + +## **Vierteljährlich:** +- [ ] Umami-Version prüfen und ggf. updaten +- [ ] Datenschutzerklärung überprüfen + +## **Jährlich:** +- [ ] Rechtsgrundlagen aktualisieren +- [ ] Consent-Version erhöhen (bei Änderungen) + +--- + +# TROUBLESHOOTING + +## **Problem: Banner erscheint nicht** + +```javascript +// In Browser-Console: +localStorage.removeItem('analytics-consent'); +localStorage.removeItem('analytics-consent-details'); +location.reload(); +``` + +--- + +## **Problem: Tracking funktioniert nicht trotz Zustimmung** + +**Check 1:** LocalStorage +```javascript +JSON.parse(localStorage.getItem('analytics-consent')) +// Erwartet: { value: "accepted", expires: ... } +``` + +**Check 2:** Script geladen? +```javascript +document.querySelector('script[src="/analytics-und-so/datenblick.js"]') +// Erwartet: ` → muss zu `js/vorschau-app.js` +- `videos/hero-slide-X-monitoring.mp4` → muss zu `assets/videos/vorschau-hero/hero-slide-X-monitoring.mp4` + +```bash +ssh claude-dev@46.225.225.49 'cd /opt/v2-Docker/aegis-website/html && \ + sed -i "s|href=\"css/style.css\"|href=\"/css/vorschau-style.css\"|g" index.html && \ + sed -i "s|src=\"js/app.js\"|src=\"/js/vorschau-app.js\"|g" index.html && \ + sed -i "s|src=\"videos/hero-slide-|src=\"/assets/videos/vorschau-hero/hero-slide-|g" index.html && \ + grep -E "(vorschau-style|vorschau-app|vorschau-hero)" index.html | head -10' +``` + +Erwartet: 7 Treffer (1× CSS, 1× JS, 5× Videos). + +### 2.5 Passwort-Gate aus `index.html` entfernen + +Folgenden Block ersatzlos löschen — es sind drei zusammenhängende Bereiche: +1. `` Kommentar + dahinterliegender ` + + + + +
+ + + + + Zurück zur Hauptseite + + +
+ AegisSight +

AccountForger

+

Video-Demo

+
+ +
+
+ +
+
+ +
+
+
+
0:00 / 0:00
+
+ +
+
+
+
+ +
+
+
+ +
+ + + + \ No newline at end of file diff --git a/analytics-events.js b/analytics-events.js new file mode 100644 index 0000000..fb4b10c --- /dev/null +++ b/analytics-events.js @@ -0,0 +1,73 @@ +/** + * AegisSight Analytics - Custom Events + * Trackt wichtige Nutzerinteraktionen via Umami + */ +(function() { + "use strict"; + + // Nur tracken wenn umami geladen ist + function track(name, data) { + if (typeof umami !== "undefined" && umami.track) { + umami.track(name, data); + } + } + + document.addEventListener("DOMContentLoaded", function() { + + // 1. Produkt-Tab geklickt + document.querySelectorAll("[data-translate]").forEach(function(el) { + if (el.closest(".products-section, .product-card, .tab-button")) { + el.addEventListener("click", function() { + var text = el.textContent.trim().substring(0, 50); + track("Produkt-Interesse", { element: text }); + }); + } + }); + + // 2. About-Tabs (Unternehmen, Mission, Kernkompetenzen, Versprechen) + document.querySelectorAll(".about-tab, .tab-btn, [data-tab]").forEach(function(el) { + el.addEventListener("click", function() { + var tab = el.getAttribute("data-tab") || el.textContent.trim().substring(0, 30); + track("About-Tab", { tab: tab }); + }); + }); + + // 3. Kontaktbereich erreicht (Scroll) + var contactTracked = false; + var observer = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting && !contactTracked) { + contactTracked = true; + track("Kontakt-Bereich-erreicht"); + } + }); + }, { threshold: 0.5 }); + + var footer = document.querySelector("footer, .contact-section, #kontakt, #contact"); + if (footer) observer.observe(footer); + + // 4. Sprachenwechsel + document.querySelectorAll(".lang-switch, .language-btn, [data-lang]").forEach(function(el) { + el.addEventListener("click", function() { + var lang = el.getAttribute("data-lang") || el.textContent.trim(); + track("Sprachenwechsel", { sprache: lang }); + }); + }); + + // 5. Lagebild-Seite: Tab gewechselt + if (window.location.pathname.indexOf("lagen") > -1) { + document.querySelectorAll(".tab-button, [data-tab]").forEach(function(el) { + el.addEventListener("click", function() { + var tab = el.getAttribute("data-tab") || el.textContent.trim().substring(0, 30); + track("Lagebild-Tab", { tab: tab }); + }); + }); + track("Lagebild-Besuch"); + } + + // 6. AccountForger Video aufgerufen + if (window.location.pathname.indexOf("accountforger") > -1) { + track("AccountForger-Video-Aufruf"); + } + }); +})(); diff --git a/assets/fonts/BebasNeue-Regular.ttf b/assets/fonts/BebasNeue-Regular.ttf new file mode 100644 index 0000000..85c1805 Binary files /dev/null and b/assets/fonts/BebasNeue-Regular.ttf differ diff --git a/assets/fonts/Inter-Bold.ttf b/assets/fonts/Inter-Bold.ttf new file mode 100644 index 0000000..aecc86e Binary files /dev/null and b/assets/fonts/Inter-Bold.ttf differ diff --git a/assets/fonts/Inter-Light.ttf b/assets/fonts/Inter-Light.ttf new file mode 100644 index 0000000..3c64d3f Binary files /dev/null and b/assets/fonts/Inter-Light.ttf differ diff --git a/assets/fonts/Inter-Regular.ttf b/assets/fonts/Inter-Regular.ttf new file mode 100644 index 0000000..399a6e0 Binary files /dev/null and b/assets/fonts/Inter-Regular.ttf differ diff --git a/assets/fonts/Inter-SemiBold.ttf b/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000..4a57a1a Binary files /dev/null and b/assets/fonts/Inter-SemiBold.ttf differ diff --git a/assets/handshake.svg b/assets/handshake.svg new file mode 100644 index 0000000..d67a2b0 --- /dev/null +++ b/assets/handshake.svg @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/images/flags/flag-de.svg b/assets/images/flags/flag-de.svg new file mode 100644 index 0000000..20a017e --- /dev/null +++ b/assets/images/flags/flag-de.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/assets/images/flags/flag-en.svg b/assets/images/flags/flag-en.svg new file mode 100644 index 0000000..016c075 --- /dev/null +++ b/assets/images/flags/flag-en.svg @@ -0,0 +1,50 @@ + + + \ No newline at end of file diff --git a/assets/images/icons/arrow-down.svg b/assets/images/icons/arrow-down.svg new file mode 100644 index 0000000..3fde56f --- /dev/null +++ b/assets/images/icons/arrow-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/images/icons/check-circle-filled.svg b/assets/images/icons/check-circle-filled.svg new file mode 100644 index 0000000..09f1f71 --- /dev/null +++ b/assets/images/icons/check-circle-filled.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/check-circle.svg b/assets/images/icons/check-circle.svg new file mode 100644 index 0000000..09f1f71 --- /dev/null +++ b/assets/images/icons/check-circle.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/chevron-down.svg b/assets/images/icons/chevron-down.svg new file mode 100644 index 0000000..24b755d --- /dev/null +++ b/assets/images/icons/chevron-down.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/images/icons/clock-circle.svg b/assets/images/icons/clock-circle.svg new file mode 100644 index 0000000..916e222 --- /dev/null +++ b/assets/images/icons/clock-circle.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/clock.svg b/assets/images/icons/clock.svg new file mode 100644 index 0000000..1517cfa --- /dev/null +++ b/assets/images/icons/clock.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/cube.svg b/assets/images/icons/cube.svg new file mode 100644 index 0000000..8442aff --- /dev/null +++ b/assets/images/icons/cube.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/document.svg b/assets/images/icons/document.svg new file mode 100644 index 0000000..9b1436c --- /dev/null +++ b/assets/images/icons/document.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/german-flag.svg b/assets/images/icons/german-flag.svg new file mode 100644 index 0000000..fa0b512 --- /dev/null +++ b/assets/images/icons/german-flag.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/images/icons/globe.svg b/assets/images/icons/globe.svg new file mode 100644 index 0000000..a5d738d --- /dev/null +++ b/assets/images/icons/globe.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/images/icons/languages.svg b/assets/images/icons/languages.svg new file mode 100644 index 0000000..75d07e1 --- /dev/null +++ b/assets/images/icons/languages.svg @@ -0,0 +1 @@ + diff --git a/assets/images/icons/location.svg b/assets/images/icons/location.svg new file mode 100644 index 0000000..4b64bfc --- /dev/null +++ b/assets/images/icons/location.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/lock.svg b/assets/images/icons/lock.svg new file mode 100644 index 0000000..07987ee --- /dev/null +++ b/assets/images/icons/lock.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/images/icons/monitor.svg b/assets/images/icons/monitor.svg new file mode 100644 index 0000000..f097b32 --- /dev/null +++ b/assets/images/icons/monitor.svg @@ -0,0 +1 @@ + diff --git a/assets/images/icons/plus-circle.svg b/assets/images/icons/plus-circle.svg new file mode 100644 index 0000000..0328cd2 --- /dev/null +++ b/assets/images/icons/plus-circle.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/images/icons/pyramid.svg b/assets/images/icons/pyramid.svg new file mode 100644 index 0000000..0831566 --- /dev/null +++ b/assets/images/icons/pyramid.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/images/icons/shield-check.svg b/assets/images/icons/shield-check.svg new file mode 100644 index 0000000..9727e16 --- /dev/null +++ b/assets/images/icons/shield-check.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/shield-play.svg b/assets/images/icons/shield-play.svg new file mode 100644 index 0000000..a2a2197 --- /dev/null +++ b/assets/images/icons/shield-play.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/assets/images/icons/shield.svg b/assets/images/icons/shield.svg new file mode 100644 index 0000000..ee3f889 --- /dev/null +++ b/assets/images/icons/shield.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/images/icons/video-camera.svg b/assets/images/icons/video-camera.svg new file mode 100644 index 0000000..c56f1fa --- /dev/null +++ b/assets/images/icons/video-camera.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/images/icons/world-globe.svg b/assets/images/icons/world-globe.svg new file mode 100644 index 0000000..4f4d46d --- /dev/null +++ b/assets/images/icons/world-globe.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/logos/AegisSightLogo_NavyGold.svg b/assets/images/logos/AegisSightLogo_NavyGold.svg new file mode 100644 index 0000000..835ad75 --- /dev/null +++ b/assets/images/logos/AegisSightLogo_NavyGold.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/logos/Logo+Schrift_Rechts.png b/assets/images/logos/Logo+Schrift_Rechts.png new file mode 100644 index 0000000..a3a75f7 Binary files /dev/null and b/assets/images/logos/Logo+Schrift_Rechts.png differ diff --git a/assets/images/logos/Logo+Schrift_Rechts.svg b/assets/images/logos/Logo+Schrift_Rechts.svg new file mode 100644 index 0000000..a604957 --- /dev/null +++ b/assets/images/logos/Logo+Schrift_Rechts.svg @@ -0,0 +1,20 @@ + + + + + + + + + AegisSight + + + + + + + + + + + diff --git a/assets/images/nrw.png b/assets/images/nrw.png new file mode 100644 index 0000000..d34db31 Binary files /dev/null and b/assets/images/nrw.png differ diff --git a/assets/images/og-image.png b/assets/images/og-image.png new file mode 100644 index 0000000..44707ff Binary files /dev/null and b/assets/images/og-image.png differ diff --git a/assets/videos/AFv6.mp4 b/assets/videos/AFv6.mp4 new file mode 100644 index 0000000..f713f9f --- /dev/null +++ b/assets/videos/AFv6.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:964edb5b5e6a9b746182a67d0a1d7dec43ef5de721bc0e3fca1902cfafac68aa +size 93417839 diff --git a/assets/videos/README.md b/assets/videos/README.md new file mode 100644 index 0000000..cce523f --- /dev/null +++ b/assets/videos/README.md @@ -0,0 +1,71 @@ +# Video Files / Video-Dateien + +## ✅ Alle Videos sind vollständig! + +Alle Videos wurden erfolgreich hochgeladen und sind einsatzbereit. + +## Vorhandene Videos + +| Datei | Größe | Status | Verwendung | +|-------|-------|--------|------------| +| `AFv6.mp4` | 90MB | ✅ Fertig | AccountForger Demo | +| `hero-code-abstract.mp4` | 11MB | ✅ Fertig | Hero Background 1 | +| `hero-data-flow.mp4` | 7.3MB | ✅ Fertig | Hero Background 2 | +| `hero-network-viz.mp4` | 14MB | ✅ Fertig | Hero Background 3 | + +--- + +## Original Hero Section Spezifikationen + +Die Hero-Section benötigt rotierende Hintergrund-Videos. Diese sollten folgende Eigenschaften haben: + +### Video-Spezifikationen: +- **Format:** MP4 (H.264 codec) +- **Auflösung:** 1920x1080 (Full HD) minimum +- **Länge:** 10-15 Sekunden Loop +- **Dateigröße:** Max. 5MB pro Video (komprimiert) +- **Framerate:** 24-30 fps +- **Audio:** Keine (Videos werden stumm abgespielt) + +### Benötigte Videos: + +1. **hero-tech-pattern.mp4** + - Abstrakte Tech-Muster oder Code-Visualisierung + - Helle, sanfte Bewegungen + - Farben: Weiß, Hellgrau, Hellblau + +2. **hero-data-flow.mp4** + - Datenströme oder Partikel-Animationen + - Fließende Bewegungen + - Minimalistisch und hell + +3. **hero-network-viz.mp4** + - Netzwerk-Nodes und Verbindungen + - Geometrische Formen + - Subtile Animationen + +4. **hero-code-abstract.mp4** + - Code-Editor oder Terminal-Output + - Verschwommen/abstrakt + - Heller Hintergrund + +### Empfohlene Tools zum Erstellen: +- After Effects (für professionelle Animationen) +- DaVinci Resolve (kostenlos) +- Blender (für 3D-Animationen) +- Online: Canva, Renderforest + +### Kostenlose Stock-Video Quellen: +- Pexels Videos (https://www.pexels.com/videos/) +- Pixabay (https://pixabay.com/videos/) +- Videvo (https://www.videvo.net/) +- Coverr (https://coverr.co/) + +### Komprimierung: +```bash +# FFmpeg Befehl zum Komprimieren: +ffmpeg -i input.mp4 -c:v libx264 -crf 23 -preset slow -vf scale=1920:1080 -an output.mp4 +``` + +### Fallback: +Falls keine Videos vorhanden sind, funktioniert die Website trotzdem - es wird nur der statische Gradient-Hintergrund mit Particle-Animation angezeigt. \ No newline at end of file diff --git a/assets/videos/hero-code-abstract.mp4 b/assets/videos/hero-code-abstract.mp4 new file mode 100644 index 0000000..3c7467c --- /dev/null +++ b/assets/videos/hero-code-abstract.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:979983a3de5a9179d064f43dff0a4b9e2e587d4e29e92d8b75dddd68f4ae4b0b +size 10541275 diff --git a/assets/videos/hero-data-flow.mp4 b/assets/videos/hero-data-flow.mp4 new file mode 100644 index 0000000..34f36d3 --- /dev/null +++ b/assets/videos/hero-data-flow.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:404a079edd98eb9f0011e9e5b9b50ddb47937a991456f126aa70dc616f2bf1e5 +size 7620100 diff --git a/assets/videos/hero-network-viz.mp4 b/assets/videos/hero-network-viz.mp4 new file mode 100644 index 0000000..77ee5e8 --- /dev/null +++ b/assets/videos/hero-network-viz.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71bb2b493d10e3878a9b973b309e60da191ad9e21ef38d64fe12692ca64c2592 +size 14431452 diff --git a/assets/videos/video-placeholder.txt b/assets/videos/video-placeholder.txt new file mode 100644 index 0000000..88b97e5 --- /dev/null +++ b/assets/videos/video-placeholder.txt @@ -0,0 +1,10 @@ +Die Video-Dateien sind zu groß für den direkten Upload. + +Bitte laden Sie die Videos manuell herunter und platzieren Sie diese im assets/videos/ Verzeichnis: + +1. AFv6.mp4 (90MB) - AccountForger Demo Video +2. hero-code-abstract.mp4 (11MB) - Hero Section Background Video 1 +3. hero-data-flow.mp4 (7.3MB) - Hero Section Background Video 2 +4. hero-network-viz.mp4 (14MB) - Hero Section Background Video 3 + +Alternativ können Sie die Videos über Git LFS oder einen separaten CDN/Storage-Service bereitstellen. diff --git a/cookie-consent.css b/cookie-consent.css new file mode 100644 index 0000000..9cd5724 --- /dev/null +++ b/cookie-consent.css @@ -0,0 +1,496 @@ +/** + * Cookie Consent Banner - DSGVO-konform + * AegisSight + * Angepasst an Corporate Design (Rheinmetall Style) + */ + +/* === CSS Variables (nutzt globale Tokens) === */ +:root { + --consent-primary: var(--color-navy, #0A1832); + --consent-primary-dark: var(--color-navy-dark, #060F20); + --consent-gray-light: var(--color-gray-100, #f4f4f4); + --consent-white: var(--color-white, #FFFFFF); + --consent-text-dark: var(--color-gray-800, #333333); + --consent-text-gray: var(--color-gray-600, #666666); + --consent-border: var(--color-gray-200, #e0e0e0); + --consent-shadow: 0 2px 8px rgba(0,0,0,0.1); + --consent-shadow-hover: 0 8px 24px rgba(0,0,0,0.15); +} + +/* === Demo Page Styling (AegisSight Style) === */ +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + margin: 0; + padding: 0; + line-height: 1.6; + color: var(--consent-text-dark); +} + +header { + background: linear-gradient(135deg, var(--consent-primary-dark) 0%, var(--consent-primary) 100%); + color: white; + padding: 2rem; + text-align: center; + box-shadow: var(--consent-shadow); +} + +header h1 { + font-size: 2rem; + font-weight: 600; + letter-spacing: 1px; + text-transform: uppercase; +} + +main { + max-width: 1000px; + margin: 2rem auto; + padding: 0 2rem; +} + +footer { + background: var(--consent-text-dark); + color: white; + padding: 2rem; + text-align: center; + margin-top: 4rem; +} + +footer nav { + margin-bottom: 1rem; +} + +footer nav a { + color: var(--consent-primary); + text-decoration: none; + margin: 0 1.5rem; + font-weight: 600; + transition: color 0.3s ease; +} + +footer nav a:hover { + color: #fff; + text-decoration: underline; +} + +/* === Cookie Consent Banner === */ + +/* Backdrop Overlay */ +#cookie-consent-backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + z-index: 9998; + display: none; + animation: fadeIn 0.3s ease; +} + +#cookie-consent-backdrop.active { + display: block; +} + +/* Main Banner Container */ +#cookie-consent-banner { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: rgba(255, 255, 255, 0.98); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15); + border-top: 2px solid var(--color-gold, #C8A851); + z-index: 9999; + transform: translateY(100%); + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1); + max-height: 90vh; + overflow-y: auto; +} + +#cookie-consent-banner.active { + transform: translateY(0); +} + +/* Banner Content */ +.consent-content { + max-width: 1200px; + margin: 0 auto; + padding: 1.5rem; +} + +.consent-header { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.consent-header h2 { + margin: 0; + font-size: 1.5rem; + color: var(--consent-text-dark); + font-weight: 600; +} + +.consent-text { + color: var(--consent-text-gray); + font-size: 0.95rem; + margin-bottom: 1.5rem; + line-height: 1.6; +} + +.consent-text strong { + color: var(--consent-text-dark); +} + +/* Button Group */ +.consent-buttons { + display: flex; + gap: 1rem; + flex-wrap: wrap; + margin-top: 1.5rem; +} + +.consent-btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: var(--radius-md, 8px); + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; +} + +.consent-btn:focus { + outline: 3px solid rgba(52, 152, 219, 0.5); + outline-offset: 2px; +} + +.consent-btn-primary { + background: var(--consent-primary); + color: white; + box-shadow: var(--consent-shadow); +} + +.consent-btn-primary:hover { + background: var(--consent-primary-dark); + transform: translateY(-2px); + box-shadow: var(--consent-shadow-hover); +} + +.consent-btn-secondary { + background: var(--color-gray-600, #666666); + color: white; +} + +.consent-btn-secondary:hover { + background: var(--color-gray-800, #333333); +} + +.consent-btn-outline { + background: transparent; + border: 2px solid var(--consent-primary); + color: var(--consent-primary); +} + +.consent-btn-outline:hover { + background: var(--consent-primary); + color: white; +} + +/* Settings Modal */ +#cookie-consent-settings { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%) scale(0.9); + background: white; + border-radius: var(--radius-lg, 16px); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + z-index: 10000; + max-width: 600px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + opacity: 0; + pointer-events: none; + transition: all 0.3s ease; +} + +#cookie-consent-settings.active { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + pointer-events: all; +} + +.settings-header { + background: linear-gradient(135deg, var(--consent-primary-dark) 0%, var(--consent-primary) 100%); + color: white; + padding: 1.5rem; + border-radius: var(--radius-lg, 16px) var(--radius-lg, 16px) 0 0; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: var(--consent-shadow); +} + +.settings-header h3 { + margin: 0; + font-size: 1.3rem; +} + +.settings-close { + background: transparent; + border: none; + color: white; + font-size: 1.5rem; + cursor: pointer; + padding: 0.25rem 0.5rem; + line-height: 1; + border-radius: 4px; + transition: background 0.2s ease; +} + +.settings-close:hover { + background: rgba(255, 255, 255, 0.1); +} + +.settings-content { + padding: 1.5rem; +} + +/* Category Cards */ +.cookie-category { + border: 2px solid var(--consent-border); + border-radius: var(--radius-md, 8px); + padding: 1rem; + margin-bottom: 1rem; + transition: border-color 0.2s ease; +} + +.cookie-category:hover { + border-color: var(--consent-primary); +} + +.category-header { + display: flex; + align-items: center; + justify-content: space-between; + cursor: pointer; + user-select: none; +} + +.category-title { + display: flex; + align-items: center; + gap: 0.75rem; + font-weight: 600; + color: var(--consent-text-dark); +} + +.category-toggle { + position: relative; + width: 50px; + height: 26px; + background: #ccc; + border-radius: 13px; + transition: background 0.3s ease; + cursor: pointer; +} + +.category-toggle::after { + content: ''; + position: absolute; + top: 3px; + left: 3px; + width: 20px; + height: 20px; + background: white; + border-radius: 50%; + transition: transform 0.3s ease; +} + +.category-toggle.active { + background: var(--color-gold, #C8A851); +} + +.category-toggle.active::after { + transform: translateX(24px); +} + +.category-toggle.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.category-description { + color: #666; + font-size: 0.9rem; + margin-top: 0.5rem; + line-height: 1.5; +} + +.category-details { + margin-top: 1rem; + padding: 1rem; + background: #f8f9fa; + border-radius: 6px; + font-size: 0.85rem; + color: #555; +} + +.category-details ul { + margin: 0.5rem 0; + padding-left: 1.5rem; +} + +.category-details li { + margin: 0.25rem 0; +} + +/* Badge */ +.badge { + display: inline-block; + padding: 0.25rem 0.5rem; + background: #e0e0e0; + color: #555; + font-size: 0.75rem; + border-radius: 4px; + font-weight: 600; +} + +.badge-required { + background: var(--consent-primary); + color: white; +} + +/* Settings Footer */ +.settings-footer { + padding: 1rem 1.5rem; + border-top: 1px solid #e0e0e0; + display: flex; + gap: 1rem; + flex-wrap: wrap; +} + +.settings-footer .consent-btn { + flex: 1; + min-width: 150px; +} + +/* Links */ +.settings-links { + display: flex; + gap: 1.5rem; + padding: 1rem 1.5rem; + border-top: 1px solid #e0e0e0; + font-size: 0.9rem; +} + +.settings-links a { + color: var(--consent-primary); + text-decoration: none; + font-weight: 600; +} + +.settings-links a:hover { + color: var(--consent-primary-dark); + text-decoration: underline; +} + +/* GPC Notice */ +.gpc-notice { + background: #fff3cd; + border: 2px solid #ffc107; + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; + display: flex; + align-items: start; + gap: 0.75rem; +} + +.gpc-notice-icon { + font-size: 1.5rem; +} + +.gpc-notice-text { + flex: 1; + color: #856404; +} + +.gpc-notice-text strong { + display: block; + margin-bottom: 0.25rem; +} + +/* Mobile Responsive */ +@media (max-width: 768px) { + .consent-content { + padding: 1rem; + } + + .consent-header h2 { + font-size: 1.25rem; + } + + .consent-buttons { + flex-direction: column; + } + + .consent-btn { + width: 100%; + text-align: center; + } + + #cookie-consent-settings { + width: 95%; + max-height: 90vh; + } + + .settings-footer { + flex-direction: column; + } + + .settings-footer .consent-btn { + width: 100%; + } + + .settings-links { + flex-direction: column; + gap: 0.5rem; + } +} + +/* Accessibility */ +@media (prefers-reduced-motion: reduce) { + #cookie-consent-banner, + #cookie-consent-settings, + .category-toggle::after { + transition: none; + } +} + +/* High Contrast Mode */ +@media (prefers-contrast: high) { + #cookie-consent-banner { + border-top: 3px solid #000; + } + + .cookie-category { + border-width: 3px; + } +} + +/* Animations */ +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} diff --git a/cookie-consent.js b/cookie-consent.js new file mode 100644 index 0000000..2bbd705 --- /dev/null +++ b/cookie-consent.js @@ -0,0 +1,536 @@ +/** + * Cookie Consent Manager - DSGVO-konform + * AegisSight + * Version 1.0 + */ + +(function() { + 'use strict'; + + // === CONFIGURATION === + const CONFIG = { + CONSENT_VERSION: '1.0', + CONSENT_DURATION: 365, // days + STORAGE_KEY: 'insights-consent', + STORAGE_EXPIRES: 'insights-consent-expires', + STORAGE_DETAILS: 'insights-consent-details', + TRACKING_SCRIPT: '/analytics-und-so/datenblick.js', + WEBSITE_ID: '598ef5fd-d2dc-4540-9e65-602889981dac' + }; + + // === TRANSLATIONS === + const TRANSLATIONS = { + de: { + title: 'Diese Website nutzt Cookies', + text: 'Wir verwenden AegisSight Analytics (basierend auf Umami), ein selbst gehostetes, cookieloses Analyse-Tool, um unsere Website zu verbessern. Dabei erfassen wir anonymisierte Informationen über Ihre Nutzung (besuchte Seiten, Browser, ungefährer Standort). Alle Daten bleiben auf unserem Server in Deutschland und werden niemals an Dritte weitergegeben.', + privacy: 'Mit "Alle akzeptieren" stimmen Sie der Verwendung von Analyse-Cookies zu. Sie können Ihre Einwilligung jederzeit in den Cookie-Einstellungen widerrufen.', + btnAcceptAll: '✓ Alle akzeptieren', + btnRejectAll: '✗ Nur notwendige', + btnSettings: 'Details & Einstellungen', + settingsTitle: 'Cookie-Einstellungen', + categoryNecessary: 'Notwendig', + categoryAnalytics: 'Statistik & Analyse', + necessaryDesc: 'Technisch erforderliche Cookies für Login und Sicherheit. Diese Kategorie kann nicht deaktiviert werden.', + analyticsDesc: 'Anonymisierte Auswertung der Website-Nutzung zur Verbesserung unserer Inhalte. Alle Daten bleiben auf unserem Server in Deutschland.', + btnSaveSettings: 'Auswahl speichern', + linkPrivacy: 'Datenschutzerklärung', + linkImprint: 'Impressum', + gpcTitle: 'Global Privacy Control erkannt', + gpcText: 'Ihr Browser signalisiert, dass Sie nicht getrackt werden möchten (GPC). Wir respektieren diese Einstellung und haben Analyse-Cookies automatisch deaktiviert.' + }, + en: { + title: 'This website uses cookies', + text: 'We use AegisSight Analytics (based on Umami), a self-hosted, cookieless analytics tool to improve our website. We collect anonymized information about your usage (pages visited, browser, approximate location). All data remains on our server in Germany and is never shared with third parties.', + privacy: 'By clicking "Accept all", you consent to the use of analytics cookies. You can revoke your consent at any time in the cookie settings.', + btnAcceptAll: '✓ Accept all', + btnRejectAll: '✗ Only necessary', + btnSettings: 'Details & Settings', + settingsTitle: 'Cookie Settings', + categoryNecessary: 'Necessary', + categoryAnalytics: 'Statistics & Analytics', + necessaryDesc: 'Technically required cookies for login and security. This category cannot be disabled.', + analyticsDesc: 'Anonymized analysis of website usage to improve our content. All data remains on our server in Germany.', + btnSaveSettings: 'Save selection', + linkPrivacy: 'Privacy Policy', + linkImprint: 'Imprint', + gpcTitle: 'Global Privacy Control detected', + gpcText: 'Your browser signals that you do not want to be tracked (GPC). We respect this setting and have automatically disabled analytics cookies.' + } + }; + + // === STATE === + let currentLanguage = document.documentElement.lang || 'de'; + let consentState = { + necessary: true, + analytics: false + }; + + // === UTILITY FUNCTIONS === + + function getTranslation(key) { + return TRANSLATIONS[currentLanguage]?.[key] || TRANSLATIONS.de[key]; + } + + function setStorageWithExpiry(key, value, days) { + const now = new Date(); + const item = { + value: value, + expires: now.getTime() + (days * 24 * 60 * 60 * 1000) + }; + try { + localStorage.setItem(key, JSON.stringify(item)); + } catch (e) { + console.warn('[CookieConsent] LocalStorage not available:', e); + } + } + + function getStorageWithExpiry(key) { + try { + const itemStr = localStorage.getItem(key); + if (!itemStr) return null; + + const item = JSON.parse(itemStr); + const now = new Date(); + + if (now.getTime() > item.expires) { + localStorage.removeItem(key); + return null; + } + + return item.value; + } catch (e) { + console.warn('[CookieConsent] Error reading from LocalStorage:', e); + return null; + } + } + + function detectGPC() { + // Check for Global Privacy Control + if (navigator.globalPrivacyControl === true) { + return true; + } + // Check DNT as fallback (deprecated but still used) + if (navigator.doNotTrack === '1' || window.doNotTrack === '1') { + return true; + } + return false; + } + + function saveConsentDetails() { + const details = { + timestamp: new Date().toISOString(), + version: CONFIG.CONSENT_VERSION, + categories: consentState, + language: currentLanguage, + userAgent: navigator.userAgent, + gpcSignal: detectGPC() + }; + + try { + localStorage.setItem(CONFIG.STORAGE_DETAILS, JSON.stringify(details)); + } catch (e) { + console.warn('[CookieConsent] Could not save consent details:', e); + } + } + + function deleteCookie(name) { + document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + } + + // === TRACKING CONTROL === + + function loadTracking() { + if (consentState.analytics) { + // Check if script already loaded + if (document.querySelector(`script[src="${CONFIG.TRACKING_SCRIPT}"]`)) { + console.log('[CookieConsent] Tracking script already loaded'); + return; + } + + const script = document.createElement('script'); + script.src = CONFIG.TRACKING_SCRIPT; + script.setAttribute("data-website-id", CONFIG.WEBSITE_ID); + script.defer = true; + script.onerror = () => { + console.error('[CookieConsent] Failed to load tracking script'); + }; + document.head.appendChild(script); + // Custom Analytics Events laden + const eventsScript = document.createElement("script"); + eventsScript.src = "/analytics-events.js"; + eventsScript.defer = true; + document.head.appendChild(eventsScript); + console.log('[CookieConsent] Analytics enabled - tracking script loaded'); + } + } + + function disableTracking() { + // Remove tracking script if present + const trackingScript = document.querySelector(`script[src="${CONFIG.TRACKING_SCRIPT}"]`); + if (trackingScript) { + trackingScript.remove(); + } + + // Analytics Events Script entfernen + const eventsScript = document.querySelector('script[src="/analytics-events.js"]'); + if (eventsScript) { + eventsScript.remove(); + } + + console.log('[CookieConsent] Analytics disabled - tracking blocked'); + } + + // === CONSENT MANAGEMENT === + + function saveConsent(analytics) { + consentState.analytics = analytics; + + const consentValue = analytics ? 'accepted' : 'rejected'; + setStorageWithExpiry(CONFIG.STORAGE_KEY, consentValue, CONFIG.CONSENT_DURATION); + + saveConsentDetails(); + + if (analytics) { + loadTracking(); + } else { + disableTracking(); + } + + console.log('[CookieConsent] Consent saved:', consentValue); + } + + function loadConsent() { + const consent = getStorageWithExpiry(CONFIG.STORAGE_KEY); + + if (consent === null) { + // Check for GPC - auto-reject if enabled + if (detectGPC()) { + console.log('[CookieConsent] GPC detected - auto-rejecting analytics'); + consentState.analytics = false; + return null; // Still show banner with GPC notice + } + return null; + } + + consentState.analytics = (consent === 'accepted'); + + // Check version + try { + const details = JSON.parse(localStorage.getItem(CONFIG.STORAGE_DETAILS) || '{}'); + if (details.version !== CONFIG.CONSENT_VERSION) { + console.log('[CookieConsent] Version mismatch - re-prompting'); + return null; + } + } catch (e) { + console.warn('[CookieConsent] Could not verify consent version'); + } + + return consent; + } + + // === UI CREATION === + + function createBannerHTML() { + const gpcDetected = detectGPC(); + + return ` + + + `; + } + + function createSettingsHTML() { + return ` + + `; + } + + // === UI CONTROL === + + function showBanner() { + // Check if already exists + if (document.getElementById('cookie-consent-banner')) { + const banner = document.getElementById('cookie-consent-banner'); + const backdrop = document.getElementById('cookie-consent-backdrop'); + banner.classList.add('active'); + backdrop.classList.add('active'); + return; + } + + // Create and append + const container = document.createElement('div'); + container.innerHTML = createBannerHTML(); + document.body.appendChild(container.firstElementChild); // backdrop + document.body.appendChild(container.lastElementChild); // banner + + // Add event listeners + document.getElementById('btn-accept-all').addEventListener('click', handleAcceptAll); + document.getElementById('btn-reject-all').addEventListener('click', handleRejectAll); + document.getElementById('btn-settings').addEventListener('click', showSettings); + + // Prevent page scroll + document.body.style.overflow = 'hidden'; + + // Focus trap + document.getElementById('btn-accept-all').focus(); + } + + function hideBanner() { + const banner = document.getElementById('cookie-consent-banner'); + const backdrop = document.getElementById('cookie-consent-backdrop'); + + if (banner) { + banner.classList.remove('active'); + backdrop.classList.remove('active'); + + setTimeout(() => { + banner.remove(); + backdrop.remove(); + }, 400); + } + + // Re-enable page scroll + document.body.style.overflow = ''; + } + + function showSettings() { + // Create settings modal if not exists + if (!document.getElementById('cookie-consent-settings')) { + const container = document.createElement('div'); + container.innerHTML = createSettingsHTML(); + document.body.appendChild(container.firstElementChild); + + // Add event listeners + document.querySelector('.settings-close').addEventListener('click', hideSettings); + document.getElementById('btn-save-settings').addEventListener('click', handleSaveSettings); + document.getElementById('btn-accept-all-settings').addEventListener('click', handleAcceptAll); + + // Analytics toggle + const analyticsToggle = document.getElementById('analytics-toggle'); + const analyticsHeader = document.getElementById('analytics-category-header'); + + analyticsHeader.addEventListener('click', () => { + consentState.analytics = !consentState.analytics; + updateToggle(); + }); + + analyticsToggle.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + consentState.analytics = !consentState.analytics; + updateToggle(); + } + }); + + function updateToggle() { + analyticsToggle.classList.toggle('active', consentState.analytics); + analyticsToggle.setAttribute('aria-checked', consentState.analytics); + } + + updateToggle(); + } + + const settings = document.getElementById('cookie-consent-settings'); + settings.classList.add('active'); + + // Focus trap + document.querySelector('.settings-close').focus(); + } + + function hideSettings() { + const settings = document.getElementById('cookie-consent-settings'); + if (settings) { + settings.classList.remove('active'); + setTimeout(() => settings.remove(), 300); + } + } + + // === EVENT HANDLERS === + + function handleAcceptAll() { + saveConsent(true); + hideBanner(); + hideSettings(); + } + + function handleRejectAll() { + saveConsent(false); + hideBanner(); + hideSettings(); + } + + function handleSaveSettings() { + saveConsent(consentState.analytics); + hideSettings(); + hideBanner(); + } + + // === INITIALIZATION === + + function init() { + console.log('[CookieConsent] Initializing v' + CONFIG.CONSENT_VERSION); + + // Load existing consent + const consent = loadConsent(); + + if (consent === null) { + // No consent yet - show banner + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', showBanner); + } else { + showBanner(); + } + } else { + // Consent exists - apply settings + if (consentState.analytics) { + loadTracking(); + } + console.log('[CookieConsent] Existing consent loaded:', consent); + } + + // Cookie settings link in footer + document.addEventListener('DOMContentLoaded', () => { + const settingsLink = document.getElementById('cookie-settings-link'); + if (settingsLink) { + settingsLink.addEventListener('click', (e) => { + e.preventDefault(); + showBanner(); + }); + } + }); + } + + // === PUBLIC API === + + window.CookieConsent = { + show: showBanner, + hide: hideBanner, + showSettings: showSettings, + acceptAll: handleAcceptAll, + rejectAll: handleRejectAll, + getStatus: function() { + const consent = getStorageWithExpiry(CONFIG.STORAGE_KEY); + const details = JSON.parse(localStorage.getItem(CONFIG.STORAGE_DETAILS) || '{}'); + + return { + consent: consent, + analytics: consentState.analytics, + version: details.version, + timestamp: details.timestamp, + expires: localStorage.getItem(CONFIG.STORAGE_KEY) ? + JSON.parse(localStorage.getItem(CONFIG.STORAGE_KEY)).expires : null, + gpc: detectGPC() + }; + }, + setLanguage: function(lang) { + if (TRANSLATIONS[lang]) { + currentLanguage = lang; + console.log('[CookieConsent] Language set to:', lang); + } + } + }; + + // Auto-initialize + init(); + +})(); diff --git a/css/about-modern.css b/css/about-modern.css new file mode 100644 index 0000000..750bb94 --- /dev/null +++ b/css/about-modern.css @@ -0,0 +1,612 @@ +/* Modern About Section Redesign */ + +/* About Section Background */ +.about-section { + background: linear-gradient(135deg, var(--color-white) 0%, var(--color-gray-50) 100%); + position: relative; + overflow: hidden; + padding: var(--space-4xl) 0; +} + +.about-section::before { + content: ''; + position: absolute; + top: -50%; + right: -20%; + width: 60%; + height: 60%; + background: radial-gradient(circle, rgba(10, 24, 50, 0.04) 0%, transparent 70%); + border-radius: 50%; + animation: float-slow 20s ease-in-out infinite; +} + +@keyframes float-slow { + 0%, 100% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(-30px, -30px) scale(1.05); } + 66% { transform: translate(30px, -20px) scale(0.95); } +} + +/* Modern Tab Navigation */ +.about-tabs { + display: flex; + justify-content: center; + gap: 20px; + margin-bottom: 4rem; + position: relative; + padding: 10px; + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + border-radius: 100px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + max-width: 800px; + margin-left: auto; + margin-right: auto; + border: none; +} + +.about-tab { + background: transparent; + border: none; + color: var(--color-gray-600); + padding: 15px 30px; + cursor: pointer; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + font-size: 1rem; + font-weight: 600; + border-radius: 50px; + position: relative; + overflow: hidden; + z-index: 1; +} + +.about-tab::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50px; + background: var(--color-navy); + transform: translate(-50%, -50%); + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + z-index: -1; +} + +.about-tab.active::before { + width: 100%; + height: 100%; +} + +.about-tab.active { + color: var(--color-white); + transform: scale(1.05); + box-shadow: 0 8px 20px rgba(10, 24, 50, 0.3); +} + +.about-tab:hover:not(.active) { + color: var(--color-gold-dark); + transform: translateY(-2px); + background: rgba(200, 168, 81, 0.08); +} + +/* Tab Content Panels */ +.about-content { + max-width: 1200px; + margin: 0 auto; + position: relative; +} + +.about-panel { + display: none; + animation: fadeInUp 0.6s cubic-bezier(0.4, 0, 0.2, 1); +} + +.about-panel.active { + display: block; +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Company Tab - Cards Layout */ +#who-we-are .panel-text { + display: flex; + gap: 40px; + align-items: stretch; + min-height: 500px; +} + +.company-cards-wrapper { + display: flex; + flex-direction: column; + gap: 30px; + width: 50%; + justify-content: space-between; +} + +.company-card { + background: white; + border-radius: 20px; + padding: 40px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + position: relative; + overflow: hidden; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + flex: 1; + display: flex; + flex-direction: column; +} + +.company-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + background: #0A1832; + transform: scaleY(0); + transition: transform 0.4s; +} + +.company-card:hover::before { + transform: scaleY(1); +} + +.company-card:hover { + transform: translateX(6px); + box-shadow: 0 12px 32px rgba(10, 24, 50, 0.1); +} + +.company-card h4 { + color: var(--color-navy); + font-size: 1.4rem; + margin-bottom: 15px; + display: flex; + align-items: center; + gap: 15px; +} + +.company-card-icon { + width: 70px; + height: 70px; + background: linear-gradient(135deg, rgba(15, 114, 181, 0.1), rgba(0, 64, 110, 0.05)); + border-radius: 20px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: all 0.4s; +} + +.company-card:hover .company-card-icon { + transform: scale(1.1) rotate(5deg); + background: #0A1832; +} + +.company-card-icon img { + width: 35px; + height: 35px; + filter: brightness(0) saturate(100%) invert(42%) sepia(82%) saturate(723%) hue-rotate(178deg) brightness(98%) contrast(92%); + transition: filter 0.4s; +} + +.company-card:hover .company-card-icon img { + filter: brightness(0) saturate(100%) invert(100%); +} + +/* Mission & Values - Modern Grid */ +.mission-grid { + text-align: left; +} + +.mission-header { + background: #0A1832; + color: white; + padding: 60px; + border-radius: 30px; + margin-bottom: 40px; + position: relative; + overflow: hidden; +} + +.mission-header::after { + content: ''; + position: absolute; + top: -50%; + right: -10%; + width: 50%; + height: 200%; + background: rgba(255, 255, 255, 0.1); + transform: rotate(45deg); +} + +.mission-header h3 { + font-size: 2.5rem; + margin-bottom: 20px; + position: relative; + z-index: 1; + color: #ffffff; +} + +.mission-header p { + font-size: 1.2rem; + position: relative; + z-index: 1; + opacity: 0.95; +} + +.values-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 30px; + margin: 3rem 0; +} + +.value-card { + background: white; + border-radius: 24px; + padding: 35px; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + border: 2px solid transparent; + cursor: pointer; +} + +.value-card::after { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, rgba(15, 114, 181, 0.1) 0%, transparent 100%); + opacity: 0; + transition: opacity 0.4s; +} + +.value-card:hover::after { + opacity: 1; +} + +.value-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 32px rgba(10, 24, 50, 0.12); + border-color: var(--color-navy); +} + +.value-icon { + width: 100px; + height: 100px; + margin-bottom: 20px; + background: linear-gradient(135deg, rgba(15, 114, 181, 0.1), rgba(0, 64, 110, 0.05)); + border-radius: 30px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.4s; +} + +.value-icon img { + width: 50px; + height: 50px; + filter: brightness(0) saturate(100%) invert(42%) sepia(82%) saturate(723%) hue-rotate(178deg) brightness(98%) contrast(92%); + transition: filter 0.4s; +} + +.value-card:hover .value-icon { + transform: scale(1.1) rotate(5deg); + background: #0A1832; +} + +.value-card:hover .value-icon img { + filter: brightness(0) saturate(100%) invert(100%); +} + +/* Competencies - Timeline Style */ +.competencies-list { + position: relative; + padding-left: 40px; +} + +.competencies-list::before { + content: ''; + position: absolute; + left: 10px; + top: 0; + width: 3px; + height: 100%; + background: #0A1832; + border-radius: 2px; +} + +.competency-item { + display: grid; + grid-template-columns: auto 1fr; + gap: 30px; + align-items: center; + padding: 30px; + margin-bottom: 30px; + background: white; + border-radius: 20px; + position: relative; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05); +} + +.competency-item::before { + content: ''; + position: absolute; + left: -30px; + top: 50%; + transform: translateY(-50%); + width: 20px; + height: 20px; + background: white; + border: 4px solid #0f72b5; + border-radius: 50%; + z-index: 1; +} + +.competency-item:hover { + transform: translateX(10px); + box-shadow: 0 8px 24px rgba(10, 24, 50, 0.1); +} + +.competency-number { + font-size: 3rem; + font-weight: 700; + background: linear-gradient(135deg, #C8A851, #B39645); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + opacity: 1; +} + +/* Why AegisSight - 2x2 Grid */ +.why-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 30px; + grid-auto-rows: minmax(250px, auto); +} + +.why-card { + grid-column: span 1; + grid-row: span 1; +} + +.why-card { + background: white; + border-radius: 24px; + padding: 35px; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: space-between; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08); + border: 2px solid transparent; +} + +.why-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(10, 24, 50, 0.1); + border-color: rgba(10, 24, 50, 0.2); +} + +.why-icon { + width: 100px; + height: 100px; + margin: 0 auto 25px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, rgba(15, 114, 181, 0.1), rgba(0, 64, 110, 0.05)); + border-radius: 30px; + transition: all 0.4s; +} + +.why-icon img { + width: 50px; + height: 50px; + filter: brightness(0) saturate(100%) invert(42%) sepia(82%) saturate(723%) hue-rotate(178deg) brightness(98%) contrast(92%); + transition: filter 0.4s; +} + +/* German Flag Icon Special Styling */ +.german-flag-icon { + background: transparent !important; + padding: 15px; +} + +.german-flag-icon img { + width: 70px !important; + height: 42px !important; + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + filter: none !important; +} + +.why-card:hover .german-flag-icon { + background: transparent !important; + transform: scale(1.15); +} + +.why-card:hover .german-flag-icon img { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25); + filter: none !important; +} + +.why-card:hover .why-icon { + transform: scale(1.1) rotate(5deg); + background: #0A1832; +} + +.why-card:hover .why-icon img { + filter: brightness(0) saturate(100%) invert(100%); +} + +.why-card h4 { + font-size: 1.6rem; + margin-bottom: 20px; + color: var(--color-navy); + text-align: center; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.why-card p { + color: var(--color-gray-600); + line-height: 1.8; + flex-grow: 1; + text-align: center; + font-size: 1.05rem; +} + +/* Location Section with Map */ +.location-section { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 45%; + margin-left: auto; + height: 100%; +} + +.mini-germany-map { + flex: 1; + width: 100%; + max-width: 350px; + padding: 30px; + background: white; + border-radius: 20px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s; + margin-bottom: 20px; + position: relative; + overflow: hidden; +} + +.mini-germany-map::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: radial-gradient(circle at 35% 45%, rgba(10, 24, 50, 0.3) 0%, transparent 40%); + opacity: 0; + transition: opacity 0.4s ease; + pointer-events: none; + z-index: 2; +} + +.mini-germany-map:hover::before { + opacity: 1; +} + +.mini-germany-map:hover { + transform: scale(1.02); + box-shadow: 0 15px 50px rgba(10, 24, 50, 0.15); +} + +.mini-germany-map img { + width: 100%; + height: auto; + max-height: 100%; + object-fit: contain; + transition: all 0.4s ease; + position: relative; + z-index: 1; +} + +.mini-germany-map:hover img { + filter: brightness(1.1) contrast(1.1); +} + +/* Pulsing glow effect for NRW region */ +@keyframes nrwPulse { + 0%, 100% { + filter: drop-shadow(0 0 10px rgba(10, 24, 50, 0.5)); + } + 50% { + filter: drop-shadow(0 0 25px rgba(10, 24, 50, 0.8)); + } +} + +.mini-germany-map:hover img { + animation: nrwPulse 2s ease-in-out infinite; +} + +/* Location Badge Enhancement */ +.location-badge { + display: inline-flex; + align-items: center; + gap: 12px; + padding: 12px 24px; + background: linear-gradient(135deg, rgba(15, 114, 181, 0.1), rgba(0, 64, 110, 0.05)); + border-radius: 100px; + color: #0f72b5; + font-weight: 600; + border: 2px solid rgba(15, 114, 181, 0.2); + transition: all 0.3s; +} + +.location-badge:hover { + background: #0A1832; + color: white; + transform: scale(1.05); + box-shadow: 0 10px 30px rgba(15, 114, 181, 0.3); +} + +.location-badge svg { + width: 24px; + height: 24px; + transition: all 0.3s; +} + +.location-badge:hover svg { + animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; +} + +@keyframes ping { + 0%, 100% { + transform: scale(1); + opacity: 1; + } + 50% { + transform: scale(1.2); + opacity: 0.8; + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .about-tabs { + flex-direction: column; + border-radius: 20px; + gap: 10px; + } + + .why-grid { + grid-template-columns: 1fr; + } + + .competencies-list { + padding-left: 20px; + } +} \ No newline at end of file diff --git a/css/animations-enhanced.css b/css/animations-enhanced.css new file mode 100644 index 0000000..23efc87 --- /dev/null +++ b/css/animations-enhanced.css @@ -0,0 +1,222 @@ +/* Enhanced Modern Animations & Effects */ + +/* Glassmorphism Base */ +.glass { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.glass-dark { + background: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +/* Smooth Fade In Animations */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInScale { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(-50px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Staggered Animation Classes */ +.animate-in { + opacity: 0; + animation: fadeInUp 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; +} + +.animate-in-scale { + opacity: 0; + animation: fadeInScale 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; +} + +.stagger-1 { animation-delay: 0.1s; } +.stagger-2 { animation-delay: 0.2s; } +.stagger-3 { animation-delay: 0.3s; } +.stagger-4 { animation-delay: 0.4s; } +.stagger-5 { animation-delay: 0.5s; } + +/* Floating Animation */ +@keyframes float { + 0%, 100% { transform: translateY(0px); } + 50% { transform: translateY(-20px); } +} + +.floating { + animation: float 6s ease-in-out infinite; +} + +/* Pulse Glow Animation - Dezent */ +@keyframes pulseGlow { + 0%, 100% { + box-shadow: + 0 0 5px rgba(10, 24, 50, 0.2), + 0 0 10px rgba(10, 24, 50, 0.1); + } + 50% { + box-shadow: + 0 0 10px rgba(10, 24, 50, 0.3), + 0 0 20px rgba(10, 24, 50, 0.15); + } +} + +.pulse-glow { + animation: pulseGlow 3s ease-in-out infinite; +} + +/* Gradient Animation */ +@keyframes gradientShift { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.gradient-animated { + background: linear-gradient( + -45deg, + var(--color-navy), + var(--color-navy-light), + var(--color-navy), + var(--color-blue) + ); + background-size: 400% 400%; + animation: gradientShift 15s ease infinite; +} + +/* Text Reveal Animation */ +@keyframes textReveal { + from { + clip-path: polygon(0 0, 0 0, 0 100%, 0% 100%); + } + to { + clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); + } +} + +.text-reveal { + animation: textReveal 1.5s cubic-bezier(0.77, 0, 0.175, 1) forwards; +} + +/* Card Hover Effects - Subtil */ +.card-hover-lift { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.card-hover-lift:hover { + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(10, 24, 50, 0.12); +} + +/* Magnetic Button Effect */ +.magnetic-button { + position: relative; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +.magnetic-button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.magnetic-button:hover::before { + width: 300px; + height: 300px; +} + +/* Parallax Layers */ +.parallax-slow { transform: translateZ(-1px) scale(1.5); } +.parallax-medium { transform: translateZ(-2px) scale(2); } +.parallax-fast { transform: translateZ(-3px) scale(2.5); } + +/* Reveal on Scroll */ +.reveal { + opacity: 0; + transform: translateY(50px); + transition: all 1s cubic-bezier(0.4, 0, 0.2, 1); +} + +.reveal.active { + opacity: 1; + transform: translateY(0); +} + +/* Smooth Scroll Indicator */ +@keyframes scrollDown { + 0% { + transform: translateY(0); + opacity: 0; + } + 40% { + opacity: 1; + } + 80% { + transform: translateY(20px); + opacity: 0; + } + 100% { + opacity: 0; + } +} + +.scroll-indicator-animated { + animation: scrollDown 2s infinite; +} + +/* Loading Shimmer - Für Gold-Akzente */ +@keyframes shimmer { + 0% { + background-position: -1000px 0; + } + 100% { + background-position: 1000px 0; + } +} + +.shimmer { + background: linear-gradient( + 90deg, + rgba(200, 168, 81, 0) 0%, + rgba(200, 168, 81, 0.2) 50%, + rgba(200, 168, 81, 0) 100% + ); + background-size: 1000px 100%; + animation: shimmer 3s infinite; +} diff --git a/css/animations.css b/css/animations.css new file mode 100644 index 0000000..a0b4152 --- /dev/null +++ b/css/animations.css @@ -0,0 +1,271 @@ +/* Global Styles */ +:root { + --primary-blue: #0f72b5; + --dark-blue: #00406e; + --accent-blue: #0f72b5; + --secondary-blue: #00406e; + --light-gray: #f4f4f4; + --white: #FFFFFF; + --text-dark: #333333; + --border-gray: #e0e0e0; + --alert-red: #FF4444; + --success-green: #4CAF50; + --warning-yellow: #FFC107; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + background-color: var(--white); + color: var(--text-dark); + overflow-x: hidden; + line-height: 1.6; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 20px; +} + +/* Typography */ +h1, h2, h3, h4 { + font-family: 'Bebas Neue', cursive; + letter-spacing: 1px; +} + +.section-title { + font-size: 3.5rem; + text-align: center; + margin-bottom: 1rem; + position: relative; + display: inline-block; + width: 100%; +} + +.section-subtitle { + font-size: 1.2rem; + text-align: center; + opacity: 0.8; + margin-bottom: 3rem; + font-weight: 300; +} + +/* Navigation */ +.navbar { + position: fixed; + top: 0; + width: 100%; + z-index: 1000; + transition: all 0.3s ease; + background: var(--white); + border-bottom: 1px solid var(--border-gray); + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.nav-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1.5rem 2rem; +} + +.logo-img { + height: 55px; + width: auto; + filter: none; +} + +.nav-menu { + display: flex; + list-style: none; + gap: 2rem; +} + +.nav-menu a { + color: var(--text-dark); + text-decoration: none; + font-weight: 500; + font-size: 1rem; + transition: all 0.3s ease; + position: relative; +} + +.nav-menu a::after { + content: ''; + position: absolute; + bottom: -5px; + left: 0; + width: 0; + height: 2px; + background: var(--primary-blue); + transition: width 0.3s ease; +} + +.nav-menu a:hover::after { + width: 100%; +} + +.nav-extras { + display: flex; + align-items: center; + gap: 1rem; +} + +.lang-toggle { + background: transparent; + border: 1px solid var(--primary-blue); + color: var(--primary-blue); + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 500; +} + +.lang-toggle:hover { + background: var(--primary-blue); + color: var(--white); +} + +.cta-button, .primary-button, .secondary-button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 4px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + text-transform: none; + letter-spacing: 0.5px; +} + +.cta-button, .primary-button { + background: var(--primary-blue); + color: var(--white); +} + +.cta-button:hover, .primary-button:hover { + background: var(--dark-blue); + transform: translateY(-2px); + box-shadow: 0 5px 20px rgba(15, 114, 181, 0.3); +} + +.secondary-button { + background: transparent; + color: var(--primary-blue); + border: 2px solid var(--primary-blue); +} + +.secondary-button:hover { + background: var(--primary-blue); + color: var(--white); +} + +.large { + padding: 1rem 2rem; + font-size: 1.1rem; +} + +/* Hero Section */ +.hero { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + padding-top: 100px; + background: #000000; +} + +/* Clean transition */ +.hero::after { + display: none; +} + +/* Video Background */ +.hero-video { + position: absolute; + top: 50%; + left: 50%; + min-width: 100%; + min-height: 100%; + width: auto; + height: auto; + transform: translate(-50%, -50%); + z-index: 0; + object-fit: cover; +} + +/* Video Overlay to match brand colors */ +.video-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, + rgba(15, 114, 181, 0.7) 0%, + rgba(0, 64, 110, 0.7) 50%, + rgba(15, 114, 181, 0.7) 100%); + mix-blend-mode: multiply; + z-index: 1; +} + +#particleCanvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 2; +} + +.hero-content { + text-align: center; + z-index: 3; + position: relative; +} + +.hero-title { + margin-bottom: 2rem; +} + +.subtitle { + display: block; + font-size: 1.2rem; + margin-bottom: 0.5rem; + color: var(--text-dark); + font-weight: 400; + letter-spacing: 1px; +} + +.main-title { + display: block; + font-size: 3.5rem; + letter-spacing: 2px; + color: var(--primary-blue); + font-weight: 700; +} + +.hero-text { + font-size: 1.1rem; + margin-bottom: 3rem; + color: var(--text-dark); +} + + +.hero-cta { + display: flex; + gap: 1rem; + justify-content: center; + margin-top: 2rem; +} + + + diff --git a/css/fonts.css b/css/fonts.css new file mode 100644 index 0000000..286bb90 --- /dev/null +++ b/css/fonts.css @@ -0,0 +1,47 @@ +/* Local Font Definitions - DSGVO-compliant */ +/* + * Schrift-Zuordnung: + * - Bebas Neue: Nur für Hero-Titel "SICHERHEIT MADE IN GERMANY" (Display) + * - Inter: Navigation, Überschriften, Fließtext, Buttons (alles andere) + * - System-Fonts: Nur als Fallback + */ + +@font-face { + font-family: 'Bebas Neue'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../assets/fonts/BebasNeue-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url('../assets/fonts/Inter-Light.ttf') format('truetype'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url('../assets/fonts/Inter-Regular.ttf') format('truetype'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url('../assets/fonts/Inter-SemiBold.ttf') format('truetype'); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url('../assets/fonts/Inter-Bold.ttf') format('truetype'); +} diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..2586172 --- /dev/null +++ b/css/main.css @@ -0,0 +1,1550 @@ +/* Global Styles - AegisSight Corporate Design */ +:root { + /* Primärfarben (Logo) */ + --color-navy: #0A1832; + --color-gold: #C8A851; + --color-gold-light: #D4B96A; + --color-gold-dark: #B39645; + + /* Navy-Abstufungen */ + --color-navy-light: #132844; + --color-navy-dark: #060F20; + + /* Blau-Akzent (abgeleitet von Navy, für Links/Buttons) */ + --color-blue: #0f72b5; + --color-blue-hover: #0d62a0; + + /* Neutrale Farben */ + --color-white: #FFFFFF; + --color-gray-50: #F8FAFB; + --color-gray-100: #f4f4f4; + --color-gray-200: #e0e0e0; + --color-gray-600: #666666; + --color-gray-800: #333333; + + /* Spacing-System (8px-Grid) */ + --space-xs: 8px; + --space-sm: 16px; + --space-md: 24px; + --space-lg: 32px; + --space-xl: 48px; + --space-2xl: 64px; + --space-3xl: 80px; + --space-4xl: 96px; + + /* Border-Radius-System */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 16px; + --radius-xl: 24px; + --radius-pill: 100px; + + /* Z-Index-Skala */ + --z-content: 1; + --z-sticky: 100; + --z-overlay: 1000; + --z-modal: 9000; + --z-cookie: 10000; + + /* Shadows */ + --shadow: 0 2px 8px rgba(0,0,0,0.1); + --shadow-hover: 0 8px 24px rgba(0,0,0,0.15); + + /* Legacy Aliases (für Rückwärtskompatibilität) */ + --primary-blue: var(--color-blue); + --dark-blue: var(--color-blue-hover); + --accent-gold: var(--color-gold); + --light-gray: var(--color-gray-100); + --white: var(--color-white); + --text-dark: var(--color-gray-800); + --text-gray: var(--color-gray-600); + --border-gray: var(--color-gray-200); +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + /* Disable text selection */ + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-touch-callout: none; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + background-color: var(--color-navy); + color: var(--color-gray-800); + line-height: 1.6; + overflow-x: hidden; +} + +.container { + max-width: 1280px; + margin: 0 auto; + padding: 0 20px; +} + +/* Typography */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.2; + color: var(--text-dark); +} + +h1 { font-size: 3rem; } +h2 { font-size: 2.5rem; } +h3 { font-size: 2rem; } +h4 { font-size: 1.5rem; } +h5 { font-size: 1.25rem; } +h6 { font-size: 1.1rem; } + +.section-title { + font-size: 2.5rem; + text-align: center; + margin-bottom: 1rem; + color: var(--color-navy); +} + +#about .section-title { + color: var(--color-navy); +} + +.section-subtitle { + font-size: 1.1rem; + text-align: center; + color: var(--text-gray); + margin-bottom: 3rem; +} + +/* Skip Navigation */ +.skip-nav { + position: absolute; + top: -40px; + left: 0; + background: var(--primary-blue); + color: var(--white); + padding: 8px; + text-decoration: none; + z-index: 100; +} + +.skip-nav:focus { + top: 0; +} + +/* Navigation */ +.navbar { + position: fixed; + top: 0; + width: 100%; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + box-shadow: 0 1px 0 rgba(0,0,0,0.1); + z-index: var(--z-overlay); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.navbar.scrolled { + background: rgba(255, 255, 255, 0.98); + box-shadow: 0 4px 20px rgba(0,0,0,0.08); +} + +.nav-container { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 2rem; + max-width: 1280px; + margin: 0 auto; +} + +.logo-img { + height: 50px; + width: auto; + max-width: 100%; + display: block; + object-fit: contain; +} + +.nav-menu { + display: flex; + list-style: none; + gap: 2rem; + margin: 0; + padding: 0; +} + +.nav-menu a { + color: var(--color-navy); + text-decoration: none; + font-weight: 700; + font-size: 1.1rem; + text-transform: uppercase; + letter-spacing: 1px; + transition: color 0.3s ease; + position: relative; + padding: 0.5rem 0; +} + +.nav-menu a:hover { + color: var(--color-navy); + transform: translateY(-2px); +} + +.nav-menu a::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 2px; + background: var(--color-navy); + transition: width 0.3s ease; +} + +.nav-menu a:hover::after { + width: 100%; +} + +.nav-extras { + display: flex; + align-items: center; + gap: 1rem; +} + +.lang-toggle { + background: transparent; + border: 1px solid var(--color-gray-200); + color: var(--color-navy); + padding: 0.5rem 1rem; + border-radius: var(--radius-md); + cursor: pointer; + transition: all 0.3s ease; + font-size: 0.9rem; +} + +.lang-toggle:hover { + background: var(--primary-blue); + color: var(--white); + border-color: var(--primary-blue); + transform: scale(1.05); + box-shadow: 0 4px 15px rgba(15, 114, 181, 0.3); +} + +/* Buttons */ +.cta-button, +.primary-button, +.secondary-button { + padding: 0.75rem 1.5rem; + border: none; + border-radius: var(--radius-md); + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + font-size: 1rem; +} + +.cta-button, +.primary-button { + background: var(--primary-blue); + color: var(--white); +} + +.cta-button, +.primary-button { + position: relative; + overflow: hidden; +} + +.cta-button::before, +.primary-button::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.cta-button:hover::before, +.primary-button:hover::before { + width: 300px; + height: 300px; +} + +.cta-button:hover, +.primary-button:hover { + background: var(--dark-blue); + transform: translateY(-3px) scale(1.02); + box-shadow: 0 10px 30px rgba(15, 114, 181, 0.4); +} + +.secondary-button { + background: transparent; + color: var(--primary-blue); + border: 2px solid var(--primary-blue); +} + +.secondary-button:hover { + background: var(--primary-blue); + color: var(--white); +} + +.large { + padding: 1rem 2rem; + font-size: 1.1rem; +} + +/* Hero Section */ +.hero { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + position: relative; + padding-top: 100px; + background: var(--color-navy); + overflow: hidden; +} + +/* Hero Video Container */ +.hero-video-container { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; + background: var(--color-navy); +} + +/* Hero Videos */ +.hero-video { + position: absolute; + top: 50%; + left: 50%; + min-width: 100%; + min-height: 100%; + width: auto; + height: auto; + transform: translate(-50%, -50%); + object-fit: cover; + opacity: 0; + transition: opacity 2s ease-in-out; + filter: brightness(0.8) contrast(1.1); /* Slightly darker and more contrast like Palantir */ +} + +.hero-video.active { + opacity: 0.6; /* Clearly visible like Palantir */ +} + +.hero-video.fading-out { + opacity: 0; +} + +/* Light Overlay */ +.hero-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, + rgba(0, 0, 0, 0.6) 0%, + rgba(20, 20, 20, 0.5) 50%, + rgba(0, 0, 0, 0.6) 100%); + z-index: 2; +} + +/* Video Indicators removed - clean look without dots */ + +#particleCanvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 3; + opacity: 0.4; +} + +.hero-content { + text-align: left; + z-index: 5; + position: relative; + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; +} + +.hero-title { + margin-bottom: 2rem; + text-align: left; +} + +.subtitle { + display: block; + font-size: 1.2rem; + margin-bottom: 0.5rem; + color: var(--white); + font-weight: 400; + opacity: 0.9; +} + +.main-title { + display: block; + font-family: 'Bebas Neue', sans-serif; + font-size: clamp(3rem, 8vw, 7rem); + color: var(--color-white); + font-weight: 400; + margin-bottom: 1rem; + line-height: 1.1; + text-align: left; + letter-spacing: 2px; +} + +.hero-text { + font-size: 1.1rem; + margin-bottom: 3rem; + color: var(--white); + opacity: 0.95; + text-align: left; +} + +.hero-cta { + display: flex; + gap: 1rem; + justify-content: center; +} + +.hero-cta .primary-button, +.hero-cta .secondary-button { + background: var(--white); + color: var(--primary-blue); +} + +.hero-cta .primary-button:hover { + background: var(--light-gray); +} + +.hero-cta .secondary-button { + background: transparent; + border: 2px solid var(--white); + color: var(--white); +} + +.hero-cta .secondary-button:hover { + background: var(--white); + color: var(--primary-blue); +} + +/* About Section */ +.about-section { + padding: var(--space-4xl) 0; + background: var(--color-white); +} + +.about-tabs { + display: flex; + justify-content: center; + gap: 0; + margin-bottom: 3rem; + border-bottom: 1px solid var(--border-gray); +} + +.about-tab { + background: transparent; + border: none; + color: var(--text-gray); + padding: 1rem 2rem; + cursor: pointer; + transition: all 0.3s ease; + font-size: 1rem; + font-weight: 500; + border-bottom: 3px solid transparent; + margin-bottom: -1px; +} + +.about-tab.active { + color: var(--primary-blue); + border-bottom-color: var(--primary-blue); +} + +.about-tab:hover { + color: var(--primary-blue); + background: var(--light-gray); +} + +.about-content { + max-width: 1000px; + margin: 0 auto; +} + +.about-panel { + display: none; + animation: fadeIn 0.5s ease; +} + +.about-panel.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.panel-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3rem; + align-items: center; +} + +.panel-text h3 { + font-size: 2rem; + margin-bottom: 1rem; + color: var(--primary-blue); +} + +.panel-text p { + margin-bottom: 1rem; + line-height: 1.8; + color: var(--text-dark); +} + +.location-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--light-gray); + border-radius: 4px; + margin-top: 1rem; + color: var(--text-dark); + transition: all 0.3s ease; +} + +.location-badge:hover { + background: var(--dark-blue); + color: var(--white); +} + +.location-badge img { + width: 20px; + height: 20px; + filter: brightness(0) saturate(100%) invert(42%) sepia(82%) saturate(723%) hue-rotate(178deg) brightness(98%) contrast(92%); + transition: filter 0.3s ease; +} + +.location-badge:hover img { + filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%); +} + +.shield-animation { + display: flex; + justify-content: center; + align-items: center; +} + +.shield-animation svg { + width: 300px; + height: 300px; +} + +/* Mission & Values */ +.mission-grid { + text-align: center; +} + +.mission-statement h3 { + color: var(--primary-blue); + margin-bottom: 1rem; +} + +.values-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 2rem; + margin: 3rem 0; +} + +.value-card { + background: var(--color-gray-100); + border-radius: var(--radius-lg); + padding: var(--space-lg); + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + position: relative; + overflow: hidden; +} + +.value-card::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient(45deg, transparent, rgba(15, 114, 181, 0.1), transparent); + transform: rotate(45deg); + transition: all 0.6s; + opacity: 0; +} + +.value-card:hover::before { + animation: shimmer 0.6s; +} + +@keyframes shimmer { + 0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); } + 100% { transform: translateX(100%) translateY(100%) rotate(45deg); } +} + +.value-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(10, 24, 50, 0.12); + background: linear-gradient(135deg, var(--color-gray-100) 0%, rgba(10, 24, 50, 0.03) 100%); +} + +.value-icon { + width: 50px; + height: 50px; + margin: 0 auto 1rem; + color: var(--primary-blue); +} + +.value-icon svg { + width: 100%; + height: 100%; +} + +.value-card h4 { + color: var(--primary-blue); + margin-bottom: 0.5rem; +} + +.principle-note { + background: var(--light-gray); + border-left: 4px solid var(--primary-blue); + padding: 1.5rem; + margin-top: 2rem; + text-align: left; +} + +/* Competencies */ +.competencies-list { + display: grid; + gap: 1.5rem; +} + +.competency-item { + display: flex; + gap: 2rem; + align-items: center; + padding: 1.5rem; + background: var(--light-gray); + border-radius: 8px; + transition: all 0.3s ease; +} + +.competency-item:hover { + box-shadow: var(--shadow-hover); + transform: translateX(10px); +} + +.competency-number { + font-size: 2rem; + font-weight: 700; + color: var(--primary-blue); + opacity: 0.3; +} + +.competency-content h4 { + color: var(--primary-blue); + margin-bottom: 0.5rem; +} + +/* Why AegisSight */ +.why-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 2rem; +} + +.why-card { + background: var(--color-white); + border: 1px solid var(--color-gray-200); + border-radius: var(--radius-lg); + padding: var(--space-lg); + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 2px 10px rgba(0,0,0,0.05); + position: relative; + overflow: hidden; +} + +.why-card::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 3px; + background: linear-gradient(90deg, transparent, var(--primary-blue), transparent); + transition: left 0.6s; +} + +.why-card:hover::before { + left: 100%; +} + +.why-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(10, 24, 50, 0.12); + border-color: var(--color-navy); + background: linear-gradient(135deg, var(--color-white) 0%, rgba(10, 24, 50, 0.02) 100%); +} + +.why-card:hover .why-icon { + animation: float 2s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +.why-icon { + width: 60px; + height: 60px; + margin-bottom: 1rem; + color: var(--primary-blue); +} + +.why-icon svg { + width: 100%; + height: 100%; +} + +.why-card h4 { + color: var(--primary-blue); + margin-bottom: 0.5rem; +} + +/* Products Section */ +.products-section { + padding: var(--space-4xl) 0; + background: var(--color-gray-100); +} + +.product-showcase { + background: var(--white); + border-radius: 8px; + padding: 3rem; + margin-bottom: 3rem; + box-shadow: var(--shadow); +} + +.product-header { + text-align: center; + margin-bottom: 3rem; +} + +.product-header h3 { + font-size: 2rem; + color: var(--primary-blue); + margin-bottom: 1rem; +} + +.product-description { + max-width: 800px; + margin: 0 auto; + line-height: 1.8; + color: var(--text-gray); +} + +.tools-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 2rem; + margin-bottom: 3rem; + max-height: 1000px; + overflow: hidden; + transition: max-height 0.5s ease-in-out, opacity 0.3s ease-in-out; +} + +.tools-grid.collapsed { + max-height: 0; + margin-bottom: 0; + opacity: 0; +} + +.expand-button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin-top: 1rem; + padding: 0.75rem 1.5rem; + background: transparent; + color: var(--primary-blue); + border: 2px solid var(--primary-blue); + border-radius: 6px; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; +} + +.expand-button:hover { + background: var(--primary-blue); + color: white; +} + +.expand-button .expand-icon { + width: 20px; + height: 20px; + transition: transform 0.3s ease; +} + +.expand-button[data-expanded="true"] .expand-icon { + transform: rotate(180deg); +} + +.tool-card { + background: var(--color-gray-100); + border-radius: var(--radius-lg); + padding: var(--space-lg); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + position: relative; + overflow: hidden; + border: 1px solid transparent; +} + +.tool-card::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(135deg, rgba(15, 114, 181, 0.1) 0%, transparent 100%); + opacity: 0; + transition: opacity 0.4s; +} + +.tool-card:hover::after { + opacity: 1; +} + +.tool-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 24px rgba(10, 24, 50, 0.15); + border-color: rgba(10, 24, 50, 0.2); + background: linear-gradient(135deg, var(--color-white) 0%, var(--color-gray-100) 100%); +} + +.tool-card:hover .tool-icon { + transform: scale(1.1) rotate(5deg); + filter: drop-shadow(0 4px 8px rgba(15, 114, 181, 0.3)); +} + +.tool-icon { + transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +.tool-icon { + width: 60px; + height: 60px; + margin-bottom: 1rem; + color: var(--primary-blue); +} + +.tool-icon svg { + width: 100%; + height: 100%; +} + +.tool-card h4 { + color: var(--primary-blue); + margin-bottom: 1rem; +} + +.tool-features ul { + list-style: none; + padding: 0; +} + +.tool-features li { + position: relative; + padding-left: 25px; + margin-bottom: 0.8rem; + line-height: 1.6; + color: var(--text-dark); +} + +.tool-features li::before { + content: '✓'; + position: absolute; + left: 0; + color: var(--primary-blue); +} + +.product-cta { + text-align: center; + display: flex; + gap: 1rem; + justify-content: center; +} + +/* Protected Product */ +.product-protected { + background: var(--white); + border: 2px solid var(--primary-blue); + border-radius: 8px; + padding: 3rem; + text-align: center; +} + +.protected-header { + margin-bottom: 2rem; +} + +.lock-icon { + width: 80px; + height: 80px; + margin: 0 auto 1rem; +} + +.lock-icon svg { + width: 100%; + height: 100%; +} + +.protected-header h3 { + color: var(--primary-blue); + font-size: 2rem; + margin-bottom: 0.5rem; +} + +.protected-notice { + color: var(--primary-blue); + font-size: 1.1rem; +} + +.protected-content { + color: var(--text-gray); +} + +/* Contact Section */ +.contact-section { + padding: var(--space-4xl) 0; + background: var(--color-white); +} + +.contact-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 4rem; + margin-bottom: 3rem; +} + +.contact-form-wrapper h3 { + color: var(--primary-blue); + margin-bottom: 2rem; +} + +.contact-form { + display: grid; + gap: 1.5rem; +} + +.form-group { + display: flex; + flex-direction: column; +} + +.form-group label { + margin-bottom: 0.5rem; + font-weight: 500; + color: var(--text-dark); +} + +.form-group input, +.form-group textarea { + background: var(--color-white); + border: 1px solid var(--color-gray-200); + color: var(--color-gray-800); + padding: 0.75rem; + border-radius: var(--radius-md); + transition: all 0.3s ease; + font-family: inherit; +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: var(--primary-blue); + box-shadow: 0 0 0 3px rgba(15, 114, 181, 0.1); +} + +.checkbox-label { + display: flex; + align-items: flex-start; + gap: 0.5rem; + cursor: pointer; +} + +.checkbox-label input[type="checkbox"] { + margin-top: 0.25rem; +} + +.checkbox-label a { + color: var(--primary-blue); + text-decoration: underline; +} + +.contact-info h3 { + color: var(--primary-blue); + margin-bottom: 2rem; +} + +.contact-items { + display: grid; + gap: 2rem; +} + +.contact-item { + display: flex; + gap: 1rem; + align-items: start; +} + +.contact-icon { + width: 40px; + height: 40px; + flex-shrink: 0; + color: var(--primary-blue); +} + +.contact-icon svg { + width: 100%; + height: 100%; +} + +.contact-details h4 { + color: var(--primary-blue); + margin-bottom: 0.5rem; +} + +.contact-details a { + color: var(--text-dark); + text-decoration: none; + transition: color 0.3s ease; +} + +.contact-details a:hover { + color: var(--primary-blue); +} + +.secure-note { + font-size: 0.9rem; + color: var(--text-gray); + margin-top: 0.5rem; +} + +.security-notice { + background: var(--light-gray); + border-left: 4px solid var(--primary-blue); + border-radius: 4px; + padding: 1.5rem; + display: flex; + gap: 1rem; + align-items: center; + margin-top: 2rem; +} + +.notice-icon { + width: 50px; + height: 50px; + flex-shrink: 0; + color: var(--primary-blue); +} + +.notice-icon svg { + width: 100%; + height: 100%; +} + +.notice-text h4 { + color: var(--primary-blue); + margin-bottom: 0.5rem; +} + +/* CTA Banner */ +.cta-banner { + background: linear-gradient(135deg, var(--primary-blue), var(--dark-blue)); + border-radius: 8px; + padding: 3rem; + text-align: center; + color: var(--white); +} + +.cta-title { + font-size: 2.5rem; + margin-bottom: 1rem; +} + +.cta-subtitle { + font-size: 1.2rem; + margin-bottom: 2rem; + opacity: 0.95; +} + +.cta-buttons { + display: flex; + gap: 1rem; + justify-content: center; + margin-bottom: 3rem; +} + +.cta-banner .primary-button, +.cta-banner .secondary-button { + background: var(--white); + color: var(--primary-blue); +} + +.cta-banner .secondary-button { + background: transparent; + border: 2px solid var(--white); + color: var(--white); +} + +.cta-banner .secondary-button:hover { + background: var(--white); + color: var(--primary-blue); +} + +.certifications { + display: flex; + justify-content: center; + gap: 3rem; +} + +.cert-card { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.cert-icon { + width: 50px; + height: 50px; + opacity: 0.8; +} + +.cert-icon svg { + width: 100%; + height: 100%; + color: var(--white); +} + +.cert-name { + font-size: 0.9rem; + opacity: 0.9; +} + +/* Legal Section */ +.legal-section { + padding: var(--space-4xl) 0; + background: var(--color-gray-100); +} + +.legal-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3rem; +} + +.legal-card { + background: var(--color-white); + border-radius: var(--radius-lg); + padding: var(--space-lg); + box-shadow: var(--shadow); +} + +.legal-card h3 { + color: var(--primary-blue); + margin-bottom: 1.5rem; +} + +.legal-content { + line-height: 1.8; + color: var(--text-dark); +} + +.legal-content h4 { + color: var(--primary-blue); + margin: 1.5rem 0 0.5rem; +} + +.legal-content p { + margin-bottom: 1rem; +} + +/* Footer */ +.footer { + background: var(--color-navy-dark); + color: var(--color-white); + padding: 3rem 0 1rem; +} + +.footer-content { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 3rem; + margin-bottom: 2rem; +} + +.footer-section h4 { + color: var(--white); + margin-bottom: 1rem; +} + +.footer-section ul { + list-style: none; + padding: 0; +} + +.footer-section li { + margin-bottom: 0.5rem; +} + +.footer-section a { + color: rgba(255, 255, 255, 0.8); + text-decoration: none; + transition: color 0.3s ease; +} + +.footer-section a:hover { + color: var(--color-gold); +} + +.copyright { + text-align: center; + padding-top: 2rem; + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.6); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .nav-menu { + display: none; + } + + .hero-title .main-title { + font-size: 2rem; + } + + .about-tabs { + flex-wrap: wrap; + } + + .panel-grid, + .values-grid, + .why-grid, + .contact-grid, + .legal-grid, + .footer-content { + grid-template-columns: 1fr; + } + + .tools-grid { + grid-template-columns: 1fr; + } + + .cta-buttons, + .hero-cta { + flex-direction: column; + } +} + +/* Animations */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideIn { + from { transform: translateY(30px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +/* Germany Map Styles */ +.germany-map-container { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; +} + +.map-wrapper { + position: relative; + width: 100%; + max-width: 400px; + margin: 0 auto; +} + +.germany-base-map { + width: 100%; + height: auto; + display: block; + filter: brightness(0.95) contrast(1.05); + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.map-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; +} + +.map-overlay .langenfeld-pin, +.map-overlay .nrw-highlight { + pointer-events: all; +} + +.nrw-highlight { + animation: dashAnimation 20s linear infinite; + transition: all 0.3s ease; +} + +@keyframes dashAnimation { + to { + stroke-dashoffset: -30; + } +} + +.nrw-highlight:hover { + stroke-width: 4; + stroke: #ff0000; +} + +.langenfeld-pin { + cursor: pointer; + transition: all 0.3s ease; +} + +.langenfeld-pin:hover { + transform: translate(210px, 420px) scale(1.15); +} + +.langenfeld-pin .pulse-ring { + animation: pulse 2s infinite; + transform-origin: center; +} + +@keyframes pulse { + 0% { + transform: scale(1); + opacity: 0.8; + } + 50% { + transform: scale(2); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 0.8; + } +} + +.city-markers text { + font-family: 'Inter', sans-serif; + font-size: 10px; + fill: #6c757d; + pointer-events: none; +} + +.map-legend { + margin-top: 20px; + display: flex; + gap: 20px; + flex-wrap: wrap; + justify-content: center; +} + +.legend-item { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.875rem; + color: #495057; +} + +.legend-box { + width: 20px; + height: 12px; + border: 2px solid #dc3545; + background: rgba(220, 53, 69, 0.1); + border-radius: 2px; +} + +.legend-pin { + width: 12px; + height: 12px; + background: #dc3545; + border-radius: 50%; + position: relative; +} + +.legend-pin::after { + content: ''; + position: absolute; + bottom: -6px; + left: 50%; + transform: translateX(-50%); + width: 2px; + height: 6px; + background: #dc3545; +} + +/* Panel visual adjustments for map */ +.panel-visual { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .germany-map-container { + padding: 10px; + } + + .germany-map { + max-width: 280px; + } + + .map-legend { + flex-direction: column; + align-items: center; + gap: 10px; + } +} + +/* Scroll Indicator */ +.scroll-indicator { + position: absolute; + bottom: 60px; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + cursor: pointer; + z-index: 30; + transition: opacity 0.3s ease; +} + +.scroll-indicator:hover { + opacity: 0.8; +} + +.scroll-text { + color: #ffffff; + font-size: 0.9rem; + font-weight: 600; + letter-spacing: 1px; + text-transform: uppercase; + text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); + background: rgba(0, 0, 0, 0.3); + padding: 8px 16px; + border-radius: 20px; + backdrop-filter: blur(5px); +} + +.scroll-arrow { + width: 32px; + height: 32px; + color: #0A1832; + animation: bounceArrow 2s infinite; + background: rgba(10, 24, 50, 0.15); + border-radius: 50%; + padding: 4px; + box-shadow: 0 4px 12px rgba(10, 24, 50, 0.3); +} + +.scroll-arrow svg { + width: 100%; + height: 100%; + filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.5)); +} + +@keyframes bounceArrow { + 0%, 20%, 50%, 80%, 100% { + transform: translateY(0); + } + 40% { + transform: translateY(8px); + } + 60% { + transform: translateY(4px); + } +} + +/* Hide scroll indicator when scrolled */ +.hero.scrolled .scroll-indicator { + opacity: 0; + pointer-events: none; +} + +/* Utility Classes */ +.text-center { text-align: center; } +.mt-1 { margin-top: 0.5rem; } +.mt-2 { margin-top: 1rem; } +.mt-3 { margin-top: 1.5rem; } +.mt-4 { margin-top: 2rem; } +.mb-1 { margin-bottom: 0.5rem; } +.mb-2 { margin-bottom: 1rem; } +.mb-3 { margin-bottom: 1.5rem; } +.mb-4 { margin-bottom: 2rem; } +/* Hide mobile menu button on desktop */ +.mobile-menu-toggle { + display: none; +} + +/* Ensure mobile menu is hidden by default */ +.nav-menu-mobile, +.mobile-menu-overlay { + display: none; +} + +/* Show only on mobile screens */ +@media screen and (max-width: 768px) { + .mobile-menu-toggle { + display: block; + } + + .nav-menu-mobile { + display: block; + } +} diff --git a/css/mobile.css b/css/mobile.css new file mode 100644 index 0000000..f193131 --- /dev/null +++ b/css/mobile.css @@ -0,0 +1,539 @@ +/* Mobile Responsive Styles - AegisSight */ +/* Mobile-First Approach with Progressive Enhancement */ + +/* Base Mobile Styles (320px and up) */ +@media screen and (max-width: 480px) { + /* Typography Scaling */ + html { + font-size: 14px; + } + + body { + overflow-x: hidden; + } + + /* Hero Section Mobile */ + .hero { + min-height: 100vh; + padding: 1rem; + } + + .hero-title .main-title { + font-size: 2rem; + line-height: 1.2; + word-break: break-word; + } + + .hero-text { + font-size: 1rem; + padding: 0 1rem; + } + + /* Keep videos on mobile but optimize */ + .hero-video-container { + display: block; + } + + .hero-video { + object-fit: cover; + } + + /* Navigation Mobile */ + .navbar { + padding: 0.5rem 1rem; + } + + .nav-container { + justify-content: space-between; + } + + .logo-img { + max-width: 120px; + height: auto; + } + + /* Hide desktop menu */ + .nav-menu { + display: none; + } + + /* Mobile Menu Styles */ + .mobile-menu-toggle { + display: block; + background: none; + border: none; + cursor: pointer; + padding: 0.5rem; + z-index: 1001; + } + + .hamburger { + display: flex; + flex-direction: column; + gap: 4px; + } + + .hamburger span { + display: block; + width: 25px; + height: 3px; + background: var(--color-navy, #0A1832); + transition: all 0.3s ease; + } + + /* Hamburger Animation */ + .mobile-menu-toggle.active .hamburger span:nth-child(1) { + transform: rotate(45deg) translate(5px, 5px); + } + + .mobile-menu-toggle.active .hamburger span:nth-child(2) { + opacity: 0; + } + + .mobile-menu-toggle.active .hamburger span:nth-child(3) { + transform: rotate(-45deg) translate(7px, -6px); + } + + /* Mobile Navigation Menu */ + .nav-menu-mobile { + position: fixed; + top: 0; + right: -100%; + width: 80%; + max-width: 300px; + height: 100vh; + background: rgba(10, 24, 50, 0.98); + backdrop-filter: blur(10px); + transition: right 0.3s ease; + z-index: 1000; + padding: 4rem 2rem 2rem; + overflow-y: auto; + } + + /* Close button inside mobile menu */ + .mobile-menu-close { + position: absolute; + top: 1rem; + right: 1rem; + background: none; + border: none; + cursor: pointer; + padding: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 50%; + transition: background 0.3s ease; + } + + .mobile-menu-close:hover { + background: rgba(200, 168, 81, 0.15); + } + + .mobile-menu-close svg { + width: 24px; + height: 24px; + stroke: var(--color-white, #fff); + stroke-width: 2; + } + + .nav-menu-mobile.active { + right: 0; + } + + .nav-menu-mobile ul { + list-style: none; + padding: 0; + margin: 0; + } + + .nav-menu-mobile li { + margin-bottom: 1.5rem; + } + + .nav-menu-mobile a { + color: #fff; + text-decoration: none; + font-size: 1.2rem; + display: block; + padding: 0.5rem 0; + transition: color 0.3s ease; + } + + .nav-menu-mobile a:hover { + color: var(--color-gold, #C8A851); + } + + /* Mobile Overlay */ + .mobile-menu-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + z-index: 999; + } + + .mobile-menu-overlay.active { + opacity: 1; + visibility: visible; + } + + /* Sections Mobile */ + .section-title { + font-size: 1.75rem; + } + + .section-subtitle { + font-size: 1rem; + } + + /* Cards and Grids Mobile */ + .tool-grid { + grid-template-columns: 1fr; + gap: 1rem; + padding: 1rem; + } + + .tool-card { + padding: 1.5rem; + } + + /* Buttons Mobile - Touch Friendly */ + button, + .btn, + .cta-button, + .lang-toggle { + min-height: 44px; + min-width: 44px; + padding: 0.75rem 1.5rem; + } + + /* About Tabs Mobile */ + .about-tabs { + flex-direction: column; + gap: 0.5rem; + } + + .about-tab { + width: 100%; + padding: 0.75rem; + font-size: 0.9rem; + } + + /* Products Section Mobile */ + .products-grid { + grid-template-columns: 1fr; + padding: 1rem; + gap: 1.5rem; + } + + .product-card { + padding: 1.5rem; + } + + /* Product header mobile - override grid to stack icon above text */ + .product-header { + display: flex !important; + flex-direction: column !important; + grid-template-columns: none !important; + padding: 1.5rem !important; + text-align: center; + align-items: center; + gap: 1rem; + } + + .product-icon-wrapper { + margin-bottom: 0.5rem; + } + + .product-title-wrapper { + display: flex; + flex-direction: column; + gap: 0.25rem; + align-items: center; + width: 100%; + text-align: center !important; + } + + .product-title { + font-size: 1.1rem; + line-height: 1.2; + margin: 0; + text-align: center !important; + width: 100%; + } + + .product-tagline { + font-size: 0.75rem; + margin: 0; + line-height: 1.2; + text-align: center !important; + width: 100%; + white-space: normal; + } + + /* Specific fix for AccountForger tagline */ + .product-card:nth-child(2) .product-tagline { + display: block; + margin: 0 auto; + white-space: nowrap; /* Verhindert Umbruch innerhalb der Zeilen */ + } + + /* Force line break only after "mit" */ + .product-card:nth-child(2) .product-tagline::before { + content: "Zugang nur mit\A Berechtigung"; + white-space: pre-line; + } + + /* Hide original text */ + .product-card:nth-child(2) .product-tagline { + font-size: 0; + } + + .product-card:nth-child(2) .product-tagline::before { + font-size: 0.75rem; + } + + /* Footer Mobile */ + .footer-content { + flex-direction: column; + text-align: center; + gap: 2rem; + } + + .footer-links { + flex-direction: column; + gap: 1rem; + } + + /* About Section - Company Cards Mobile Fix */ + .company-cards-wrapper { + width: 100% !important; + flex-direction: column; + gap: 1.5rem; + } + + .company-card { + width: 100%; + padding: 1.5rem !important; + } + + .company-card h4 { + font-size: 1.1rem; + } + + .company-card p { + font-size: 0.9rem; + line-height: 1.6; + } + + /* About panel layout mobile */ + .about-panel { + padding: 1rem; + } + + .panel-text { + max-width: 100%; + display: flex; + flex-direction: column; + } + + /* Location section mobile - move to bottom */ + .location-section { + width: 100% !important; + margin-left: 0 !important; + margin-top: 2rem; + order: 2; /* Move to bottom */ + height: auto !important; + padding: 1.5rem; + background: rgba(10, 15, 28, 0.05); + border-radius: 20px; + } + + .company-cards-wrapper { + order: 1; /* Keep at top */ + } + + .mini-germany-map { + max-width: 150px; + margin-bottom: 1rem; + } + + .mini-germany-map img { + width: 100%; + height: auto; + } + + .location-badge { + font-size: 0.9rem; + padding: 0.5rem 1rem; + } +} + +/* Tablet Styles (481px - 768px) */ +@media screen and (min-width: 481px) and (max-width: 768px) { + /* Typography */ + html { + font-size: 15px; + } + + + .hero-title .main-title { + font-size: 2.5rem; + } + + /* Videos already visible from mobile */ + + /* Tool Grid - 2 columns */ + .tool-grid { + grid-template-columns: repeat(2, 1fr); + } + + /* About Tabs - Horizontal with wrap */ + .about-tabs { + flex-direction: row; + flex-wrap: wrap; + } + + .about-tab { + flex: 1 1 calc(50% - 0.5rem); + } +} + +/* Small Desktop (769px - 1024px) */ +@media screen and (min-width: 769px) and (max-width: 1024px) { + /* Container widths */ + .container { + max-width: 960px; + padding: 0 2rem; + } + + /* Navigation adjustments */ + .nav-menu { + gap: 1.5rem; + } + + .nav-menu a { + font-size: 0.9rem; + } + + /* Grid adjustments */ + .tool-grid { + grid-template-columns: repeat(3, 1fr); + } + + .products-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +/* Touch Device Optimizations */ +@media (hover: none) and (pointer: coarse) { + /* Remove hover effects on touch devices */ + .tool-card:hover, + .product-card:hover { + transform: none; + } + + /* Larger touch targets */ + a, button { + min-height: 44px; + display: inline-flex; + align-items: center; + } +} + +/* Landscape Mobile Optimization */ +@media screen and (max-width: 768px) and (orientation: landscape) { + .hero { + min-height: auto; + padding: 2rem 1rem; + } + + .hero-title .main-title { + font-size: 1.75rem; + } +} + +/* High Resolution Mobile Displays */ +@media screen and (max-width: 480px) and (-webkit-min-device-pixel-ratio: 2) { + /* Sharper borders and shadows */ + .tool-card, + .product-card { + border: 0.5px solid rgba(255, 255, 255, 0.1); + } +} + +/* Performance Optimizations for Mobile */ +@media screen and (max-width: 768px) { + /* Disable complex animations on mobile */ + .animate-in, + .stagger-1, + .stagger-2, + .stagger-3 { + animation: none !important; + opacity: 1 !important; + transform: none !important; + } + + /* Reduce particle effects */ + #particleCanvas { + display: none; + } + + /* Optimize images */ + img { + image-rendering: -webkit-optimize-contrast; + } +} + +/* Tablet and Small Desktop (768px - 1024px) */ +@media screen and (min-width: 768px) and (max-width: 1024px) { + /* Navigation adjustments for tablets */ + .nav-container { + padding: 1rem; + } + + /* Keep centered logo but adjust size */ + .logo-img { + height: 50px; + } + + /* Adjust menu spacing */ + .nav-menu { + gap: 1.5rem; + } + + .nav-menu a { + font-size: 1rem; + } + + /* Language toggle smaller on tablets */ + .lang-toggle { + padding: 0.4rem 0.8rem; + font-size: 0.85rem; + } +} + + +/* Print Styles */ +@media print { + .navbar, + .hero-video-container, + .scroll-indicator, + .mobile-menu-toggle { + display: none !important; + } + + body { + background: white; + color: black; + } +} \ No newline at end of file diff --git a/css/products-modern.css b/css/products-modern.css new file mode 100644 index 0000000..bb2e062 --- /dev/null +++ b/css/products-modern.css @@ -0,0 +1,502 @@ +/* Modern Products Section Design */ + +.products-section { + background: var(--color-navy); + position: relative; + padding: var(--space-4xl) 0; + overflow: hidden; +} + +/* Animated Background */ +.products-section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: + radial-gradient(circle at 20% 50%, rgba(19, 40, 68, 0.3) 0%, transparent 50%), + radial-gradient(circle at 80% 50%, rgba(19, 40, 68, 0.2) 0%, transparent 50%), + radial-gradient(circle at 50% 100%, rgba(200, 168, 81, 0.05) 0%, transparent 50%); + animation: backgroundShift 20s ease-in-out infinite; +} + +@keyframes backgroundShift { + 0%, 100% { transform: translate(0, 0) scale(1); } + 33% { transform: translate(-20px, -20px) scale(1.1); } + 66% { transform: translate(20px, -10px) scale(0.95); } +} + +/* Section Title */ +.products-section .section-title { + color: #ffffff; + font-size: 3.5rem; + text-transform: uppercase; + letter-spacing: 3px; + margin-bottom: 20px; + position: relative; + display: inline-block; + animation: titleGlow 3s ease-in-out infinite; +} + +@keyframes titleGlow { + 0%, 100% { text-shadow: 0 0 15px rgba(200, 168, 81, 0.3); } + 50% { text-shadow: 0 0 25px rgba(200, 168, 81, 0.5), 0 0 40px rgba(200, 168, 81, 0.2); } +} + +.products-section .section-subtitle { + color: rgba(255, 255, 255, 0.7); + font-size: 1.3rem; + margin-bottom: 80px; +} + +/* Products Container */ +.products-container { + max-width: 1400px; + margin: 0 auto; + padding: 0 20px; + position: relative; + z-index: 2; +} + +/* Products Grid */ +.products-grid { + display: flex; + flex-direction: column; + align-items: center; + gap: 30px; + margin-bottom: 60px; +} + +/* Product Card */ +.product-card { + background: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(245, 245, 245, 0.95)); + border: 1px solid rgba(200, 168, 81, 0.3); + border-radius: var(--radius-lg); + padding: 0; + position: relative; + overflow: visible; + transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + width: 90%; + max-width: 900px; + display: flex; + flex-direction: column; + backdrop-filter: blur(10px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); +} + +.product-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, transparent 0%, rgba(15, 114, 181, 0.05) 100%); + opacity: 0; + transition: opacity 0.5s; + border-radius: 20px; +} + +.product-card:hover::before { + opacity: 1; +} + +.product-card:hover { + transform: translateY(-6px); + background: linear-gradient(135deg, rgba(255, 255, 255, 1), rgba(250, 250, 250, 1)); + border-color: var(--color-gold); + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.3), + 0 0 40px rgba(200, 168, 81, 0.1); +} + +/* Product Header */ +.product-header { + padding: 40px 40px 30px; + background: linear-gradient(135deg, rgba(10, 24, 50, 0.1) 0%, transparent 50%); + border-bottom: 1px solid rgba(200, 168, 81, 0.2); + display: grid; + grid-template-columns: 80px 1fr; + align-items: center; + gap: 25px; +} + +.product-icon-wrapper { + width: 80px; + height: 80px; + position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.product-icon-bg { + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(135deg, var(--color-navy), var(--color-gold)); + border-radius: var(--radius-lg); + opacity: 0.1; + transition: all 0.5s; +} + +.product-card:hover .product-icon-bg { + opacity: 0.25; + transform: rotate(5deg) scale(1.05); + background: linear-gradient(135deg, var(--color-navy), var(--color-gold-dark)); +} + +.product-icon { + position: relative; + z-index: 1; + width: 50px; + height: 50px; + filter: brightness(0) saturate(100%) invert(8%) sepia(13%) saturate(4290%) hue-rotate(189deg) brightness(95%) contrast(97%); + transition: all 0.5s; +} + +.product-card:hover .product-icon { + transform: scale(1.1); + filter: brightness(0) saturate(100%) invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%); +} + +.product-title-wrapper { + display: flex; + flex-direction: column; + gap: 5px; + align-items: center; + text-align: center; + padding-right: 105px; +} + +.product-title { + color: #0A1832; + font-size: 1.8rem; + font-weight: 700; + margin: 0; + transition: all 0.3s; +} + +/* Specific styling for product titles */ +.product-title[data-translate="productAccountForgerTitle"], +.product-title[data-translate="productOsintMonitorTitle"] { + color: #0A1832; +} + +.product-card:hover .product-title { + color: var(--color-navy); +} + +.product-tagline { + color: rgba(0, 0, 0, 0.6); + font-size: 0.9rem; + text-transform: uppercase; + letter-spacing: 2px; + font-weight: 500; + margin: 0; +} + +/* Product Body */ +.product-body { + padding: 30px 40px; + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.product-description { + color: rgba(0, 0, 0, 0.7); + line-height: 1.8; + margin-bottom: 30px; + flex-grow: 1; +} + +/* Product Features */ +.product-features { + list-style: none; + padding: 0; + margin: 0 0 30px 0; +} + +.product-features li { + color: rgba(255, 255, 255, 0.7); + padding: 8px 0; + padding-left: 30px; + position: relative; + transition: all 0.3s; +} + +.product-features li::before { + content: '▸'; + position: absolute; + left: 0; + color: var(--color-gold); + font-size: 1.2rem; + transition: all 0.3s; +} + +.product-card:hover .product-features li { + color: rgba(255, 255, 255, 0.9); + transform: translateX(5px); +} + +.product-card:hover .product-features li::before { + color: var(--color-gold-light); + transform: translateX(3px); +} + +/* Product Footer */ +.product-footer { + padding: 30px 40px; + background: rgba(10, 24, 50, 0.05); + border-top: 1px solid rgba(200, 168, 81, 0.2); + display: flex; + justify-content: space-between; + align-items: center; +} + +.product-status { + display: flex; + align-items: center; + gap: 10px; +} + +.status-dot { + width: 8px; + height: 8px; + background: #00ff00; + border-radius: 50%; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } +} + +.status-text { + color: rgba(255, 255, 255, 0.6); + font-size: 0.9rem; +} + +/* Learn More Button */ +.product-learn-more { + background: var(--color-gold); + border: 2px solid var(--color-gold); + color: var(--color-navy); + padding: 10px 25px; + border-radius: 50px; + cursor: pointer; + transition: all 0.3s; + font-size: 0.95rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + position: relative; + overflow: hidden; +} + +.product-learn-more::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + background: var(--color-navy); + transform: translate(-50%, -50%); + transition: all 0.5s; + border-radius: 50px; +} + +.product-learn-more:hover::before { + width: 100%; + height: 100%; +} + +.product-learn-more:hover { + color: var(--color-gold); + border-color: var(--color-gold); + transform: translateY(-2px); + box-shadow: 0 10px 30px rgba(200, 168, 81, 0.3); +} + +.product-learn-more span { + position: relative; + z-index: 1; +} + +/* Tools Grid Styling - Inside Product Body */ +.product-body .tools-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 15px; + margin-top: 20px; + max-height: 0; + overflow: hidden; + opacity: 0; + transition: max-height 0.6s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.4s ease-in-out; + visibility: hidden; +} + +.product-body .tools-grid.expanded { + max-height: 2000px; + opacity: 1; + visibility: visible; +} + +/* Responsive grid adjustment */ +@media (max-width: 768px) { + .product-body .tools-grid { + grid-template-columns: 1fr; + } +} + +@media (min-width: 769px) and (max-width: 1024px) { + .product-body .tools-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +.tools-grid .tool-card { + background: rgba(0, 0, 0, 0.03); + border-radius: 12px; + padding: 20px; + transition: all 0.3s; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.tools-grid .tool-card:hover { + transform: translateY(-3px); + background: rgba(0, 0, 0, 0.05); + border-color: rgba(200, 168, 81, 0.3); + box-shadow: 0 5px 15px rgba(10, 24, 50, 0.1); +} + +.tools-grid .tool-icon { + width: 60px; + height: 60px; + margin-bottom: 15px; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, rgba(15, 114, 181, 0.15), rgba(0, 64, 110, 0.1)); + border-radius: 15px; +} + +.tools-grid .tool-icon img { + width: 35px; + height: 35px; + filter: brightness(0) saturate(100%) invert(52%) sepia(82%) saturate(723%) hue-rotate(178deg) brightness(108%) contrast(92%); +} + +.tools-grid h4 { + color: var(--color-navy); + font-size: 1.2rem; + margin-bottom: 15px; + font-weight: 600; +} + +.tools-grid .tool-features ul { + list-style: none; + padding: 0; +} + +.tools-grid .tool-features li { + color: rgba(0, 0, 0, 0.7); + padding: 5px 0; + font-size: 0.9rem; + position: relative; + padding-left: 20px; + line-height: 1.5; +} + +.tools-grid .tool-features li::before { + content: '▸'; + position: absolute; + left: 0; + color: var(--color-gold); + font-weight: bold; +} + +/* Responsive Design */ +@media (max-width: 968px) { + .products-grid { + grid-template-columns: 1fr; + } + + .product-card.featured { + grid-column: span 1; + } + + .products-section .section-title { + font-size: 2.5rem; + } +} + +/* Floating Tech Particles */ +.tech-particles { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + overflow: hidden; + pointer-events: none; +} + +.particle { + position: absolute; + background: rgba(200, 168, 81, 0.3); + border-radius: 50%; + pointer-events: none; +} + +.particle:nth-child(1) { + width: 3px; + height: 3px; + top: 10%; + left: 20%; + animation: float1 15s infinite; +} + +.particle:nth-child(2) { + width: 2px; + height: 2px; + top: 70%; + left: 80%; + animation: float2 20s infinite; +} + +.particle:nth-child(3) { + width: 4px; + height: 4px; + top: 40%; + left: 60%; + animation: float3 18s infinite; +} + +@keyframes float1 { + 0%, 100% { transform: translate(0, 0); opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { transform: translate(100px, -100px); opacity: 0; } +} + +@keyframes float2 { + 0%, 100% { transform: translate(0, 0); opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { transform: translate(-100px, -150px); opacity: 0; } +} + +@keyframes float3 { + 0%, 100% { transform: translate(0, 0); opacity: 0; } + 10% { opacity: 1; } + 90% { opacity: 1; } + 100% { transform: translate(50px, -120px); opacity: 0; } +} \ No newline at end of file diff --git a/css/section-transitions.css b/css/section-transitions.css new file mode 100644 index 0000000..d680490 --- /dev/null +++ b/css/section-transitions.css @@ -0,0 +1,437 @@ +/* Modern Section Transitions & Dividers */ + +/* Simple fade transition between sections */ +.section-fade { + position: relative; + opacity: 0; + transform: translateY(30px); + transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); +} + +.section-fade.visible { + opacity: 1; + transform: translateY(0); +} + +/* Subtle gradient overlay at section edges */ +.section-gradient-top { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100px; + background: linear-gradient(to bottom, rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%); + pointer-events: none; + z-index: 1; +} + +.section-gradient-bottom { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 100px; + background: linear-gradient(to top, rgba(244,244,244,1) 0%, rgba(244,244,244,0) 100%); + pointer-events: none; + z-index: 1; +} + +/* Animated Gradient Divider */ +.gradient-divider { + position: relative; + height: 200px; + margin: -100px 0; + background: linear-gradient( + 135deg, + transparent 0%, + rgba(15, 114, 181, 0.03) 25%, + rgba(15, 114, 181, 0.08) 50%, + rgba(15, 114, 181, 0.03) 75%, + transparent 100% + ); + z-index: 5; + overflow: hidden; +} + +.gradient-divider::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 200%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(15, 114, 181, 0.2), + transparent + ); + animation: shimmerDivider 8s infinite; +} + +@keyframes shimmerDivider { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +/* Diagonal Section Transitions */ +.diagonal-section { + position: relative; + padding: 120px 0 80px; + margin-top: -50px; + clip-path: polygon(0 50px, 100% 0, 100% 100%, 0 100%); +} + +.diagonal-section-reverse { + position: relative; + padding: 120px 0 80px; + margin-top: -50px; + clip-path: polygon(0 0, 100% 50px, 100% 100%, 0 100%); +} + +/* Blob Divider */ +.blob-divider { + position: absolute; + bottom: -150px; + left: 0; + width: 100%; + height: 300px; + z-index: 5; + pointer-events: none; +} + +.blob-shape { + position: absolute; + width: 100%; + height: 100%; + background: linear-gradient(135deg, var(--color-blue), var(--color-blue-hover)); + opacity: 0.1; + border-radius: 40% 60% 60% 40% / 60% 30% 70% 40%; + animation: morphBlob 20s ease-in-out infinite; +} + +@keyframes morphBlob { + 0%, 100% { + border-radius: 40% 60% 60% 40% / 60% 30% 70% 40%; + transform: translate(0, 0) scale(1); + } + 33% { + border-radius: 60% 40% 30% 70% / 60% 70% 30% 40%; + transform: translate(-30px, -20px) scale(1.1); + } + 66% { + border-radius: 30% 70% 70% 30% / 30% 60% 40% 70%; + transform: translate(30px, 20px) scale(0.9); + } +} + +/* Particle Bridge */ +.particle-bridge { + position: absolute; + width: 100%; + height: 200px; + bottom: -100px; + left: 0; + z-index: 8; + overflow: hidden; +} + +.particle { + position: absolute; + width: 4px; + height: 4px; + background: var(--color-blue); + border-radius: 50%; + opacity: 0.6; +} + +@keyframes floatParticle { + 0% { + transform: translateY(100px) translateX(0); + opacity: 0; + } + 10% { + opacity: 0.6; + } + 90% { + opacity: 0.6; + } + 100% { + transform: translateY(-100px) translateX(100px); + opacity: 0; + } +} + +/* Curved Section */ +.curved-section { + position: relative; + padding-top: 100px; + margin-top: -80px; +} + +.curved-section::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 80px; + background: inherit; + border-radius: 0 0 50% 50% / 0 0 100% 100%; + transform: scaleX(1.5); +} + +/* Glass Transition */ +.glass-transition { + position: relative; + margin: 50px 0; + padding: 40px 0; + background: linear-gradient( + 135deg, + rgba(255, 255, 255, 0.1) 0%, + rgba(255, 255, 255, 0.05) 100% + ); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border-top: 1px solid rgba(15, 114, 181, 0.1); + border-bottom: 1px solid rgba(15, 114, 181, 0.1); +} + +/* Zigzag Border */ +.zigzag-top { + position: relative; + padding-top: 40px; +} + +.zigzag-top::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 30px; + background: linear-gradient( + 135deg, + transparent 33.33%, + var(--color-blue) 33.33%, + var(--color-blue) 66.66%, + transparent 66.66% + ); + background-size: 30px 60px; + opacity: 0.1; +} + +/* Flowing Lines */ +.flow-lines { + position: absolute; + width: 100%; + height: 200px; + bottom: -100px; + left: 0; + overflow: hidden; + z-index: 5; +} + +.flow-line { + position: absolute; + height: 2px; + background: linear-gradient(90deg, transparent, var(--color-blue), transparent); + animation: flowLine 6s infinite; +} + +.flow-line:nth-child(1) { + top: 20%; + animation-delay: 0s; + width: 60%; +} + +.flow-line:nth-child(2) { + top: 40%; + animation-delay: 1s; + width: 80%; +} + +.flow-line:nth-child(3) { + top: 60%; + animation-delay: 2s; + width: 70%; +} + +.flow-line:nth-child(4) { + top: 80%; + animation-delay: 3s; + width: 90%; +} + +@keyframes flowLine { + 0% { + transform: translateX(-100%); + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + transform: translateX(200%); + opacity: 0; + } +} + +/* Reveal Sections with Mask */ +.section-reveal { + position: relative; + overflow: hidden; +} + +.section-reveal::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient( + 90deg, + transparent, + rgba(255, 255, 255, 0.4), + transparent + ); + animation: revealMask 2s ease-out forwards; +} + +@keyframes revealMask { + to { + left: 100%; + } +} + +/* Geometric Pattern Divider */ +.geometric-divider { + position: relative; + height: 100px; + margin: 40px 0; + background-image: + repeating-linear-gradient( + 45deg, + transparent, + transparent 10px, + rgba(15, 114, 181, 0.05) 10px, + rgba(15, 114, 181, 0.05) 20px + ), + repeating-linear-gradient( + -45deg, + transparent, + transparent 10px, + rgba(15, 114, 181, 0.05) 10px, + rgba(15, 114, 181, 0.05) 20px + ); +} + +/* Animated Border */ +.animated-border { + position: relative; + padding: var(--space-4xl) 0; +} + +.animated-border::before, +.animated-border::after { + content: ''; + position: absolute; + left: 0; + width: 100%; + height: 2px; + background: linear-gradient( + 90deg, + transparent, + var(--color-blue) 20%, + var(--color-blue) 80%, + transparent + ); +} + +.animated-border::before { + top: 0; + animation: borderSlide 4s infinite; +} + +.animated-border::after { + bottom: 0; + animation: borderSlide 4s infinite reverse; +} + +@keyframes borderSlide { + 0%, 100% { + transform: translateX(-100%); + } + 50% { + transform: translateX(100%); + } +} + +/* Section Fade Transitions */ +.fade-section { + opacity: 0; + transform: translateY(50px); + transition: all 1s cubic-bezier(0.4, 0, 0.2, 1); +} + +.fade-section.visible { + opacity: 1; + transform: translateY(0); +} + +/* Parallax Background Sections */ +.parallax-section { + position: relative; + min-height: 500px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} + +.parallax-bg { + position: absolute; + top: -20%; + left: 0; + width: 100%; + height: 120%; + background-image: linear-gradient(135deg, rgba(15, 114, 181, 0.05) 0%, transparent 100%); + background-attachment: fixed; + background-position: center; + background-repeat: no-repeat; + background-size: cover; + will-change: transform; +} + +/* Split Color Section */ +.split-section { + position: relative; + overflow: hidden; +} + +.split-section::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: linear-gradient( + 45deg, + transparent 48%, + rgba(15, 114, 181, 0.05) 49%, + rgba(15, 114, 181, 0.05) 51%, + transparent 52% + ); + animation: rotateSplit 20s linear infinite; +} + +@keyframes rotateSplit { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/datenschutz-en.html b/datenschutz-en.html new file mode 100644 index 0000000..68162ae --- /dev/null +++ b/datenschutz-en.html @@ -0,0 +1,320 @@ + + + + + + Privacy Policy - AegisSight + + + + + + + + + + + + + + + + + + +
+
+

Privacy Policy

+
+ +
+
+

1. Data Protection at a Glance

+ +

General Information

+

The following information provides a simple overview of what happens to your personal data when you visit our website. Personal data is any data that can personally identify you. Detailed information on the subject of data protection can be found in our privacy policy listed below this text.

+ +

Data Collection on Our Website

+

Who is responsible for data collection on this website?
+ Data processing on this website is carried out by the website operator. You can find their contact details in the legal notice of this website.

+ +

How do we collect your data?
+ Your data is collected, on the one hand, by you providing it to us. This could be data that you enter in a contact form, for example.

+ +

Other data is collected automatically by our IT systems when you visit the website. This is primarily technical data (e.g., internet browser, operating system, or time of page access). This data is collected automatically as soon as you enter our website.

+ +

What do we use your data for?
+ Part of the data is collected to ensure error-free provision of the website. Other data may be used to analyze your user behavior.

+ +

What rights do you have regarding your data?
+ You have the right to receive information about the origin, recipient, and purpose of your stored personal data free of charge at any time. You also have the right to request the correction, blocking, or deletion of this data. You can contact us at any time at the address given in the legal notice if you have further questions about data protection. Furthermore, you have the right to lodge a complaint with the competent supervisory authority.

+
+ +
+

2. General Information and Mandatory Information

+ +

Data Protection

+

The operators of these pages take the protection of your personal data very seriously. We treat your personal data confidentially and in accordance with the statutory data protection regulations and this privacy policy.

+ +

When you use this website, various personal data is collected. Personal data is data that can personally identify you. This privacy policy explains what data we collect and what we use it for. It also explains how and for what purpose this happens.

+ +

We would like to point out that data transmission over the Internet (e.g., when communicating by email) can have security gaps. Complete protection of data against access by third parties is not possible.

+ +

Note on the Responsible Party

+

The responsible party for data processing on this website is:

+ +
+

+ AegisSight UG (limited liability)
+ Hendrik Gebhardt
+ Monami Homma
+ Gladbacher Strasse 3-5
+ 40764 Langenfeld
+ Germany

+ Email: info@aegis-sight.de +

+
+ +

The responsible party is the natural or legal person who alone or jointly with others decides on the purposes and means of processing personal data (e.g., names, email addresses, etc.).

+ +

Revocation of Your Consent to Data Processing

+

Many data processing operations are only possible with your express consent. You can revoke consent you have already given at any time. An informal notification by email to us is sufficient. The legality of the data processing carried out until the revocation remains unaffected by the revocation.

+ +

Right to Lodge a Complaint with the Competent Supervisory Authority

+

In the event of violations of data protection law, the person affected has the right to lodge a complaint with the competent supervisory authority. The competent supervisory authority for data protection issues is the state data protection officer of the federal state in which our company is headquartered. A list of data protection officers and their contact details can be found at the following link: https://www.bfdi.bund.de.

+
+ +
+

3. Data Collection on Our Website

+ +

Server Log Files

+

The provider of the pages automatically collects and stores information in so-called server log files, which your browser automatically transmits to us. These are:

+ +
    +
  • Browser type and browser version
  • +
  • Operating system used
  • +
  • Referrer URL
  • +
  • Host name of the accessing computer
  • +
  • Time of the server request
  • +
  • IP address
  • +
+ +

This data is not merged with other data sources.

+ +

The basis for data processing is Art. 6 para. 1 lit. f GDPR, which permits the processing of data to fulfill a contract or pre-contractual measures.

+ +

SSL or TLS Encryption

+

This site uses SSL or TLS encryption for security reasons and to protect the transmission of confidential content, such as orders or inquiries that you send to us as the site operator. You can recognize an encrypted connection by the fact that the address line of the browser changes from "http://" to "https://" and by the lock symbol in your browser line.

+ +

If SSL or TLS encryption is activated, the data you transmit to us cannot be read by third parties.

+
+ +
+

4. Our Web Analytics

+

We use our own analytics system that stores data exclusively on our servers in Germany. No data is passed on to third parties. The analytics serves to improve our website and to analyze user behavior.

+ +

The following data is collected:

+
    +
  • Pages visited
  • +
  • Time of access
  • +
  • Anonymized IP address
  • +
  • Browser information
  • +
+ +

This data is not personal and cannot be used to identify individual users.

+
+ +
+

5. Your Rights

+

You have the following rights regarding your personal data:

+ +
    +
  • Right to information: You can request information about your stored data.
  • +
  • Right to correction: You can request the correction of incorrect data.
  • +
  • Right to deletion: You can request the deletion of your data.
  • +
  • Right to restriction of processing: You can request the restriction of data processing.
  • +
  • Right to data portability: You can request to receive your data in a structured format.
  • +
  • Right to object: You can object to the processing of your data.
  • +
+ +

To exercise these rights, please contact us at info@aegis-sight.de.

+
+ +
+

6. Changes to This Privacy Policy

+

We reserve the right to adapt this privacy policy so that it always complies with current legal requirements or to implement changes to our services in the privacy policy, e.g., when introducing new services. The new privacy policy will then apply to your next visit.

+
+ +
+

7. Automated Access and AI Agents

+ +

Prohibition of Automated Access

+

Automated querying, scraping, or crawling of this website by bots, spiders, scrapers, AI agents (including LLM-based systems), "buy-for-me" agents, or similar automated tools is prohibited without our express written permission.

+ +

This includes in particular:

+
    +
  • Automated data collection and extraction
  • +
  • Training of AI models using content from this website
  • +
  • Automated end-to-end processes without human review
  • +
  • Systematic reading of content by automated systems
  • +
+ +

Violations of this policy may be subject to civil and criminal prosecution. The instructions contained in our robots.txt file are binding and form part of these terms of use.

+ +

Exceptions

+

Excluded from this prohibition are search engine crawlers that comply with our robots.txt guidelines, as well as services to which we have expressly granted permission.

+
+
+
+ + +
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/datenschutz.html b/datenschutz.html new file mode 100644 index 0000000..9fad1e5 --- /dev/null +++ b/datenschutz.html @@ -0,0 +1,362 @@ + + + + + + Datenschutz - AegisSight + + + + + + + + + + + + + + + + + + + +
+
+

Datenschutzerklärung

+
+ +
+
+

1. Datenschutz auf einen Blick

+ +

Allgemeine Hinweise

+

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. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgeführten Datenschutzerklärung.

+ +

Datenerfassung auf dieser Website

+

Wer ist verantwortlich für die Datenerfassung auf dieser Website?

+

Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Abschnitt „Hinweis zur Verantwortlichen Stelle" in dieser Datenschutzerklärung entnehmen.

+ +

Wie erfassen wir Ihre Daten?

+

Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z. B. um Daten handeln, die Sie in ein Kontaktformular eingeben.

+

Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z. B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie diese Website betreten.

+ +

Wofür nutzen wir Ihre Daten?

+

Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden. Sofern über die Website Verträge geschlossen oder angebahnt werden können, werden die übermittelten Daten auch für Vertragsangebote, Bestellungen oder sonstige Auftragsanfragen verarbeitet.

+ +

Welche Rechte haben Sie bezüglich Ihrer Daten?

+

Sie haben jederzeit das Recht, unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung oder Löschung dieser Daten zu verlangen. Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben, können Sie diese Einwilligung jederzeit für die Zukunft widerrufen. Außerdem haben Sie das Recht, unter bestimmten Umständen die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu.

+

Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit an uns wenden.

+ +

Analyse-Tools und Tools von Drittanbietern

+

Beim Besuch dieser Website kann Ihr Surf-Verhalten statistisch ausgewertet werden. Das geschieht vor allem mit sogenannten Analyseprogrammen.

+

Detaillierte Informationen zu diesen Analyseprogrammen finden Sie in der folgenden Datenschutzerklärung.

+
+ +
+

2. Hosting

+

Wir hosten die Inhalte unserer Website bei folgendem Anbieter:

+ +

Hetzner

+

Anbieter ist die Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen (nachfolgend Hetzner).

+

Details entnehmen Sie der Datenschutzerklärung von Hetzner: https://www.hetzner.com/de/legal/privacy-policy/.

+

Die Verwendung von Hetzner erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Wir haben ein berechtigtes Interesse an einer möglichst zuverlässigen Darstellung unserer Website. Sofern eine entsprechende Einwilligung abgefragt wurde, erfolgt die Verarbeitung ausschließlich auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1 TDDDG, soweit die Einwilligung die Speicherung von Cookies oder den Zugriff auf Informationen im Endgerät des Nutzers (z. B. Device-Fingerprinting) im Sinne des TDDDG umfasst. Die Einwilligung ist jederzeit widerrufbar.

+
+ +
+

3. Allgemeine Hinweise und Pflichtinformationen

+ +

Datenschutz

+

Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.

+

Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Die vorliegende Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck das geschieht.

+

Wir weisen darauf hin, dass die Datenübertragung im Internet (z. B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich.

+ +

Hinweis zur verantwortlichen Stelle

+

Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist:

+

+ AegisSight UG (haftungsbeschränkt)
+ Gladbacher Strasse 3-5
+ 40764 Langenfeld

+ E-Mail: info@aegis-sight.de +

+

Verantwortliche Stelle ist die natürliche oder juristische Person, die allein oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z. B. Namen, E-Mail-Adressen o. Ä.) entscheidet.

+ +

Speicherdauer

+

Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer genannt wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck für die Datenverarbeitung entfällt. Wenn Sie ein berechtigtes Löschersuchen geltend machen oder eine Einwilligung zur Datenverarbeitung widerrufen, werden Ihre Daten gelöscht, sofern wir keine anderen rechtlich zulässigen Gründe für die Speicherung Ihrer personenbezogenen Daten haben (z. B. steuer- oder handelsrechtliche Aufbewahrungsfristen); im letztgenannten Fall erfolgt die Löschung nach Fortfall dieser Gründe.

+ +

Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung auf dieser Website

+

Sofern Sie in die Datenverarbeitung eingewilligt haben, verarbeiten wir Ihre personenbezogenen Daten auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO bzw. Art. 9 Abs. 2 lit. a DSGVO, sofern besondere Datenkategorien nach Art. 9 Abs. 1 DSGVO verarbeitet werden. Im Falle einer ausdrücklichen Einwilligung in die Übertragung personenbezogener Daten in Drittstaaten erfolgt die Datenverarbeitung außerdem auf Grundlage von Art. 49 Abs. 1 lit. a DSGVO. Sofern Sie in die Speicherung von Cookies oder in den Zugriff auf Informationen in Ihr Endgerät (z. B. via Device-Fingerprinting) eingewilligt haben, erfolgt die Datenverarbeitung zusätzlich auf Grundlage von § 25 Abs. 1 TDDDG. Die Einwilligung ist jederzeit widerrufbar. Sind Ihre Daten zur Vertragserfüllung oder zur Durchführung vorvertraglicher Maßnahmen erforderlich, verarbeiten wir Ihre Daten auf Grundlage des Art. 6 Abs. 1 lit. b DSGVO. Des Weiteren verarbeiten wir Ihre Daten, sofern diese zur Erfüllung einer rechtlichen Verpflichtung erforderlich sind auf Grundlage von Art. 6 Abs. 1 lit. c DSGVO. Die Datenverarbeitung kann ferner auf Grundlage unseres berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO erfolgen. Über die jeweils im Einzelfall einschlägigen Rechtsgrundlagen wird in den folgenden Absätzen dieser Datenschutzerklärung informiert.

+ +

Empfänger von personenbezogenen Daten

+

Im Rahmen unserer Geschäftstätigkeit arbeiten wir mit verschiedenen externen Stellen zusammen. Dabei ist teilweise auch eine Übermittlung von personenbezogenen Daten an diese externen Stellen erforderlich. Wir geben personenbezogene Daten nur dann an externe Stellen weiter, wenn dies im Rahmen einer Vertragserfüllung erforderlich ist, wenn wir gesetzlich hierzu verpflichtet sind (z. B. Weitergabe von Daten an Steuerbehörden), wenn wir ein berechtigtes Interesse nach Art. 6 Abs. 1 lit. f DSGVO an der Weitergabe haben oder wenn eine sonstige Rechtsgrundlage die Datenweitergabe erlaubt. Beim Einsatz von Auftragsverarbeitern geben wir personenbezogene Daten unserer Kunden nur auf Grundlage eines gültigen Vertrags über Auftragsverarbeitung weiter. Im Falle einer gemeinsamen Verarbeitung wird ein Vertrag über gemeinsame Verarbeitung geschlossen.

+ +

Widerruf Ihrer Einwilligung zur Datenverarbeitung

+

Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.

+ +

Widerspruchsrecht gegen die Datenerhebung in besonderen Fällen sowie gegen Direktwerbung (Art. 21 DSGVO)

+
+

WENN DIE DATENVERARBEITUNG AUF GRUNDLAGE VON ART. 6 ABS. 1 LIT. E ODER F DSGVO ERFOLGT, HABEN SIE JEDERZEIT DAS RECHT, AUS GRÜNDEN, DIE SICH AUS IHRER BESONDEREN SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN WIDERSPRUCH EINZULEGEN; DIES GILT AUCH FÜR EIN AUF DIESE BESTIMMUNGEN GESTÜTZTES PROFILING. DIE JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT, ENTNEHMEN SIE DIESER DATENSCHUTZERKLÄRUNG. WENN SIE WIDERSPRUCH EINLEGEN, WERDEN WIR IHRE BETROFFENEN PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES SEI DENN, WIR KÖNNEN ZWINGENDE SCHUTZWÜRDIGE GRÜNDE FÜR DIE VERARBEITUNG NACHWEISEN, DIE IHRE INTERESSEN, RECHTE UND FREIHEITEN ÜBERWIEGEN ODER DIE VERARBEITUNG DIENT DER GELTENDMACHUNG, AUSÜBUNG ODER VERTEIDIGUNG VON RECHTSANSPRÜCHEN (WIDERSPRUCH NACH ART. 21 ABS. 1 DSGVO).

+

WERDEN IHRE PERSONENBEZOGENEN DATEN VERARBEITET, UM DIREKTWERBUNG ZU BETREIBEN, SO HABEN SIE DAS RECHT, JEDERZEIT WIDERSPRUCH GEGEN DIE VERARBEITUNG SIE BETREFFENDER PERSONENBEZOGENER DATEN ZUM ZWECKE DERARTIGER WERBUNG EINZULEGEN; DIES GILT AUCH FÜR DAS PROFILING, SOWEIT ES MIT SOLCHER DIREKTWERBUNG IN VERBINDUNG STEHT. WENN SIE WIDERSPRECHEN, WERDEN IHRE PERSONENBEZOGENEN DATEN ANSCHLIESSEND NICHT MEHR ZUM ZWECKE DER DIREKTWERBUNG VERWENDET (WIDERSPRUCH NACH ART. 21 ABS. 2 DSGVO).

+
+ +

Beschwerderecht bei der zuständigen Aufsichtsbehörde

+

Im Falle von Verstößen gegen die DSGVO steht den Betroffenen ein Beschwerderecht bei einer Aufsichtsbehörde, insbesondere in dem Mitgliedstaat ihres gewöhnlichen Aufenthalts, ihres Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes zu. Das Beschwerderecht besteht unbeschadet anderweitiger verwaltungsrechtlicher oder gerichtlicher Rechtsbehelfe.

+ +

Recht auf Datenübertragbarkeit

+

Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.

+ +

Auskunft, Berichtigung und Löschung

+

Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit an uns wenden.

+ +

Recht auf Einschränkung der Verarbeitung

+

Sie haben das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Hierzu können Sie sich jederzeit an uns wenden. Das Recht auf Einschränkung der Verarbeitung besteht in folgenden Fällen:

+
    +
  • Wenn Sie die Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten bestreiten, benötigen wir in der Regel Zeit, um dies zu überprüfen. Für die Dauer der Prüfung haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.
  • +
  • Wenn die Verarbeitung Ihrer personenbezogenen Daten unrechtmäßig geschah/geschieht, können Sie statt der Löschung die Einschränkung der Datenverarbeitung verlangen.
  • +
  • Wenn wir Ihre personenbezogenen Daten nicht mehr benötigen, Sie sie jedoch zur Ausübung, Verteidigung oder Geltendmachung von Rechtsansprüchen benötigen, haben Sie das Recht, statt der Löschung die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.
  • +
  • Wenn Sie einen Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss eine Abwägung zwischen Ihren und unseren Interessen vorgenommen werden. Solange noch nicht feststeht, wessen Interessen überwiegen, haben Sie das Recht, die Einschränkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.
  • +
+

Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschränkt haben, dürfen diese Daten – von ihrer Speicherung abgesehen – nur mit Ihrer Einwilligung oder zur Geltendmachung, Ausübung oder Verteidigung von Rechtsansprüchen oder zum Schutz der Rechte einer anderen natürlichen oder juristischen Person oder aus Gründen eines wichtigen öffentlichen Interesses der Europäischen Union oder eines Mitgliedstaats verarbeitet werden.

+ +

SSL- bzw. TLS-Verschlüsselung

+

Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von „http://" auf „https://" wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.

+

Wenn die SSL- bzw. TLS-Verschlüsselung aktiviert ist, können die Daten, die Sie an uns übermitteln, nicht von Dritten mitgelesen werden.

+ +

Widerspruch gegen Werbe-E-Mails

+

Der Nutzung von im Rahmen der Impressumspflicht veröffentlichten Kontaktdaten zur Übersendung von nicht ausdrücklich angeforderter Werbung und Informationsmaterialien wird hiermit widersprochen. Die Betreiber der Seiten behalten sich ausdrücklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-E-Mails, vor.

+
+ +
+

4. Datenerfassung auf dieser Website

+ +

Cookies

+

Unsere Internetseiten verwenden so genannte „Cookies". Cookies sind kleine Datenpakete und richten auf Ihrem Endgerät keinen Schaden an. Sie werden entweder vorübergehend für die Dauer einer Sitzung (Session-Cookies) oder dauerhaft (permanente Cookies) auf Ihrem Endgerät gespeichert. Session-Cookies werden nach Ende Ihres Besuchs automatisch gelöscht. Permanente Cookies bleiben auf Ihrem Endgerät gespeichert, bis Sie diese selbst löschen oder eine automatische Löschung durch Ihren Webbrowser erfolgt.

+

Cookies können von uns (First-Party-Cookies) oder von Drittunternehmen stammen (sog. Third-Party-Cookies). Third-Party-Cookies ermöglichen die Einbindung bestimmter Dienstleistungen von Drittunternehmen innerhalb von Webseiten (z. B. Cookies zur Abwicklung von Zahlungsdienstleistungen).

+

Cookies haben verschiedene Funktionen. Zahlreiche Cookies sind technisch notwendig, da bestimmte Webseitenfunktionen ohne diese nicht funktionieren würden (z. B. die Warenkorbfunktion oder die Anzeige von Videos). Andere Cookies können zur Auswertung des Nutzerverhaltens oder zu Werbezwecken verwendet werden.

+

Cookies, die zur Durchführung des elektronischen Kommunikationsvorgangs, zur Bereitstellung bestimmter, von Ihnen erwünschter Funktionen (z. B. für die Warenkorbfunktion) oder zur Optimierung der Website (z. B. Cookies zur Messung des Webpublikums) erforderlich sind (notwendige Cookies), werden auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO gespeichert, sofern keine andere Rechtsgrundlage angegeben wird. Der Websitebetreiber hat ein berechtigtes Interesse an der Speicherung von notwendigen Cookies zur technisch fehlerfreien und optimierten Bereitstellung seiner Dienste. Sofern eine Einwilligung zur Speicherung von Cookies und vergleichbaren Wiedererkennungstechnologien abgefragt wurde, erfolgt die Verarbeitung ausschließlich auf Grundlage dieser Einwilligung (Art. 6 Abs. 1 lit. a DSGVO und § 25 Abs. 1 TDDDG); die Einwilligung ist jederzeit widerrufbar.

+

Sie können Ihren Browser so einstellen, dass Sie über das Setzen von Cookies informiert werden und Cookies nur im Einzelfall erlauben, die Annahme von Cookies für bestimmte Fälle oder generell ausschließen sowie das automatische Löschen der Cookies beim Schließen des Browsers aktivieren. Bei der Deaktivierung von Cookies kann die Funktionalität dieser Website eingeschränkt sein.

+

Welche Cookies und Dienste auf dieser Website eingesetzt werden, können Sie dieser Datenschutzerklärung entnehmen.

+
+ +
+

5. Kontaktformular und Anfragen

+ +

Datenerhebung über das Kontaktformular

+

Wenn Sie uns über das auf unserer Website bereitgestellte Kontaktformular eine Anfrage zukommen lassen, werden Ihre Angaben aus dem Formular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Erhoben werden: Name, Organisation (optional), E-Mail-Adresse und Ihre Nachricht. Eine Weitergabe an Dritte erfolgt nicht.

+ +

Rechtsgrundlage

+

Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO, sofern Ihre Anfrage mit der Erfüllung eines Vertrags zusammenhängt oder zur Durchführung vorvertraglicher Maßnahmen erforderlich ist. In allen übrigen Fällen beruht die Verarbeitung auf unserem berechtigten Interesse an der effektiven Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) und/oder auf Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO), sofern diese abgefragt wurde; die Einwilligung ist jederzeit widerrufbar.

+ +

Speicherdauer

+

Die von Ihnen im Kontaktformular eingegebenen Daten verbleiben bei uns, bis Sie uns zur Löschung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck für die Datenspeicherung entfällt (z. B. nach abgeschlossener Bearbeitung Ihrer Anfrage). Zwingende gesetzliche Bestimmungen – insbesondere Aufbewahrungsfristen – bleiben unberührt.

+ +

Übertragung

+

Ihre Anfrage wird verschlüsselt (TLS) an unseren Server übertragen und dort als E-Mail an info@aegis-sight.de weitergeleitet. Die E-Mail-Übertragung erfolgt verschlüsselt über Mailserver in Deutschland (IONOS).

+
+ +
+

6. Newsletter

+ +

Newsletterdaten

+

Wenn Sie den auf der Website angebotenen Newsletter beziehen möchten, benötigen wir von Ihnen eine E-Mail-Adresse sowie Informationen, welche uns die Überprüfung gestatten, dass Sie der Inhaber der angegebenen E-Mail-Adresse sind und mit dem Empfang des Newsletters einverstanden sind. Weitere Daten werden nicht bzw. nur auf freiwilliger Basis erhoben. Diese Daten verwenden wir ausschließlich für den Versand der angeforderten Informationen und geben diese nicht an Dritte weiter.

+

Die Verarbeitung der in das Newsletteranmeldeformular eingegebenen Daten erfolgt ausschließlich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Die erteilte Einwilligung zur Speicherung der Daten, der E-Mail-Adresse sowie deren Nutzung zum Versand des Newsletters können Sie jederzeit widerrufen, etwa über den „Austragen"-Link im Newsletter. Die Rechtmäßigkeit der bereits erfolgten Datenverarbeitungsvorgänge bleibt vom Widerruf unberührt.

+

Die von Ihnen zum Zwecke des Newsletter-Bezugs bei uns hinterlegten Daten werden von uns bis zu Ihrer Austragung aus dem Newsletter bei uns bzw. dem Newsletterdiensteanbieter gespeichert und nach der Abbestellung des Newsletters oder nach Zweckfortfall aus der Newsletterverteilerliste gelöscht. Wir behalten uns vor, E-Mail-Adressen aus unserem Newsletterverteiler nach eigenem Ermessen im Rahmen unseres berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO zu löschen oder zu sperren.

+

Daten, die zu anderen Zwecken bei uns gespeichert wurden, bleiben hiervon unberührt.

+

Nach Ihrer Austragung aus der Newsletterverteilerliste wird Ihre E-Mail-Adresse bei uns bzw. dem Newsletterdiensteanbieter ggf. in einer Blacklist gespeichert, sofern dies zur Verhinderung künftiger Mailings erforderlich ist. Die Daten aus der Blacklist werden nur für diesen Zweck verwendet und nicht mit anderen Daten zusammengeführt. Dies dient sowohl Ihrem Interesse als auch unserem Interesse an der Einhaltung der gesetzlichen Vorgaben beim Versand von Newslettern (berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO). Die Speicherung in der Blacklist ist zeitlich nicht befristet. Sie können der Speicherung widersprechen, sofern Ihre Interessen unser berechtigtes Interesse überwiegen.

+
+ +
+

7. Plugins und Tools

+ +

AegisSight Analytics (Umami)

+

Wir nutzen auf dieser Website AegisSight Analytics, eine selbstgehostete Instanz der Open-Source-Analyse-Software Umami. Mit Umami erfassen wir anonymisierte Informationen zur Nutzung unserer Website (z. B. besuchte Seiten, Browser, ungefähre geografische Region auf Länderebene) zur Verbesserung unserer Inhalte.

+

Selbstgehostet in Deutschland: Sämtliche Daten verbleiben auf unserem Server in Nürnberg, Deutschland (Hosting: Hetzner). Eine Übertragung an Dritte findet nicht statt.

+

Cookielos und IP-anonymisiert: Umami setzt keine Cookies und speichert keine personenbezogenen Daten. IP-Adressen werden vor der Speicherung anonymisiert (Hashing). Es findet kein Cross-Site-Tracking statt.

+

Rechtsgrundlage und Einwilligung: Die Reichweitenmessung wird ausschließlich nach Ihrer Einwilligung über unser Cookie-Consent-Banner aktiviert (Art. 6 Abs. 1 lit. a DSGVO i. V. m. § 25 Abs. 1 TDDDG). Sie können Ihre Einwilligung jederzeit über den Footer-Link „Cookie-Einstellungen" widerrufen. Wir respektieren das „Global Privacy Control" (GPC)-Signal Ihres Browsers und deaktivieren in diesem Fall die Reichweitenmessung automatisch.

+ +

Google Fonts (lokales Hosting)

+

Diese Seite nutzt zur einheitlichen Darstellung von Schriftarten so genannte Google Fonts, die von Google bereitgestellt werden. Die Google Fonts sind lokal installiert. Eine Verbindung zu Servern von Google findet dabei nicht statt.

+

Weitere Informationen zu Google Fonts finden Sie unter https://developers.google.com/fonts/faq und in der Datenschutzerklärung von Google: https://policies.google.com/privacy?hl=de.

+
+ +
+

8. Automatisierte Zugriffe und KI-Agenten

+ +

Verbot automatisierter Zugriffe

+

Die automatisierte Abfrage, das Scraping oder Crawling dieser Website durch Bots, Spider, Scraper, KI-Agenten (einschließlich LLM-basierter Systeme), „Buy-for-me"-Agenten oder ähnliche automatisierte Tools ist ohne unsere ausdrückliche schriftliche Genehmigung untersagt.

+ +

Dies umfasst insbesondere:

+
    +
  • Automatisierte Datenerfassung und -extraktion
  • +
  • Training von KI-Modellen mit Inhalten dieser Website
  • +
  • Automatisierte End-to-End-Prozesse ohne menschliche Überprüfung
  • +
  • Systematisches Auslesen von Inhalten durch automatisierte Systeme
  • +
+ +

Verstöße gegen diese Regelung können zivil- und strafrechtlich verfolgt werden. Die in unserer robots.txt-Datei enthaltenen Anweisungen sind verbindlich und Teil dieser Nutzungsbedingungen.

+ +

Ausnahmen

+

Ausgenommen von diesem Verbot sind Suchmaschinen-Crawler, die sich an die Vorgaben unserer robots.txt halten, sowie Dienste, denen wir ausdrücklich eine Genehmigung erteilt haben.

+
+ + +
+
+ + +
+
+ + +
+
+ + + + + + + + \ No newline at end of file diff --git a/de.svg b/de.svg new file mode 100644 index 0000000..20a017e --- /dev/null +++ b/de.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/docs/Sitemap_IntelSight_UG.docx b/docs/Sitemap_IntelSight_UG.docx new file mode 100644 index 0000000..2e35ec0 Binary files /dev/null and b/docs/Sitemap_IntelSight_UG.docx differ diff --git a/docs/datenschutzerklaerung_intelsight_de_de.pdf b/docs/datenschutzerklaerung_intelsight_de_de.pdf new file mode 100644 index 0000000..ae9adaa Binary files /dev/null and b/docs/datenschutzerklaerung_intelsight_de_de.pdf differ diff --git a/docs/impressum_intelsight_de_de.pdf b/docs/impressum_intelsight_de_de.pdf new file mode 100644 index 0000000..90a3a6f Binary files /dev/null and b/docs/impressum_intelsight_de_de.pdf differ diff --git a/downloads/af-updates/session_manager.py b/downloads/af-updates/session_manager.py new file mode 100644 index 0000000..001591e --- /dev/null +++ b/downloads/af-updates/session_manager.py @@ -0,0 +1,452 @@ +""" +Session Manager für die Lizenz-Session-Verwaltung mit Heartbeat. +""" + +import threading +import time +import logging +import json +import os +import requests +from datetime import datetime +from typing import Optional, Dict, Any +from .api_client import LicenseAPIClient +from .hardware_fingerprint import HardwareFingerprint + +logger = logging.getLogger("session_manager") +logger.setLevel(logging.DEBUG) +# Füge Console Handler hinzu falls noch nicht vorhanden +if not logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) + logger.addHandler(handler) + + +class SessionManager: + """Verwaltet die Lizenz-Session und Heartbeat.""" + + SESSION_FILE = os.path.join("config", ".session_data") + HEARTBEAT_INTERVAL = 60 # Sekunden + + def __init__(self, api_client: Optional[LicenseAPIClient] = None): + """ + Initialisiert den Session Manager. + + Args: + api_client: Optional vorkonfigurierter API Client + """ + self.api_client = api_client or LicenseAPIClient() + self.hardware_fingerprint = HardwareFingerprint() + + self.session_token: Optional[str] = None + self.license_key: Optional[str] = None + self.activation_id: Optional[int] = None + self.heartbeat_thread: Optional[threading.Thread] = None + self.stop_heartbeat = threading.Event() + self.is_active = False + + # Lade Session-IP-Konfiguration + self._load_ip_config() + + # Session-Daten laden falls vorhanden + self._load_session_data() + + def _save_session_data(self) -> None: + """Speichert die aktuelle Session-Daten.""" + try: + os.makedirs("config", exist_ok=True) + session_data = { + "session_token": self.session_token, + "license_key": self.license_key, + "activation_id": self.activation_id, + "timestamp": datetime.now().isoformat() + } + with open(self.SESSION_FILE, 'w') as f: + json.dump(session_data, f) + logger.debug("Session-Daten gespeichert") + except Exception as e: + logger.error(f"Fehler beim Speichern der Session-Daten: {e}") + + def _load_session_data(self) -> None: + """Lädt gespeicherte Session-Daten.""" + if os.path.exists(self.SESSION_FILE): + try: + with open(self.SESSION_FILE, 'r') as f: + data = json.load(f) + self.session_token = data.get("session_token") + self.license_key = data.get("license_key") + self.activation_id = data.get("activation_id") + logger.info("Session-Daten geladen") + except Exception as e: + logger.warning(f"Fehler beim Laden der Session-Daten: {e}") + + def _clear_session_data(self) -> None: + """Löscht die gespeicherten Session-Daten.""" + try: + if os.path.exists(self.SESSION_FILE): + os.remove(self.SESSION_FILE) + logger.debug("Session-Daten gelöscht") + except Exception as e: + logger.error(f"Fehler beim Löschen der Session-Daten: {e}") + + def start_session(self, license_key: str, activation_id: Optional[int] = None) -> Dict[str, Any]: + """ + Startet eine neue Session für die Lizenz. + + Args: + license_key: Der Lizenzschlüssel + activation_id: Optional die Aktivierungs-ID + + Returns: + Dictionary mit Session-Informationen oder Fehler + """ + if self.is_active: + logger.warning("Session läuft bereits") + return { + "success": False, + "error": "Session already active" + } + + # Hardware-Info sammeln + hw_hash = self.hardware_fingerprint.get_or_create_fingerprint() + machine_name = self.hardware_fingerprint.get_machine_name() + + # IP-Adresse ermitteln + client_ip = self._get_session_ip() + + logger.info(f"Starte Session für Lizenz: {license_key[:4]}...") + logger.debug(f"Session-Parameter: machine_name={machine_name}, hw_hash={hw_hash[:8]}..., ip={client_ip}") + + # Session-Start API Call mit IP-Adresse + result = self.api_client.start_session( + license_key=license_key, + machine_id=machine_name, + hardware_hash=hw_hash, + version="1.0.0", # TODO: Version aus config lesen + ip_address=client_ip # NEU: IP-Adresse hinzugefügt + ) + + logger.debug(f"Session-Start Response: {result}") + + if result.get("success"): + data = result.get("data", {}) + + # Prüfe ob die Session wirklich erfolgreich war + if data.get("success") is False: + # Session wurde abgelehnt + error_msg = data.get("message", "Session start failed") + logger.error(f"Session abgelehnt: {error_msg}") + return { + "success": False, + "error": error_msg, + "code": "SESSION_REJECTED" + } + + self.session_token = data.get("session_token") + self.license_key = license_key + self.activation_id = activation_id or data.get("activation_id") + self.is_active = True if self.session_token else False + + # Session-Daten speichern + self._save_session_data() + + # Heartbeat starten + self._start_heartbeat() + + logger.info(f"Session erfolgreich gestartet: {self.session_token}") + + # Update-Info prüfen + if data.get("update_available"): + logger.info(f"Update verfügbar: {data.get('latest_version')}") + + return { + "success": True, + "session_token": self.session_token, + "update_info": { + "available": data.get("update_available", False), + "version": data.get("latest_version"), + "download_url": data.get("download_url") + } + } + else: + error = result.get("error", "Unknown error") + logger.error(f"Session-Start fehlgeschlagen: {error}") + + # Bei Konflikt (409) bedeutet es, dass bereits eine Session läuft + if result.get("status") == 409: + return { + "success": False, + "error": "Another session is already active for this license", + "code": "SESSION_CONFLICT" + } + + return { + "success": False, + "error": error, + "code": result.get("code", "SESSION_START_FAILED") + } + + def _start_heartbeat(self) -> None: + """Startet den Heartbeat-Thread.""" + if self.heartbeat_thread and self.heartbeat_thread.is_alive(): + logger.warning("Heartbeat läuft bereits") + return + + self.stop_heartbeat.clear() + self.heartbeat_thread = threading.Thread( + target=self._heartbeat_worker, + daemon=True, + name="LicenseHeartbeat" + ) + self.heartbeat_thread.start() + logger.info("Heartbeat-Thread gestartet") + + def _heartbeat_worker(self) -> None: + """Worker-Funktion für den Heartbeat-Thread.""" + logger.info(f"Heartbeat-Worker gestartet (Interval: {self.HEARTBEAT_INTERVAL}s)") + + while not self.stop_heartbeat.is_set(): + try: + # Warte das Interval oder bis Stop-Signal + if self.stop_heartbeat.wait(self.HEARTBEAT_INTERVAL): + break + + # Sende Heartbeat + if self.session_token and self.license_key: + logger.debug("Sende Heartbeat...") + result = self.api_client.session_heartbeat( + session_token=self.session_token, + license_key=self.license_key + ) + + # Pruefe sowohl HTTP-Status als auch Body-Success + http_ok = result.get("success") + body_data = result.get("data", {}) + body_ok = body_data.get("success", True) if isinstance(body_data, dict) else True + + if http_ok and body_ok: + logger.debug("Heartbeat erfolgreich") + else: + body_msg = body_data.get("message", "") if isinstance(body_data, dict) else "" + logger.error(f"Heartbeat fehlgeschlagen: {body_msg or result.get('error')}") + + # Bei HTTP-Fehlern oder Body-Fehler Session beenden + if result.get("status") in [401, 404] or (http_ok and not body_ok): + logger.error("Session ungueltig, beende...") + self.end_session() + break + else: + logger.warning("Keine Session-Daten für Heartbeat") + + except Exception as e: + logger.error(f"Fehler im Heartbeat-Worker: {e}") + + logger.info("Heartbeat-Worker beendet") + + def end_session(self) -> Dict[str, Any]: + """ + Beendet die aktuelle Session. + + Returns: + Dictionary mit Informationen über die beendete Session + """ + if not self.is_active: + logger.warning("Keine aktive Session zum Beenden") + return { + "success": False, + "error": "No active session" + } + + logger.info("Beende Session...") + + # Heartbeat stoppen + self.stop_heartbeat.set() + if self.heartbeat_thread: + self.heartbeat_thread.join(timeout=5) + + # Session beenden API Call + result = {"success": True} + if self.session_token: + result = self.api_client.end_session(self.session_token) + + if result.get("success"): + logger.info("Session erfolgreich beendet") + else: + logger.error(f"Fehler beim Beenden der Session: {result.get('error')}") + + # Session-Daten löschen + self.session_token = None + self.license_key = None + self.activation_id = None + self.is_active = False + self._clear_session_data() + + return result + + def resume_session(self) -> bool: + """ + Versucht eine gespeicherte Session fortzusetzen. + + Returns: + True wenn erfolgreich, False sonst + """ + if self.is_active: + logger.info("Session läuft bereits") + return True + + if not self.session_token or not self.license_key: + logger.info("Keine gespeicherten Session-Daten vorhanden") + return False + + logger.info("Versuche Session fortzusetzen...") + + # Teste mit Heartbeat ob Session noch gültig ist + result = self.api_client.session_heartbeat( + session_token=self.session_token, + license_key=self.license_key + ) + + # Pruefe sowohl HTTP-Status als auch Body-Success + http_ok = result.get("success") + body_data = result.get("data", {}) + body_ok = body_data.get("success", True) if isinstance(body_data, dict) else True + + if http_ok and body_ok: + logger.info("Session erfolgreich fortgesetzt") + self.is_active = True + self._start_heartbeat() + return True + else: + body_msg = body_data.get("message", "") if isinstance(body_data, dict) else "" + logger.warning(f"Gespeicherte Session ungueltig: {body_msg or result.get('error', 'unbekannt')}") + self._clear_session_data() + return False + + def is_session_active(self) -> bool: + """ + Prüft ob eine Session aktiv ist. + + Returns: + True wenn aktiv, False sonst + """ + return self.is_active + + def get_session_info(self) -> Dict[str, Any]: + """ + Gibt Informationen über die aktuelle Session zurück. + + Returns: + Dictionary mit Session-Informationen + """ + return { + "active": self.is_active, + "session_token": self.session_token[:8] + "..." if self.session_token else None, + "license_key": self.license_key[:4] + "..." if self.license_key else None, + "activation_id": self.activation_id, + "heartbeat_interval": self.HEARTBEAT_INTERVAL + } + + def set_heartbeat_interval(self, seconds: int) -> None: + """ + Setzt das Heartbeat-Interval. + + Args: + seconds: Interval in Sekunden (min 30, max 300) + """ + if 30 <= seconds <= 300: + self.HEARTBEAT_INTERVAL = seconds + logger.info(f"Heartbeat-Interval auf {seconds}s gesetzt") + + # Restart Heartbeat wenn aktiv + if self.is_active: + self.stop_heartbeat.set() + if self.heartbeat_thread: + self.heartbeat_thread.join(timeout=5) + self._start_heartbeat() + else: + logger.warning(f"Ungültiges Heartbeat-Interval: {seconds}") + + def _load_ip_config(self) -> None: + """Lädt die IP-Konfiguration aus license_config.json.""" + config_path = os.path.join("config", "license_config.json") + self.session_ip_mode = "auto" # Default + self.ip_fallback = "0.0.0.0" + + try: + if os.path.exists(config_path): + with open(config_path, 'r') as f: + config = json.load(f) + self.session_ip_mode = config.get("session_ip_mode", "auto") + self.ip_fallback = config.get("ip_fallback", "0.0.0.0") + logger.debug(f"IP-Konfiguration geladen: mode={self.session_ip_mode}, fallback={self.ip_fallback}") + except Exception as e: + logger.warning(f"Fehler beim Laden der IP-Konfiguration: {e}") + + def _get_session_ip(self) -> str: + """ + Ermittelt die IP-Adresse für die Session basierend auf der Konfiguration. + + TESTBETRIEB: Temporäre Lösung - wird durch Server-Ressourcenmanagement ersetzt + + Returns: + Die IP-Adresse als String + """ + if self.session_ip_mode == "auto": + # TESTBETRIEB: Auto-Erkennung der öffentlichen IP + logger.info("TESTBETRIEB: Ermittle öffentliche IP-Adresse automatisch") + try: + response = requests.get("https://api.ipify.org?format=json", timeout=5) + if response.status_code == 200: + ip = response.json().get("ip") + logger.info(f"Öffentliche IP ermittelt: {ip}") + return ip + else: + logger.warning(f"IP-Ermittlung fehlgeschlagen: Status {response.status_code}") + except Exception as e: + logger.error(f"Fehler bei IP-Ermittlung: {e}") + + # Fallback verwenden + logger.warning(f"Verwende Fallback-IP: {self.ip_fallback}") + return self.ip_fallback + + elif self.session_ip_mode == "server_assigned": + # TODO: Implementierung für Server-zugewiesene IPs + logger.info("Server-assigned IP mode noch nicht implementiert, verwende Fallback") + return self.ip_fallback + + elif self.session_ip_mode == "proxy": + # TODO: Proxy-IP verwenden wenn Proxy aktiv + logger.info("Proxy IP mode noch nicht implementiert, verwende Fallback") + return self.ip_fallback + + else: + logger.warning(f"Unbekannter IP-Modus: {self.session_ip_mode}, verwende Fallback") + return self.ip_fallback + + +# Test-Funktion +if __name__ == "__main__": + logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + + print("=== Session Manager Test ===\n") + + # Session Manager erstellen + session_mgr = SessionManager() + + # Session-Info anzeigen + print("Aktuelle Session-Info:") + info = session_mgr.get_session_info() + for key, value in info.items(): + print(f" {key}: {value}") + + # Versuche gespeicherte Session fortzusetzen + print("\nVersuche Session fortzusetzen...") + if session_mgr.resume_session(): + print(" ✓ Session fortgesetzt") + else: + print(" ✗ Keine gültige Session gefunden") + + print("\n=== Test abgeschlossen ===") \ No newline at end of file diff --git a/en.svg b/en.svg new file mode 100644 index 0000000..016c075 --- /dev/null +++ b/en.svg @@ -0,0 +1,50 @@ + + + \ No newline at end of file diff --git a/favicon.svg b/favicon.svg new file mode 100644 index 0000000..835ad75 --- /dev/null +++ b/favicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/impressum-en.html b/impressum-en.html new file mode 100644 index 0000000..429bb89 --- /dev/null +++ b/impressum-en.html @@ -0,0 +1,205 @@ + + + + + + Legal Notice - AegisSight + + + + + + + + + + + + + + + + + + +
+
+

Legal Notice

+
+ +
+
+

Company Information

+

+ AegisSight UG (limited liability)
+ Gladbacher Strasse 3-5
+ 40764 Langenfeld
+ Germany +

+
+ +
+

Represented by

+

Hendrik Gebhardt
+ Monami Homma

+
+ +
+

Contact

+

+ Email: info@aegis-sight.de
+ Website: aegis-sight.de +

+
+ +
+

Commercial Register

+

+ Entry in the Commercial Register
+ Registry Court: District Court Düsseldorf
+ Registration number: HRB 110105 +

+
+ +
+

VAT

+

VAT identification number according to §27 a of the German VAT Act:
+ DE457846602

+
+ +
+

Consumer Dispute Resolution/Universal Arbitration Board

+

We are not willing or obliged to participate in dispute resolution proceedings before a consumer arbitration board.

+
+ +
+

Liability for Content

+

As a service provider, we are responsible for our own content on these pages according to § 7 para.1 TMG under general law. However, according to §§ 8 to 10 TMG, we are not obligated as a service provider to monitor transmitted or stored third-party information or to investigate circumstances that indicate illegal activity.

+

Obligations to remove or block the use of information under general law remain unaffected. However, liability in this regard is only possible from the time of knowledge of a specific infringement. Upon becoming aware of such legal violations, we will remove this content immediately.

+
+ +
+

Liability for Links

+

Our offer contains links to external third-party websites over whose content we have no influence. Therefore, we cannot assume any liability for this third-party content. The respective provider or operator of the pages is always responsible for the content of the linked pages. The linked pages were checked for possible legal violations at the time of linking. Illegal content was not recognizable at the time of linking.

+

However, permanent content control of the linked pages is not reasonable without concrete evidence of a violation of law. Upon becoming aware of legal violations, we will remove such links immediately.

+
+ +
+

Copyright

+

The content and works created by the site operators on these pages are subject to German copyright law. The reproduction, editing, distribution and any kind of exploitation outside the limits of copyright require the written consent of the respective author or creator. Downloads and copies of this site are only permitted for private, non-commercial use.

+

Insofar as the content on this site was not created by the operator, the copyrights of third parties are respected. In particular, third-party content is marked as such. Should you nevertheless become aware of a copyright infringement, please inform us accordingly. Upon becoming aware of legal violations, we will remove such content immediately.

+
+
+
+ + +
+ + +
+ + + + + + + + \ No newline at end of file diff --git a/impressum.html b/impressum.html new file mode 100644 index 0000000..b6648cf --- /dev/null +++ b/impressum.html @@ -0,0 +1,199 @@ + + + + + + Impressum - AegisSight + + + + + + + + + + + + + + + + + + + +
+
+

Impressum

+
+ +
+
+

Angaben gemäß § 5 TMG

+

AegisSight UG (haftungsbeschränkt)

+

Gladbacher Strasse 3-5
+ 40764 Langenfeld

+
+ +
+

Vertreten durch

+

Hendrik Gebhardt
+ Monami Homma

+
+ +
+

Kontakt

+

E-Mail: info@aegis-sight.de

+
+ +
+

Registereintrag

+

Handelsregister: HRB 110105
+ Registergericht: Amtsgericht Düsseldorf

+
+ +
+

Umsatzsteuer-ID

+

Umsatzsteuer-Identifikationsnummer gemäß § 27 a Umsatzsteuergesetz:
+ DE457846602

+
+ +
+

Verbraucherstreitbeilegung/Universalschlichtungsstelle

+

Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.

+
+ +
+

Haftungsausschluss

+

Haftung für Inhalte
+ Die Inhalte unserer Seiten wurden mit größter Sorgfalt erstellt. Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können wir jedoch keine Gewähr übernehmen. Als Diensteanbieter sind wir gemäß § 7 Abs.1 TMG für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Nach §§ 8 bis 10 TMG sind wir als Diensteanbieter jedoch nicht verpflichtet, übermittelte oder gespeicherte fremde Informationen zu überwachen oder nach Umständen zu forschen, die auf eine rechtswidrige Tätigkeit hinweisen.

+ +

Haftung für Links
+ Unser Angebot enthält Links zu externen Webseiten Dritter, auf deren Inhalte wir keinen Einfluss haben. Deshalb können wir für diese fremden Inhalte auch keine Gewähr übernehmen. Für die Inhalte der verlinkten Seiten ist stets der jeweilige Anbieter oder Betreiber der Seiten verantwortlich.

+ +

Urheberrecht
+ Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.

+ +

+ Quelle: eRecht24 +

+
+
+
+ + +
+
+ + +
+
+ + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..896b640 --- /dev/null +++ b/index.html @@ -0,0 +1,373 @@ + + + + + + AegisSight - Sicherheit Made in Germany + + + + + + + + + + + + + + + + + + + + + + Zum Hauptinhalt springen + + + + + + + + +
+ + +
+ +
+ +
+ + + +
+ + +
+ + + + +
+

+ SICHERHEIT MADE IN GERMANY +

+

Spezialist für hochsichere, maßgeschneiderte IT-Lösungen für Behörden

+
+ + +
+ Scroll to Explore +
+ Scroll Down +
+
+
+ + +
+
+

ÜBER UNS

+

Ihr Partner für sichere Behördensoftware

+ + +
+ + + + +
+ + +
+ +
+
+
+
+

+ + Security + + Spezialist für Behördensoftware +

+

AegisSight UG ist Ihr Spezialist für hochsichere, maßgeschneiderte IT-Lösungen aus Nordrhein-Westfalen. Wir entwickeln innovative Software speziell für staatliche Sicherheits- und Ermittlungsbehörden.

+
+
+

+ + Future + + Unser Ansatz +

+

Unser Ansatz vereint modernste Technologie mit einem tiefen Verständnis für die besonderen Anforderungen von Behörden. Dabei steht die Balance zwischen Sicherheit, Effizienz und rechtskonformer Umsetzung im Mittelpunkt unserer Arbeit.

+
+
+
+
+ NRW Map +
+
+ Location + Nordrhein-Westfalen, Deutschland +
+
+
+
+ + +
+
+
+

Unsere Mission

+

Wir schaffen effiziente, sichere und datenschutzkonforme Lösungen für moderne Strafverfolgung und Sicherheitsbehörden.

+
+
+
+
+ Verified +
+

Integrität

+

Höchste ethische Standards in allem was wir tun

+
+
+
+ Time +
+

Transparenz

+

Offene Kommunikation und nachvollziehbare Prozesse

+
+
+
+ Scale +
+

Demokratische Prinzipien

+

Kooperation nur mit Behörden im Einklang mit der freiheitlich demokratischen Grundordnung

+
+
+
+

Unser Ziel: Technologie, die Recht und Sicherheit stärkt und die freiheitlich demokratische Grundordnung schützt.

+
+
+
+ + +
+
+
+
01
+
+

Behördenspezifische Software

+

Entwicklung mit höchsten Sicherheitsstandards, maßgeschneidert für staatliche Anforderungen

+
+
+
+
02
+
+

Intuitive Bedienkonzepte

+

Benutzerfreundliche Oberflächen trotz komplexer Funktionen für effizientes Arbeiten

+
+
+
+
03
+
+

Langzeit-Support

+

Kontinuierliche Sicherheitsupdates und zuverlässige Wartung über den gesamten Lebenszyklus

+
+
+
+
+ + +
+
+
+
+ Check +
+

Enge Zusammenarbeit

+

Wir arbeiten Hand in Hand mit unseren Kunden für maßgeschneiderte Lösungen

+
+
+
+ German Flag +
+

Made in Germany

+

Klare, robuste und sichere Software nach deutschen Qualitätsstandards

+
+
+
+ Handshake +
+

Verlässliche Partnerschaft

+

Basierend auf gemeinsamen Werten und langfristigem Vertrauen

+
+
+
+ Clock +
+

Nachhaltigkeit

+

Fokus auf Sicherheit, Professionalität & zukunftssichere Lösungen

+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+

PRODUKTE & LÖSUNGEN

+

Professionelle Werkzeuge für moderne Ermittlungsarbeit

+
+ +
+ +
+
+
+
+ AegisSight Monitor +
+
+

AegisSight Monitor

+

Open Source Intelligence - automatisiert

+
+
+
+

AegisSight Monitor aggregiert, analysiert und verifiziert Informationen aus öffentlich zugänglichen Quellen in Echtzeit. Erleben Sie die Plattform live am Beispiel des Iran-Livetickers.

+ + Iran-Liveticker öffnen + +
+
+ +
+
+
+
+ AccountForger +
+
+

AccountForger

+

Zugang nur mit Berechtigung

+
+
+
+

Dieses Produkt ist speziell für autorisierte Behörden entwickelt und erfordert eine Authentifizierung.

+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/js/animations-enhanced.js b/js/animations-enhanced.js new file mode 100644 index 0000000..2650052 --- /dev/null +++ b/js/animations-enhanced.js @@ -0,0 +1,233 @@ +/** + * Enhanced Animations and Interactions + * Premium effects for modern web experience + */ + +const EnhancedAnimations = { + init() { + this.initScrollAnimations(); + this.initParallaxEffects(); + this.initMagneticButtons(); + this.initTextAnimations(); + this.initCardTilt(); + this.initSmoothScroll(); + this.initCursorEffects(); + this.initRevealOnScroll(); + this.initNavbarEffects(); + }, + + // Smooth scroll with easing + initSmoothScroll() { + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + e.preventDefault(); + const target = document.querySelector(this.getAttribute('href')); + if (target) { + const offset = 100; + const targetPosition = target.offsetTop - offset; + window.scrollTo({ + top: targetPosition, + behavior: 'smooth' + }); + } + }); + }); + }, + + // Parallax scrolling effects + initParallaxEffects() { + const parallaxElements = document.querySelectorAll('.parallax'); + let ticking = false; + + function updateParallax() { + const scrolled = window.pageYOffset; + + parallaxElements.forEach(element => { + const speed = element.dataset.speed || 0.5; + const yPos = -(scrolled * speed); + element.style.transform = `translateY(${yPos}px)`; + }); + + ticking = false; + } + + function requestTick() { + if (!ticking) { + window.requestAnimationFrame(updateParallax); + ticking = true; + } + } + + window.addEventListener('scroll', requestTick); + }, + + // Magnetic button effects + initMagneticButtons() { + const magneticButtons = document.querySelectorAll('.primary-button, .secondary-button, .cta-button'); + + magneticButtons.forEach(button => { + button.addEventListener('mousemove', (e) => { + const rect = button.getBoundingClientRect(); + const x = e.clientX - rect.left - rect.width / 2; + const y = e.clientY - rect.top - rect.height / 2; + + button.style.transform = `translate(${x * 0.2}px, ${y * 0.2}px) scale(1.05)`; + }); + + button.addEventListener('mouseleave', () => { + button.style.transform = ''; + }); + }); + }, + + // Advanced text animations + initTextAnimations() { + // Typewriter effect for hero title - DISABLED to prevent duplication + // The title already has CSS animations applied + + /* Commented out to fix duplication issue + const heroTitle = document.querySelector('.main-title'); + if (heroTitle && !heroTitle.dataset.animated) { + heroTitle.dataset.animated = 'true'; + const text = heroTitle.textContent; + heroTitle.textContent = ''; + heroTitle.style.opacity = '1'; + + let index = 0; + const typeWriter = () => { + if (index < text.length) { + heroTitle.textContent += text.charAt(index); + index++; + setTimeout(typeWriter, 50); + } + }; + + // Start typewriter after a short delay + setTimeout(typeWriter, 500); + } + */ + + // Word-by-word reveal for hero text + const heroText = document.querySelector('.hero-text'); + if (heroText) { + const words = heroText.textContent.split(' '); + heroText.innerHTML = words.map(word => + `${word}` + ).join(' '); + + const wordSpans = heroText.querySelectorAll('.word-reveal'); + wordSpans.forEach((word, index) => { + setTimeout(() => { + word.style.opacity = '1'; + word.style.transform = 'translateY(0)'; + }, 1000 + index * 100); + }); + } + }, + + // Card tilt removed - zu verspielt für Behördenkontext + initCardTilt() { + // Deaktiviert - CSS hover-Effekte reichen aus + }, + + // Custom cursor effects - DISABLED + initCursorEffects() { + // Cursor removed as requested + return; + }, + + + // Reveal elements on scroll + initRevealOnScroll() { + const revealElements = document.querySelectorAll('.about-panel, .tool-card, .value-card, .why-card, .competency-item'); + + revealElements.forEach((element, index) => { + element.style.opacity = '0'; + element.style.transform = 'translateY(50px)'; + element.style.transition = 'all 0.8s cubic-bezier(0.4, 0, 0.2, 1)'; + }); + + const revealOnScroll = () => { + const windowHeight = window.innerHeight; + + revealElements.forEach((element, index) => { + const elementTop = element.getBoundingClientRect().top; + const elementVisible = 100; + + if (elementTop < windowHeight - elementVisible) { + setTimeout(() => { + element.style.opacity = '1'; + element.style.transform = 'translateY(0)'; + }, index * 50); + } + }); + }; + + window.addEventListener('scroll', revealOnScroll); + revealOnScroll(); // Check on initial load + }, + + // Scroll-based animations + initScrollAnimations() { + let lastScrollY = window.scrollY; + let ticking = false; + + function updateScrollAnimations() { + const scrollY = window.scrollY; + const scrollDirection = scrollY > lastScrollY ? 'down' : 'up'; + + // Hero parallax + const hero = document.querySelector('.hero-content'); + if (hero) { + hero.style.transform = `translateY(${scrollY * 0.5}px)`; + hero.style.opacity = 1 - (scrollY / 800); + } + + // Video parallax + const heroVideos = document.querySelector('.hero-video-container'); + if (heroVideos) { + heroVideos.style.transform = `translateY(${scrollY * 0.3}px) scale(${1 + scrollY * 0.0003})`; + } + + lastScrollY = scrollY; + ticking = false; + } + + function requestTick() { + if (!ticking) { + window.requestAnimationFrame(updateScrollAnimations); + ticking = true; + } + } + + window.addEventListener('scroll', requestTick); + }, + + // Enhanced navbar effects + initNavbarEffects() { + const navbar = document.querySelector('.navbar'); + let lastScrollY = window.scrollY; + + window.addEventListener('scroll', () => { + const scrollY = window.scrollY; + + if (scrollY > 50) { + navbar.classList.add('scrolled'); + } else { + navbar.classList.remove('scrolled'); + } + + // Keep navbar always visible + navbar.style.transform = 'translateY(0)'; + + lastScrollY = scrollY; + }); + } +}; + +// Initialize when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => EnhancedAnimations.init()); +} else { + EnhancedAnimations.init(); +} \ No newline at end of file diff --git a/js/animations.js b/js/animations.js new file mode 100644 index 0000000..ee3ac80 --- /dev/null +++ b/js/animations.js @@ -0,0 +1,403 @@ +/** + * Animation module for AegisSight website + * Contains all animation logic and visual effects + */ + +// Particle Animation System +const ParticleAnimation = { + canvas: null, + ctx: null, + particles: [], + + /** + * Initialize particle animation + */ + init() { + this.canvas = document.querySelector(SELECTORS.PARTICLE_CANVAS); + if (!this.canvas) return; + + this.ctx = this.canvas.getContext('2d'); + this.resizeCanvas(); + this.createParticles(); + this.animate(); + + // Handle window resize + window.addEventListener('resize', () => this.resizeCanvas()); + }, + + /** + * Resize canvas to window size + */ + resizeCanvas() { + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight; + }, + + /** + * Create particle objects + */ + createParticles() { + this.particles = []; + for (let i = 0; i < CONFIG.ANIMATION.PARTICLE_COUNT; i++) { + this.particles.push(new Particle(this.canvas)); + } + }, + + /** + * Main animation loop + */ + animate() { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Update and draw particles + this.particles.forEach(particle => { + particle.update(this.canvas); + particle.draw(this.ctx); + }); + + // Draw connections between particles + this.drawConnections(); + + requestAnimationFrame(() => this.animate()); + }, + + /** + * Draw connections between nearby particles + */ + drawConnections() { + for (let a = 0; a < this.particles.length; a++) { + for (let b = a + 1; b < this.particles.length; b++) { + const distance = Math.sqrt( + Math.pow(this.particles[a].x - this.particles[b].x, 2) + + Math.pow(this.particles[a].y - this.particles[b].y, 2) + ); + + if (distance < CONFIG.ANIMATION.CONNECTION_DISTANCE) { + const opacity = 0.15 * (1 - distance / CONFIG.ANIMATION.CONNECTION_DISTANCE); + // Use darker blue for better visibility on light background + this.ctx.strokeStyle = `rgba(15, 114, 181, ${opacity})`; + this.ctx.lineWidth = 1; + this.ctx.beginPath(); + this.ctx.moveTo(this.particles[a].x, this.particles[a].y); + this.ctx.lineTo(this.particles[b].x, this.particles[b].y); + this.ctx.stroke(); + } + } + } + } +}; + +/** + * Particle class for animation + */ +class Particle { + constructor(canvas) { + this.x = Math.random() * canvas.width; + this.y = Math.random() * canvas.height; + this.size = Math.random() * (CONFIG.ANIMATION.PARTICLE_SIZE_MAX - CONFIG.ANIMATION.PARTICLE_SIZE_MIN) + CONFIG.ANIMATION.PARTICLE_SIZE_MIN; + this.speedX = (Math.random() - 0.5) * CONFIG.ANIMATION.PARTICLE_SPEED; + this.speedY = (Math.random() - 0.5) * CONFIG.ANIMATION.PARTICLE_SPEED; + this.opacity = Math.random() * 0.5 + 0.2; + } + + update(canvas) { + this.x += this.speedX; + this.y += this.speedY; + + // Wrap around screen edges + if (this.x > canvas.width) this.x = 0; + else if (this.x < 0) this.x = canvas.width; + + if (this.y > canvas.height) this.y = 0; + else if (this.y < 0) this.y = canvas.height; + } + + draw(ctx) { + // Use darker blue for better visibility on light background + ctx.fillStyle = `rgba(15, 114, 181, ${this.opacity * 0.7})`; + ctx.beginPath(); + ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); + ctx.fill(); + } +} + +// Counter Animation +const CounterAnimation = { + /** + * Animate all counter elements + */ + animateAll() { + const counters = document.querySelectorAll(SELECTORS.INDICATOR_VALUE); + counters.forEach(counter => this.animateCounter(counter)); + }, + + /** + * Animate a single counter + * @param {HTMLElement} counter - Counter element to animate + */ + animateCounter(counter) { + const target = parseFloat(counter.getAttribute(DATA_ATTRS.TARGET)); + const increment = target / CONFIG.ANIMATION.COUNTER_SPEED; + let current = 0; + + const updateCounter = () => { + current += increment; + + if (current < target) { + counter.innerText = Math.ceil(current); + setTimeout(updateCounter, CONFIG.TIMING.COUNTER_UPDATE_INTERVAL); + } else { + // Set final value with proper formatting + if (target === CONFIG.TRUST_INDICATORS.AVAILABILITY) { + counter.innerText = target + '%'; + } else if (target === CONFIG.TRUST_INDICATORS.AUTHORITIES_COUNT) { + counter.innerText = target + '+'; + } else if (target === CONFIG.TRUST_INDICATORS.SUPPORT_HOURS) { + counter.innerText = target + '/7'; + } + } + }; + + updateCounter(); + } +}; + +// Scroll Animations +const ScrollAnimations = { + scrollIndicator: null, + + /** + * Initialize scroll-based animations + */ + init() { + this.scrollIndicator = document.querySelector(SELECTORS.SCROLL_INDICATOR); + this.setupScrollIndicator(); + this.setupIntersectionObserver(); + }, + + /** + * Setup scroll indicator behavior + */ + setupScrollIndicator() { + if (!this.scrollIndicator) return; + + // Click to scroll to about section + this.scrollIndicator.addEventListener('click', () => { + const aboutSection = document.querySelector('#about'); + if (aboutSection) { + aboutSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }); + + // Hide/show based on scroll position + let scrollTimeout; + window.addEventListener('scroll', () => { + const hero = document.querySelector(SELECTORS.HERO); + + if (window.scrollY > CONFIG.ANIMATION.SCROLL_THRESHOLD) { + if (hero) hero.classList.add(CLASSES.SCROLLED); + if (this.scrollIndicator) this.scrollIndicator.style.opacity = '0'; + } else { + if (hero) hero.classList.remove(CLASSES.SCROLLED); + if (this.scrollIndicator) this.scrollIndicator.style.opacity = '1'; + } + + clearTimeout(scrollTimeout); + scrollTimeout = setTimeout(() => { + if (window.scrollY > CONFIG.ANIMATION.SCROLL_THRESHOLD && this.scrollIndicator) { + this.scrollIndicator.style.display = 'none'; + } else if (this.scrollIndicator) { + this.scrollIndicator.style.display = 'flex'; + } + }, CONFIG.TIMING.SCROLL_HIDE_DELAY); + }); + }, + + /** + * Setup intersection observer for scroll-triggered animations + */ + setupIntersectionObserver() { + const observerOptions = { + threshold: CONFIG.OBSERVER.THRESHOLD, + rootMargin: CONFIG.OBSERVER.ROOT_MARGIN + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + // Trust indicators animation + if (entry.target.classList.contains('trust-indicators')) { + CounterAnimation.animateAll(); + observer.unobserve(entry.target); + } + + // Timeline animation + if (entry.target.classList.contains('timeline')) { + const items = entry.target.querySelectorAll('.timeline-item'); + items.forEach((item, index) => { + setTimeout(() => { + item.classList.add(CLASSES.VISIBLE); + }, index * 300); + }); + observer.unobserve(entry.target); + } + + // Feature nodes animation + if (entry.target.classList.contains('feature-nodes')) { + const nodes = entry.target.querySelectorAll('.node'); + nodes.forEach((node, index) => { + setTimeout(() => { + node.style.opacity = '1'; + node.style.transform = 'translateY(0)'; + }, index * 150); + }); + observer.unobserve(entry.target); + } + } + }); + }, observerOptions); + + // Observe elements + const trustIndicators = document.querySelector(SELECTORS.TRUST_INDICATORS); + if (trustIndicators) { + trustIndicators.style.opacity = '0'; + observer.observe(trustIndicators); + } + + const timeline = document.querySelector('.timeline'); + if (timeline) observer.observe(timeline); + + const featureNodes = document.querySelector('.feature-nodes'); + if (featureNodes) { + document.querySelectorAll('.node').forEach(node => { + node.style.opacity = '0'; + node.style.transform = 'translateY(30px)'; + node.style.transition = 'all 0.6s ease'; + }); + observer.observe(featureNodes); + } + } +}; + +// Glitch Effect +const GlitchEffect = { + /** + * Apply glitch effect to element on hover + * @param {HTMLElement} element - Element to apply effect to + */ + apply(element) { + if (!element) return; + + let glitchInterval; + element.addEventListener('mouseenter', () => { + let count = 0; + glitchInterval = setInterval(() => { + element.style.textShadow = ` + ${Math.random() * 5}px ${Math.random() * 5}px 0 rgba(0, 212, 255, 0.5), + ${Math.random() * -5}px ${Math.random() * 5}px 0 rgba(255, 0, 128, 0.5) + `; + count++; + if (count > CONFIG.ANIMATION.GLITCH_ITERATIONS) { + clearInterval(glitchInterval); + element.style.textShadow = 'none'; + } + }, CONFIG.ANIMATION.GLITCH_INTERVAL); + }); + } +}; + +// Interactive Elements +const InteractiveElements = { + /** + * Initialize all interactive element animations + */ + init() { + this.setupNodeHoverEffects(); + this.setupWidgetHoverEffects(); + this.setupInteractiveIcon(); + }, + + /** + * Setup hover effects for node elements + */ + setupNodeHoverEffects() { + document.querySelectorAll('.node').forEach(node => { + node.addEventListener('mouseenter', function() { + const icon = this.querySelector('.node-icon'); + if (icon) icon.style.transform = 'scale(1.2) rotate(5deg)'; + }); + + node.addEventListener('mouseleave', function() { + const icon = this.querySelector('.node-icon'); + if (icon) icon.style.transform = 'scale(1) rotate(0deg)'; + }); + }); + }, + + /** + * Setup hover effects for widget elements + */ + setupWidgetHoverEffects() { + document.querySelectorAll('.widget').forEach(widget => { + widget.addEventListener('mouseenter', function() { + this.style.boxShadow = '0 5px 20px rgba(0, 212, 255, 0.3)'; + }); + + widget.addEventListener('mouseleave', function() { + this.style.boxShadow = 'none'; + }); + }); + }, + + /** + * Setup 3D interactive icon effect + */ + setupInteractiveIcon() { + const icon = document.querySelector(SELECTORS.INTERACTIVE_ICON); + if (!icon) return; + + document.addEventListener('mousemove', (e) => { + const rect = icon.getBoundingClientRect(); + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + + const mouseX = (e.clientX - centerX) / 20; + const mouseY = (e.clientY - centerY) / 20; + + icon.style.transform = `perspective(1000px) rotateY(${mouseX}deg) rotateX(${-mouseY}deg)`; + }); + } +}; + +// Initialize all animations +const Animations = { + /** + * Initialize all animation systems + */ + init() { + // Core animations + ParticleAnimation.init(); + ScrollAnimations.init(); + InteractiveElements.init(); + + // Apply glitch effect to main title + const mainTitle = document.querySelector('.main-title'); + if (mainTitle) { + GlitchEffect.apply(mainTitle); + } + + // Page load animations + window.addEventListener('load', () => { + document.body.classList.add(CLASSES.LOADED); + + // Fade in hero content + setTimeout(() => { + const heroContent = document.querySelector(SELECTORS.HERO_CONTENT); + if (heroContent) { + heroContent.style.opacity = '1'; + heroContent.style.transform = 'translateY(0)'; + } + }, 100); + }); + } +}; \ No newline at end of file diff --git a/js/components.js b/js/components.js new file mode 100644 index 0000000..3c9be96 --- /dev/null +++ b/js/components.js @@ -0,0 +1,515 @@ +/** + * UI Components module for AegisSight website + * Contains all interactive UI component logic + */ + +// Language Toggle Component +const LanguageToggle = { + element: null, + + /** + * Initialize language toggle + */ + init() { + this.element = document.querySelector(SELECTORS.LANG_TOGGLE); + if (!this.element) return; + + this.element.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + this.toggle(); + }); + }, + + /** + * Toggle between languages + */ + toggle() { + const newLanguage = getCurrentLanguage() === 'de' ? 'en' : 'de'; + switchLanguage(newLanguage); + + // Update expand button text after language change + ProductShowcase.updateExpandButtonText(); + } +}; + +// Navigation Component +const Navigation = { + navbar: null, + + /** + * Initialize navigation component + */ + init() { + this.navbar = document.querySelector(SELECTORS.NAVBAR); + this.setupSmoothScrolling(); + this.setupMobileMenu(); + }, + + /** + * Setup smooth scrolling for anchor links + */ + setupSmoothScrolling() { + document.querySelectorAll(SELECTORS.SMOOTH_LINKS).forEach(anchor => { + anchor.addEventListener('click', function(e) { + e.preventDefault(); + const targetId = this.getAttribute('href'); + const target = document.querySelector(targetId); + + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); + }, + + /** + * Setup mobile menu functionality + */ + setupMobileMenu() { + // Mobile menu logic would go here if needed + // Currently not implemented as per YAGNI principle + } +}; + +// About Section Tabs +const AboutTabs = { + tabs: null, + panels: null, + + /** + * Initialize about section tabs + */ + init() { + this.tabs = document.querySelectorAll(SELECTORS.ABOUT_TABS); + this.panels = document.querySelectorAll(SELECTORS.ABOUT_PANELS); + + if (!this.tabs.length) return; + + this.tabs.forEach(tab => { + tab.addEventListener('click', () => this.switchTab(tab)); + }); + }, + + /** + * Switch to selected tab + * @param {HTMLElement} selectedTab - Tab element that was clicked + */ + switchTab(selectedTab) { + const targetPanelId = selectedTab.getAttribute(DATA_ATTRS.TAB); + + // Remove active class from all tabs and panels + this.tabs.forEach(tab => tab.classList.remove(CLASSES.ACTIVE)); + this.panels.forEach(panel => panel.classList.remove(CLASSES.ACTIVE)); + + // Add active class to selected tab and corresponding panel + selectedTab.classList.add(CLASSES.ACTIVE); + const targetPanel = document.getElementById(targetPanelId); + if (targetPanel) { + targetPanel.classList.add(CLASSES.ACTIVE); + } + } +}; + +// Product Showcase Component +const ProductShowcase = { + expandButton: null, + toolsGrid: null, + + /** + * Initialize product showcase + */ + init() { + this.expandButton = document.querySelector(SELECTORS.EXPAND_BUTTON); + this.toolsGrid = document.querySelector(SELECTORS.TOOLS_GRID); + + if (!this.expandButton || !this.toolsGrid) return; + + this.expandButton.addEventListener('click', () => this.toggleExpand()); + }, + + /** + * Toggle expand/collapse state + */ + toggleExpand() { + const isExpanded = this.expandButton.getAttribute(DATA_ATTRS.EXPANDED) === 'true'; + + if (isExpanded) { + this.collapse(); + } else { + this.expand(); + } + }, + + /** + * Expand the tools grid + */ + expand() { + this.toolsGrid.classList.remove(CLASSES.COLLAPSED); + this.expandButton.setAttribute(DATA_ATTRS.EXPANDED, 'true'); + this.updateExpandButtonText(); + }, + + /** + * Collapse the tools grid + */ + collapse() { + this.toolsGrid.classList.add(CLASSES.COLLAPSED); + this.expandButton.setAttribute(DATA_ATTRS.EXPANDED, 'false'); + this.updateExpandButtonText(); + }, + + /** + * Update expand button text based on state + */ + updateExpandButtonText() { + const expandText = this.expandButton?.querySelector('.expand-text'); + if (!expandText) return; + + const isExpanded = this.expandButton.getAttribute(DATA_ATTRS.EXPANDED) === 'true'; + expandText.textContent = getTranslation(isExpanded ? 'hideDetails' : 'expandDetails'); + } +}; + +// Login Modal Component +const LoginModal = { + modalElement: null, + modalStyles: null, + + /** + * Show login modal + */ + show() { + this.createModal(); + this.attachEventListeners(); + }, + + /** + * Create modal HTML and styles + */ + createModal() { + // Create modal element + this.modalElement = document.createElement('div'); + this.modalElement.className = 'login-modal'; + this.modalElement.innerHTML = this.getModalHTML(); + document.body.appendChild(this.modalElement); + + // Add modal styles if not already added + if (!this.modalStyles) { + this.addModalStyles(); + } + }, + + /** + * Get modal HTML content + * @returns {string} Modal HTML + */ + getModalHTML() { + const t = getTranslation; + return ` + + `; + }, + + /** + * Add modal styles to document + */ + addModalStyles() { + this.modalStyles = document.createElement('style'); + this.modalStyles.textContent = ` + .login-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(10, 24, 50, 0.85); + backdrop-filter: blur(12px); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + animation: fadeIn 0.3s ease; + } + .modal-content { + background: #0A1832; + border-radius: 12px; + padding: 2.5rem; + max-width: 400px; + width: 90%; + position: relative; + border: 1px solid rgba(200, 168, 81, 0.3); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + } + .modal-header { + text-align: center; + margin-bottom: 1.5rem; + } + .modal-header .lock-icon { + width: 48px; + height: 48px; + color: #C8A851; + margin-bottom: 1rem; + } + .modal-close { + position: absolute; + top: 1rem; + right: 1rem; + background: none; + border: none; + color: rgba(255, 255, 255, 0.4); + font-size: 2rem; + cursor: pointer; + transition: all 0.3s ease; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + } + .modal-close:hover { + background: rgba(255, 255, 255, 0.1); + color: #fff; + } + .modal-content h3 { + color: #FFFFFF; + margin-bottom: 0.5rem; + font-size: 1.5rem; + font-weight: 600; + } + .modal-content p { + color: rgba(255, 255, 255, 0.6); + margin-bottom: 2rem; + text-align: center; + } + .modal-content .form-group { + margin-bottom: 1.5rem; + } + .modal-content label { + display: block; + color: rgba(255, 255, 255, 0.8); + margin-bottom: 0.5rem; + font-weight: 500; + } + .modal-content input { + width: 100%; + padding: 0.875rem; + background: rgba(255, 255, 255, 0.05); + border: 2px solid rgba(255, 255, 255, 0.15); + border-radius: 8px; + color: #FFFFFF; + font-size: 1rem; + transition: all 0.3s ease; + } + .modal-content input:focus { + outline: none; + border-color: #C8A851; + background: rgba(255, 255, 255, 0.08); + } + .modal-content input::placeholder { + color: rgba(255, 255, 255, 0.3); + } + .modal-content .primary-button { + width: 100%; + padding: 0.875rem; + background: #C8A851; + color: #0A1832; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + } + .modal-content .primary-button:hover { + background: #D4B96A; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(200, 168, 81, 0.4); + } + .auth-note { + text-align: center; + margin-top: 1.5rem; + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.4); + } + .auth-note a { + color: #C8A851; + text-decoration: none; + } + .auth-note a:hover { + text-decoration: underline; + } + `; + document.head.appendChild(this.modalStyles); + }, + + /** + * Attach event listeners to modal + */ + attachEventListeners() { + // Close button + const closeBtn = this.modalElement.querySelector('.modal-close'); + closeBtn.addEventListener('click', () => this.close()); + + // Form submission + const form = this.modalElement.querySelector('#loginForm'); + form.addEventListener('submit', (e) => this.handleSubmit(e)); + + // Click outside to close + this.modalElement.addEventListener('click', (e) => { + if (e.target === this.modalElement) { + this.close(); + } + }); + }, + + /** + * Handle form submission + * @param {Event} e - Submit event + */ + async handleSubmit(e) { + e.preventDefault(); + const password = document.getElementById('auth-password').value; + + try { + // Validate token via Insights API + const response = await fetch('/insights/api/validate-token', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ token: password }) + }); + + const result = await response.json(); + + if (result.valid) { + sessionStorage.setItem(CONFIG.AUTH.SESSION_KEY, 'true'); + this.close(); + window.location.href = CONFIG.AUTH.REDIRECT_PAGE; + } else { + alert(getTranslation('wrongCode')); + document.getElementById('auth-password').value = ''; + document.getElementById('auth-password').focus(); + } + } catch (error) { + console.error('Token validation error:', error); + alert(getTranslation('wrongCode')); + } + }, + + /** + * Close and remove modal + */ + close() { + if (this.modalElement) { + this.modalElement.remove(); + this.modalElement = null; + } + } +}; + +// Contact Form Component +const ContactForm = { + form: null, + + /** + * Initialize contact form + */ + init() { + this.form = document.querySelector(SELECTORS.CONTACT_FORM); + if (!this.form) return; + + this.form.addEventListener('submit', (e) => this.handleSubmit(e)); + }, + + /** + * Handle form submission + * @param {Event} e - Submit event + */ + handleSubmit(e) { + e.preventDefault(); + + // Get form data + const formData = new FormData(this.form); + const data = Object.fromEntries(formData.entries()); + + // In production, this would send data to server + console.log('Form submission:', data); + + // Show success message + alert(getTranslation('contactFormSuccess')); + this.form.reset(); + } +}; + +// Demo Request Handler +const DemoRequest = { + /** + * Initialize demo request buttons + */ + init() { + document.querySelectorAll('.primary-button, .secondary-button, .cta-button').forEach(button => { + if (button.textContent.toLowerCase().includes('demo')) { + button.addEventListener('click', (e) => this.handleDemoRequest(e)); + } + }); + }, + + /** + * Handle demo request + * @param {Event} e - Click event + */ + handleDemoRequest(e) { + e.preventDefault(); + alert(getTranslation('demoRequestAlert')); + } +}; + +// Initialize all components +const Components = { + /** + * Initialize all UI components + */ + init() { + LanguageToggle.init(); + Navigation.init(); + AboutTabs.init(); + ProductShowcase.init(); + ContactForm.init(); + DemoRequest.init(); + } +}; + +// Make showLoginModal globally available for onclick attribute +window.showLoginModal = function() { + LoginModal.show(); +}; + +// Make closeLoginModal globally available for onclick attribute +window.closeLoginModal = function() { + LoginModal.close(); +}; \ No newline at end of file diff --git a/js/config.js b/js/config.js new file mode 100644 index 0000000..4815459 --- /dev/null +++ b/js/config.js @@ -0,0 +1,145 @@ +/** + * Central configuration file for AegisSight website + * Contains all constants, settings and selectors + */ + +// Application Configuration +const CONFIG = { + // Animation Settings + ANIMATION: { + PARTICLE_COUNT: 100, + PARTICLE_SPEED: 3, + PARTICLE_SIZE_MIN: 1, + PARTICLE_SIZE_MAX: 3, + CONNECTION_DISTANCE: 100, + COUNTER_SPEED: 200, + SCROLL_THRESHOLD: 50, + FADE_DURATION: 500, + GLITCH_ITERATIONS: 5, + GLITCH_INTERVAL: 50 + }, + + // Hero Video Settings + HERO_VIDEOS: { + ROTATION_INTERVAL: 12000, // 12 seconds per video (slower like Palantir) + FADE_DURATION: 3000, // 3 second fade transition (much slower) + VIDEO_SOURCES: [ + 'assets/videos/hero-data-flow.mp4', + 'assets/videos/hero-network-viz.mp4', + 'assets/videos/hero-code-abstract.mp4' + ] + }, + + // Language Settings + I18N: { + DEFAULT_LANGUAGE: 'de', + SUPPORTED_LANGUAGES: ['de', 'en'], + STORAGE_KEY: 'aegissight_language' + }, + + // Intersection Observer Settings + OBSERVER: { + THRESHOLD: 0.3, + ROOT_MARGIN: '0px' + }, + + // Authentication Settings + AUTH: { + SESSION_KEY: 'accountForgerAuth', + REDIRECT_PAGE: 'accountforger-video.html' + }, + + // Timeouts and Intervals + TIMING: { + SCROLL_HIDE_DELAY: 500, + COUNTER_UPDATE_INTERVAL: 10, + MAP_POINT_SPAWN_INTERVAL: 5000, + RESPONSE_TIMER_UPDATE: 2000, + LIVE_COUNTER_UPDATE: 3000 + } +}; + +// DOM Selectors +const SELECTORS = { + // Navigation + NAVBAR: '.navbar', + NAV_MENU: '.nav-menu', + LANG_TOGGLE: '.lang-toggle', + + // Hero Section + HERO: '.hero', + HERO_CONTENT: '.hero-content', + HERO_VIDEO: '.hero-video', + PARTICLE_CANVAS: '#particleCanvas', + SCROLL_INDICATOR: '.scroll-indicator', + + + // About Section + ABOUT_TABS: '.about-tab', + ABOUT_PANELS: '.about-panel', + + // Products Section + EXPAND_BUTTON: '.expand-button', + TOOLS_GRID: '.tools-grid', + TOOL_CARDS: '.tool-card', + + // Modals + LOGIN_MODAL: '.login-modal', + MODAL_CLOSE: '.modal-close', + + // Forms + CONTACT_FORM: '#contactForm', + LOGIN_FORM: '#loginForm', + + // Animation Elements + INTERACTIVE_ICON: '#interactiveIcon', + NEURAL_CANVAS: '#neuralCanvas', + DATA_PARTICLES: '#dataParticles', + LIVE_COUNTER: '#liveCounter', + RESPONSE_TIMER: '#responseTimer', + MAP_POINTS: '#mapPoints', + + // Generic + TRANSLATABLE: '[data-translate]', + SMOOTH_LINKS: 'a[href^="#"]', + SKIP_NAV: '.skip-nav' +}; + +// CSS Classes +const CLASSES = { + ACTIVE: 'active', + SCROLLED: 'scrolled', + COLLAPSED: 'collapsed', + VISIBLE: 'visible', + LOADED: 'loaded', + EXPANDED: 'expanded', + HIDDEN: 'hidden', + + // Animation Classes + FADE_IN: 'fade-in', + FADE_OUT: 'fade-out', + SLIDE_UP: 'slide-up', + SLIDE_DOWN: 'slide-down', + + // Component Classes + PARTICLE: 'particle', + DATA_PARTICLE: 'data-particle', + MAP_POINT: 'map-point', + NODE: 'node', + WIDGET: 'widget', + TAB: 'tab', + PANEL: 'panel' +}; + +// Data Attributes +const DATA_ATTRS = { + TRANSLATE: 'data-translate', + TAB: 'data-tab', + EXPANDED: 'data-expanded', + LANG: 'data-lang', + TARGET: 'data-target', + TOOL: 'data-tool' +}; + +// Export for use in other modules (if using module system) +// For now, these are global constants available to all scripts \ No newline at end of file diff --git a/js/hero-videos.js b/js/hero-videos.js new file mode 100644 index 0000000..336d850 --- /dev/null +++ b/js/hero-videos.js @@ -0,0 +1,209 @@ +/** + * Hero Video Rotation System + * Manages rotating background videos in hero section + */ + +const HeroVideoRotation = { + videos: [], + currentIndex: 0, + rotationInterval: null, + isTransitioning: false, + + /** + * Initialize the video rotation system + */ + init() { + // Get all video elements + this.videos = document.querySelectorAll('.hero-video'); + + if (!this.videos.length) return; + + // Setup event listeners + this.setupEventListeners(); + + // Start rotation + this.startRotation(); + + // Ensure first video is playing + this.playVideo(0); + }, + + /** + * Setup event listeners for videos + */ + setupEventListeners() { + // Indicators removed - no click handlers needed + + // Pause rotation on hover (optional) + const heroSection = document.querySelector('.hero'); + if (heroSection) { + heroSection.addEventListener('mouseenter', () => { + // Optional: pause rotation on hover + // this.stopRotation(); + }); + + heroSection.addEventListener('mouseleave', () => { + // Optional: resume rotation + // this.startRotation(); + }); + } + + // Handle video load errors gracefully + this.videos.forEach((video, index) => { + video.addEventListener('error', () => { + console.warn(`Video ${index} failed to load, skipping...`); + // If current video fails, move to next + if (index === this.currentIndex) { + this.nextVideo(); + } + }); + + // Ensure videos are ready to play + video.addEventListener('loadeddata', () => { + console.log(`Video ${index} loaded successfully`); + }); + }); + }, + + /** + * Start automatic rotation + */ + startRotation() { + // Clear any existing interval + this.stopRotation(); + + // Set new interval + this.rotationInterval = setInterval(() => { + this.nextVideo(); + }, CONFIG.HERO_VIDEOS.ROTATION_INTERVAL); + }, + + /** + * Stop automatic rotation + */ + stopRotation() { + if (this.rotationInterval) { + clearInterval(this.rotationInterval); + this.rotationInterval = null; + } + }, + + /** + * Switch to next video + */ + nextVideo() { + const nextIndex = (this.currentIndex + 1) % this.videos.length; + this.switchToVideo(nextIndex); + }, + + /** + * Switch to previous video + */ + previousVideo() { + const prevIndex = (this.currentIndex - 1 + this.videos.length) % this.videos.length; + this.switchToVideo(prevIndex); + }, + + /** + * Switch to specific video by index + * @param {number} index - Video index to switch to + */ + switchToVideo(index) { + if (this.isTransitioning || index === this.currentIndex) return; + + this.isTransitioning = true; + + const currentVideo = this.videos[this.currentIndex]; + const nextVideo = this.videos[index]; + + // Indicators removed - no update needed + + // Prepare next video + this.prepareVideo(nextVideo); + + // Fade out current video + currentVideo.classList.add('fading-out'); + + // After half the fade duration, start fading in the next video + setTimeout(() => { + nextVideo.classList.add('active'); + nextVideo.classList.remove('fading-out'); + + // Play next video + this.playVideo(index); + }, CONFIG.HERO_VIDEOS.FADE_DURATION / 2); + + // Complete transition + setTimeout(() => { + currentVideo.classList.remove('active', 'fading-out'); + this.currentIndex = index; + this.isTransitioning = false; + }, CONFIG.HERO_VIDEOS.FADE_DURATION); + }, + + + /** + * Prepare video for playback + * @param {HTMLVideoElement} video - Video element to prepare + */ + prepareVideo(video) { + // Reset video to beginning + video.currentTime = 0; + + // Ensure video is ready to play + const playPromise = video.play(); + if (playPromise !== undefined) { + playPromise.catch(error => { + console.warn('Video autoplay was prevented:', error); + }); + } + }, + + /** + * Play specific video + * @param {number} index - Index of video to play + */ + playVideo(index) { + const video = this.videos[index]; + if (video) { + const playPromise = video.play(); + if (playPromise !== undefined) { + playPromise.catch(error => { + console.warn(`Could not play video ${index}:`, error); + }); + } + } + }, + + /** + * Pause all videos + */ + pauseAllVideos() { + this.videos.forEach(video => { + video.pause(); + }); + }, + + /** + * Handle page visibility change (pause when tab is not visible) + */ + handleVisibilityChange() { + if (document.hidden) { + this.stopRotation(); + this.pauseAllVideos(); + } else { + this.playVideo(this.currentIndex); + this.startRotation(); + } + } +}; + +// Initialize when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + HeroVideoRotation.init(); +}); + +// Handle page visibility API +document.addEventListener('visibilitychange', () => { + HeroVideoRotation.handleVisibilityChange(); +}); \ No newline at end of file diff --git a/js/legal-pages.js b/js/legal-pages.js new file mode 100644 index 0000000..76650f3 --- /dev/null +++ b/js/legal-pages.js @@ -0,0 +1,54 @@ +/** + * Minimal JavaScript for legal pages (Impressum & Datenschutz) + * Only includes necessary functionality for language switching + */ + +// Set current year in footer +function setCurrentYear() { + const currentYear = new Date().getFullYear(); + const yearElements = document.querySelectorAll('.current-year'); + yearElements.forEach(element => { + element.textContent = currentYear; + }); +} + +// Simple language toggle for legal pages +document.addEventListener('DOMContentLoaded', function() { + // Set current year immediately + setCurrentYear(); + + // Get the language toggle button + const langToggle = document.querySelector('.lang-toggle'); + + if (langToggle) { + langToggle.addEventListener('click', function(e) { + e.preventDefault(); + + // Get current language from button + const currentLang = this.getAttribute('data-lang') || 'de'; + const newLang = currentLang === 'de' ? 'en' : 'de'; + + // Store language preference + if (typeof(Storage) !== 'undefined') { + localStorage.setItem('aegissight_language', newLang); + } + + // Get current page name + const currentPage = window.location.pathname.split('/').pop(); + + // Determine redirect URL + let redirectUrl = ''; + + if (currentPage === 'impressum.html' || currentPage === 'impressum-en.html') { + redirectUrl = newLang === 'en' ? 'impressum-en.html' : 'impressum.html'; + } else if (currentPage === 'datenschutz.html' || currentPage === 'datenschutz-en.html') { + redirectUrl = newLang === 'en' ? 'datenschutz-en.html' : 'datenschutz.html'; + } + + // Redirect to the appropriate version + if (redirectUrl) { + window.location.href = redirectUrl; + } + }); + } +}); \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..ea96e7b --- /dev/null +++ b/js/main.js @@ -0,0 +1,305 @@ +/** + * Main application entry point for AegisSight website + * Initializes all modules and coordinates application startup + */ + +/** + * Toggle tools grid visibility + */ +function toggleTools(button) { + // Find the tools grid within the same product card + const productCard = button.closest('.product-card'); + const toolsGrid = productCard.querySelector('.tools-grid'); + + if (toolsGrid) { + const isExpanded = toolsGrid.classList.contains('expanded'); + const currentLang = getCurrentLanguage ? getCurrentLanguage() : 'de'; + + if (isExpanded) { + toolsGrid.classList.remove('expanded'); + toolsGrid.classList.add('collapsed'); + button.setAttribute('data-expanded', 'false'); + button.querySelector('span').textContent = currentLang === 'de' ? 'Details anzeigen' : 'Show Details'; + } else { + // Force browser reflow before adding expanded class + toolsGrid.style.display = 'grid'; + void toolsGrid.offsetHeight; // Trigger reflow + + toolsGrid.classList.remove('collapsed'); + toolsGrid.classList.add('expanded'); + button.setAttribute('data-expanded', 'true'); + button.querySelector('span').textContent = currentLang === 'de' ? 'Details verbergen' : 'Hide Details'; + + // Ensure all tool cards are visible + setTimeout(() => { + const toolCards = toolsGrid.querySelectorAll('.tool-card'); + toolCards.forEach((card, index) => { + card.style.opacity = '1'; + card.style.transform = 'translateY(0)'; + }); + }, 100); + } + } +} + +/** + * Application initialization + */ +const App = { + /** + * Initialize the entire application + */ + init() { + // Check DOM ready state + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => this.start()); + } else { + // DOM is already ready + this.start(); + } + }, + + /** + * Start the application after DOM is ready + */ + start() { + console.log('AegisSight Website Initializing...'); + + // Initialize modules in correct order + try { + // 1. Initialize translations first (includes year replacement) + initTranslations(); + console.log('✓ Translations initialized'); + + // 2. Initialize UI components + Components.init(); + console.log('✓ Components initialized'); + + // 3. Initialize animations + Animations.init(); + console.log('✓ Animations initialized'); + + // 4. Setup error handling + this.setupErrorHandling(); + + // 5. Setup performance monitoring + this.setupPerformanceMonitoring(); + + console.log('AegisSight Website Ready!'); + + } catch (error) { + console.error('Failed to initialize application:', error); + this.handleInitError(error); + } + }, + + /** + * Setup global error handling + */ + setupErrorHandling() { + window.addEventListener('error', (event) => { + console.error('Global error:', event.error); + // In production, this would send errors to monitoring service + }); + + window.addEventListener('unhandledrejection', (event) => { + console.error('Unhandled promise rejection:', event.reason); + // In production, this would send errors to monitoring service + }); + }, + + /** + * Setup performance monitoring + */ + setupPerformanceMonitoring() { + // Monitor page load performance + window.addEventListener('load', () => { + if (window.performance && window.performance.timing) { + const timing = window.performance.timing; + const loadTime = timing.loadEventEnd - timing.navigationStart; + console.log(`Page load time: ${loadTime}ms`); + + // Log other performance metrics + const metrics = { + domContentLoaded: timing.domContentLoadedEventEnd - timing.navigationStart, + domComplete: timing.domComplete - timing.navigationStart, + firstPaint: this.getFirstPaintTime() + }; + + console.log('Performance metrics:', metrics); + } + }); + }, + + /** + * Set current year in footer and update translations dynamically + */ + setCurrentYear() { + const currentYear = new Date().getFullYear(); + + // Set current year in main footer span element + const yearElement = document.getElementById('currentYear'); + if (yearElement) { + yearElement.textContent = currentYear; + } + + // Set current year in legal pages footer spans + const legalYearElements = document.querySelectorAll('.current-year'); + legalYearElements.forEach(element => { + element.textContent = currentYear; + }); + + // Update copyright translation with current year + if (window.translations) { + Object.keys(window.translations).forEach(lang => { + if (window.translations[lang].copyright) { + window.translations[lang].copyright = window.translations[lang].copyright.replace('{year}', currentYear); + } + }); + } + }, + + /** + * Get first paint time if available + * @returns {number|null} First paint time in milliseconds + */ + getFirstPaintTime() { + if (window.performance && window.performance.getEntriesByType) { + const paintEntries = window.performance.getEntriesByType('paint'); + const firstPaint = paintEntries.find(entry => entry.name === 'first-paint'); + return firstPaint ? Math.round(firstPaint.startTime) : null; + } + return null; + }, + + /** + * Handle initialization errors + * @param {Error} error - The error that occurred + */ + handleInitError(error) { + // Create a fallback error message for users + const errorContainer = document.createElement('div'); + errorContainer.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #ff4444; + color: white; + padding: 15px 20px; + border-radius: 5px; + z-index: 10000; + max-width: 300px; + box-shadow: 0 2px 10px rgba(0,0,0,0.2); + `; + errorContainer.textContent = 'Ein Fehler ist aufgetreten. Bitte laden Sie die Seite neu.'; + document.body.appendChild(errorContainer); + + // Auto-remove after 5 seconds + setTimeout(() => { + errorContainer.remove(); + }, 5000); + } +}; + +/** + * Utility functions + */ +const Utils = { + /** + * Debounce function to limit function calls + * @param {Function} func - Function to debounce + * @param {number} wait - Wait time in milliseconds + * @returns {Function} Debounced function + */ + debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }, + + /** + * Throttle function to limit function calls + * @param {Function} func - Function to throttle + * @param {number} limit - Time limit in milliseconds + * @returns {Function} Throttled function + */ + throttle(func, limit) { + let inThrottle; + return function(...args) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; + }, + + /** + * Check if element is in viewport + * @param {HTMLElement} element - Element to check + * @returns {boolean} True if element is in viewport + */ + isInViewport(element) { + const rect = element.getBoundingClientRect(); + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && + rect.right <= (window.innerWidth || document.documentElement.clientWidth) + ); + }, + + /** + * Load script dynamically + * @param {string} src - Script source URL + * @returns {Promise} Promise that resolves when script is loaded + */ + loadScript(src) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = src; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + }, + + /** + * Get cookie value by name + * @param {string} name - Cookie name + * @returns {string|null} Cookie value or null + */ + getCookie(name) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) { + return parts.pop().split(';').shift(); + } + return null; + }, + + /** + * Set cookie + * @param {string} name - Cookie name + * @param {string} value - Cookie value + * @param {number} days - Days until expiration + */ + setCookie(name, value, days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + const expires = `expires=${date.toUTCString()}`; + document.cookie = `${name}=${value};${expires};path=/`; + } +}; + +// Make Utils globally available if needed +window.Utils = Utils; + +// Start the application +App.init(); \ No newline at end of file diff --git a/js/mobile-nav.js b/js/mobile-nav.js new file mode 100644 index 0000000..6cbd780 --- /dev/null +++ b/js/mobile-nav.js @@ -0,0 +1,196 @@ +/** + * Mobile Navigation Handler + * Clean, accessible mobile navigation implementation + */ + +class MobileNavigation { + constructor() { + this.menuToggle = document.querySelector('.mobile-menu-toggle'); + this.mobileMenu = document.querySelector('.nav-menu-mobile'); + this.overlay = document.querySelector('.mobile-menu-overlay'); + this.menuLinks = document.querySelectorAll('.nav-menu-mobile a'); + this.closeButton = document.querySelector('.mobile-menu-close'); + this.isOpen = false; + + this.init(); + } + + init() { + if (!this.menuToggle || !this.mobileMenu) return; + + // Toggle button click + this.menuToggle.addEventListener('click', () => this.toggleMenu()); + + // Close button click + if (this.closeButton) { + this.closeButton.addEventListener('click', () => this.closeMenu()); + } + + // Overlay click closes menu + this.overlay.addEventListener('click', () => this.closeMenu()); + + // Menu links click closes menu + this.menuLinks.forEach(link => { + link.addEventListener('click', () => this.closeMenu()); + }); + + // ESC key closes menu + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && this.isOpen) { + this.closeMenu(); + } + }); + + // Prevent body scroll when menu is open + this.handleBodyScroll(); + } + + toggleMenu() { + this.isOpen ? this.closeMenu() : this.openMenu(); + } + + openMenu() { + this.isOpen = true; + this.menuToggle.classList.add('active'); + this.mobileMenu.classList.add('active'); + this.overlay.classList.add('active'); + + // Update ARIA attributes + this.menuToggle.setAttribute('aria-expanded', 'true'); + this.mobileMenu.setAttribute('aria-hidden', 'false'); + + // Prevent body scroll + document.body.style.overflow = 'hidden'; + + // Focus management + setTimeout(() => { + const firstLink = this.mobileMenu.querySelector('a'); + if (firstLink) firstLink.focus(); + }, 300); + } + + closeMenu() { + this.isOpen = false; + this.menuToggle.classList.remove('active'); + this.mobileMenu.classList.remove('active'); + this.overlay.classList.remove('active'); + + // Update ARIA attributes + this.menuToggle.setAttribute('aria-expanded', 'false'); + this.mobileMenu.setAttribute('aria-hidden', 'true'); + + // Restore body scroll + document.body.style.overflow = ''; + + // Return focus to toggle button + this.menuToggle.focus(); + } + + handleBodyScroll() { + // Save scroll position when menu opens + let scrollPosition = 0; + + const observer = new MutationObserver(() => { + if (this.isOpen) { + scrollPosition = window.pageYOffset; + document.body.style.position = 'fixed'; + document.body.style.top = `-${scrollPosition}px`; + document.body.style.width = '100%'; + } else { + document.body.style.position = ''; + document.body.style.top = ''; + document.body.style.width = ''; + window.scrollTo(0, scrollPosition); + } + }); + + observer.observe(this.mobileMenu, { + attributes: true, + attributeFilter: ['class'] + }); + } +} + +// Smooth scroll for anchor links +class SmoothScroll { + constructor() { + this.init(); + } + + init() { + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', (e) => { + const href = anchor.getAttribute('href'); + if (href === '#') return; + + e.preventDefault(); + const target = document.querySelector(href); + + if (target) { + const offset = 80; // Account for fixed navbar + const targetPosition = target.offsetTop - offset; + + window.scrollTo({ + top: targetPosition, + behavior: 'smooth' + }); + } + }); + }); + } +} + +// Responsive image loading +class ResponsiveImages { + constructor() { + this.init(); + } + + init() { + // Check if user prefers reduced motion + const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + if (prefersReducedMotion) { + // Disable animations + document.documentElement.style.setProperty('--animation-duration', '0.01s'); + } + + // Lazy load images on mobile + if ('IntersectionObserver' in window && window.innerWidth <= 768) { + const images = document.querySelectorAll('img[data-src]'); + + const imageObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.src; + img.removeAttribute('data-src'); + imageObserver.unobserve(img); + } + }); + }); + + images.forEach(img => imageObserver.observe(img)); + } + } +} + +// Initialize on DOM load +document.addEventListener('DOMContentLoaded', () => { + new MobileNavigation(); + new SmoothScroll(); + new ResponsiveImages(); + + // Hide mobile menu button styles until JS loads + document.documentElement.classList.add('js-loaded'); +}); + +// Handle orientation change +window.addEventListener('orientationchange', () => { + // Close mobile menu on orientation change + const mobileNav = document.querySelector('.nav-menu-mobile'); + if (mobileNav && mobileNav.classList.contains('active')) { + const event = new Event('click'); + document.querySelector('.mobile-menu-overlay').dispatchEvent(event); + } +}); \ No newline at end of file diff --git a/js/section-transitions.js b/js/section-transitions.js new file mode 100644 index 0000000..e0ac19d --- /dev/null +++ b/js/section-transitions.js @@ -0,0 +1,130 @@ +/** + * Section Transitions & Effects + * Modern animations for section dividers + */ + +const SectionTransitions = { + init() { + this.initParticleBridge(); + this.initScrollReveal(); + this.initWaveAnimation(); + this.initParallaxDividers(); + }, + + // Animated particles between sections + initParticleBridge() { + const bridge = document.getElementById('particleBridge'); + if (!bridge) return; + + // Create floating particles + for (let i = 0; i < 30; i++) { + const particle = document.createElement('div'); + particle.className = 'particle'; + particle.style.left = Math.random() * 100 + '%'; + particle.style.animationDelay = Math.random() * 5 + 's'; + particle.style.animationDuration = (5 + Math.random() * 5) + 's'; + particle.style.animation = `floatParticle ${5 + Math.random() * 5}s linear infinite`; + bridge.appendChild(particle); + } + }, + + // Reveal sections on scroll + initScrollReveal() { + const sections = document.querySelectorAll('.fade-section'); + + const revealSection = (entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('visible'); + + // Add shimmer effect on reveal + const shimmer = document.createElement('div'); + shimmer.style.cssText = ` + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(15, 114, 181, 0.2), transparent); + animation: shimmerPass 1s ease-out forwards; + pointer-events: none; + z-index: 100; + `; + entry.target.style.position = 'relative'; + entry.target.appendChild(shimmer); + + setTimeout(() => shimmer.remove(), 1000); + } + }); + }; + + const observer = new IntersectionObserver(revealSection, { + threshold: 0.1, + rootMargin: '0px 0px -100px 0px' + }); + + sections.forEach(section => observer.observe(section)); + }, + + // Animate wave dividers + initWaveAnimation() { + const waves = document.querySelectorAll('.wave-divider path'); + + waves.forEach(wave => { + let time = 0; + const animateWave = () => { + time += 0.02; + const points = []; + + for (let i = 0; i <= 10; i++) { + const x = (i / 10) * 1200; + const y = Math.sin((i / 10) * Math.PI * 2 + time) * 10 + 56; + points.push(`${x},${y}`); + } + + // Create smooth wave path + const d = `M0,56 Q${points[2]} T${points[4]} T${points[6]} T${points[8]} T1200,56 L1200,0 L0,0 Z`; + wave.setAttribute('d', d); + + requestAnimationFrame(animateWave); + }; + + // Start wave animation + // animateWave(); // Commented out for performance, uncomment for wave motion + }); + }, + + // Parallax effect for dividers + initParallaxDividers() { + const dividers = document.querySelectorAll('.blob-divider, .gradient-divider, .flow-lines'); + + window.addEventListener('scroll', () => { + const scrolled = window.pageYOffset; + + dividers.forEach(divider => { + const speed = divider.dataset.speed || 0.5; + const yPos = -(scrolled * speed); + divider.style.transform = `translateY(${yPos}px)`; + }); + }); + } +}; + +// Add shimmer animation +const style = document.createElement('style'); +style.textContent = ` + @keyframes shimmerPass { + to { + left: 100%; + opacity: 0; + } + } +`; +document.head.appendChild(style); + +// Initialize when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => SectionTransitions.init()); +} else { + SectionTransitions.init(); +} \ No newline at end of file diff --git a/js/translations.js b/js/translations.js new file mode 100644 index 0000000..db6eee7 --- /dev/null +++ b/js/translations.js @@ -0,0 +1,498 @@ +/** + * Translation system for AegisSight website + * Supports German (de) and English (en) + */ + +// Translation strings +const translations = { + de: { + // Page meta + pageTitle: 'AegisSight - Sicherheit Made in Germany', + + // Navigation + skipNav: 'Zum Hauptinhalt springen', + navHome: 'Startseite', + navAbout: 'Über uns', + navProducts: 'Lösungen', + navContact: 'Kontakt', + navLagebild: 'Lagebild', + langSwitch: 'DE | EN', + + // Hero Section + heroTitle: 'SICHERHEIT MADE IN GERMANY', + heroSubtitle: 'Spezialist für hochsichere, maßgeschneiderte IT-Lösungen für Behörden', + + // Trust Indicators + + // Scroll Indicator + scrollToExplore: 'Nach unten scrollen', + + // About Section + aboutTitle: 'ÜBER UNS', + aboutSubtitle: 'Ihr Partner für sichere Behördensoftware', + + // About Tabs + tabWhoWeAre: 'Unternehmen', + tabMission: 'Mission & Werte', + tabCompetencies: 'Kernkompetenzen', + tabWhyUs: 'Unser Versprechen', + + // Who We Are + whoWeAreTitle: 'Unternehmen', + companyCardTitle1: 'Spezialist für Behördensoftware', + companyCardTitle2: 'Unser Ansatz', + whoWeArePara1: 'AegisSight UG ist Ihr Spezialist für hochsichere, maßgeschneiderte IT-Lösungen aus Nordrhein-Westfalen. Wir entwickeln innovative Software speziell für staatliche Sicherheits- und Ermittlungsbehörden.', + whoWeArePara2: 'Unser Ansatz vereint modernste Technologie mit einem tiefen Verständnis für die besonderen Anforderungen von Behörden. Dabei steht die Balance zwischen Sicherheit, Effizienz und rechtskonformer Umsetzung im Mittelpunkt unserer Arbeit.', + locationBadge: 'Nordrhein-Westfalen, Deutschland', + nrwLabel: 'Nordrhein-Westfalen', + headquartersLabel: 'Unser Standort: Langenfeld', + + // Mission & Values + missionTitle: 'Unsere Mission', + missionStatement: 'Wir schaffen effiziente, sichere und datenschutzkonforme Lösungen für moderne Strafverfolgung und Sicherheitsbehörden.', + valueIntegrityTitle: 'Integrität', + valueIntegrityDesc: 'Höchste ethische Standards in allem was wir tun', + valueTransparencyTitle: 'Transparenz', + valueTransparencyDesc: 'Offene Kommunikation und nachvollziehbare Prozesse', + valueDemocracyTitle: 'Demokratische Prinzipien', + valueDemocracyDesc: 'Kooperation nur mit Behörden im Einklang mit der freiheitlich demokratischen Grundordnung', + principleNote: 'Unser Ziel: Technologie, die Recht und Sicherheit stärkt und die freiheitlich demokratische Grundordnung schützt.', + + // Competencies + competenciesTitle: 'Unsere Kernkompetenzen', + comp1Title: 'Behördenspezifische Software', + comp1Desc: 'Entwicklung mit höchsten Sicherheitsstandards, maßgeschneidert für staatliche Anforderungen', + comp2Title: 'Intuitive Bedienkonzepte', + comp2Desc: 'Benutzerfreundliche Oberflächen trotz komplexer Funktionen für effizientes Arbeiten', + comp3Title: 'Langzeit-Support', + comp3Desc: 'Kontinuierliche Sicherheitsupdates und zuverlässige Wartung über den gesamten Lebenszyklus', + + // Why Us + whyUsTitle: 'Warum AegisSight UG?', + why1Title: 'Enge Zusammenarbeit', + why1Desc: 'Wir arbeiten Hand in Hand mit unseren Kunden für maßgeschneiderte Lösungen', + why2Title: 'Made in Germany', + why2Desc: 'Klare, robuste und sichere Software nach deutschen Qualitätsstandards', + why3Title: 'Verlässliche Partnerschaft', + why3Desc: 'Basierend auf gemeinsamen Werten und langfristigem Vertrauen', + why4Title: 'Nachhaltigkeit', + why4Desc: 'Fokus auf Sicherheit, Professionalität & zukunftssichere Lösungen', + + // Products Section + productsTitle: 'LÖSUNGEN', + productsSubtitle: 'Professionelle Werkzeuge für moderne Ermittlungsarbeit', + + // Professional Toolbox + productToolboxTitle: 'Professional Toolbox', + productToolboxDesc: 'Eine leistungsstarke Desktop-Anwendung mit fünf essentiellen Tools für behördliche OSINT-Ermittler und Analysten. Modernes Design, intuitive Bedienung, professionelle Funktionen.', + expandDetails: 'Details anzeigen', + hideDetails: 'Details verbergen', + + // Tools + tool1Title: 'Metadata Analyzer', + tool1Feature1: 'Extrahiert versteckte Informationen (EXIF, GPS, Erstellungsdaten)', + tool1Feature2: 'Forensische Analyse von Dokumenten & Bildern', + tool1Feature3: 'Export als JSON', + + tool2Title: 'Screen Recorder', + tool2Feature1: 'Bildschirmaufnahme mit Audio (System & Mikrofon)', + tool2Feature2: 'Bereichsauswahl oder Vollbild', + tool2Feature3: 'Wählbare Qualitätsstufen', + + tool3Title: 'Video Crawler', + tool3Feature1: 'Download von Videos aus 1000+ Plattformen', + tool3Feature2: 'Automatischer Untertitel-Download', + tool3Feature3: 'Qualitätsauswahl', + + tool4Title: 'Website Crawler', + tool4Feature1: 'Archiviert Webseiten offline', + tool4Feature2: 'Einstellbare Crawling-Tiefe', + tool4Feature3: 'Erhält Originalstruktur inkl. CSS, JS & Medien', + + tool5Title: 'Multimedia Converter', + tool5Feature1: 'Konvertierung von Bildern, Videos, Audio', + tool5Feature2: 'Batch-Verarbeitung', + tool5Feature3: 'Drag & Drop Unterstützung', + + // AccountForger + productAccountForgerTitle: 'AccountForger', + accessRestricted: 'Zugang nur mit Berechtigung', + protectedProductDesc: 'Dieses Produkt ist speziell für autorisierte Behörden entwickelt und erfordert eine Authentifizierung.', + loginForAccess: 'Anmelden für Zugriff', + // OSINT Monitor + productOsintMonitorTitle: 'AegisSight Monitor', + productOsintMonitorTagline: 'Open Source Intelligence - automatisiert', + productOsintMonitorDesc: 'AegisSight Monitor aggregiert, analysiert und verifiziert Informationen aus öffentlich zugänglichen Quellen in Echtzeit. Erleben Sie die Plattform live am Beispiel des Iran-Livetickers.', + osintMonitorButton: 'Iran-Liveticker öffnen', + + + // Lagebild Page + lagebildPageTitle: 'Lagebild - AegisSight', + lagebildTitle: 'LAGEBILD', + lagebildLive: 'LIVE-LAGEBILD', + lagebildSubtitle: 'Automatisierte Situationsberichte vom AegisSight Monitor', + lagebildSelectSnapshot: 'Lagebild vom:', + lagebildCurrent: 'Aktuell', + lagebildPoweredBy: 'Erstellt durch', + lagebildAnalysis: 'Lageanalyse', + lagebildSources: 'Quellen', + lagebildFactChecks: 'Faktenchecks', + lagebildFactChecksDesc: 'Automatisierte Verifizierung durch KI-gestützte Quellenanalyse', + lagebildArticles: 'Quellenberichte', + lagebildArticlesDesc: 'Automatisch aggregierte Meldungen aus internationalen Quellen', + lagebildCtaTitle: 'Interesse an AegisSight Monitor?', + lagebildCtaText: 'Erhalten Sie Echtzeit-Lagebilder für Ihre Organisation mit KI-gestützter Analyse und Faktencheck.', + lagebildCtaButton: 'Kontakt aufnehmen', + // Footer + footerCompanyTitle: 'AegisSight UG (haftungsbeschränkt)', + footerCompanyAddress1: 'Gladbacher Strasse 3-5', + footerCompanyAddress2: '40764 Langenfeld', + footerNavTitle: 'Navigation', + footerNavHome: 'Startseite', + footerNavAbout: 'Über uns', + footerNavProducts: 'Lösungen', + + footerLegalTitle: 'Rechtliches', + footerImprint: 'Impressum', + footerPrivacy: 'Datenschutz', + footerCookies: 'Cookie-Einstellungen', + + footerContactTitle: 'Kontakt', + copyright: '© {year} AegisSight UG (haftungsbeschränkt). Alle Rechte vorbehalten.', + + // Modal texts + authRequired: 'Authentifizierung erforderlich', + authDescription: 'Dieser Bereich ist nur für autorisierte Behörden zugänglich.', + accessCode: 'Zugangscode', + accessCodePlaceholder: 'Bitte Zugangscode eingeben', + grantAccess: 'Zugang gewähren', + noAccess: 'Noch keinen Zugang?', + contactUs: 'Kontaktieren Sie uns', + accessGranted: 'Zugang gewährt! AccountForger wird geladen...', + wrongCode: 'Falscher Zugangscode. Bitte versuchen Sie es erneut.', + demoRequestAlert: 'Demo-Anfrage-Funktion würde hier implementiert werden', + contactFormSuccess: 'Vielen Dank für Ihre Nachricht! Wir werden uns schnellstmöglich bei Ihnen melden.' + }, + + en: { + // Page meta + pageTitle: 'AegisSight - Sicherheit Made in Germany', + + // Navigation + skipNav: 'Skip to main content', + navHome: 'Home', + navAbout: 'About Us', + navProducts: 'Solutions', + navContact: 'Contact', + navLagebild: 'Situation Report', + langSwitch: 'EN | DE', + + // Hero Section + heroTitle: 'SICHERHEIT MADE IN GERMANY', + heroSubtitle: 'Specialist for highly secure, customized IT solutions for government agencies', + + // Trust Indicators + + // Scroll Indicator + scrollToExplore: 'Scroll to Explore', + + // About Section + aboutTitle: 'About Us', + aboutSubtitle: 'Your Partner for Secure Government Software', + + // About Tabs + tabWhoWeAre: 'Company', + tabMission: 'Mission & Values', + tabCompetencies: 'Core Competencies', + tabWhyUs: 'Our Promise', + + // Who We Are + whoWeAreTitle: 'Company', + companyCardTitle1: 'Government Software Specialist', + companyCardTitle2: 'Our Approach', + whoWeArePara1: 'AegisSight UG is your specialist for highly secure, customized IT solutions from North Rhine-Westphalia. We develop innovative software specifically for government security and law enforcement agencies.', + whoWeArePara2: 'Our approach combines cutting-edge technology with a deep understanding of the special requirements of government agencies. The balance between security, efficiency and legally compliant implementation is at the center of our work.', + locationBadge: 'North Rhine-Westphalia, Germany', + nrwLabel: 'North Rhine-Westphalia', + headquartersLabel: 'Our Location: Langenfeld', + + // Mission & Values + missionTitle: 'Our Mission', + missionStatement: 'We create efficient, secure and data protection compliant solutions for modern law enforcement and security agencies.', + valueIntegrityTitle: 'Integrity', + valueIntegrityDesc: 'Highest ethical standards in everything we do', + valueTransparencyTitle: 'Transparency', + valueTransparencyDesc: 'Open communication and comprehensible processes', + valueDemocracyTitle: 'Democratic Principles', + valueDemocracyDesc: 'Cooperation only with agencies in accordance with the liberal democratic basic order', + principleNote: 'Our Goal: Technology that strengthens law and security and protects the liberal democratic basic order.', + + // Competencies + competenciesTitle: 'Our Core Competencies', + comp1Title: 'Agency-Specific Software', + comp1Desc: 'Development with highest security standards, tailored for government requirements', + comp2Title: 'Intuitive Operating Concepts', + comp2Desc: 'User-friendly interfaces despite complex functions for efficient work', + comp3Title: 'Long-term Support', + comp3Desc: 'Continuous security updates and reliable maintenance throughout the entire lifecycle', + + // Why Us + whyUsTitle: 'Why AegisSight UG?', + why1Title: 'Close Collaboration', + why1Desc: 'We work hand in hand with our customers for customized solutions', + why2Title: 'Made in Germany', + why2Desc: 'Clear, robust and secure software according to German quality standards', + why3Title: 'Reliable Partnership', + why3Desc: 'Based on shared values and long-term trust', + why4Title: 'Sustainability', + why4Desc: 'Focus on security, professionalism & future-proof solutions', + + // Products Section + productsTitle: 'Solutions', + productsSubtitle: 'Professional Tools for Modern Investigation Work', + + // Professional Toolbox + productToolboxTitle: 'Professional Toolbox', + productToolboxDesc: 'A powerful desktop application with five essential tools for government OSINT investigators and analysts. Modern design, intuitive operation, professional functions.', + expandDetails: 'Show Details', + hideDetails: 'Hide Details', + + // Tools + tool1Title: 'Metadata Analyzer', + tool1Feature1: 'Extracts hidden information (EXIF, GPS, creation dates)', + tool1Feature2: 'Forensic analysis of documents & images', + tool1Feature3: 'Export as JSON', + + tool2Title: 'Screen Recorder', + tool2Feature1: 'Screen recording with audio (system & microphone)', + tool2Feature2: 'Area selection or full screen', + tool2Feature3: 'Selectable quality levels', + + tool3Title: 'Video Crawler', + tool3Feature1: 'Download videos from 1000+ platforms', + tool3Feature2: 'Automatic subtitle download', + tool3Feature3: 'Quality selection', + + tool4Title: 'Website Crawler', + tool4Feature1: 'Archives websites offline', + tool4Feature2: 'Adjustable crawling depth', + tool4Feature3: 'Preserves original structure incl. CSS, JS & media', + + tool5Title: 'Multimedia Converter', + tool5Feature1: 'Conversion of images, videos, audio', + tool5Feature2: 'Batch processing', + tool5Feature3: 'Drag & Drop support', + + // AccountForger + productAccountForgerTitle: 'AccountForger', + accessRestricted: 'Access by authorization only', + protectedProductDesc: 'This product is specifically developed for authorized agencies and requires authentication.', + loginForAccess: 'Login for Access', + // OSINT Monitor + productOsintMonitorTitle: 'AegisSight Monitor', + productOsintMonitorTagline: 'Open Source Intelligence - Automated', + productOsintMonitorDesc: 'AegisSight Monitor aggregates, analyzes and verifies information from publicly available sources in real time. Experience the platform live with the Iran Live Ticker.', + osintMonitorButton: 'Open Iran Live Ticker', + + + // Lagebild Page + lagebildPageTitle: 'Situation Report - AegisSight', + lagebildTitle: 'SITUATION REPORT', + lagebildLive: 'LIVE REPORT', + lagebildSubtitle: 'Automated situation reports from AegisSight Monitor', + lagebildSelectSnapshot: 'Report from:', + lagebildCurrent: 'Current', + lagebildPoweredBy: 'Powered by', + lagebildAnalysis: 'Situation Analysis', + lagebildSources: 'Sources', + lagebildFactChecks: 'Fact Checks', + lagebildFactChecksDesc: 'Automated verification through AI-powered source analysis', + lagebildArticles: 'Source Reports', + lagebildArticlesDesc: 'Automatically aggregated reports from international sources', + lagebildCtaTitle: 'Interested in AegisSight Monitor?', + lagebildCtaText: 'Get real-time situation reports for your organization with AI-powered analysis and fact checking.', + lagebildCtaButton: 'Get in touch', + // Footer + footerCompanyTitle: 'AegisSight UG (haftungsbeschränkt)', + footerCompanyAddress1: 'Gladbacher Strasse 3-5', + footerCompanyAddress2: '40764 Langenfeld', + footerNavTitle: 'Navigation', + footerNavHome: 'Home', + footerNavAbout: 'About Us', + footerNavProducts: 'Solutions', + + footerLegalTitle: 'Legal', + footerImprint: 'Imprint', + footerPrivacy: 'Privacy Policy', + footerCookies: 'Cookie Settings', + + footerContactTitle: 'Contact', + copyright: '© {year} AegisSight UG (haftungsbeschränkt). All rights reserved.', + + // Modal texts + authRequired: 'Authentication Required', + authDescription: 'This area is only accessible to authorized agencies.', + accessCode: 'Access Code', + accessCodePlaceholder: 'Please enter access code', + grantAccess: 'Grant Access', + noAccess: 'No access yet?', + contactUs: 'Contact Us', + accessGranted: 'Access granted! AccountForger is loading...', + wrongCode: 'Wrong access code. Please try again.', + demoRequestAlert: 'Demo request function would be implemented here', + contactFormSuccess: 'Thank you for your message! We will get back to you as soon as possible.' + } +}; + +// Current language state +let currentLanguage = CONFIG.I18N.DEFAULT_LANGUAGE; + +/** + * Initialize the translation system + */ +function initTranslations() { + // Try to get saved language from localStorage + const savedLanguage = localStorage.getItem(CONFIG.I18N.STORAGE_KEY); + if (savedLanguage && CONFIG.I18N.SUPPORTED_LANGUAGES.includes(savedLanguage)) { + currentLanguage = savedLanguage; + } + + // Apply initial translations + applyTranslations(currentLanguage); +} + +/** + * Switch to a different language + * @param {string} language - Language code ('de' or 'en') + */ +function switchLanguage(language) { + if (!CONFIG.I18N.SUPPORTED_LANGUAGES.includes(language)) { + console.error(`Language '${language}' is not supported`); + return; + } + + currentLanguage = language; + localStorage.setItem(CONFIG.I18N.STORAGE_KEY, language); + applyTranslations(language); + + // Update cookie consent language + if (typeof CookieConsent !== 'undefined' && CookieConsent.setLanguage) { + CookieConsent.setLanguage(language); + } +} + +/** + * Apply translations to all elements with data-translate attribute + * @param {string} language - Language code to apply + */ +function applyTranslations(language) { + const t = translations[language]; + + if (!t) { + console.error(`Translations for language '${language}' not found`); + return; + } + + // Update page title + document.title = t.pageTitle; + + // Update HTML lang attribute + document.documentElement.lang = language; + + // Update all translatable elements + document.querySelectorAll(SELECTORS.TRANSLATABLE).forEach(element => { + const key = element.getAttribute(DATA_ATTRS.TRANSLATE); + + if (t[key]) { + let content = t[key]; + + // Replace {year} placeholder with current year + if (content.includes('{year}')) { + const currentYear = new Date().getFullYear(); + content = content.replace('{year}', currentYear); + } + + // Check if content contains HTML tags + if (content.includes('') || content.includes('')) { + element.innerHTML = content; + } else { + element.textContent = content; + } + } + }); + + // Update language toggle button + const langToggle = document.querySelector(SELECTORS.LANG_TOGGLE); + if (langToggle) { + langToggle.textContent = t.langSwitch; + langToggle.setAttribute(DATA_ATTRS.LANG, language); + } + + // Update expand button text if it exists + updateExpandButtonText(language); + + // Update footer legal links based on language + updateFooterLinks(language); +} + +/** + * Update expand button text based on current state + * @param {string} language - Current language + */ +function updateExpandButtonText(language) { + const expandButton = document.querySelector(SELECTORS.EXPAND_BUTTON); + if (expandButton) { + const expandText = expandButton.querySelector('.expand-text'); + const isExpanded = expandButton.getAttribute(DATA_ATTRS.EXPANDED) === 'true'; + const t = translations[language]; + + if (expandText && t) { + expandText.textContent = isExpanded ? t.hideDetails : t.expandDetails; + } + } +} + +/** + * Get a specific translation string + * @param {string} key - Translation key + * @returns {string} Translated text + */ +function getTranslation(key) { + return translations[currentLanguage][key] || key; +} + +/** + * Get current language + * @returns {string} Current language code + */ +function getCurrentLanguage() { + return currentLanguage; +} + +/** + * Update footer legal links based on language + * @param {string} language - Current language code + */ +function updateFooterLinks(language) { + // Get footer links + const impressumLink = document.querySelector('a[href="impressum.html"], a[href="impressum-en.html"]'); + const datenschutzLink = document.querySelector('a[href="datenschutz.html"], a[href="datenschutz-en.html"]'); + + if (language === 'en') { + // Switch to English versions + if (impressumLink) { + impressumLink.href = 'impressum-en.html'; + } + if (datenschutzLink) { + datenschutzLink.href = 'datenschutz-en.html'; + } + } else { + // Switch to German versions + if (impressumLink) { + impressumLink.href = 'impressum.html'; + } + if (datenschutzLink) { + datenschutzLink.href = 'datenschutz.html'; + } + } +} \ No newline at end of file diff --git a/lagen/cyberangriffe/index.html b/lagen/cyberangriffe/index.html new file mode 100644 index 0000000..ee016ee --- /dev/null +++ b/lagen/cyberangriffe/index.html @@ -0,0 +1,141 @@ + + + + + + Lagebild: Cyberangriffe auf deutsche Infrastruktur - AegisSight + + + + + + + + + + + + +
+ + +
+
+ +
+
+

LAGEBILD

+

+
+
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+
+
+
+

Daten bereitgestellt durch AegisSight Monitor

+
+
+

Neueste Entwicklungen

+ +
+
+
+
+
+
+
+

Daten bereitgestellt durch AegisSight Monitor

+
+
+

Lagebild

+ +
+
+
+
+ +
+
+
+
+

Quellen

Alle vom AegisSight Monitor überwachten Quellen

+
+
+
+
+
+

Karte

Geografische Einordnung der Meldungen

+
+
+
+
+
+

Faktenchecks

KI-gestützte Verifizierung aller zentralen Aussagen gegen unabhängige Quellen.

+
+
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/lagen/deepfakes/index.html b/lagen/deepfakes/index.html new file mode 100644 index 0000000..89a9a1f --- /dev/null +++ b/lagen/deepfakes/index.html @@ -0,0 +1,212 @@ + + + + + + Recherche: Rechtliche Lage von Deepfakes in Deutschland - AegisSight + + + + + + + + + + + + + +
+ + + + + + +
+ + +
+ +
+
+

RECHERCHE

+

+
+
+
+ + +
+
+
+
+
+
+
+ + + + + +
+
+
+ + +
+
+ +
+

Daten bereitgestellt durch AegisSight Monitor

+
+
+

Zusammenfassung

+ +
+
+
+
+
+
+
+
+
+
+ + +
+

Daten bereitgestellt durch AegisSight Monitor

+
+
+

Recherche

+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+

Quellen

+

Alle vom AegisSight Monitor ausgewerteten Quellen

+
+
+
+
+
+
+
+
+
+ + +
+
+
+

Karte

+

Geografische Einordnung der Quellen

+
+
+
+
+
+
+ + +
+
+
+

Faktenchecks

+

KI-gestützte Verifizierung aller zentralen Aussagen gegen unabhängige Quellen.

+
+
+
+
+
+
+ + + + + + + + + + + + + + diff --git a/lagen/iran-konflikt/index.html b/lagen/iran-konflikt/index.html new file mode 100644 index 0000000..09ac7e2 --- /dev/null +++ b/lagen/iran-konflikt/index.html @@ -0,0 +1,218 @@ + + + + + + Lagebild Irankonflikt - AegisSight + + + + + + + + + + + + + +
+ + + + + + +
+ + +
+ +
+
+

LAGEBILD

+

+ + +
+ + +
+
+ + +
+
+
+
+
+
+
+ + + + + +
+
+
+ + +
+
+ +
+

Daten bereitgestellt durch AegisSight Monitor

+
+
+

Neueste Entwicklungen

+ +
+
+
+
+
+
+
+
+
+
+ + +
+

Daten bereitgestellt durch AegisSight Monitor

+
+
+

Lagebild

+ +
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+

Quellen

+

Alle vom AegisSight Monitor überwachten Quellen

+
+
+
+
+
+
+
+
+
+ + +
+
+
+

Karte

+

Geografische Einordnung der Meldungen

+
+
+
+
+
+
+ + +
+
+
+

Faktenchecks

+

KI-gestützte Verifizierung aller zentralen Aussagen gegen unabhängige Quellen. Alle Aussagen werden kontinuierlich gegen neue Quellen geprüft – bei neuer Evidenz ändert sich der Status automatisch.

+
+
+
+
+ + +
+
+ + + + + + + + + + + + + + diff --git a/lagen/iran-konflikt/lagebild.css b/lagen/iran-konflikt/lagebild.css new file mode 100644 index 0000000..0942f58 --- /dev/null +++ b/lagen/iran-konflikt/lagebild.css @@ -0,0 +1,1633 @@ +/* ========================================================================== + AegisSight Lagebild Page - Dark Theme Design Refresh + ========================================================================== */ + +/* ---------- Variables ---------- */ +.lagebild-page { + --lb-bg: #0B1121; + --lb-bg-card: #151D2E; + --lb-bg-secondary: #1A2440; + --lb-border: #1E2D45; + --lb-text: #E8ECF4; + --lb-text-sec: #8896AB; + --lb-accent: #C8A851; + --lb-accent-hover: #B5923E; + --lb-success: #10B981; + --lb-warning: #F59E0B; + --lb-error: #EF4444; + --lb-glow: rgba(200, 168, 81, 0.35); + --lb-glow-soft: rgba(200, 168, 81, 0.15); +} + +/* ---------- Page Base ---------- */ +.lagebild-page { + background: var(--lb-bg); + min-height: 100vh; + color: var(--lb-text); +} + +/* ---------- Scroll Progress Bar ---------- */ +.scroll-progress { + position: fixed; + top: 0; + left: 0; + height: 3px; + width: 0; + background: linear-gradient(90deg, var(--lb-accent), #E8D48B); + z-index: 99999; + transition: width 0.08s linear; + box-shadow: 0 0 10px var(--lb-glow), 0 0 3px var(--lb-accent); +} + +/* ---------- Navigation Dark Override ---------- */ +.lagebild-page .navbar { + background: rgba(11, 17, 33, 0.95) !important; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-bottom: 1px solid var(--lb-border) !important; + box-shadow: none !important; +} +.lagebild-page .logo-img { + background: rgba(160, 175, 200, 0.85); + padding: 4px 10px; + border-radius: 6px; +} +.lagebild-page .nav-menu a { + color: var(--lb-text) !important; +} +.lagebild-page .nav-menu a:hover { + color: var(--lb-accent) !important; +} +.lagebild-page .nav-menu a.nav-active { + color: var(--lb-accent) !important; +} +.lagebild-page .lang-toggle { + color: var(--lb-text-sec) !important; + border-color: var(--lb-border) !important; +} +.lagebild-page .lang-toggle:hover { + color: var(--lb-accent) !important; + border-color: var(--lb-accent) !important; +} +.lagebild-page .hamburger span { + background: var(--lb-text) !important; +} + +/* ---------- Footer Dark Override ---------- */ +.lagebild-page .footer { + background: #080D1A !important; + border-top: 1px solid var(--lb-border); +} +.lagebild-page .footer h4 { + color: var(--lb-accent) !important; +} +.lagebild-page .footer p, +.lagebild-page .footer a, +.lagebild-page .footer li, +.lagebild-page .footer ul li a { + color: var(--lb-text-sec) !important; +} +.lagebild-page .footer a:hover { + color: var(--lb-accent) !important; +} +.lagebild-page .copyright { + color: var(--lb-text-sec) !important; + opacity: 0.5; +} + +/* ---------- Hero Section ---------- */ +.lagebild-hero { + background: linear-gradient(135deg, #0a0f1c 0%, #111B30 50%, #0B1121 100%); + padding: 150px 20px 70px; + text-align: center; + color: var(--lb-text); + font-weight: normal; + position: relative; + overflow: hidden; +} +.hero-bg-pattern { + position: absolute; + inset: 0; + background: + radial-gradient(circle at 20% 80%, rgba(200, 168, 81, 0.08) 0%, transparent 50%), + radial-gradient(circle at 80% 20%, rgba(200, 168, 81, 0.05) 0%, transparent 50%), + radial-gradient(circle at 50% 50%, rgba(15, 114, 181, 0.06) 0%, transparent 60%); + pointer-events: none; +} +.lagebild-hero .container { + position: relative; + z-index: 1; +} + +/* Hero Particles Canvas */ +#hero-particles { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; +} + +/* Hero Badge */ +.hero-badge { + display: inline-flex; + align-items: center; + gap: 8px; + background: rgba(200, 168, 81, 0.12); + border: 1px solid rgba(200, 168, 81, 0.25); + padding: 6px 16px; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 1.5px; + color: var(--lb-accent); + margin-bottom: 1.5rem; + box-shadow: 0 0 20px var(--lb-glow-soft); +} +.badge-dot { + width: 8px; + height: 8px; + background: var(--lb-accent); + border-radius: 50%; + animation: pulse-dot 2s infinite; + box-shadow: 0 0 8px var(--lb-glow); +} +@keyframes pulse-dot { + 0%, 100% { opacity: 1; transform: scale(1); } + 50% { opacity: 0.4; transform: scale(0.7); } +} + +/* Data Source Note */ +.data-source-note { + font-size: 0.75rem; + color: var(--lb-text-sec); + letter-spacing: 0.3px; + margin: 0 0 12px; + padding: 0; +} + +/* Hero Title */ +.lagebild-hero h1 { + font-family: 'Bebas Neue', sans-serif; + font-size: 3.5rem; + letter-spacing: 5px; + color: var(--lb-text); + font-weight: normal; + margin: 0 0 0.3rem; + line-height: 1; +} +.hero-incident-title { + font-size: 1.5rem; + font-weight: 300; + color: rgba(255, 255, 255, 0.85); + margin: 0 0 0.5rem; +} +.hero-date-info { + font-size: 0.85em; + color: rgba(255, 255, 255, 0.5); + font-weight: 400; +} + +/* Hero Stat Cards */ +.hero-stats { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 14px; + margin-top: 2rem; + max-width: 650px; + margin-left: auto; + margin-right: auto; +} +.stat-card { + display: flex; + align-items: center; + gap: 12px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.08); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + padding: 14px 16px; + border-radius: var(--radius-md, 8px); + transition: border-color 0.3s, background 0.3s, box-shadow 0.3s; +} +.stat-card:hover { + border-color: rgba(200, 168, 81, 0.4); + background: rgba(255, 255, 255, 0.08); + box-shadow: 0 0 24px var(--lb-glow-soft), 0 4px 16px rgba(0, 0, 0, 0.2); +} +.stat-card-icon { + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(200, 168, 81, 0.1); + border-radius: var(--radius-md, 8px); + color: var(--lb-accent); + flex-shrink: 0; +} +.stat-card-icon svg { + width: 18px; + height: 18px; +} +.stat-card-content { + min-width: 0; +} +.stat-card-value { + display: block; + font-size: 1.35rem; + font-weight: 700; + color: var(--lb-text); + font-weight: normal; + line-height: 1.1; +} +.stat-card-label { + display: block; + font-size: 0.72rem; + color: rgba(255, 255, 255, 0.55); + text-transform: uppercase; + letter-spacing: 0.5px; + margin-top: 2px; +} + +/* ---------- Live Feed Ticker ---------- */ +.live-feed { + margin-top: 1.5rem; + height: 28px; + overflow: hidden; + position: relative; +} +.live-feed-item { + position: absolute; + width: 100%; + text-align: center; + font-size: 0.82rem; + color: rgba(255, 255, 255, 0.5); + letter-spacing: 0.3px; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + opacity: 0; + transform: translateY(10px); + transition: opacity 0.5s, transform 0.5s; +} +.live-feed-item.active { + opacity: 1; + transform: translateY(0); +} +.live-feed-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--lb-accent); + box-shadow: 0 0 6px var(--lb-glow); + animation: pulse-dot 2s infinite; + flex-shrink: 0; +} + +/* ---------- Control Bar ---------- */ +.control-bar { + background: var(--lb-bg-card); + border-bottom: 1px solid var(--lb-border); + padding: 0 20px; +} +.control-bar .container { + max-width: 1000px; + margin: 0 auto; +} + +/* Timeline Strip */ +.timeline-wrapper { + padding: 12px 0; + border-bottom: 1px solid var(--lb-border); +} +.timeline-strip { + display: flex; + gap: 4px; + overflow-x: auto; + scroll-behavior: smooth; + padding: 4px 0; + scrollbar-width: thin; + scrollbar-color: rgba(200, 168, 81, 0.4) rgba(200, 168, 81, 0.08); +} +.timeline-strip::-webkit-scrollbar { + height: 4px; +} +.timeline-strip::-webkit-scrollbar-track { + background: transparent; +} +.timeline-strip::-webkit-scrollbar-thumb { + background: var(--lb-border); + border-radius: 2px; +} +.timeline-day { + display: flex; + flex-direction: column; + align-items: center; + padding: 8px 14px; + border-radius: var(--radius-sm, 4px); + border: 1px solid var(--lb-border); + background: transparent; + cursor: pointer; + transition: all 0.2s; + flex-shrink: 0; + min-width: 64px; + position: relative; + color: var(--lb-text-sec); + font-family: inherit; +} +.timeline-day:hover { + background: var(--lb-bg-secondary); + border-color: rgba(200, 168, 81, 0.4); +} +.timeline-day.active { + background: rgba(200, 168, 81, 0.1); + border-color: var(--lb-accent); + box-shadow: 0 0 12px var(--lb-glow-soft); +} +.timeline-dot { + position: absolute; + top: -4px; + right: -4px; + width: 10px; + height: 10px; + background: var(--lb-accent); + border-radius: 50%; + animation: pulse-dot 2s infinite; + border: 2px solid var(--lb-bg-card); + box-shadow: 0 0 8px var(--lb-glow); +} +.timeline-day-num { + font-size: 1.1rem; + font-weight: 700; + line-height: 1; +} +.timeline-day.active .timeline-day-num { + color: var(--lb-accent); +} +.timeline-day-month { + font-size: 0.62rem; + text-transform: uppercase; + letter-spacing: 0.5px; + opacity: 0.7; + margin-top: 1px; +} +.timeline-day-count { + font-size: 0.68rem; + margin-top: 4px; + padding-top: 4px; + border-top: 1px solid var(--lb-border); + width: 100%; + text-align: center; + opacity: 0.6; +} +.timeline-day.active .timeline-day-count { + opacity: 1; + color: var(--lb-accent); +} +.timeline-day-updates { + font-size: 0.58rem; + opacity: 0.4; +} +.timeline-day-label { + font-size: 0.58rem; + color: var(--lb-accent); + font-weight: 600; + margin-top: 2px; +} + +/* Timeline Dropdown (Horizontal Timeline) */ +.timeline-dropdown { + display: none; + background: var(--lb-bg-secondary); + border: 1px solid var(--lb-border); + border-top: none; + border-radius: 0 0 var(--radius-sm, 4px) var(--radius-sm, 4px); + padding: 8px 0; + margin-top: 4px; + overflow-x: auto; + scrollbar-width: thin; + scrollbar-color: rgba(200, 168, 81, 0.4) rgba(200, 168, 81, 0.08); +} +.timeline-dropdown.open { + display: block; +} +.timeline-dropdown::-webkit-scrollbar { + height: 6px; +} +.timeline-dropdown::-webkit-scrollbar-track { + background: rgba(200, 168, 81, 0.08); + border-radius: 3px; +} +.timeline-dropdown::-webkit-scrollbar-thumb { + background: rgba(200, 168, 81, 0.35); + border-radius: 3px; +} +.timeline-dropdown::-webkit-scrollbar-thumb:hover { + background: rgba(200, 168, 81, 0.55); +} +.h-timeline-track { + display: flex; + align-items: flex-start; + min-width: min-content; + padding: 4px 24px; + gap: 32px; +} +.h-timeline-point { + display: flex; + flex-direction: column; + align-items: center; + flex: 0 0 auto; + min-width: 70px; + position: relative; + cursor: pointer; + padding: 4px 0; + background: none; + border: none; + font-family: inherit; + transition: all 0.2s; +} +/* Connecting line through dot center */ +.h-timeline-point::before { + content: ''; + position: absolute; + top: 29px; + left: -16px; + right: -16px; + height: 2px; + background: rgba(200, 168, 81, 0.2); + z-index: 0; +} +.h-timeline-point:first-child::before { + left: 50%; + right: -16px; +} +.h-timeline-point:last-child::before { + left: -16px; + right: 50%; +} +.h-timeline-point:only-child::before { + display: none; +} +.h-timeline-time { + font-size: 0.75rem; + font-weight: 600; + color: var(--lb-text-sec); + margin-bottom: 8px; + line-height: 1; + transition: color 0.2s; +} +.h-timeline-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: rgba(200, 168, 81, 0.35); + z-index: 1; + position: relative; + transition: all 0.2s; + box-shadow: 0 0 4px rgba(200, 168, 81, 0.1); +} +.h-timeline-meta { + font-size: 0.65rem; + color: var(--lb-text-sec); + line-height: 1.4; + margin-top: 2px; + transition: color 0.2s; + white-space: normal; + text-align: center; +} +.h-timeline-meta:first-of-type { + margin-top: 8px; +} +/* Hover */ +.h-timeline-point:hover .h-timeline-time { + color: var(--lb-text); +} +.h-timeline-point:hover .h-timeline-dot { + background: var(--lb-accent); + box-shadow: 0 0 8px var(--lb-glow-soft); + transform: scale(1.2); +} +/* Active */ +.h-timeline-point.active .h-timeline-time { + color: var(--lb-accent); +} +.h-timeline-point.active .h-timeline-dot { + width: 12px; + height: 12px; + margin: -1px 0; + background: var(--lb-accent); + box-shadow: 0 0 12px var(--lb-glow); + animation: h-timeline-pulse 2s infinite; +} +@keyframes h-timeline-pulse { + 0%, 100% { box-shadow: 0 0 12px var(--lb-glow); transform: scale(1); } + 50% { box-shadow: 0 0 20px var(--lb-glow), 0 0 6px var(--lb-accent); transform: scale(1.15); } +} +.h-timeline-point.active .h-timeline-meta { + color: var(--lb-accent); + font-weight: 600; +} + +/* Tab Navigation */ +.tab-nav { + justify-content: center; + display: flex; + gap: 0; +} +.tab-btn { + padding: 12px 20px; + border: none; + background: transparent; + cursor: pointer; + font-size: 0.9rem; + font-weight: 600; + color: var(--lb-text-sec); + border-bottom: 3px solid transparent; + transition: all 0.2s; + font-family: inherit; +} +.tab-btn:hover { + color: var(--lb-text); + text-shadow: 0 0 12px var(--lb-glow-soft); +} +.tab-btn.active { + color: var(--lb-accent); + border-bottom-color: var(--lb-accent); + text-shadow: 0 0 16px var(--lb-glow-soft); +} +.tab-badge { + display: inline-block; + background: rgba(200, 168, 81, 0.12); + color: var(--lb-accent); + padding: 1px 8px; + border-radius: 10px; + font-size: 0.7rem; + font-weight: 600; + margin-left: 4px; + vertical-align: middle; +} +.tab-btn.active .tab-badge { + background: rgba(200, 168, 81, 0.22); +} + +/* Tab Panels with Slide Animation */ +.tab-panel { display: none; } +.tab-panel.active { + display: block; + animation: tabFadeSlideIn 0.35s ease-out; +} +@keyframes tabFadeSlideIn { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} + +/* ---------- Main Content ---------- */ +.lagebild-main { + padding: 2rem 20px 4rem; +} +.lagebild-main .container { + max-width: 1000px; + margin: 0 auto; +} + +/* ---------- Content Cards ---------- */ +.content-card { + background: var(--lb-bg-card); + border-radius: var(--radius-sm, 4px); + border: 1px solid var(--lb-border); + margin-bottom: 1.5rem; + overflow: hidden; + position: relative; +} +.content-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--lb-accent); + box-shadow: 0 2px 12px var(--lb-glow-soft); +} +.card-header { + padding: 24px 32px 0; + display: flex; + justify-content: space-between; + align-items: baseline; + flex-wrap: wrap; + gap: 8px; +} +.card-header h2 { + font-size: 1.3rem; + font-weight: 700; + color: var(--lb-text); + margin: 0; +} +.card-timestamp { + font-size: 0.85rem; + color: var(--lb-text-sec); + font-weight: 500; +} +.card-description { + font-size: 0.85rem; + color: var(--lb-text-sec); + margin: 4px 0 0; + width: 100%; +} +.card-body { + padding: 20px 32px 28px; +} +.card-footer { + padding: 0 32px 28px; + border-top: 1px solid var(--lb-border); + padding-top: 20px; + margin: 0 32px; +} + +/* ---------- Lagebild Summary ---------- */ +#summary-content { + line-height: 1.7; + font-size: 0.9rem; + color: var(--lb-text); +} +#summary-content h2 { + font-size: 1.15rem; + font-weight: 700; + color: var(--lb-accent); + margin: 1.8rem 0 0.8rem; + padding-bottom: 0.4rem; + border-bottom: 2px solid var(--lb-border); +} +#summary-content h2:first-child { + margin-top: 0; +} +#summary-content h3 { + font-size: 1.05rem; + font-weight: 600; + color: var(--lb-text); + margin: 1.5rem 0 0.6rem; +} +#summary-content p { + margin: 0 0 1rem; +} +#summary-content ul { + margin: 0 0 1rem; + padding-left: 1.5rem; +} +#summary-content li { + margin-bottom: 0.4rem; + color: var(--lb-text); +} +#summary-content strong { + color: var(--lb-text); + font-weight: normal; +} + +/* Citations */ +.citation-ref { + color: var(--lb-accent); + font-weight: 700; + cursor: pointer; + text-decoration: none; + font-size: 0.78em; + vertical-align: super; + line-height: 1; + padding: 0 1px; + transition: color 0.2s; +} +.citation-ref:hover { + color: var(--lb-accent-hover); + text-decoration: underline; +} + +/* Source Highlight (smooth scroll target) */ +.source-highlight { + background: rgba(200, 168, 81, 0.15) !important; + border-radius: 4px; + transition: background 1.5s ease-out; +} + +/* ---------- Source Overview Grid ---------- */ +.sources-overview-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + flex-wrap: wrap; + gap: 12px; +} +.sources-overview-title { + font-size: 1.05rem; + font-weight: 700; + color: var(--lb-text); +} +.sources-lang-chips { + display: flex; + gap: 8px; +} +.sources-lang-chip { + background: rgba(200, 168, 81, 0.12); + color: var(--lb-accent); + padding: 4px 12px; + border-radius: 12px; + font-size: 0.78rem; + font-weight: 600; +} +.sources-grid { + display: grid; + grid-template-columns: repeat(6, 1fr); + gap: 10px; +} +.source-tile { + background: var(--lb-bg-secondary); + border: 1px solid var(--lb-border); + border-radius: 4px; + padding: 10px; + cursor: pointer; + transition: all 0.2s; + position: relative; + display: flex; + flex-direction: column; + gap: 6px; + min-height: 64px; + min-width: 0; + overflow: hidden; +} +.source-tile::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: var(--lb-border); + border-radius: 4px 4px 0 0; + transition: background 0.2s; +} +.source-tile:hover { + border-color: rgba(200, 168, 81, 0.4); + box-shadow: 0 0 20px var(--lb-glow-soft); +} +.source-tile:hover::before { + background: var(--lb-accent); +} +.source-tile.active { + border-color: var(--lb-accent); + box-shadow: 0 0 24px var(--lb-glow-soft); + background: rgba(200, 168, 81, 0.06); +} +.source-tile.active::before { + background: var(--lb-accent); +} +.source-tile-top { + display: flex; + align-items: center; + gap: 6px; + min-width: 0; +} +.source-tile-favicon { + width: 16px; + height: 16px; + border-radius: 2px; + flex-shrink: 0; +} +.source-tile-name { + font-size: 0.78rem; + font-weight: 600; + color: var(--lb-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.source-tile-bottom { + display: flex; + justify-content: space-between; + align-items: flex-end; + margin-top: auto; +} +.source-tile-lang { + font-size: 0.65rem; + color: var(--lb-text-sec); + background: var(--lb-bg-card); + padding: 2px 6px; + border-radius: 3px; + border: 1px solid var(--lb-border); + text-transform: uppercase; +} +.source-tile-count { + font-size: 1.1rem; + font-weight: 800; + color: var(--lb-accent); + line-height: 1; +} + +/* Source Detail Panel */ +.source-detail-panel { + grid-column: 1 / -1; + background: var(--lb-bg-card); + border: 1px solid rgba(200, 168, 81, 0.3); + border-radius: 4px; + overflow: hidden; + animation: sourceDetailSlideIn 0.3s ease-out; +} +@keyframes sourceDetailSlideIn { + from { opacity: 0; transform: translateY(-8px); } + to { opacity: 1; transform: translateY(0); } +} +.source-detail-header { + display: flex; + align-items: center; + gap: 12px; + padding: 16px 20px; + border-bottom: 1px solid var(--lb-border); + background: rgba(200, 168, 81, 0.04); +} +.source-detail-name { + font-size: 1rem; + font-weight: 700; + color: var(--lb-text); + display: flex; + align-items: center; + gap: 8px; +} +.source-detail-count { + font-size: 0.85rem; + color: var(--lb-text-sec); + margin-left: auto; +} +.source-detail-close { + background: none; + border: none; + color: var(--lb-text-sec); + font-size: 1.5rem; + cursor: pointer; + padding: 0 4px; + line-height: 1; + transition: color 0.2s; + font-family: inherit; +} +.source-detail-close:hover { + color: var(--lb-text); +} +.source-detail-articles { + padding: 8px 20px 16px; + max-height: 500px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--lb-border) transparent; +} +.source-detail-article { + display: flex; + justify-content: space-between; + align-items: center; + gap: 16px; + padding: 8px 0; + border-bottom: 1px solid var(--lb-border); +} +.source-detail-article:last-child { + border-bottom: none; +} +.source-detail-article-title { + font-size: 0.88rem; + color: var(--lb-text); + text-decoration: none; + flex: 1; + min-width: 0; + line-height: 1.4; +} +a.source-detail-article-title:hover { + color: var(--lb-accent); +} +.source-detail-article-date { + font-size: 0.78rem; + color: var(--lb-text-sec); + white-space: nowrap; + flex-shrink: 0; +} + +/* Inline sources (Lagebild tab footer) - Collapsible */ +.inline-sources-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 0.9rem; + font-weight: 600; + color: var(--lb-text-sec); + cursor: pointer; + padding: 10px 16px; + user-select: none; + transition: all 0.2s; + background: var(--lb-bg-secondary); + border: 1px solid var(--lb-border); + border-radius: var(--radius-sm, 4px); +} +.inline-sources-toggle:hover { + color: var(--lb-accent); + border-color: rgba(200, 168, 81, 0.4); + background: rgba(200, 168, 81, 0.06); +} +.inline-sources-arrow { + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + font-size: 1.1rem; + transition: transform 0.2s; + color: var(--lb-accent); +} +#inline-sources.open .inline-sources-arrow { + transform: rotate(90deg); +} +.inline-sources-body { + display: none; + padding: 8px 0 0; + font-size: 0.82rem; + line-height: 1.8; + color: var(--lb-text-sec); +} +#inline-sources.open .inline-sources-body { + display: block; +} +.src-inline .src-nr { + color: var(--lb-accent); + font-weight: 700; +} +.src-sep { + color: var(--lb-border); + margin: 0 2px; +} +.inline-sources-body a { + color: var(--lb-accent); + text-decoration: none; +} +.inline-sources-body a:hover { + text-decoration: underline; +} + + + +/* ---------- Map ---------- */ +#map-container { + background: var(--lb-bg-secondary); +} +.lagebild-page .map-legend { + background: var(--lb-bg-card) !important; + color: var(--lb-text) !important; + border: 1px solid var(--lb-border) !important; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3) !important; +} +/* Leaflet controls dark */ +.lagebild-page .leaflet-control-zoom a { + background: var(--lb-bg-card) !important; + color: var(--lb-text) !important; + border-color: var(--lb-border) !important; +} +.lagebild-page .leaflet-control-zoom a:hover { + background: var(--lb-bg-secondary) !important; +} +.lagebild-page .leaflet-control-attribution { + background: rgba(11, 17, 33, 0.8) !important; + color: var(--lb-text-sec) !important; +} +.lagebild-page .leaflet-control-attribution a { + color: var(--lb-accent) !important; +} + +/* Map Pulse Markers */ +.pulse-marker-wrapper { + position: relative; + width: 20px; + height: 20px; +} +.pulse-marker-ring { + position: absolute; + width: 20px; + height: 20px; + border-radius: 50%; + border: 2px solid; + animation: mapPulseRing 2.5s ease-out infinite; + opacity: 0; +} +.pulse-marker-ring:nth-child(2) { + animation-delay: 0.8s; +} +.pulse-marker-dot { + position: absolute; + width: 10px; + height: 10px; + border-radius: 50%; + top: 5px; + left: 5px; +} +@keyframes mapPulseRing { + 0% { transform: scale(1); opacity: 0.8; } + 100% { transform: scale(3.5); opacity: 0; } +} + +/* ---------- Fact Checks (Filter + Accordion) ---------- */ +.fc-stats { + justify-content: center; + display: flex; + gap: 10px; + margin-bottom: 20px; + flex-wrap: wrap; +} +.fc-stat { + background: var(--lb-bg-secondary); + border-radius: var(--radius-sm, 4px); + padding: 12px 18px; + text-align: center; + min-width: 75px; + border: 1px solid var(--lb-border); + cursor: pointer; + transition: all 0.2s; + font-family: inherit; +} +.fc-stat:hover { + border-color: rgba(200, 168, 81, 0.4); + box-shadow: 0 0 16px var(--lb-glow-soft); +} +.fc-stat.active { + border-color: var(--lb-accent); + box-shadow: 0 0 20px var(--lb-glow-soft); + background: rgba(200, 168, 81, 0.06); +} +.fc-stat.confirmed { + background: rgba(16, 185, 129, 0.08); + border-color: rgba(16, 185, 129, 0.2); +} +.fc-stat.confirmed.active { + border-color: var(--lb-success); + box-shadow: 0 0 16px rgba(16, 185, 129, 0.2); +} +.fc-stat.unconfirmed { + background: rgba(245, 158, 11, 0.08); + border-color: rgba(245, 158, 11, 0.2); +} +.fc-stat.unconfirmed.active { + border-color: var(--lb-warning); + box-shadow: 0 0 16px rgba(245, 158, 11, 0.2); +} +.fc-stat.contradicted { + background: rgba(239, 68, 68, 0.08); + border-color: rgba(239, 68, 68, 0.2); +} +.fc-stat.contradicted.active { + border-color: var(--lb-error); + box-shadow: 0 0 16px rgba(239, 68, 68, 0.2); +} +.fc-stat-num { + display: block; + font-size: 1.5rem; + font-weight: 800; + color: var(--lb-text); +} +.fc-stat.confirmed .fc-stat-num { color: var(--lb-success); } +.fc-stat.unconfirmed .fc-stat-num { color: var(--lb-warning); } +.fc-stat.contradicted .fc-stat-num { color: var(--lb-error); } +.fc-stat-label { + font-size: 0.72rem; + color: var(--lb-text-sec); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 600; +} + +/* Factcheck Icon (from real Monitor) */ +.fc-icon { + flex-shrink: 0; + width: 22px; + height: 22px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; +} +.fc-icon.small { + width: 18px; + height: 18px; + font-size: 10px; +} +.fc-icon.confirmed, .fc-icon.established { + background: rgba(16, 185, 129, 0.15); + color: #34d399; +} +.fc-icon.unconfirmed, .fc-icon.unverified { + background: rgba(245, 158, 11, 0.15); + color: #fbbf24; +} +.fc-icon.contradicted, .fc-icon.disputed, .fc-icon.false { + background: rgba(239, 68, 68, 0.15); + color: #f87171; +} +.fc-icon.developing { + background: rgba(59, 130, 246, 0.15); + color: #60a5fa; +} + +/* Accordion List */ +.fc-list { + border: 1px solid var(--lb-border); + border-radius: 4px; + overflow: hidden; +} +.fc-row { + border-bottom: 1px solid var(--lb-border); +} +.fc-row:last-child { + border-bottom: none; +} +.fc-row-header { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + cursor: pointer; + transition: background 0.15s; +} +.fc-row-header:hover { + background: var(--lb-bg-secondary); +} +.fc-row.open .fc-row-header { + background: var(--lb-bg-secondary); + border-bottom: 1px solid var(--lb-border); +} +.fc-row-claim { + flex: 1; + font-size: 0.88rem; + color: var(--lb-text); + line-height: 1.4; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.fc-row.open .fc-row-claim { + white-space: normal; + text-overflow: unset; +} +.fc-row-sources { + flex-shrink: 0; + font-size: 0.72rem; + color: var(--lb-text-sec); + background: var(--lb-bg-card); + border: 1px solid var(--lb-border); + padding: 2px 7px; + border-radius: 3px; + font-weight: 600; +} +.fc-row-history-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--lb-accent); + flex-shrink: 0; + box-shadow: 0 0 6px var(--lb-glow); +} +.fc-row-chevron { + flex-shrink: 0; + color: var(--lb-text-sec); + font-size: 0.8rem; + transition: transform 0.2s; + width: 16px; + text-align: center; +} +.fc-row.open .fc-row-chevron { + transform: rotate(90deg); + color: var(--lb-accent); +} + +/* Expandable Detail */ +.fc-row-detail { + display: none; + background: rgba(0, 0, 0, 0.15); +} +.fc-row.open .fc-row-detail { + display: block; + animation: sourceDetailSlideIn 0.25s ease-out; +} +.fc-row-detail-inner { + padding: 14px 14px 14px 46px; +} +.fc-detail-status { + font-size: 0.85rem; + color: var(--lb-text); + margin-bottom: 8px; + display: flex; + align-items: center; + gap: 6px; +} +.fc-detail-evidence { + font-size: 0.82rem; + color: var(--lb-text-sec); + line-height: 1.6; + padding: 10px 14px; + background: rgba(0, 0, 0, 0.2); + border-radius: 4px; + border: 1px solid var(--lb-border); + margin-bottom: 8px; +} +.fc-detail-evidence strong { + color: var(--lb-text); +} +.fc-detail-evidence a { + color: var(--lb-accent) !important; + word-break: break-all; +} +.fc-detail-progression { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.78rem; + flex-wrap: wrap; + padding-top: 8px; + border-top: 1px solid var(--lb-border); +} +.fc-detail-prog-label { + color: var(--lb-text-sec); + font-weight: 500; + margin-right: 4px; +} +.progression-arrow { + color: var(--lb-accent); + font-size: 1rem; +} +.progression-step { + display: flex; + align-items: center; + gap: 4px; +} +.progression-time { + color: var(--lb-text-sec); + font-size: 0.7rem; +} + +/* ---------- Floating CTA Pill ---------- */ +.floating-cta { + position: fixed; + bottom: 24px; + left: 50%; + transform: translateX(-50%) translateY(100px); + display: flex; + align-items: center; + gap: 16px; + background: rgba(21, 29, 46, 0.92); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + border: 1px solid rgba(200, 168, 81, 0.25); + border-radius: 50px; + padding: 10px 14px 10px 24px; + z-index: 1000; + opacity: 0; + transition: opacity 0.4s, transform 0.4s; + pointer-events: none; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); +} +.floating-cta.visible { + opacity: 1; + transform: translateX(-50%) translateY(0); + pointer-events: auto; +} +.floating-cta.dismissed { + opacity: 0; + transform: translateX(-50%) translateY(100px); + pointer-events: none; +} +.floating-cta-text { + font-size: 0.88rem; + color: var(--lb-text-sec); + font-weight: 500; + white-space: nowrap; +} +.floating-cta-btn { + display: inline-block; + background: var(--lb-accent); + color: #0a0f1c; + padding: 8px 20px; + border-radius: 50px; + font-weight: 700; + text-decoration: none; + font-size: 0.85rem; + white-space: nowrap; + transition: background 0.2s, transform 0.2s, box-shadow 0.2s; + box-shadow: 0 0 16px var(--lb-glow-soft); +} +.floating-cta-btn:hover { + background: var(--lb-accent-hover); + transform: scale(1.03); + box-shadow: 0 0 28px var(--lb-glow); +} +.floating-cta-close { + background: none; + border: none; + color: var(--lb-text-sec); + font-size: 1.3rem; + cursor: pointer; + padding: 0 4px; + line-height: 1; + transition: color 0.2s; + font-family: inherit; +} +.floating-cta-close:hover { + color: var(--lb-text); +} + +@media (max-width: 768px) { + .floating-cta { + left: 12px; + right: 12px; + transform: translateX(0) translateY(100px); + border-radius: 16px; + gap: 10px; + padding: 10px 12px 10px 16px; + } + .floating-cta.visible { + transform: translateX(0) translateY(0); + } + .floating-cta.dismissed { + transform: translateX(0) translateY(100px); + } + .floating-cta-text { + font-size: 0.8rem; + white-space: normal; + } + .floating-cta-btn { + font-size: 0.8rem; + padding: 7px 14px; + flex-shrink: 0; + } +} + +/* ---------- Loading Skeleton ---------- */ +.loading-skeleton { padding: 20px 0; } +.skeleton-line { + height: 16px; + background: linear-gradient(90deg, var(--lb-bg-secondary) 25%, var(--lb-border) 50%, var(--lb-bg-secondary) 75%); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; + border-radius: var(--radius-sm, 4px); + margin-bottom: 12px; +} +.skeleton-line.short { width: 60%; } +@keyframes shimmer { + 0% { background-position: 200% 0; } + 100% { background-position: -200% 0; } +} + +/* Error */ +.lagebild-error { + text-align: center; + padding: 40px 20px; + color: var(--lb-text-sec); +} + +/* ---------- Scroll Reveal ---------- */ +.reveal { + opacity: 0; + transform: translateY(20px); + transition: opacity 0.4s ease-out, transform 0.4s ease-out; +} +.reveal.revealed { + opacity: 1; + transform: translateY(0); +} + +/* ---------- Responsive ---------- */ + +/* Tablet */ +@media (max-width: 1024px) { + .sources-grid { + grid-template-columns: repeat(4, 1fr); + } + .hero-stats { + max-width: 550px; + } + .stat-card { + padding: 12px 14px; + } + .stat-card-value { + font-size: 1.15rem; + } +} + +/* Mobile */ +@media (max-width: 768px) { + .lagebild-hero { + padding: 120px 16px 50px; + } + .lagebild-hero h1 { + font-size: 2.5rem; + letter-spacing: 3px; + } + .hero-incident-title { + font-size: 1.2rem; + } + .hero-stats { + grid-template-columns: repeat(2, 1fr); + gap: 10px; + margin-top: 1.5rem; + } + .stat-card { + padding: 10px 12px; + gap: 10px; + } + .stat-card-icon { + width: 32px; + height: 32px; + } + .stat-card-value { + font-size: 1.1rem; + } + .stat-card-label { + font-size: 0.65rem; + } + + .control-bar .container { + flex-direction: column; + align-items: stretch; + } + .timeline-strip { + gap: 3px; + } + .timeline-day { + padding: 6px 10px; + min-width: 54px; + } + .timeline-day-num { + font-size: 0.95rem; + } + .timeline-dropdown { + padding: 6px 0; + } + .h-timeline-track { + padding: 4px 12px; + } + .h-timeline-point { + min-width: 64px; + } + .h-timeline-time { + font-size: 0.68rem; + } + .h-timeline-meta { + font-size: 0.6rem; + } + .tab-nav { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .tab-btn { + padding: 10px 14px; + font-size: 0.82rem; + white-space: nowrap; + } + + .card-header { + padding: 16px 20px 0; + } + .card-body { + padding: 16px 20px 20px; + } + .card-footer { + margin: 0 20px; + padding: 16px 20px 20px; + } + .lagebild-main { + padding: 1rem 12px 3rem; + } + .fc-row-claim { + font-size: 0.82rem; + } + .fc-row-detail-inner { + padding: 12px 12px 12px 12px; + } + .fc-stat { + padding: 10px 14px; + min-width: 65px; + } + .sources-grid { + grid-template-columns: repeat(2, 1fr); + gap: 8px; + } + .source-tile { + padding: 10px; + min-height: 70px; + } + .source-tile-count { + font-size: 1.1rem; + } + .source-detail-header { + padding: 12px 16px; + } + .source-detail-articles { + padding: 8px 16px 12px; + max-height: 350px; + } + .source-detail-article { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + #map-container { + height: 350px !important; + } + .fc-stats { + justify-content: center; + gap: 8px; + } + .fc-stat { + padding: 10px 14px; + min-width: 70px; + } + .status-progression { + gap: 4px; + } + + + .live-feed { + margin-top: 1rem; + } +} + +/* Marker-Cluster Dark Theme */ +.marker-cluster-small, +.marker-cluster-medium, +.marker-cluster-large { + background: rgba(21, 29, 46, 0.8); +} +.marker-cluster-small div, +.marker-cluster-medium div, +.marker-cluster-large div { + background: rgba(200, 168, 81, 0.9); + color: #0A1832; + font-weight: 600; +} + +/* === Neueste Entwicklungen / Ueberblick-Tab === */ +.dev-list { + display: flex; + flex-direction: column; + gap: 8px; +} +.dev-bullet { + background: rgba(30, 45, 69, 0.5); + border-left: 3px solid #C8A851; + border-radius: 4px; + padding: 10px 14px; +} +.dev-bullet-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + margin-bottom: 6px; + flex-wrap: wrap; +} +.dev-sources { + display: inline-flex; + flex-wrap: wrap; + gap: 6px; + align-items: center; + min-width: 0; +} +.dev-source-pill { + display: inline-block; + padding: 3px 10px; + background: rgba(200, 168, 81, 0.18); + color: #E8ECF4; + border-radius: 3px; + font-size: 0.78rem; + font-weight: 500; + text-decoration: none; + line-height: 1.5; + white-space: normal; + overflow-wrap: anywhere; + transition: background 0.15s; +} +a.dev-source-pill:hover { + background: rgba(200, 168, 81, 0.35); + text-decoration: none; + color: #E8ECF4; +} +.dev-time { + color: #8896AB; + font-size: 0.78rem; + font-variant-numeric: tabular-nums; + white-space: nowrap; + flex-shrink: 0; +} +.dev-body { + font-size: 0.95rem; + line-height: 1.5; + color: #E8ECF4; +} +.empty-hint { + color: #8896AB; + font-style: italic; + padding: 16px 0; +} diff --git a/lagen/iran-konflikt/lagebild.js b/lagen/iran-konflikt/lagebild.js new file mode 100644 index 0000000..1467588 --- /dev/null +++ b/lagen/iran-konflikt/lagebild.js @@ -0,0 +1,1465 @@ +/** + * AegisSight Lagebild Page - Dark Theme Design Refresh + * Count-Up, Timeline, Scroll-Reveal, Particles, Live-Feed, Pulse-Markers + */ + +/** Feste Zeitzone fuer alle Anzeigen - NIEMALS aendern. */ +var TIMEZONE = 'Europe/Berlin'; + +var Lagebild = { + data: null, + allSnapshots: {}, + currentView: null, + map: null, + timelineGroups: null, + + /* ===== Inline SVG Icons ===== */ + /* ===== LANGUAGE SUPPORT ===== */ + lang: { + de: { + hero: "LAGEBILD", heroResearch: "RECHERCHE", + tabBriefing: "Lagebild", tabBriefingResearch: "Recherche", + tabUeberblick: "Neueste Entwicklungen", tabUeberblickResearch: "Zusammenfassung", + tabMap: "Karte", tabFactchecks: "Faktenchecks", tabSources: "Quellen", + statArticles: "Artikel", statSources: "Quellen", statFactchecks: "Faktenchecks", + dataSource: "Daten bereitgestellt durch AegisSight Monitor", + timelineCurrent: "Aktuell", timelineArticles: "Artikel", timelineFcs: "Faktenchecks", + srcArticlesFrom: "{count} Artikel aus {sources} Quellen", + srcArticle: "Artikel", srcClose: "Schlie\u00dfen", + mapNoData: "Keine Standortdaten verf\u00fcgbar", mapLegend: "Legende", mapArticles: "Artikel", + fcTotal: "Gesamt", fcConfirmed: "Best\u00e4tigt", fcOpen: "Offen", fcContradicted: "Widerlegt", + fcEvidence: "Evidenz:", fcProgression: "Verlauf:", fcSources: "unabh\u00e4ngige Quellen", + fcNone: "Keine Faktenchecks verf\u00fcgbar.", + fcCleaned: "{count} von {total} Faktenchecks verf\u00fcgbar (\u00e4ltere wurden bereinigt)", + ctaText: "AegisSight Monitor f\u00fcr Ihre Organisation", ctaButton: "Kontakt aufnehmen \u2192", + errorLoad: "Das Lagebild konnte nicht geladen werden. Bitte versuchen Sie es sp\u00e4ter erneut.", + snapshotHint: "", + standPrefix: "Stand: ", standSuffix: " Uhr", + stConfirmed: "Best\u00e4tigt", stUnconfirmed: "Unbest\u00e4tigt", stContradicted: "Widerlegt", + stDeveloping: "Unklar", stEstablished: "Gesichert", stDisputed: "Umstritten", + stFalse: "Falsch", stUnverified: "Nicht verifiziert", + sourceRef: "Quelle", + lastUpdate: "Letzte Aktualisierung: ", + minAgo: "vor {n} Min", hrsAgo: "vor {n} Std", + }, + en: { + hero: "SITUATION REPORT", heroResearch: "RESEARCH BRIEFING", + tabBriefing: "Briefing", tabBriefingResearch: "Research", + tabUeberblick: "Latest Developments", tabUeberblickResearch: "Summary", + tabMap: "Map", tabFactchecks: "Fact Checks", tabSources: "Sources", + statArticles: "Articles", statSources: "Sources", statFactchecks: "Fact Checks", + dataSource: "Data provided by AegisSight Monitor", + timelineCurrent: "Current", timelineArticles: "Articles", timelineFcs: "Fact Checks", + srcArticlesFrom: "{count} articles from {sources} sources", + srcArticle: "Articles", srcClose: "Close", + mapNoData: "No location data available", mapLegend: "Legend", mapArticles: "Articles", + fcTotal: "Total", fcConfirmed: "Confirmed", fcOpen: "Open", fcContradicted: "Contradicted", + fcEvidence: "Evidence:", fcProgression: "History:", fcSources: "independent sources", + fcNone: "No fact checks available.", + fcCleaned: "{count} of {total} fact checks available (older ones were cleaned up)", + ctaText: "AegisSight Monitor for your organization", ctaButton: "Contact us \u2192", + errorLoad: "The briefing could not be loaded. Please try again later.", + snapshotHint: "Historical data available in German only", + standPrefix: "As of: ", standSuffix: "", + stConfirmed: "Confirmed", stUnconfirmed: "Unconfirmed", stContradicted: "Contradicted", + stDeveloping: "Developing", stEstablished: "Established", stDisputed: "Disputed", + stFalse: "False", stUnverified: "Unverified", + sourceRef: "Source", + lastUpdate: "Last update: ", + minAgo: "{n} min ago", hrsAgo: "{n} hrs ago", + } + }, + + curLang: function() { + return (typeof getCurrentLanguage === 'function') ? getCurrentLanguage() : 'de'; + }, + + t: function(key) { + var cl = this.curLang(); + return (this.lang[cl] && this.lang[cl][key]) || this.lang.de[key] || key; + }, + + getLocale: function() { + return this.curLang() === 'en' ? 'en-GB' : 'de-DE'; + }, + + getHeadline: function(article) { + if (this.curLang() === 'en') return article.headline || article.headline_de || ''; + return article.headline_de || article.headline || ''; + }, + + icons: { + clock: '', + fileText: '', + globe: '', + shieldCheck: '', + externalLink: '' + }, + + async init() { + if (typeof initTranslations === 'function') { + try { initTranslations(); } catch(e) {} + } + this.initScrollProgress(); + this.initParticles(); + try { + var savedLang = (typeof getCurrentLanguage === 'function') ? getCurrentLanguage() : 'de'; + var jsonFile = savedLang === 'en' ? 'data/current_en.json' : 'data/current.json'; + var resp = await fetch(jsonFile + '?t=' + Date.now()); + if (!resp.ok && savedLang === 'en') { resp = await fetch('data/current.json?t=' + Date.now()); } + if (!resp.ok) throw new Error('HTTP ' + resp.status); + this.data = await resp.json(); + this.currentView = { + summary: this.data.current_lagebild.summary_markdown, + sources_json: this.data.current_lagebild.sources_json, + updated_at: this.data.current_lagebild.updated_at || this.data.generated_at, + articles: this.data.articles, + fact_checks: this.data.fact_checks, + article_count: this.data.incident.article_count, + fact_check_count: this.data.incident.factcheck_count + }; + this.render(); + this.initTabs(); + this.initLangToggle(); + this.initScrollReveal(); + this.initFloatingCta(); + this.initLiveFeed(); + } catch (e) { + console.error('Lagebild laden fehlgeschlagen:', e); + this.showError(); + } + }, + + render: function() { + this.renderHero(); + this.renderTimeline(); + this.renderTabBadges(); + this.renderCurrentView(); + this.renderUeberblick(); + }, + + /* ===== TAB: UEBERBLICK (Neueste Entwicklungen / Zusammenfassung) ===== */ + renderUeberblick: function() { + var inc = (this.data && this.data.incident) || {}; + var el = document.getElementById('ueberblick-content'); + var tsEl = document.getElementById('ueberblick-timestamp'); + if (!el) return; + if (tsEl) tsEl.textContent = this.fmtDT(this.currentView && this.currentView.updated_at); + + if (inc.type === 'adhoc') { + var dev = inc.latest_developments || ''; + var sources = (this.currentView && this.currentView.sources_json) || []; + var html = this.renderLatestDevelopmentsHtml(dev, sources); + el.innerHTML = html || '

Noch keine Entwicklungen erfasst.

'; + } else { + var md = (this.currentView && this.currentView.summary) || ''; + var zf = this.extractZusammenfassung(md); + if (!zf) { + el.innerHTML = '

Keine Zusammenfassung verfügbar.

'; + return; + } + var body = this.mdToHtml(this.fixUmlauts(zf)); + var srcMap = {}; + var sources = (this.currentView && this.currentView.sources_json) || []; + for (var i = 0; i < sources.length; i++) srcMap[String(sources[i].nr)] = sources[i]; + var self = this; + body = body.replace(/\[(\d+[a-z]?)\]/g, function(match, nr) { + var src = srcMap[nr]; + if (src && src.url) { + return '[' + nr + ']'; + } + return '[' + nr + ']'; + }); + el.innerHTML = body; + } + }, + + extractZusammenfassung: function(md) { + if (!md) return ''; + var sections = md.split(/^## /m); + for (var i = 0; i < sections.length; i++) { + var s = sections[i]; + if (/^zusammenfassung/i.test(s.trim())) { + var next = s.split(/\n## /)[0]; + return next.replace(/^[^\n]*\n?/, '').trim(); + } + } + return ''; + }, + + stripZusammenfassung: function(md) { + if (!md) return md; + var lines = md.split('\n'); + var result = []; + var skipping = false; + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + if (/^##\s+zusammenfassung\b/i.test(line)) { skipping = true; continue; } + if (skipping && /^##\s+/.test(line)) skipping = false; + if (!skipping) result.push(line); + } + return result.join('\n').replace(/^\s+/, ''); + }, + + renderLatestDevelopmentsHtml: function(text, sources) { + if (!text) return ''; + sources = Array.isArray(sources) ? sources : []; + var self = this; + + var lines = text.split('\n').map(function(l){return l.trim();}) + .filter(function(l){return l && (l.charAt(0) === '-' || l.charAt(0) === '[');}); + if (!lines.length) return ''; + + var bulletRe = /^(?:-\s*)?\[\s*(\d{1,2})\.(\d{1,2})\.?(?:\d{2,4})?\s+(\d{1,2}:\d{2})\s*\]\s*(.+?)\s*$/; + var trailingRe = /\s*\{([^{}]+)\}\s*\.?\s*$/; + var citationRe = /\[(\d+[a-z]?)\]/g; + var junkRe = /^(unbekannt|unknown|n\/?a|keine|keine quelle|tba)$/i; + + function normName(s) { return String(s||'').toLowerCase().replace(/^(der|die|das)\s+/,'').replace(/\s+/g,' ').trim(); } + function lookupByName(name) { + var n = normName(name); if (!n) return null; + var exact = sources.find(function(s){ return normName(s.name) === n; }); + if (exact) return exact; + return sources.find(function(s){ var sn=normName(s.name); return sn && (sn.indexOf(n)!==-1 || n.indexOf(sn)!==-1); }) || null; + } + function buildPill(src, name) { + var disp = (src && src.name) || name; + var url = (src && src.url) || ''; + var tgMatch = url.match(/^https?:\/\/t\.me\/([^\/?#]+)/i); + var label = tgMatch ? disp + ' (t.me/' + tgMatch[1] + ')' : disp; + var e = self.esc(label); + var titleEsc = self.esc(disp); + if (src && src.url) return ''+e+''; + return ''+e+''; + } + + var cards = []; + for (var i = 0; i < lines.length; i++) { + var m = bulletRe.exec(lines[i]); if (!m) continue; + var date = String(m[1]).padStart(2,'0') + '.' + String(m[2]).padStart(2,'0') + '.'; + var time = m[3]; var body = m[4]; + + var pills = ''; + var t = trailingRe.exec(body); + if (t) { + body = body.replace(trailingRe, '').trim(); + var items = t[1].split(',').map(function(n){return n.trim();}).filter(Boolean); + var seen = {}; + pills = items.map(function(item){ + var pipeIdx = item.indexOf('|'); + var itemName = pipeIdx >= 0 ? item.slice(0, pipeIdx).trim() : item.trim(); + var itemUrl = pipeIdx >= 0 ? item.slice(pipeIdx + 1).trim() : ''; + if (!itemName || junkRe.test(itemName)) return ''; + var k = normName(itemName); if (seen[k]) return ''; seen[k] = true; + if (itemUrl) { + return buildPill({ name: itemName, url: itemUrl }, itemName); + } + return buildPill(lookupByName(itemName), itemName); + }).filter(Boolean).join(''); + } + if (!pills) { + var nums = []; var cm; + while ((cm = citationRe.exec(body)) !== null) { if (nums.indexOf(cm[1]) === -1) nums.push(cm[1]); } + citationRe.lastIndex = 0; + if (nums.length) { + body = body.replace(citationRe, '').replace(/\s+/g, ' ').trim(); + pills = nums.map(function(num){ + var src = sources.find(function(s){ return String(s.nr) === num || Number(s.nr) === Number(num); }); + return src ? buildPill(src, src.name) : ''; + }).filter(Boolean).join(''); + } + } + + var head = '
' + + '' + pills + '' + + '' + self.esc(time) + ' \u00b7 ' + self.esc(date) + '' + + '
'; + cards.push('
' + head + '
' + self.esc(body) + '
'); + } + + return cards.length ? '
' + cards.join('') + '
' : ''; + }, + + /* ===== SCROLL PROGRESS BAR ===== */ + initScrollProgress: function() { + var bar = document.getElementById('scroll-progress'); + if (!bar) return; + window.addEventListener('scroll', function() { + var scrollTop = window.scrollY; + var docHeight = document.documentElement.scrollHeight - window.innerHeight; + if (docHeight <= 0) return; + bar.style.width = ((scrollTop / docHeight) * 100) + '%'; + }); + }, + + /* ===== HERO PARTICLES ===== */ + initParticles: function() { + var canvas = document.getElementById('hero-particles'); + if (!canvas) return; + var ctx = canvas.getContext('2d'); + var particles = []; + var count = 35; + var connectDist = 120; + + function resize() { + var hero = canvas.parentElement; + canvas.width = hero.offsetWidth; + canvas.height = hero.offsetHeight; + } + resize(); + window.addEventListener('resize', resize); + + for (var i = 0; i < count; i++) { + particles.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + vx: (Math.random() - 0.5) * 0.4, + vy: (Math.random() - 0.5) * 0.4, + r: Math.random() * 1.5 + 0.5 + }); + } + + function draw() { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // Draw connections + for (var i = 0; i < particles.length; i++) { + for (var j = i + 1; j < particles.length; j++) { + var dx = particles[i].x - particles[j].x; + var dy = particles[i].y - particles[j].y; + var dist = Math.sqrt(dx * dx + dy * dy); + if (dist < connectDist) { + var alpha = (1 - dist / connectDist) * 0.15; + ctx.beginPath(); + ctx.strokeStyle = 'rgba(200, 168, 81, ' + alpha + ')'; + ctx.lineWidth = 0.5; + ctx.moveTo(particles[i].x, particles[i].y); + ctx.lineTo(particles[j].x, particles[j].y); + ctx.stroke(); + } + } + } + + // Draw & move particles + for (var k = 0; k < particles.length; k++) { + var p = particles[k]; + ctx.beginPath(); + ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); + ctx.fillStyle = 'rgba(200, 168, 81, 0.4)'; + ctx.fill(); + + p.x += p.vx; + p.y += p.vy; + if (p.x < 0 || p.x > canvas.width) p.vx *= -1; + if (p.y < 0 || p.y > canvas.height) p.vy *= -1; + } + + requestAnimationFrame(draw); + } + draw(); + }, + + /* ===== LIVE FEED TICKER ===== */ + initLiveFeed: function() { + var container = document.getElementById('live-feed'); + if (!container) return; + + var d = this.data; + var genDate = new Date(this.toUTC(d.generated_at)); + var diffMin = Math.max(1, Math.round((Date.now() - genDate.getTime()) / 60000)); + var diffText = diffMin < 60 ? this.t('minAgo').replace('{n}', diffMin) : this.t('hrsAgo').replace('{n}', Math.round(diffMin / 60)); + + container.innerHTML = '
' + + '' + + '' + this.t('lastUpdate') + diffText + '' + + '
'; + }, + + /* ===== HERO ===== */ + renderHero: function() { + var d = this.data; + document.getElementById('incident-title').innerHTML = + this.esc(this.fixUmlauts(d.incident.title)) + + ' \u2013 ' + this.t('standPrefix') + this.fmtDateOnly(d.generated_at) + ', ' + this.fmtTimeOnly(d.generated_at) + this.t('standSuffix') + ''; + + // Stat Cards (3: Artikel, Quellen, Faktenchecks) + var statsHtml = ''; + statsHtml += this.statCard(this.icons.fileText, '0', this.t("statArticles")); + statsHtml += this.statCard(this.icons.globe, '0', this.t("statSources")); + statsHtml += this.statCard(this.icons.shieldCheck, '0', this.t("statFactchecks")); + document.getElementById('hero-stats').innerHTML = statsHtml; + + // Start count-up animations + var self = this; + requestAnimationFrame(function() { + var els = document.querySelectorAll('.count-up'); + for (var i = 0; i < els.length; i++) { + self.animateCount(els[i], parseInt(els[i].getAttribute('data-target')), 800); + } + }); + }, + + statCard: function(icon, value, label) { + return '
' + + '
' + icon + '
' + + '
' + + '' + value + '' + + '' + label + '' + + '
'; + }, + + /* ===== COUNT-UP ANIMATION ===== */ + animateCount: function(element, target, duration) { + var start = performance.now(); + function update(now) { + var elapsed = now - start; + var progress = Math.min(elapsed / duration, 1); + var eased = 1 - Math.pow(1 - progress, 3); // easeOutCubic + var current = Math.round(target * eased); + element.textContent = current.toLocaleString(Lagebild.getLocale()); + if (progress < 1) { + requestAnimationFrame(update); + } + } + requestAnimationFrame(update); + }, + + /* ===== TIMELINE STRIP ===== */ + renderTimeline: function() { + var snaps = this.data.available_snapshots || []; + var lagebildUpdated = (this.data.current_lagebild.updated_at || '').replace(' ', 'T'); + var newestSnap = snaps.length > 0 ? snaps[0] : null; + var newestSnapTime = newestSnap ? newestSnap.created_at.replace(' ', 'T') : ''; + + var all; + if (!newestSnap || lagebildUpdated !== newestSnapTime) { + // Neues Lagebild seit letztem Snapshot – eigenen current-Eintrag zeigen + var current = { + id: 'current', + article_count: this.data.incident.article_count, + fact_check_count: this.data.incident.factcheck_count, + created_at: this.data.generated_at + }; + all = [current].concat(snaps); + } else { + // Lagebild identisch mit neuestem Snapshot – kein Geister-Eintrag, + // stattdessen Snapshot als "current" mit aktuellen Live-Counts markieren + var merged = {}; + for (var key in newestSnap) { + if (newestSnap.hasOwnProperty(key)) merged[key] = newestSnap[key]; + } + merged.id = 'current'; + merged.article_count = this.data.incident.article_count; + merged.fact_check_count = this.data.incident.factcheck_count; + all = [merged].concat(snaps.slice(1)); + } + + // Group by date + var groups = {}; + for (var i = 0; i < all.length; i++) { + var s = all[i]; + var dateKey = this.toDateKey(s.created_at); + if (!groups[dateKey]) groups[dateKey] = []; + groups[dateKey].push(s); + } + + // Sort each group descending (newest first) + for (var dk in groups) { + groups[dk].sort(function(a, b) { + return new Date(Lagebild.toUTC(b.created_at)) - new Date(Lagebild.toUTC(a.created_at)); + }); + } + + this.timelineGroups = groups; + var dates = Object.keys(groups).sort(); + var strip = document.getElementById('timeline-strip'); + var h = ''; + + for (var j = 0; j < dates.length; j++) { + var date = dates[j]; + var daySnaps = groups[date]; + var isActive = (j === dates.length - 1); + var defaultSnap = isActive ? daySnaps[0] : daySnaps[daySnaps.length - 1]; + var d = new Date(date + 'T12:00:00Z'); + + h += ''; + } + + strip.innerHTML = h; + + // Scroll to active day + var active = strip.querySelector('.timeline-day.active'); + if (active) { + setTimeout(function() { + active.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); + }, 150); + } + + // Click handler for day buttons + var self = this; + strip.addEventListener('click', function(e) { + var btn = e.target.closest('.timeline-day'); + if (!btn) return; + + var allDays = strip.querySelectorAll('.timeline-day'); + for (var k = 0; k < allDays.length; k++) allDays[k].classList.remove('active'); + btn.classList.add('active'); + + var dateKey = btn.getAttribute('data-date'); + var snapId = btn.getAttribute('data-snapshot-id'); + + self.showTimelineDropdown(dateKey, snapId); + + if (snapId === 'current') { + self.currentView = { + summary: self.data.current_lagebild.summary_markdown, + sources_json: self.data.current_lagebild.sources_json, + updated_at: self.data.current_lagebild.updated_at || self.data.generated_at, + articles: self.data.articles, + fact_checks: self.data.fact_checks, + article_count: self.data.incident.article_count, + fact_check_count: self.data.incident.factcheck_count + }; + self.renderCurrentView(); + } else { + self.loadSnapshot(parseInt(snapId)); + } + }); + + // Click handler for dropdown snapshot items (delegated, set up once) + var dropdown = document.getElementById('timeline-dropdown'); + dropdown.addEventListener('click', function(e) { + var item = e.target.closest('.h-timeline-point'); + if (!item) return; + + var items = dropdown.querySelectorAll('.h-timeline-point'); + for (var k = 0; k < items.length; k++) items[k].classList.remove('active'); + item.classList.add('active'); + + var snapId = item.getAttribute('data-snapshot-id'); + if (snapId === 'current') { + self.currentView = { + summary: self.data.current_lagebild.summary_markdown, + sources_json: self.data.current_lagebild.sources_json, + updated_at: self.data.current_lagebild.updated_at || self.data.generated_at, + articles: self.data.articles, + fact_checks: self.data.fact_checks, + article_count: self.data.incident.article_count, + fact_check_count: self.data.incident.factcheck_count + }; + self.renderCurrentView(); + } else { + self.loadSnapshot(parseInt(snapId)); + } + }); + + // Show dropdown for newest day by default + var newestDate = dates[dates.length - 1]; + if (newestDate) { + this.showTimelineDropdown(newestDate, groups[newestDate][0].id); + } + }, + + showTimelineDropdown: function(dateKey, activeSnapId) { + var dropdown = document.getElementById('timeline-dropdown'); + var snaps = this.timelineGroups[dateKey]; + + if (!snaps || snaps.length === 0) { + dropdown.classList.remove('open'); + dropdown.innerHTML = ''; + return; + } + + // Oldest left, newest right + var ordered = snaps.slice().reverse(); + + var h = '
'; + for (var i = 0; i < ordered.length; i++) { + var snap = ordered[i]; + var isActive = (String(snap.id) === String(activeSnapId)); + h += ''; + } + h += '
'; + + dropdown.innerHTML = h; + dropdown.classList.add('open'); + + // Scroll to active point + var activePoint = dropdown.querySelector('.h-timeline-point.active'); + if (activePoint) { + setTimeout(function() { + activePoint.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' }); + }, 50); + } + }, + + toDateKey: function(iso) { + if (!iso) return ''; + var d = new Date(this.toUTC(iso)); + return d.toLocaleDateString('en-CA', { timeZone: TIMEZONE }); + }, + + /* ===== TAB BADGES ===== */ + renderTabBadges: function() { + var quellenBadge = document.getElementById('tab-badge-quellen'); + var fcBadge = document.getElementById('tab-badge-faktenchecks'); + if (quellenBadge) quellenBadge.textContent = this.data.incident.source_count; + if (fcBadge) fcBadge.textContent = this.data.incident.factcheck_count; + }, + + /* ===== SNAPSHOT LOADING ===== */ + loadSnapshot: async function(id) { + if (this.allSnapshots[id]) { + this.currentView = this.allSnapshots[id]; + this.renderCurrentView(); + return; + } + try { + var resp = await fetch('data/snapshot-' + id + '.json'); + if (!resp.ok) throw new Error('HTTP ' + resp.status); + var sd = await resp.json(); + var sj = sd.sources_json; + if (typeof sj === 'string') { try { sj = JSON.parse(sj); } catch(e) { sj = []; } } + this.currentView = { + summary: sd.summary, + sources_json: sj || [], + updated_at: sd.created_at, + articles: this.filterArticlesAtTime(sd.created_at), + fact_checks: this.getFactChecksAtTime(sd.created_at), + article_count: sd.article_count, + fact_check_count: sd.fact_check_count + }; + this.allSnapshots[id] = this.currentView; + this.renderCurrentView(); + } catch (e) { console.error('Snapshot Fehler:', e); } + }, + + renderCurrentView: function() { + this.renderSummary(); + this.renderInlineSources(); + this.renderSourcesTab(); + this.renderArticlesTab(); + this.renderFactChecksTab(); + if (document.getElementById('panel-karte').classList.contains('active')) { + this.renderMap(); + } + // Counts aktualisieren (Hero + Badges) + var articles = this.currentView.articles || []; + var artCount = this.currentView.article_count || articles.length; + var srcCount = new Set(articles.map(function(a) { return a.source; })).size; + var fcCount = this.currentView.fact_check_count || (this.currentView.fact_checks || []).length; + + var heroArt = document.getElementById('hero-art-count'); + if (heroArt) heroArt.textContent = artCount.toLocaleString(this.getLocale()); + var heroSrc = document.getElementById('hero-src-count'); + if (heroSrc) heroSrc.textContent = srcCount.toLocaleString(this.getLocale()); + var heroFc = document.getElementById('hero-fc-count'); + if (heroFc) heroFc.textContent = fcCount.toLocaleString(this.getLocale()); + + var fcBadge = document.getElementById('tab-badge-faktenchecks'); + if (fcBadge) fcBadge.textContent = fcCount; + var quellenBadge = document.getElementById('tab-badge-quellen'); + if (quellenBadge) quellenBadge.textContent = srcCount; + }, + + /* ===== TAB: LAGEBILD ===== */ + renderSummary: function() { + var v = this.currentView; + document.getElementById('lagebild-timestamp').textContent = this.fmtDT(v.updated_at); + var md = this.fixUmlauts(v.summary || ''); + md = this.stripZusammenfassung(md); + var html = this.mdToHtml(md); + + // Build source lookup for citation links + var srcMap = {}; + var sources = v.sources_json || []; + for (var i = 0; i < sources.length; i++) { + srcMap[String(sources[i].nr)] = sources[i]; + } + var self = this; + html = html.replace(/\[(\d+[a-z]?)\]/g, function(match, nr) { + var src = srcMap[nr]; + if (src && src.url) { + return '[' + nr + ']'; + } + return '[' + nr + ']'; + }); + + document.getElementById('summary-content').innerHTML = html; + }, + + renderInlineSources: function() { + document.getElementById('inline-sources').innerHTML = ''; + }, + + /* ===== TAB: QUELLEN (Tile Grid) ===== */ + renderSourcesTab: function() { + var articles = this.currentView.articles || []; + var container = document.getElementById('sources-grid-container'); + if (!container) return; + + // Aggregate by source + var sourceMap = {}; + for (var i = 0; i < articles.length; i++) { + var a = articles[i]; + var name = a.source || 'Unknown'; + if (!sourceMap[name]) sourceMap[name] = { count: 0, articles: [], languages: {}, domain: null }; + sourceMap[name].count++; + sourceMap[name].articles.push(a); + var lang = (a.language || '').toUpperCase(); + if (lang) sourceMap[name].languages[lang] = (sourceMap[name].languages[lang] || 0) + 1; + if (!sourceMap[name].domain && a.source_url) sourceMap[name].domain = this.extractDomain(a.source_url); + } + + // Sort by count desc + var sources = []; + for (var name in sourceMap) { + sources.push({ name: name, data: sourceMap[name] }); + } + sources.sort(function(a, b) { return b.data.count - a.data.count; }); + + // Language totals + var langTotals = {}; + for (var i = 0; i < articles.length; i++) { + var lang = (articles[i].language || '').toUpperCase(); + if (lang) langTotals[lang] = (langTotals[lang] || 0) + 1; + } + + var h = ''; + + // Header + h += '
'; + h += '' + this.t('srcArticlesFrom').replace('{count}', articles.length).replace('{sources}', sources.length) + ''; + h += '
'; + var langKeys = Object.keys(langTotals).sort(function(a, b) { return langTotals[b] - langTotals[a]; }); + for (var i = 0; i < langKeys.length; i++) { + h += '' + langKeys[i] + ' ' + langTotals[langKeys[i]] + ''; + } + h += '
'; + + // Grid + h += '
'; + for (var i = 0; i < sources.length; i++) { + var s = sources[i]; + var langBadge = Object.keys(s.data.languages).join('/'); + h += '
'; + h += '
'; + if (s.data.domain) { + h += ''; + } + h += '' + this.esc(s.name) + ''; + h += '
'; + h += '
'; + h += '' + langBadge + ''; + h += '' + s.data.count + ''; + h += '
'; + h += '
'; + } + h += '
'; + + container.innerHTML = h; + this._sourceTiles = sources; + + // Click handler + var self = this; + document.getElementById('sources-grid').addEventListener('click', function(e) { + var tile = e.target.closest('.source-tile'); + if (!tile) return; + self.toggleSourceDetail(tile); + }); + }, + + toggleSourceDetail: function(tile) { + var grid = document.getElementById('sources-grid'); + var idx = parseInt(tile.getAttribute('data-source-index')); + var existingPanel = grid.querySelector('.source-detail-panel'); + var wasActive = tile.classList.contains('active'); + + // Remove active from all tiles + var allTiles = grid.querySelectorAll('.source-tile'); + for (var k = 0; k < allTiles.length; k++) allTiles[k].classList.remove('active'); + + // Remove existing panel + if (existingPanel) existingPanel.remove(); + + // If same tile was active, just close + if (wasActive) return; + + tile.classList.add('active'); + + // Find last tile in same visual row (by offsetTop) + var clickedTop = tile.offsetTop; + var lastInRow = tile; + for (var k = 0; k < allTiles.length; k++) { + if (allTiles[k].offsetTop === clickedTop) { + lastInRow = allTiles[k]; + } + } + + // Build detail panel + var src = this._sourceTiles[idx]; + var arts = src.data.articles.slice().sort(function(a, b) { + var da = new Date(Lagebild.toUTC(a.published_at || a.collected_at || '')); + var db = new Date(Lagebild.toUTC(b.published_at || b.collected_at || '')); + return db - da; + }); + + var h = '
'; + h += '
'; + h += ''; + if (src.data.domain) { + h += ' '; + } + h += this.esc(src.name) + ''; + h += '' + src.data.count + ' ' + Lagebild.t('srcArticle') + ''; + h += ''; + h += '
'; + h += '
'; + for (var j = 0; j < arts.length; j++) { + var a = arts[j]; + var dt = a.published_at || a.collected_at || ''; + var dObj = dt ? new Date(this.toUTC(dt)) : null; + var hl = this.fixUmlauts(Lagebild.getHeadline(a)); + h += '
'; + if (a.source_url) { + h += '' + this.esc(hl) + ' ' + this.icons.externalLink + ''; + } else { + h += '' + this.esc(hl) + ''; + } + if (dObj && !isNaN(dObj.getTime())) { + h += ''; + } + h += '
'; + } + h += '
'; + + // Insert after last tile in row + lastInRow.insertAdjacentHTML('afterend', h); + + // Close button handler + var panel = grid.querySelector('.source-detail-panel'); + var self = this; + panel.querySelector('.source-detail-close').addEventListener('click', function(e) { + e.stopPropagation(); + panel.remove(); + tile.classList.remove('active'); + }); + + // Scroll into view + setTimeout(function() { + panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }, 50); + }, + + renderArticlesTab: function() {}, + + /* ===== TAB: KARTE (Clustered Pulse Markers) ===== */ + renderMap: function() { + if (this.map) { this.map.remove(); this.map = null; } + this.map = L.map('map-container', { + minZoom: 2, + maxBounds: [[-85, -180], [85, 180]], + maxBoundsViscosity: 1.0 + }).setView([33.0, 48.0], 5); + + L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap', + maxZoom: 19, + noWrap: true, + }).addTo(this.map); + + function pulseIcon(color) { + return L.divIcon({ + className: '', + html: '
' + + '
' + + '
' + + '
' + + '
', + iconSize: [20, 20], + iconAnchor: [10, 10], + popupAnchor: [0, -12] + }); + } + + var categoryColors = { + primary: '#ef4444', + secondary: '#f59e0b', + tertiary: '#3b82f6', + mentioned: '#7b7b7b' + }; + var defaultCategoryLabels = { + primary: 'Hauptgeschehen', + secondary: 'Reaktionen', + tertiary: 'Beteiligte', + mentioned: 'Erwaehnt' + }; + var categoryLabels = {}; + if (this.data && this.data.category_labels) { + var apiLabels = this.data.category_labels; + categoryLabels.primary = apiLabels.primary || defaultCategoryLabels.primary; + categoryLabels.secondary = apiLabels.secondary || defaultCategoryLabels.secondary; + categoryLabels.tertiary = apiLabels.tertiary || defaultCategoryLabels.tertiary; + categoryLabels.mentioned = apiLabels.mentioned || defaultCategoryLabels.mentioned; + } else { + categoryLabels = defaultCategoryLabels; + } + + var locs = (this.data && this.data.locations) ? this.data.locations : []; + + if (locs.length === 0) { + var emptyDiv = document.createElement('div'); + emptyDiv.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1000;background:#151D2E;padding:20px 30px;border-radius:8px;border:1px solid #1E2D45;color:#8896AB;text-align:center;'; + emptyDiv.innerHTML = Lagebild.t('mapNoData'); + document.getElementById('map-container').appendChild(emptyDiv); + } + + var clusterGroup = L.markerClusterGroup({ + maxClusterRadius: 50, + spiderfyOnMaxZoom: true, + showCoverageOnHover: false, + zoomToBoundsOnClick: true, + disableClusteringAtZoom: 10 + }); + + var usedCategories = {}; + var bounds = []; + for (var i = 0; i < locs.length; i++) { + var l = locs[i]; + if (!l.lat || !l.lon) continue; + var cat = l.category || 'mentioned'; + var color = categoryColors[cat] || '#7b7b7b'; + usedCategories[cat] = true; + + // Popup mit Artikel-Links + var popupText = '' + (l.name || '') + ''; + if (l.country_code) popupText += ' (' + l.country_code + ')'; + popupText += '
' + (l.article_count || 0) + ' ' + Lagebild.t('mapArticles') + ''; + if (l.top_articles && l.top_articles.length > 0) { + popupText += '
'; + for (var j = 0; j < l.top_articles.length; j++) { + var a = l.top_articles[j]; + var hl = (a.headline || '').replace(/\*\*/g, ''); + if (hl.length > 60) hl = hl.substring(0, 60) + '\u2026'; + if (a.url) { + popupText += '' + hl + ''; + } else { + popupText += '' + hl + ''; + } + popupText += '' + (a.source || '') + ''; + } + popupText += '
'; + } + + var marker; + if (cat === 'primary' || cat === 'secondary') { + marker = L.marker([l.lat, l.lon], { icon: pulseIcon(color) }); + } else { + marker = L.circleMarker([l.lat, l.lon], { + radius: 5, fillColor: color, fillOpacity: 0.7, + color: color, weight: 1, opacity: 0.9 + }); + } + marker.bindPopup(popupText, { maxWidth: 300 }); + clusterGroup.addLayer(marker); + bounds.push([l.lat, l.lon]); + } + + this.map.addLayer(clusterGroup); + + // Dark legend + var legend = L.control({ position: 'bottomright' }); + legend.onAdd = function() { + var div = L.DomUtil.create('div', 'map-legend'); + div.style.cssText = 'background:#151D2E;padding:10px 14px;border-radius:4px;border:1px solid #1E2D45;box-shadow:0 2px 8px rgba(0,0,0,0.3);font-size:0.8rem;line-height:1.8;color:#E8ECF4;'; + var html = '' + Lagebild.t('mapLegend') + '
'; + ['primary', 'secondary', 'tertiary', 'mentioned'].forEach(function(cat) { + if (usedCategories[cat] && categoryLabels[cat]) { + html += ' ' + categoryLabels[cat] + '
'; + } + }); + div.innerHTML = html; + return div; + }; + legend.addTo(this.map); + + if (bounds.length > 0) { + this.map.fitBounds(bounds, { padding: [30, 30], maxZoom: 7 }); + } + + // Dark popup styling + if (!document.getElementById('leaflet-dark-style')) { + var style = document.createElement('style'); + style.id = 'leaflet-dark-style'; + style.textContent = '.lagebild-page .leaflet-popup-content-wrapper{background:#151D2E;color:#E8ECF4;border:1px solid #1E2D45;border-radius:4px;box-shadow:0 4px 16px rgba(0,0,0,0.4);}.lagebild-page .leaflet-popup-tip{background:#151D2E;}'; + document.head.appendChild(style); + } + + setTimeout(function() { if (Lagebild.map) Lagebild.map.invalidateSize(); }, 300); + }, + + /* ===== TAB: FAKTENCHECKS ===== */ + /* ===== Factcheck Icons (from real Monitor) ===== */ + fcIcons: { + confirmed: '✓', + unconfirmed: '?', + contradicted: '✗', + developing: '↻', + established: '✓', + disputed: '⚠', + 'false': '✗', + unverified: '?' + }, + + fcLabels: {}, + + renderFactChecksTab: function() { + var checks = this.currentView.fact_checks || []; + if (!checks.length) { + document.getElementById('factchecks-content').innerHTML = '

' + this.t('fcNone') + '

'; + return; + } + + // Count stats + var stats = { confirmed: 0, unconfirmed: 0, contradicted: 0, developing: 0, established: 0, disputed: 0 }; + for (var k = 0; k < checks.length; k++) { + var st = checks[k].status || 'developing'; + if (stats[st] !== undefined) stats[st]++; + } + + var confirmedTotal = stats.confirmed + stats.established; + var openTotal = stats.unconfirmed + stats.developing; + var contradictedTotal = stats.contradicted + stats.disputed; + + // Stat cards (clickable filters) + var h = '
'; + h += ''; + h += ''; + h += ''; + if (contradictedTotal > 0) + h += ''; + h += '
'; + + // Hinweis bei unvollständiger Liste + var storedFcCount = this.currentView.fact_check_count || 0; + if (storedFcCount > 0 && checks.length < storedFcCount) { + h += '
'; + h += this.t('fcCleaned').replace('{count}', checks.length).replace('{total}', storedFcCount); + h += '
'; + } + + // Sort: status_history first, then by sources_count + checks = checks.slice().sort(function(a, b) { + var aH = (a.status_history || []).length; + var bH = (b.status_history || []).length; + if (bH !== aH) return bH - aH; + return (b.sources_count || 0) - (a.sources_count || 0); + }); + + // Compact accordion list + h += '
'; + for (var i = 0; i < checks.length; i++) { + var fc = checks[i]; + var status = fc.status || 'developing'; + var filterGroup = 'all'; + if (status === 'confirmed' || status === 'established') filterGroup = 'confirmed'; + else if (status === 'unconfirmed' || status === 'developing') filterGroup = 'unconfirmed'; + else if (status === 'contradicted' || status === 'disputed') filterGroup = 'contradicted'; + + var hasProg = fc.status_history && fc.status_history.length > 1; + var icon = this.fcIcons[status] || '?'; + var label = this.stLabel(status); + + h += '
'; + h += '
'; + h += '' + icon + ''; + h += '' + this.esc(this.fixUmlauts(fc.claim || '')) + ''; + h += '' + (fc.sources_count || 0) + ''; + if (hasProg) h += ''; + h += ''; + h += '
'; + + // Expandable detail + h += '
'; + h += '
'; + h += '
' + icon + ' ' + label + ' – ' + (fc.sources_count || 0) + ' ' + this.t('fcSources') + '
'; + + if (fc.evidence) { + var ev = this.fixUmlauts(fc.evidence); + ev = this.esc(ev).replace(/(https?:\/\/[^\s,)]+)/g, '$1'); + h += '
' + this.t('fcEvidence') + ' ' + ev + '
'; + } + + if (hasProg) { + h += '
'; + h += '' + this.t('fcProgression') + ''; + for (var j = 0; j < fc.status_history.length; j++) { + var step = fc.status_history[j]; + if (j > 0) h += ''; + h += ''; + h += '' + (this.fcIcons[step.status] || '?') + ''; + if (step.at) h += '' + this.fmtShort(step.at) + ''; + h += ''; + } + h += '
'; + } + h += '
'; + h += '
'; + } + h += '
'; + + document.getElementById('factchecks-content').innerHTML = h; + + // Filter click handler + var statBtns = document.querySelectorAll('.fc-stat'); + statBtns.forEach(function(btn) { + btn.addEventListener('click', function() { + for (var k = 0; k < statBtns.length; k++) statBtns[k].classList.remove('active'); + btn.classList.add('active'); + var filter = btn.getAttribute('data-filter'); + var rows = document.querySelectorAll('.fc-row'); + for (var k = 0; k < rows.length; k++) { + if (filter === 'all' || rows[k].getAttribute('data-status-group') === filter) { + rows[k].style.display = ''; + } else { + rows[k].style.display = 'none'; + } + } + }); + }); + + // Accordion click handler + document.getElementById('fc-list').addEventListener('click', function(e) { + var header = e.target.closest('.fc-row-header'); + if (!header) return; + var row = header.closest('.fc-row'); + var wasOpen = row.classList.contains('open'); + + // Close all open rows + var allRows = document.querySelectorAll('.fc-row.open'); + for (var k = 0; k < allRows.length; k++) allRows[k].classList.remove('open'); + + // Toggle clicked row + if (!wasOpen) { + row.classList.add('open'); + var detail = row.querySelector('.fc-row-detail'); + if (detail) { + setTimeout(function() { + detail.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + }, 50); + } + } + }); + }, + + /* ===== TABS ===== */ + initTabs: function() { + var btns = document.querySelectorAll('.tab-btn'); + var self = this; + for (var i = 0; i < btns.length; i++) { + btns[i].addEventListener('click', function() { + var tab = this.getAttribute('data-tab'); + for (var j = 0; j < btns.length; j++) btns[j].classList.remove('active'); + this.classList.add('active'); + var panels = document.querySelectorAll('.tab-panel'); + for (var j = 0; j < panels.length; j++) panels[j].classList.remove('active'); + var activePanel = document.getElementById('panel-' + tab); + activePanel.classList.add('active'); + + // Trigger reveal for cards in newly active panel + var revealCards = activePanel.querySelectorAll('.reveal:not(.revealed)'); + for (var k = 0; k < revealCards.length; k++) { + revealCards[k].classList.add('revealed'); + } + + if (tab === 'karte') self.renderMap(); + }); + } + }, + + initLangToggle: function() { + var btn = document.querySelector('.lang-toggle'); + if (!btn) return; + var self = this; + btn.addEventListener('click', function(e) { + e.preventDefault(); + var cur = (typeof getCurrentLanguage === 'function') ? getCurrentLanguage() : 'de'; + var newLang = cur === 'de' ? 'en' : 'de'; + if (typeof switchLanguage === 'function') switchLanguage(newLang); + self.switchContent(newLang); + }); + }, + + switchContent: async function(lang) { + var jsonFile = lang === 'en' ? 'data/current_en.json' : 'data/current.json'; + try { + var resp = await fetch(jsonFile + '?t=' + Date.now()); + if (!resp.ok && lang === 'en') { + resp = await fetch('data/current.json?t=' + Date.now()); + } + if (!resp.ok) throw new Error('HTTP ' + resp.status); + this.data = await resp.json(); + this.allSnapshots = {}; + this.currentView = { + summary: this.data.current_lagebild.summary_markdown, + sources_json: this.data.current_lagebild.sources_json, + updated_at: this.data.current_lagebild.updated_at || this.data.generated_at, + articles: this.data.articles, + fact_checks: this.data.fact_checks, + article_count: this.data.incident.article_count, + fact_check_count: this.data.incident.factcheck_count + }; + this.render(); + // Update hero title for language + var heroH1 = document.getElementById('hero-title'); + if (heroH1) { + var isResearch = this.data.incident && this.data.incident.type === 'research'; + heroH1.textContent = isResearch ? this.t('heroResearch') : this.t('hero'); + } + // Update tab labels + var tabBtns = document.querySelectorAll('.tab-btn'); + var isResearch = this.data.incident && this.data.incident.type === 'research'; + for (var i = 0; i < tabBtns.length; i++) { + var tab = tabBtns[i].getAttribute('data-tab'); + if (tab === 'lagebild') tabBtns[i].childNodes[0].textContent = isResearch ? this.t('tabBriefingResearch') : this.t('tabBriefing'); + else if (tab === 'ueberblick') tabBtns[i].childNodes[0].textContent = isResearch ? this.t('tabUeberblickResearch') : this.t('tabUeberblick'); + else if (tab === 'karte') tabBtns[i].childNodes[0].textContent = this.t('tabMap'); + else if (tab === 'faktenchecks') tabBtns[i].childNodes[0].textContent = this.t('tabFactchecks') + ' '; + else if (tab === 'quellen') tabBtns[i].childNodes[0].textContent = this.t('tabSources') + ' '; + } + // Update Ueberblick H2 title + var ueberblickH2 = document.getElementById('ueberblick-title'); + if (ueberblickH2) ueberblickH2.textContent = isResearch ? this.t('tabUeberblickResearch') : this.t('tabUeberblick'); + // Update data source note + var dsNote = document.querySelector('.data-source-note'); + if (dsNote) dsNote.textContent = this.t('dataSource'); + } catch(e) { + console.error('Language switch failed:', e); + } + }, + + /* ===== FLOATING CTA ===== */ + initFloatingCta: function() { + var cta = document.createElement('div'); + cta.className = 'floating-cta'; + cta.innerHTML = '' + this.t('ctaText') + '' + + '' + this.t('ctaButton') + '' + + ''; + document.body.appendChild(cta); + + // Show after scrolling past hero + var shown = false; + window.addEventListener('scroll', function() { + if (shown) return; + if (window.scrollY > 400) { + cta.classList.add('visible'); + shown = true; + } + }); + + // Close button + cta.querySelector('.floating-cta-close').addEventListener('click', function(e) { + e.preventDefault(); + cta.classList.add('dismissed'); + setTimeout(function() { cta.classList.remove('dismissed'); }, 60000); + }); + }, + + /* ===== SCROLL REVEAL ===== */ + initScrollReveal: function() { + var cards = document.querySelectorAll('.content-card, .lagebild-cta'); + if (!('IntersectionObserver' in window)) { + for (var i = 0; i < cards.length; i++) cards[i].classList.add('revealed'); + return; + } + var observer = new IntersectionObserver(function(entries) { + entries.forEach(function(entry) { + if (entry.isIntersecting) { + entry.target.classList.add('revealed'); + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.1 }); + + for (var i = 0; i < cards.length; i++) { + cards[i].classList.add('reveal'); + // Immediately reveal cards in the active (visible) tab panel + var panel = cards[i].closest('.tab-panel'); + if (!panel || panel.classList.contains('active')) { + cards[i].classList.add('revealed'); + } else { + observer.observe(cards[i]); + } + } + }, + + /* ===== FAKTENCHECK-FILTER NACH ZEITRAUM ===== */ + getFactChecksAtTime: function(cutoff) { + var allFCs = this.data.fact_checks || []; + if (!cutoff) return allFCs; + var cutoffTime = new Date(this.toUTC(cutoff)).getTime(); + var filtered = []; + for (var i = 0; i < allFCs.length; i++) { + var fc = allFCs[i]; + var hist = fc.status_history || []; + if (!hist.length) continue; + // Erster Eintrag = Erstellungszeitpunkt des Faktenchecks + var firstAt = new Date(this.toUTC(hist[0].at)).getTime(); + if (firstAt > cutoffTime) continue; + // Status zum gewaehlten Zeitpunkt ermitteln + var statusAtTime = hist[0].status; + for (var j = 0; j < hist.length; j++) { + var stepTime = new Date(this.toUTC(hist[j].at)).getTime(); + if (stepTime <= cutoffTime) { + statusAtTime = hist[j].status; + } + } + // Kopie mit angepasstem Status und getrimmter History + var copy = {}; + for (var key in fc) { if (fc.hasOwnProperty(key)) copy[key] = fc[key]; } + copy.status = statusAtTime; + copy.status_history = []; + for (var j = 0; j < hist.length; j++) { + if (new Date(this.toUTC(hist[j].at)).getTime() <= cutoffTime) { + copy.status_history.push(hist[j]); + } + } + filtered.push(copy); + } + return filtered; + }, + + /* ===== ARTIKEL-FILTER NACH ZEITRAUM ===== */ + filterArticlesAtTime: function(cutoff) { + var all = this.data.articles || []; + if (!cutoff) return all; + var filtered = []; + for (var i = 0; i < all.length; i++) { + if ((all[i].collected_at || '') <= cutoff) { + filtered.push(all[i]); + } + } + return filtered; + }, + + /* ===== HILFSFUNKTIONEN ===== */ + extractDomain: function(url) { + if (!url) return null; + try { return new URL(url).hostname; } catch(e) { return null; } + }, + + fixUmlauts: function(text) { + return text || ""; + }, + + stLabel: function(s) { + var key = 'st' + s.charAt(0).toUpperCase() + s.slice(1); + return this.t(key) || s; + }, + + mdToHtml: function(md) { + if (!md) return ''; + var lines = md.split('\n'), html = '', inList = false; + for (var i = 0; i < lines.length; i++) { + var l = lines[i]; + if (/^### (.+)$/.test(l)) { if (inList) { html += ''; inList = false; } html += '

' + l.replace(/^### /, '') + '

'; continue; } + if (/^## (.+)$/.test(l)) { if (inList) { html += ''; inList = false; } html += '

' + l.replace(/^## /, '') + '

'; continue; } + if (/^[-*] (.+)$/.test(l)) { if (!inList) { html += '
    '; inList = true; } html += '
  • ' + l.replace(/^[-*] /, '') + '
  • '; continue; } + if (inList) { html += '
'; inList = false; } + if (l.trim() === '') continue; + html += '

' + l + '

'; + } + if (inList) html += ''; + html = html.replace(/\*\*(.+?)\*\*/g, '$1'); + html = html.replace(/\*(.+?)\*/g, '$1'); + return html; + }, + + esc: function(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }, + + toUTC: function(s) { + if (!s) return s; + s = String(s).trim(); + if (/[Zz]$/.test(s) || /[+-]\d{2}:?\d{2}$/.test(s)) return s; + // Naive Timestamps aus der DB sind Europe/Berlin Lokalzeit. + // Korrekten UTC-Offset ermitteln (CET +01:00 / CEST +02:00). + var iso = s.replace(' ', 'T'); + var temp = new Date(iso + 'Z'); + var utc = new Date(temp.toLocaleString('en-US', { timeZone: 'UTC' })); + var local = new Date(temp.toLocaleString('en-US', { timeZone: TIMEZONE })); + var offMin = (local - utc) / 60000; + var sign = offMin >= 0 ? '+' : '-'; + var h = String(Math.floor(Math.abs(offMin) / 60)).padStart(2, '0'); + var m = String(Math.abs(offMin) % 60).padStart(2, '0'); + return iso + sign + h + ':' + m; + }, + + fmtDT: function(iso) { + if (!iso) return ''; + try { + var d = new Date(this.toUTC(iso)); + if (isNaN(d.getTime())) return iso; + var opts = { timeZone: TIMEZONE, weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }; + var locale = Lagebild.getLocale(); + var parts = new Intl.DateTimeFormat(locale, opts).formatToParts(d); + var p = {}; + parts.forEach(function(x) { p[x.type] = x.value; }); + if (locale === 'en-GB') return p.weekday + ', ' + p.day + ' ' + p.month + ' ' + p.year + ', ' + p.hour + ':' + p.minute; + return p.weekday + ', ' + p.day + '. ' + p.month + ' ' + p.year + ' um ' + p.hour + ':' + p.minute + ' Uhr'; + } catch(e) { return iso; } + }, + + fmtDateOnly: function(iso) { + if (!iso) return ''; + try { + var d = new Date(this.toUTC(iso)); + if (isNaN(d.getTime())) return iso; + return d.toLocaleDateString(Lagebild.getLocale(), { day: 'numeric', month: 'short', year: 'numeric', timeZone: TIMEZONE }); + } catch(e) { return iso; } + }, + + fmtTimeOnly: function(iso) { + if (!iso) return ''; + try { + var d = new Date(this.toUTC(iso)); + if (isNaN(d.getTime())) return iso; + return d.toLocaleTimeString(Lagebild.getLocale(), { hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE }); + } catch(e) { return iso; } + }, + + fmtShort: function(iso) { + if (!iso) return ''; + try { return new Date(this.toUTC(iso)).toLocaleDateString(Lagebild.getLocale(), { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit', timeZone: TIMEZONE }); } + catch(e) { return iso; } + }, + + showError: function() { + document.getElementById('summary-content').innerHTML = + '

' + this.t('errorLoad') + '

'; + } +}; + +document.addEventListener('DOMContentLoaded', function() { Lagebild.init(); }); diff --git a/robots-launch.txt b/robots-launch.txt new file mode 100644 index 0000000..6ece370 --- /dev/null +++ b/robots-launch.txt @@ -0,0 +1,99 @@ +# robots.txt for AegisSight UG — wird am Tag X als robots.txt aktiv geschaltet +# Allgemein: Crawling erlaubt, außer API-Endpunkte und interne Pfade + +User-agent: * +Allow: / +Disallow: /api/ +Disallow: /_archiv/ +Disallow: /vorschau/ + +# Sitemap +Sitemap: https://aegis-sight.de/sitemap.xml + +# AI-Crawler explizit blocken — keine Trainingsdaten-Verwendung +User-agent: GPTBot +Disallow: / + +User-agent: ChatGPT-User +Disallow: / + +User-agent: CCBot +Disallow: / + +User-agent: anthropic-ai +Disallow: / + +User-agent: Claude-Web +Disallow: / + +User-agent: ClaudeBot +Disallow: / + +User-agent: Bytespider +Disallow: / + +User-agent: PerplexityBot +Disallow: / + +User-agent: Google-Extended +Disallow: / + +User-agent: Applebot-Extended +Disallow: / + +User-agent: Meta-ExternalAgent +Disallow: / + +User-agent: cohere-ai +Disallow: / + +User-agent: OAI-SearchBot +Disallow: / + +# Archiv-Bots blocken +User-agent: ia_archiver +Disallow: / + +User-agent: Wayback Machine +Disallow: / + +User-agent: archive.org_bot +Disallow: / + +# SEO-/Spam-Crawler blocken +User-agent: AhrefsBot +Disallow: / + +User-agent: SemrushBot +Disallow: / + +User-agent: MJ12bot +Disallow: / + +User-agent: DotBot +Disallow: / + +User-agent: SEOkicks-Robot +Disallow: / + +User-agent: MauiBot +Disallow: / + +User-agent: Majestic-12 +Disallow: / + +User-agent: BLEXBot +Disallow: / + +User-agent: SerendeputyBot +Disallow: / + +# Download-Manager blocken +User-agent: HTTrack +Disallow: / + +User-agent: SiteSnagger +Disallow: / + +User-agent: WebCopier +Disallow: / diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..ca4047a --- /dev/null +++ b/robots.txt @@ -0,0 +1,112 @@ +# robots.txt for AegisSight UG +# Block ALL web crawlers and bots from the entire site + +# Block all bots +User-agent: * +Disallow: / +Crawl-delay: 86400 + +# Specifically block major search engines +User-agent: Googlebot +Disallow: / + +User-agent: Bingbot +Disallow: / + +User-agent: Slurp +Disallow: / + +User-agent: DuckDuckBot +Disallow: / + +User-agent: Baiduspider +Disallow: / + +User-agent: YandexBot +Disallow: / + +# Block social media crawlers +User-agent: facebookexternalhit +Disallow: / + +User-agent: Twitterbot +Disallow: / + +User-agent: LinkedInBot +Disallow: / + +User-agent: WhatsApp +Disallow: / + +User-agent: TelegramBot +Disallow: / + +# Block SEO and analysis bots +User-agent: AhrefsBot +Disallow: / + +User-agent: SemrushBot +Disallow: / + +User-agent: DotBot +Disallow: / + +User-agent: MJ12bot +Disallow: / + +User-agent: SEOkicks-Robot +Disallow: / + +User-agent: SeznamBot +Disallow: / + +User-agent: MauiBot +Disallow: / + +User-agent: Majestic-12 +Disallow: / + +User-agent: Majestic-SEO +Disallow: / + +# Block archiving bots +User-agent: ia_archiver +Disallow: / + +User-agent: Wayback Machine +Disallow: / + +User-agent: SiteSnagger +Disallow: / + +User-agent: WebCopier +Disallow: / + +# Block AI/ML crawlers +User-agent: GPTBot +Disallow: / + +User-agent: ChatGPT-User +Disallow: / + +User-agent: CCBot +Disallow: / + +User-agent: anthropic-ai +Disallow: / + +User-agent: Claude-Web +Disallow: / + +# Block download managers +User-agent: wget +Disallow: / + +User-agent: curl +Disallow: / + +User-agent: HTTrack +Disallow: / + +# No sitemap provided +# No crawl permissions granted \ No newline at end of file diff --git a/sitemap-launch.xml b/sitemap-launch.xml new file mode 100644 index 0000000..25e4af8 --- /dev/null +++ b/sitemap-launch.xml @@ -0,0 +1,33 @@ + + + + https://aegis-sight.de/ + weekly + 1.0 + + + https://aegis-sight.de/lagen/iran-konflikt/ + daily + 0.8 + + + https://aegis-sight.de/lagen/cyberangriffe/ + daily + 0.8 + + + https://aegis-sight.de/lagen/deepfakes/ + weekly + 0.7 + + + https://aegis-sight.de/impressum.html + yearly + 0.3 + + + https://aegis-sight.de/datenschutz.html + yearly + 0.3 + + diff --git a/vorschau/css/style.css b/vorschau/css/style.css new file mode 100644 index 0000000..1ca9ae9 --- /dev/null +++ b/vorschau/css/style.css @@ -0,0 +1,475 @@ +/* AegisSight Monitor - Product Page v2 (Light Mode) */ + +/* Fonts */ +@font-face { font-family:'Inter'; src:url('/assets/fonts/Inter-Regular.woff2') format('woff2'),url('/assets/fonts/Inter-Regular.ttf') format('truetype'); font-weight:400; font-display:swap; } +@font-face { font-family:'Inter'; src:url('/assets/fonts/Inter-SemiBold.woff2') format('woff2'),url('/assets/fonts/Inter-SemiBold.ttf') format('truetype'); font-weight:600; font-display:swap; } +@font-face { font-family:'Inter'; src:url('/assets/fonts/Inter-Bold.woff2') format('woff2'),url('/assets/fonts/Inter-Bold.ttf') format('truetype'); font-weight:700; font-display:swap; } +@font-face { font-family:'Inter'; src:url('/assets/fonts/Inter-Light.woff2') format('woff2'),url('/assets/fonts/Inter-Light.ttf') format('truetype'); font-weight:300; font-display:swap; } + +:root { + --navy: #0A1832; + --navy-light: #132844; + --gold: #C8A851; + --gold-light: #D4B96A; + --gold-dark: #B39645; + --white: #FAFBFD; + --base: #F5F7FA; + --alt: #EDF0F5; + --alt-solid: #F0F3F7; + --gray-100: #E4E8EE; + --gray-200: #D0D5DE; + --gray-400: #9AA5B4; + --gray-600: #5A6478; + --text: #2A2F3A; + --text-light: #5A6478; + --radius: 8px; + --radius-lg: 12px; + --shadow: 0 2px 12px rgba(10,24,50,0.06); + --shadow-lg: 0 8px 32px rgba(10,24,50,0.1); + --nav-height: 72px; +} + +*,*::before,*::after { margin:0; padding:0; box-sizing:border-box; } +html { scroll-behavior:smooth; scroll-padding-top:var(--nav-height); } +body { font-family:'Inter',system-ui,-apple-system,sans-serif; font-size:16px; line-height:1.6; color:var(--text); background:var(--base); -webkit-font-smoothing:antialiased; } +img { max-width:100%; height:auto; } +a { color:inherit; text-decoration:none; } +.container { max-width:1120px; margin:0 auto; padding:0 24px; } + +/* ==================== NAV ==================== */ +.navbar { position:fixed; top:0; left:0; right:0; height:var(--nav-height); background:rgba(250,251,253,0.92); backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px); z-index:1000; transition:box-shadow 0.3s; } +.navbar.scrolled { box-shadow:0 1px 16px rgba(10,24,50,0.08); } +.nav-container { max-width:1120px; margin:0 auto; padding:0 24px; height:100%; display:flex; align-items:center; justify-content:space-between; } +.nav-logo { display:flex; align-items:center; } +.logo-img { height:36px; width:auto; } +.nav-menu { list-style:none; display:flex; gap:32px; } +.nav-menu a { font-size:0.9rem; font-weight:500; color:var(--navy); transition:color 0.2s; } +.nav-menu a:hover { color:var(--gold); } + +.mobile-menu-toggle { display:none; background:none; border:none; cursor:pointer; width:32px; height:24px; position:relative; flex-direction:column; justify-content:space-between; } +.mobile-menu-toggle span { display:block; width:100%; height:2px; background:var(--navy); border-radius:2px; transition:transform 0.3s,opacity 0.3s; } +.mobile-menu-toggle.active span:nth-child(1) { transform:rotate(45deg) translate(7px,7px); } +.mobile-menu-toggle.active span:nth-child(2) { opacity:0; } +.mobile-menu-toggle.active span:nth-child(3) { transform:rotate(-45deg) translate(7px,-7px); } + +.mobile-menu { position:fixed; top:var(--nav-height); left:0; right:0; background:var(--white); padding:16px 24px 24px; box-shadow:var(--shadow-lg); transform:translateY(-100%); opacity:0; transition:transform 0.3s,opacity 0.3s; z-index:999; pointer-events:none; } +.mobile-menu.open { transform:translateY(0); opacity:1; pointer-events:all; } +.mobile-menu ul { list-style:none; } +.mobile-menu li { border-bottom:1px solid var(--gray-100); } +.mobile-menu a { display:block; padding:14px 0; font-size:1rem; font-weight:500; color:var(--navy); } +.mobile-overlay { position:fixed; inset:0; background:rgba(10,24,50,0.3); z-index:998; opacity:0; pointer-events:none; transition:opacity 0.3s; } +.mobile-overlay.open { opacity:1; pointer-events:all; } + +/* ==================== HERO (Full-Video mit Endcard) ==================== */ +.hero { position:relative; min-height:88vh; overflow:hidden; background:var(--navy); clip-path:polygon(0 0, 100% 0, 100% calc(100% - 60px), 50% 100%, 0 calc(100% - 60px)); margin-bottom:-60px; z-index:1; } +.gold { color:var(--gold); font-weight:600; } + +/* Overlay-Layer für Text + Navigation */ +.hero-content { position:absolute; inset:0; z-index:4; color:var(--white); pointer-events:none; } +.hero-content > * { pointer-events:auto; } + +/* Brand (Titel + Tagline): nur während Endcard sichtbar, in Hero-Mitte */ +.hero-brand { position:absolute; top:50%; left:0; right:0; padding:0 24px; text-align:center; transform:translateY(-50%); opacity:0; transition:opacity 0.5s ease; pointer-events:none; } +.hero.endcard .hero-brand { opacity:1; transition-delay:0.35s; pointer-events:auto; } +.hero-title { font-size:3.2rem; font-weight:700; line-height:1.1; letter-spacing:-0.02em; color:var(--white); margin:0; } +.hero-tagline { font-size:1.2rem; font-weight:300; color:rgba(255,255,255,0.9); margin-top:12px; } + +/* ==================== HERO SLIDER ==================== */ +/* Slider: unter der Navbar beginnen, oberhalb der Dots-Zone enden (60px Reserve fuer Dots) */ +.hero-slider { position:absolute; top:var(--nav-height); left:0; right:0; bottom:60px; z-index:1; } +.hero-slide { position:absolute; inset:0; opacity:0; transition:opacity 0.6s ease; pointer-events:none; } +.hero-slide.active { opacity:1; pointer-events:auto; } +.hero-slide.exiting { opacity:0; transition:opacity 0.4s ease; } + +/* Video füllt den Slide (contain = komplett sichtbar, Navy-Letterbox) */ +.hero-slide-video { position:absolute; inset:0; overflow:hidden; transition:opacity 0.4s ease; } +.hero-slide-video video { display:block; width:100%; height:100%; object-fit:contain; background:var(--navy); } +/* Beim Endcard-State Video ausfaden */ +.hero-slide.ended .hero-slide-video { opacity:0; } + +/* Per-Slide-Bottom (Beispieltext + CTA): nur während Endcard sichtbar, unter dem Titel */ +.hero-slide-bottom { position:absolute; left:0; right:0; bottom:140px; padding:0 32px; text-align:center; opacity:0; transition:opacity 0.5s ease; pointer-events:none; } +.hero-slide.ended .hero-slide-bottom { opacity:1; transition-delay:0.5s; pointer-events:auto; } +.hero-slide-example { font-size:1.15rem; font-weight:400; line-height:1.55; color:rgba(255,255,255,0.9); margin:0 auto 24px; max-width:820px; padding:0; border:0; } +.hero-slide-cta { display:flex; gap:16px; flex-wrap:wrap; justify-content:center; } +.hero-slide-cta .btn-placeholder { opacity:0.5; cursor:default; border-style:dashed; pointer-events:none; } + +/* Slider-Navigation: Dots zentriert ganz unten im Chevron-Band, ausserhalb des Video-Bereichs. */ +.hero-slider-nav { position:absolute; left:0; right:0; bottom:22px; display:flex; justify-content:center; padding:0 24px; pointer-events:none; z-index:5; } +.hero-slider-dots { display:flex; gap:12px; pointer-events:auto; } +.hero-dot { width:10px; height:10px; border-radius:50%; border:2px solid var(--gold); background:transparent; cursor:pointer; transition:all 0.3s; padding:0; } +.hero-dot.active { background:var(--gold); } + +.hero-slider-arrows { /* Container - Pfeile positionieren sich absolut relativ zu hero-content */ } +.hero-arrow { position:absolute; top:50%; transform:translateY(-50%); width:48px; height:48px; border-radius:50%; border:1px solid rgba(255,255,255,0.35); background:rgba(10,24,50,0.35); color:var(--white); font-size:1.2rem; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all 0.2s; backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); z-index:5; } +.hero-arrow:hover { border-color:var(--gold); color:var(--gold); background:rgba(10,24,50,0.6); } +.hero-arrow-prev { left:24px; } +.hero-arrow-next { right:24px; } + +/* Altes hero-overlay nicht mehr verwendet */ +.hero-overlay { display:none; } + +/* ==================== BUTTONS ==================== */ +.btn { display:inline-flex; align-items:center; justify-content:center; padding:12px 28px; border-radius:var(--radius); font-family:inherit; font-size:0.95rem; font-weight:600; cursor:pointer; transition:all 0.2s; border:2px solid transparent; text-decoration:none; } +.btn-primary { background:var(--gold); color:var(--navy); border-color:var(--gold); } +.btn-primary:hover { background:var(--gold-dark); border-color:var(--gold-dark); } +.btn-outline-light { background:transparent; color:var(--white); border-color:rgba(255,255,255,0.4); } +.btn-outline-light:hover { background:rgba(255,255,255,0.1); border-color:var(--white); } +.btn-outline { background:transparent; color:var(--navy); border-color:var(--navy); } +.btn-outline:hover { background:var(--navy); color:var(--white); } +.btn-lg { padding:16px 40px; font-size:1.05rem; } +.btn-block { width:100%; } + +/* ==================== SECTIONS ==================== */ +.section { padding:88px 0; } +.section-base { background:var(--base); } +.section-alt { background:var(--alt-solid); } +.section-dark { background:var(--navy-light); color:var(--white); } + +.section-title { font-size:2rem; font-weight:700; color:var(--navy); text-align:center; margin-bottom:16px; letter-spacing:-0.01em; } +.section-dark .section-title { color:var(--white); } +.section-subtitle-light { color:rgba(255,255,255,0.6); } +.section-subtitle { font-size:1.05rem; color:var(--text-light); text-align:center; max-width:600px; margin:0 auto 48px; } + +/* ==================== SECTION DIVIDERS ==================== */ +.divider { line-height:0; margin:0; overflow:hidden; } +.divider svg { display:block; width:100%; height:auto; } +.divider-chevron { background:var(--alt-solid); } +.divider-chevron-dark { background:var(--navy); } +.divider-diagonal { background:var(--base); } +.divider-diagonal-dark { background:var(--base); } +.divider-gradient-alt-to-base { height:40px; background:linear-gradient(to bottom, var(--alt-solid), var(--base)); } +.divider-gradient-base-to-alt { height:60px; background:linear-gradient(to bottom, var(--base), var(--alt-solid)); } +.divider-gradient-dark-to-base { height:80px; background:linear-gradient(to bottom, var(--navy), var(--base)); } +.divider-gradient-dark-to-alt { height:60px; background:linear-gradient(to bottom, var(--navy), var(--alt-solid)); } + +/* ==================== GRID ==================== */ +.grid-3 { display:grid; grid-template-columns:repeat(3,1fr); gap:28px; } +#features .grid-3 { justify-items:center; } +#features .grid-3 .feature-card:nth-last-child(-n+2):nth-child(3n+1) { grid-column: 1; } +#features .grid-3 { display:flex; flex-wrap:wrap; justify-content:center; } +#features .grid-3 .feature-card { width:calc(33.333% - 20px); } +.grid-4 { display:grid; grid-template-columns:repeat(4,1fr); gap:24px; } + +/* ==================== PROBLEM ==================== */ +.problem-card { text-align:center; padding:32px 20px; } +.problem-icon { width:64px; height:64px; margin:0 auto 20px; display:flex; align-items:center; justify-content:center; background:var(--white); border-radius:50%; box-shadow:var(--shadow); } +.problem-card h3 { font-size:1.1rem; font-weight:700; color:var(--navy); margin-bottom:10px; } +.problem-card p { font-size:0.93rem; color:var(--text-light); line-height:1.6; } + +/* Problem dark variant */ +.problem-card-dark { color:var(--white); } +.problem-card-dark h3 { color:var(--white); } +.problem-card-dark p { color:rgba(255,255,255,0.7); } +.problem-icon-dark { background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.1); box-shadow:none; } +.problem-icon-dark img { filter:brightness(0) invert(1); } + +/* ==================== WORKFLOW ==================== */ +.workflow { display:flex; align-items:flex-start; justify-content:center; margin-top:56px; } +.workflow-step { flex:1; max-width:300px; text-align:center; padding:0 24px; } +.step-number { width:48px; height:48px; margin:0 auto 20px; display:flex; align-items:center; justify-content:center; background:var(--gold); color:var(--navy); font-size:1.2rem; font-weight:700; border-radius:50%; } +.workflow-step h3 { font-size:1.1rem; font-weight:700; color:var(--navy); margin-bottom:10px; } +.workflow-step p { font-size:0.93rem; color:var(--text-light); line-height:1.6; } +.workflow-connector { width:60px; height:2px; background:var(--gold); margin-top:23px; flex-shrink:0; opacity:0.4; } + +/* ==================== FEATURES ==================== */ +.feature-card { background:var(--white); border-radius:var(--radius-lg); padding:28px 24px; box-shadow:var(--shadow); transition:box-shadow 0.3s,transform 0.3s,border-color 0.3s,background 0.3s; border:1px solid transparent; } +.feature-card:hover { box-shadow:var(--shadow-lg); transform:translateY(-3px); } +.feature-icon { width:48px; height:48px; display:flex; align-items:center; justify-content:center; background:var(--alt-solid); border-radius:var(--radius); margin-bottom:14px; border:1px solid transparent; } +.feature-card h3 { font-size:1rem; font-weight:700; color:var(--navy); margin-bottom:8px; } +.feature-card p { font-size:0.88rem; color:var(--text-light); line-height:1.6; } + +/* Features im dunklen Section-Kontext: Glasmorphism mit Gold-Akzent */ +.section-dark .feature-card { background:rgba(255,255,255,0.04); border-color:rgba(200,168,81,0.2); box-shadow:none; } +.section-dark .feature-card:hover { border-color:rgba(200,168,81,0.5); background:rgba(255,255,255,0.06); transform:translateY(-3px); box-shadow:0 8px 24px rgba(0,0,0,0.3); } +.section-dark .feature-card h3 { color:var(--white); } +.section-dark .feature-card p { color:rgba(255,255,255,0.7); } +.section-dark .feature-icon { background:rgba(200,168,81,0.15); border-color:rgba(200,168,81,0.3); } +.section-dark .feature-icon img { filter:brightness(0) saturate(100%) invert(74%) sepia(49%) saturate(471%) hue-rotate(2deg) brightness(91%) contrast(83%); } + +/* ==================== DEMOS SECTION ==================== */ +#demos { padding-top:48px; } + +/* ==================== LIVE STATS BAR ==================== */ +.live-stats-bar { margin-bottom:40px; text-align:center; } +.live-stats-title { font-size:1rem; font-weight:600; color:var(--gold); text-transform:uppercase; letter-spacing:0.1em; margin-bottom:16px; } +.live-stats-row { display:flex; justify-content:center; gap:20px; flex-wrap:wrap; } +.live-stat { text-align:center; background:var(--white); border-radius:var(--radius-lg); padding:20px 32px; box-shadow:var(--shadow); border:1px solid var(--gray-100); min-width:160px; } +.live-stat-value { display:block; font-size:2.4rem; font-weight:700; color:var(--navy); line-height:1.1; letter-spacing:-0.02em; } +.live-stat-label { display:block; font-size:0.8rem; color:var(--text-light); text-transform:uppercase; letter-spacing:0.08em; margin-top:4px; } + +/* ==================== FEATURE HIGHLIGHT ==================== */ +.feature-statement { text-align:center; max-width:700px; margin:0 auto 48px; padding:0 24px; } +.feature-statement-text { font-size:1.6rem; font-weight:700; color:var(--navy); line-height:1.35; margin-bottom:12px; letter-spacing:-0.01em; } +.feature-statement-sub { font-size:1rem; color:var(--text-light); line-height:1.6; } +.section-dark .feature-statement-text { color:var(--white); } +.section-dark .feature-statement-sub { color:rgba(255,255,255,0.7); } +@media(max-width:768px) { .feature-statement-text { font-size:1.3rem; } } + +/* ==================== 3D CAROUSEL ==================== */ +.carousel-viewport { overflow-x:clip; overflow-y:visible; padding:20px 0; position:relative; } +.carousel-track { display:flex; justify-content:center; position:relative; } +.carousel-card { width:860px; flex-shrink:0; background:var(--white); border-radius:var(--radius-lg); padding:28px 24px; box-shadow:var(--shadow); position:absolute; display:flex; flex-direction:column; transition:all 0.6s cubic-bezier(0.4,0,0.2,1); cursor:pointer; transform-style:preserve-3d; } +.carousel-card.active { position:relative; transform:none; z-index:3; opacity:1; } +.carousel-card.left { position:absolute; left:0; top:0; transform:scale(0.75) translateX(-70%); z-index:1; opacity:0.45; } +.carousel-card.right { position:absolute; right:0; top:0; transform:scale(0.75) translateX(70%); z-index:1; opacity:0.45; } +.carousel-card.hidden { position:absolute; transform:scale(0.5); z-index:0; opacity:0; pointer-events:none; } + +/* Carousel arrows */ +.carousel-arrow { position:absolute; top:50%; transform:translateY(-50%); z-index:10; width:44px; height:44px; border-radius:50%; border:2px solid var(--gray-200); background:var(--white); color:var(--navy); font-size:1.6rem; cursor:pointer; display:flex; align-items:center; justify-content:center; transition:all 0.2s; box-shadow:var(--shadow); line-height:1; } +.carousel-arrow:hover { border-color:var(--gold); color:var(--gold); box-shadow:var(--shadow-lg); } +.carousel-prev { left:8px; } +.carousel-next { right:8px; } +.carousel-nav { display:flex; justify-content:center; gap:10px; margin-top:24px; } +.carousel-dot { width:10px; height:10px; border-radius:50%; border:2px solid var(--gold); background:transparent; cursor:pointer; transition:all 0.3s; padding:0; } +.carousel-dot.active { background:var(--gold); } +.card-live { border:2px solid var(--gold); box-shadow:0 4px 24px rgba(200,168,81,0.15); } +.card-placeholder { border:2px dashed var(--gray-200); opacity:0.55; } +.demo-badge { display:inline-block; padding:4px 14px; border-radius:20px; font-size:0.72rem; font-weight:700; letter-spacing:0.08em; text-transform:uppercase; margin-bottom:14px; width:fit-content; background:var(--gold); color:var(--navy); } +.badge-soon { background:var(--gray-100); color:var(--gray-400); } +.demo-title { font-size:1.25rem; font-weight:700; color:var(--navy); margin-bottom:16px; } + + +.demo-excerpt { margin-bottom:16px; } +.excerpt-text { font-size:0.88rem; color:var(--text); line-height:1.65; } + +.excerpt-text h2 { font-size:1.05rem; font-weight:700; color:var(--navy); margin:20px 0 8px; } +.excerpt-text h3 { font-size:0.95rem; font-weight:600; color:var(--navy); margin:16px 0 6px; } +.excerpt-text p { margin-bottom:10px; } +.excerpt-text ul { margin:8px 0 12px 20px; } +.excerpt-text li { margin-bottom:4px; font-size:0.88rem; color:var(--text); } +.placeholder-title { color:var(--gray-400); } +.placeholder-text { font-size:0.95rem; color:var(--gray-400); flex:1; display:flex; align-items:center; justify-content:center; min-height:180px; } + +/* ==================== MAP ==================== */ +.map-section { margin-top:48px; } +.map-title { font-size:1.1rem; font-weight:600; color:var(--navy); margin-bottom:16px; text-align:center; } +.map-section { transition:opacity 0.3s; } +.map-section.map-hidden #map-container { display:none; } +.map-section.map-hidden .map-empty { display:flex!important; } +.map-empty { display:none; align-items:center; justify-content:center; height:300px; border:2px dashed var(--gray-200); border-radius:var(--radius-lg); color:var(--gray-400); font-size:1rem; background:var(--white); } +#map-container { height:420px; border-radius:var(--radius-lg); overflow:hidden; box-shadow:var(--shadow); border:1px solid var(--gray-100); } + +/* Map pulse markers (exact lagebild style) */ +.pulse-marker-wrapper { position:relative; width:20px; height:20px; } +.pulse-marker-ring { position:absolute; inset:0; border-radius:50%; border:2px solid; animation:mapPulseRing 2s infinite; opacity:0; } +.pulse-marker-ring:nth-child(2) { animation-delay:1s; } +@keyframes mapPulseRing { 0%{transform:scale(0.5);opacity:0} 30%{opacity:0.6} 100%{transform:scale(2.5);opacity:0} } +.pulse-marker-dot { position:absolute; top:50%; left:50%; width:8px; height:8px; margin:-4px 0 0 -4px; border-radius:50%; animation:pulseDot 2s infinite; } +@keyframes pulseDot { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(0.7)} } +/* Dark popup style */ +.leaflet-popup-content-wrapper { background:#151D2E!important; color:#E8ECF4!important; border:1px solid #1E2D45!important; border-radius:4px!important; box-shadow:0 4px 16px rgba(0,0,0,0.4)!important; } +.leaflet-popup-tip { background:#151D2E!important; } +.leaflet-popup-content { margin:10px 14px!important; font-size:0.85rem!important; } + +/* ==================== TRUST ==================== */ +.trust-grid { margin-top:48px; } +.trust-card { text-align:center; padding:24px 16px; } +.trust-icon-wrap { height:56px; display:flex; align-items:center; justify-content:center; margin-bottom:16px; } +.trust-icon-wrap img { filter:brightness(0) invert(1); } +.trust-icon-wrap.trust-flag img { filter:none; } +.trust-card h3 { font-size:1.05rem; font-weight:700; margin-bottom:8px; } +.trust-card p { font-size:0.88rem; opacity:0.7; line-height:1.5; } + +/* ==================== CTA ==================== */ +.cta-container { text-align:center; max-width:600px; } +.cta-text { font-size:1.1rem; color:var(--text-light); margin-bottom:32px; } +.cta-email { font-size:0.9rem; color:var(--text-light); margin-top:16px; } + + +/* ==================== CONTACT MODAL ==================== */ +.modal-overlay { position:fixed; inset:0; z-index:9999; background:rgba(10,24,50,0.6); backdrop-filter:blur(4px); display:flex; align-items:center; justify-content:center; padding:24px; } +.modal-content { background:var(--white); border-radius:var(--radius-lg); padding:40px; max-width:520px; width:100%; position:relative; box-shadow:0 24px 64px rgba(10,24,50,0.3); max-height:90vh; overflow-y:auto; } +.modal-close { position:absolute; top:16px; right:20px; background:none; border:none; font-size:1.8rem; color:var(--gray-400); cursor:pointer; line-height:1; } +.modal-close:hover { color:var(--navy); } +.modal-content h2 { font-size:1.5rem; font-weight:700; color:var(--navy); margin-bottom:8px; } +.modal-sub { font-size:0.95rem; color:var(--text-light); margin-bottom:28px; } +.form-row { display:grid; grid-template-columns:1fr 1fr; gap:16px; } +.form-group { margin-bottom:16px; } +.form-group label { display:block; font-size:0.82rem; font-weight:600; color:var(--navy); margin-bottom:6px; text-transform:uppercase; letter-spacing:0.04em; } +.form-group input, .form-group textarea { width:100%; padding:10px 14px; border:1px solid var(--gray-200); border-radius:var(--radius); font-family:inherit; font-size:0.95rem; color:var(--text); background:var(--base); transition:border-color 0.2s; } +.form-group input:focus, .form-group textarea:focus { outline:none; border-color:var(--gold); } +.form-group textarea { resize:vertical; } +.form-success { text-align:center; padding:40px 0; } +.form-success p { font-size:1.05rem; color:var(--navy); font-weight:500; } +@media(max-width:768px) { .form-row { grid-template-columns:1fr; } .modal-content { padding:28px 20px; } } +/* ==================== FOOTER ==================== */ +.footer { background:var(--navy); color:rgba(255,255,255,0.7); padding:40px 0; font-size:0.85rem; } +.footer-content { display:flex; justify-content:space-between; align-items:center; margin-bottom:24px; padding-bottom:24px; border-bottom:1px solid rgba(255,255,255,0.1); } +.footer-company { font-weight:600; color:var(--white); margin-bottom:4px; } +.footer-links { display:flex; gap:24px; } +.footer-links a { color:rgba(255,255,255,0.7); transition:color 0.2s; } +.footer-links a:hover { color:var(--white); } +.footer-copyright { text-align:center; font-size:0.8rem; opacity:0.5; } + +/* ==================== RESPONSIVE ==================== */ +@media(max-width:1024px) { + .grid-3 { grid-template-columns:repeat(2,1fr); } + .grid-4 { grid-template-columns:repeat(2,1fr); } + .hero-title { font-size:2.5rem; } + .section { padding:64px 0; } + .workflow-connector { width:40px; } +} + +@media(max-width:768px) { + .nav-menu { display:none; } + .mobile-menu-toggle { display:flex; } + .grid-3,.grid-4 { grid-template-columns:1fr; gap:20px; } + .hero { min-height:75vh; } + .hero-brand { padding:0 20px; } + .hero-title { font-size:1.9rem; } + .hero-tagline { font-size:0.95rem; margin-top:8px; } + .hero-slide-bottom { bottom:110px; padding:0 20px; } + .hero-slide-example { font-size:0.95rem; margin-bottom:18px; } + .hero-slide-cta { flex-direction:column; } + .hero-slide-cta .btn { width:100%; } + .hero-slider-nav { bottom:18px; } + .hero-slider-arrows { display:none; } + .section { padding:48px 0; } + .section-title { font-size:1.6rem; } + .workflow { flex-direction:column; align-items:center; gap:8px; } + .workflow-connector { width:2px; height:32px; margin:0; } + .workflow-step { max-width:100%; padding:16px 0; } + .footer-content { flex-direction:column; text-align:center; gap:16px; } + + #map-container { height:300px; } + #features .grid-3 { display:flex; flex-direction:column; } + #features .grid-3 .feature-card { width:100%; } + .carousel-card { width:100%!important; max-width:100%; position:relative!important; } + .carousel-card.active { transform:none; } + .carousel-card.left, .carousel-card.right { display:none; } + .carousel-track { display:flex; flex-direction:column; } + .carousel-arrow { display:none; } + .carousel-viewport { overflow:visible; } + .live-stats-bar { padding:0 8px; } + .live-stats-row { gap:12px; } + .live-stat { min-width:0; padding:16px 12px; } + .live-stat-value { font-size:1.8rem; } +} + +@media(max-width:480px) { + .hero-title { font-size:1.65rem; } + .hero-tagline { font-size:0.9rem; } + .hero-slide-example { font-size:0.9rem; } + .container { padding:0 16px; } +} + +/* Marker-Cluster Dark Theme */ +.marker-cluster-small, +.marker-cluster-medium, +.marker-cluster-large { + background: rgba(21, 29, 46, 0.8); +} +.marker-cluster-small div, +.marker-cluster-medium div, +.marker-cluster-large div { + background: rgba(200, 168, 81, 0.9); + color: #0A1832; + font-weight: 600; +} + +/* === Neueste Entwicklungen (Live-Monitoring Vorschau) === */ +.dev-list-heading { + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.8px; + text-transform: uppercase; + color: #C8A851; + margin-bottom: 10px; +} +.dev-list { + display: flex; + flex-direction: column; + gap: 6px; +} +.dev-bullet { + background: rgba(30, 45, 69, 0.45); + border-left: 3px solid #C8A851; + border-radius: 4px; + padding: 8px 12px; + text-align: left; +} +.dev-bullet-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-bottom: 4px; + flex-wrap: wrap; +} +.dev-sources { + display: inline-flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; + min-width: 0; +} +.dev-source-pill { + display: inline-block; + padding: 2px 8px; + background: rgba(200, 168, 81, 0.15); + color: #E8ECF4; + border-radius: 3px; + font-size: 0.7rem; + font-weight: 500; + text-decoration: none; + line-height: 1.5; + white-space: normal; + overflow-wrap: anywhere; + font-variant-numeric: tabular-nums; +} +a.dev-source-pill:hover { + background: rgba(200, 168, 81, 0.3); + text-decoration: none; + color: #E8ECF4; +} +.dev-time { + color: #8896AB; + font-size: 0.7rem; + font-variant-numeric: tabular-nums; + white-space: nowrap; + flex-shrink: 0; +} +.dev-body { + font-size: 0.85rem; + line-height: 1.45; + color: #E8ECF4; +} + +/* ==================== HELLIGKEITS-TONLEITER (Test) ==================== */ +/* Vier helle Sections in vier Helligkeitsstufen — Seite "atmet" beim Scrollen + sanft von hell nach kühler, mündet hart in den dunklen Footer. */ +:root { + --tone-1: #F5F7FA; /* Problem (hellster) */ + --tone-2: #ECF0F5; /* Workflow */ + --tone-3: #E4EAF1; /* Demos */ + --tone-4: #ECF2F9; /* Contact (hell blau-grau-weiß, frischer Akzent vor Trust) */ +} +#problem { background: var(--tone-1); } +#solution { background: var(--tone-2); } +#demos { background: var(--tone-3); } +#contact { background: var(--tone-4); } + +/* Divider-Übergänge an die Tonstufen anpassen (Adjacent-Sibling) */ +#problem + .divider { background: linear-gradient(to bottom, var(--tone-1), var(--tone-2)); } +#solution + .divider { background: linear-gradient(to bottom, var(--tone-2), var(--tone-3)); } +#demos + .divider { background: linear-gradient(to bottom, var(--tone-3), var(--tone-4)); height: 60px; } +#contact + .divider { background: var(--tone-4); } /* diagonal-dark Contact->Trust, Top-Farbe an Contact angleichen */ +#trust { margin-top: -1px; } /* schließt Subpixel-Lücke zum Diagonal-Divider darüber */ + +/* === EXCERPT FADE-MASK PATCH 2026-04-26 START === */ +/* Vereinheitlicht die Karussell-Kartenhoehe ueber feste Excerpt-Hoehe */ +/* + weicher Fade-out bei langen Lagen statt harter Abschneidung */ +.carousel-card .demo-excerpt { + position: relative; + height: 760px; + overflow: hidden; + -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 80px), transparent 100%); + mask-image: linear-gradient(to bottom, #000 calc(100% - 80px), transparent 100%); +} +@media(max-width:768px) { + .carousel-card .demo-excerpt { height: 640px; } +} +/* === EXCERPT FADE-MASK PATCH 2026-04-26 END === */ + + diff --git a/vorschau/index.html b/vorschau/index.html new file mode 100644 index 0000000..f1efb2c --- /dev/null +++ b/vorschau/index.html @@ -0,0 +1,522 @@ + + + + + + AegisSight Monitor – Echtzeit-Lagebilder aus offenen Quellen + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Vorschau

+

Zugang nur mit Passwort

+
+ + +
+
Falsches Passwort
+
+ + + + + + + +
+ + +
+
+
+
+

AegisSight Monitor

+

KI-gestützte Echtzeit-Lagebilder aus offenen Quellen, vollautomatisch.

+
+ +
+ +
+
+ +
+
+

Live-Beispiel: Der Iran-Konflikt wird mit über 14.900 Artikeln aus 375 Quellen kontinuierlich überwacht.

+ +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+

Beispiel: Ein Dossier zur rechtlichen Lage von Deepfakes in Deutschland, 121 Artikel aus 90 Quellen, automatisch erstellt.

+ +
+
+ + +
+
+ +
+
+

Im Iran-Konflikt werden Primärquellen in Farsi, Arabisch und Hebräisch direkt ausgewertet.

+ +
+
+ + +
+
+ +
+
+

Beispiel: Cyberangriffe auf deutsche Infrastruktur, 93 Artikel aus 41 Quellen, automatisch überwacht.

+ +
+
+
+ +
+
+ + + + + +
+
+
+ + +
+
+
+ + +
+
+

Was der Monitor leistet

+ +
+

Jede Behauptung wird automatisch gegen unabhängige Quellen geprüft.

+

Statusverlauf, Evidenz und Quellenbelege. Automatisch und nachvollziehbar.

+
+ +
+
+
+ +
+

Echtzeit-Monitoring

+

Kontinuierliche Überwachung Ihrer definierten Quellen, rund um die Uhr.

+
+ +
+
+ +
+

Quellenanalyse

+

Automatische Aggregation und Deduplizierung aus hunderten internationalen Quellen.

+
+
+
+ +
+

Geografische Verortung

+

Orte werden erkannt und auf einer interaktiven Karte dargestellt.

+
+
+
+ +
+

Mehrsprachige Auswertung

+

Quellen in verschiedenen Sprachen werden automatisch verarbeitet und zusammengeführt.

+
+
+
+ +
+

Strukturierte Lagebilder

+

Übersichtliche Zusammenfassungen mit Quellenbelegen und Zeitverläufen.

+
+
+
+
+ + +
+ + +
+
+

Warum manuelle OSINT-Auswertung nicht skaliert

+

Analysten in Sicherheitsbehörden, Redaktionen und Unternehmen stehen täglich vor der gleichen Herausforderung.

+
+
+
+ +
+

Quellenvielfalt

+

Hunderte Nachrichtenagenturen, Telegram-Kanäle und soziale Medien in dutzenden Sprachen. Kein Analyst überblickt alles gleichzeitig.

+
+
+
+ +
+

Zeitdruck

+

Neue Meldungen im Minutentakt. Manuelle Auswertung kostet Zeit, die Sie nicht haben.

+
+
+
+ +
+

Informationsflut

+

Kritische Informationen gehen in der Masse unter, Zusammenhänge bleiben unsichtbar.

+
+
+
+
+ + +
+ + +
+
+

So funktioniert der AegisSight Monitor

+
+
+
1
+

Erfassen

+

Hunderte Quellen werden kontinuierlich überwacht. Nachrichtenagenturen, Telegram, Social Media und mehr.

+
+
+
+
2
+

Analysieren

+

Meldungen werden automatisch ausgewertet, Fakten geprüft und geografisch verortet.

+
+
+
+
3
+

Berichten

+

Strukturierte Lagebilder mit Quellenbelegen, Faktencheck und Kartenansicht. In Echtzeit.

+
+
+
+
+ + +
+ + +
+
+ +
+
Großlage - Irankonflikt
+
+
+ ... + Artikel +
+
+ ... + Quellen +
+
+ ... + Faktenchecks +
+
+
+ + + + + +
+

Geografische Verortung der Meldungen

+
+ +
+
+
+ + +
+ + +
+
+

Interesse am AegisSight Monitor?

+

Sprechen Sie mit uns über Ihren Einsatzfall.

+ +
+
+ + +
+ +
+ + +
+
+

Unser Versprechen

+
+
+
+ +
+

Enge Zusammenarbeit

+

Wir arbeiten Hand in Hand mit unseren Kunden für maßgeschneiderte Lösungen

+
+
+
+ +
+

Made in Germany

+

Klare, robuste und sichere Software nach deutschen Qualitätsstandards

+
+
+
+ +
+

Verlässliche Partnerschaft

+

Basierend auf gemeinsamen Werten und langfristigem Vertrauen

+
+
+
+ +
+

Nachhaltigkeit

+

Fokus auf Sicherheit, Professionalität und zukunftssichere Lösungen

+
+
+
+
+ + +
+
+ + +
+
+ + + + + + + + + + + + diff --git a/vorschau/js/app.js b/vorschau/js/app.js new file mode 100644 index 0000000..f45b528 --- /dev/null +++ b/vorschau/js/app.js @@ -0,0 +1,719 @@ +/* AegisSight Monitor - Product Page v2 */ +(function () { + 'use strict'; + + /* ==================== NAVBAR ==================== */ + var navbar = document.getElementById('navbar'); + window.addEventListener('scroll', function () { + navbar.classList.toggle('scrolled', window.scrollY > 10); + }); + + /* ==================== MOBILE MENU ==================== */ + var toggle = document.querySelector('.mobile-menu-toggle'); + var menu = document.getElementById('mobile-menu'); + var overlay = document.getElementById('mobile-overlay'); + + function closeMenu() { + toggle.classList.remove('active'); + menu.classList.remove('open'); + overlay.classList.remove('open'); + toggle.setAttribute('aria-expanded', 'false'); + } + + toggle.addEventListener('click', function () { + var isOpen = menu.classList.contains('open'); + if (isOpen) { closeMenu(); } else { + toggle.classList.add('active'); + menu.classList.add('open'); + overlay.classList.add('open'); + toggle.setAttribute('aria-expanded', 'true'); + } + }); + + overlay.addEventListener('click', closeMenu); + menu.querySelectorAll('a').forEach(function (l) { l.addEventListener('click', closeMenu); }); + + /* ==================== SMOOTH SCROLL ==================== */ + document.querySelectorAll('a[href^="#"]').forEach(function (link) { + link.addEventListener('click', function (e) { + var t = document.querySelector(this.getAttribute('href')); + if (t) { e.preventDefault(); t.scrollIntoView({ behavior: 'smooth' }); } + }); + }); + + /* ==================== HERO SLIDER (video-driven mit Endcard) ==================== */ + var heroEl = document.querySelector('.hero'); + var heroSlides = document.querySelectorAll('.hero-slide'); + var heroDots = document.querySelectorAll('.hero-dot'); + var heroCurrentSlide = 0; + var heroEndcardTimer = null; + var heroFallbackTimer = null; + var heroIsTransitioning = false; + var HERO_ENDCARD_MS = 7000; + var HERO_FALLBACK_MS = 25000; + + function heroClearTimers() { + if (heroEndcardTimer) { clearTimeout(heroEndcardTimer); heroEndcardTimer = null; } + if (heroFallbackTimer) { clearTimeout(heroFallbackTimer); heroFallbackTimer = null; } + } + + function heroPlaySlideVideo(slide) { + var v = slide && slide.querySelector('video'); + if (!v) return; + try { v.currentTime = 0; } catch (err) { /* ignore */ } + var p = v.play(); + if (p && typeof p.catch === 'function') p.catch(function () { /* autoplay blocked */ }); + } + + function heroPauseSlideVideo(slide) { + var v = slide && slide.querySelector('video'); + if (v) v.pause(); + } + + function heroEnterEndcard() { + if (!heroSlides.length) return; + var slide = heroSlides[heroCurrentSlide]; + if (!slide || slide.classList.contains('ended')) return; + slide.classList.add('ended'); + if (heroEl) heroEl.classList.add('endcard'); + heroClearTimers(); + heroEndcardTimer = setTimeout(function () { + heroEndcardTimer = null; + heroNext(); + }, HERO_ENDCARD_MS); + } + + function heroStartSlide() { + var slide = heroSlides[heroCurrentSlide]; + if (!slide) return; + slide.classList.remove('ended'); + if (heroEl) heroEl.classList.remove('endcard'); + heroPlaySlideVideo(slide); + heroClearTimers(); + heroFallbackTimer = setTimeout(function () { + heroFallbackTimer = null; + heroEnterEndcard(); + }, HERO_FALLBACK_MS); + } + + function heroGoTo(index) { + if (heroIsTransitioning || index === heroCurrentSlide || !heroSlides.length) return; + heroIsTransitioning = true; + heroClearTimers(); + + var oldIndex = heroCurrentSlide; + heroSlides[oldIndex].classList.add('exiting'); + heroSlides[oldIndex].classList.remove('active'); + // .ended bleibt waehrend des Fade-outs erhalten - sonst blitzt das pausierte + // Video-Frame durch, waehrend die Endcard ausfadet und der Container fadet aus. + if (heroEl) heroEl.classList.remove('endcard'); + if (heroDots[oldIndex]) heroDots[oldIndex].classList.remove('active'); + + heroPauseSlideVideo(heroSlides[oldIndex]); + + setTimeout(function () { + heroSlides[oldIndex].classList.remove('exiting', 'ended'); + heroCurrentSlide = index; + heroSlides[heroCurrentSlide].classList.add('active'); + if (heroDots[heroCurrentSlide]) heroDots[heroCurrentSlide].classList.add('active'); + heroStartSlide(); + heroIsTransitioning = false; + }, 400); + } + + function heroNext() { + heroGoTo((heroCurrentSlide + 1) % heroSlides.length); + } + + function heroPrev() { + heroGoTo((heroCurrentSlide - 1 + heroSlides.length) % heroSlides.length); + } + + // Pro Video: 'ended' → Endcard-Phase starten + heroSlides.forEach(function (slide) { + var v = slide.querySelector('video'); + if (!v) return; + v.addEventListener('ended', function () { + if (slide.classList.contains('active')) heroEnterEndcard(); + }); + }); + + heroDots.forEach(function (dot, i) { + dot.addEventListener('click', function () { heroGoTo(i); }); + }); + + var heroPrevBtn = document.querySelector('.hero-arrow-prev'); + var heroNextBtn = document.querySelector('.hero-arrow-next'); + if (heroPrevBtn) heroPrevBtn.addEventListener('click', heroPrev); + if (heroNextBtn) heroNextBtn.addEventListener('click', heroNext); + + var heroSlider = document.querySelector('.hero-slider'); + if (heroSlider) { + var heroTouchStartX = 0; + heroSlider.addEventListener('touchstart', function (e) { + heroTouchStartX = e.changedTouches[0].screenX; + }, { passive: true }); + heroSlider.addEventListener('touchend', function (e) { + var diff = e.changedTouches[0].screenX - heroTouchStartX; + if (Math.abs(diff) > 50) { + if (diff < 0) heroNext(); else heroPrev(); + } + }, { passive: true }); + } + + document.addEventListener('visibilitychange', function () { + var slide = heroSlides[heroCurrentSlide]; + if (!slide) return; + if (document.hidden) { + heroClearTimers(); + heroPauseSlideVideo(slide); + return; + } + if (slide.classList.contains('ended')) { + heroEndcardTimer = setTimeout(function () { + heroEndcardTimer = null; + heroNext(); + }, HERO_ENDCARD_MS); + } else { + var v = slide.querySelector('video'); + if (v) { + var p = v.play(); + if (p && typeof p.catch === 'function') p.catch(function () {}); + } + heroFallbackTimer = setTimeout(function () { + heroFallbackTimer = null; + heroEnterEndcard(); + }, HERO_FALLBACK_MS); + } + }); + + // Initialer Start (Slide 0 ist bereits .active im HTML) + if (heroSlides.length) heroStartSlide(); + + /* ==================== MAP STATE ==================== */ + var mapInstance = null; + var markerLayer = null; + var legendControl = null; + var lageData = {}; + var dataLoaded = false; + + var lageTitles = { + 'iran-konflikt': 'Gro\u00dflage - Irankonflikt', + 'cyberangriffe': 'Cyberangriffe auf deutsche Infrastruktur', + 'deepfakes': 'Rechtliche Lage von Deepfakes in Deutschland' + }; + + /* ==================== 3D CAROUSEL ==================== */ + var cards = document.querySelectorAll('.carousel-card'); + var dots = document.querySelectorAll('.carousel-dot'); + var activeIndex = 0; + + window.positionCards = function positionCards(idx) { + activeIndex = idx; + cards.forEach(function (card, i) { + card.classList.remove('active', 'left', 'right', 'hidden'); + if (i === idx) card.classList.add('active'); + else if (i === (idx - 1 + cards.length) % cards.length) card.classList.add('left'); + else if (i === (idx + 1) % cards.length) card.classList.add('right'); + else card.classList.add('hidden'); + }); + dots.forEach(function (dot, i) { + dot.classList.toggle('active', i === idx); + }); + // Update map based on active Lage (only after data loaded) + if (!dataLoaded) return; + var lage = cards[idx].getAttribute('data-lage'); + var mapSection = document.getElementById('map-section'); + if (lage && lageData[lage]) { + mapSection.classList.remove('map-hidden'); + showMarkers(lageData[lage].locations, lageData[lage].category_labels); + // Stats-Bar aktualisieren + var titleEl = document.querySelector('.live-stats-title'); + if (titleEl) titleEl.textContent = lageTitles[lage] || lage; + countUp(document.getElementById('stat-articles'), lageData[lage].article_count); + countUp(document.getElementById('stat-sources'), lageData[lage].source_count); + countUp(document.getElementById('stat-factchecks'), lageData[lage].factcheck_count); + } else { + if (mapSection) mapSection.classList.add('map-hidden'); + clearMarkers(); + } + } + + cards.forEach(function (card, i) { + card.addEventListener('click', function () { + if (!card.classList.contains('active')) positionCards(i); + }); + }); + + dots.forEach(function (dot, i) { + dot.addEventListener('click', function () { positionCards(i); }); + }); + + positionCards(0); + + // Arrow navigation + var prevBtn = document.querySelector('.carousel-prev'); + var nextBtn = document.querySelector('.carousel-next'); + if (prevBtn) prevBtn.addEventListener('click', function () { + positionCards((activeIndex - 1 + cards.length) % cards.length); + }); + if (nextBtn) nextBtn.addEventListener('click', function () { + positionCards((activeIndex + 1) % cards.length); + }); + + /* ==================== NEUESTE ENTWICKLUNGEN (Live-Monitoring) ==================== */ + function htmlEscape(s) { + return String(s == null ? '' : s) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } + + function normalizeSourceName(s) { + return String(s || '').toLowerCase().replace(/^(der|die|das)\s+/, '').replace(/\s+/g, ' ').trim(); + } + + function renderLatestDevelopments(text, sources) { + if (!text) return null; + sources = Array.isArray(sources) ? sources : []; + + var lines = text.split('\n').map(function (l) { return l.trim(); }) + .filter(function (l) { return l && (l.charAt(0) === '-' || l.charAt(0) === '['); }); + if (!lines.length) return null; + + var bulletRe = /^(?:-\s*)?\[\s*(\d{1,2})\.(\d{1,2})\.?(?:\d{2,4})?\s+(\d{1,2}:\d{2})\s*\]\s*(.+?)\s*$/; + var trailingRe = /\s*\{([^{}]+)\}\s*\.?\s*$/; + var citationRe = /\[(\d+[a-z]?)\]/g; + var junkRe = /^(unbekannt|unknown|n\/?a|keine|keine quelle|tba)$/i; + + function buildPill(src, name) { + var disp = (src && src.name) || name; + var url = (src && src.url) || ''; + var tgMatch = url.match(/^https?:\/\/t\.me\/([^\/?#]+)/i); + var label = tgMatch ? disp + ' (t.me/' + tgMatch[1] + ')' : disp; + var e = htmlEscape(label); + var titleEsc = htmlEscape(disp); + if (src && src.url) { + return '' + e + ''; + } + return '' + e + ''; + } + + function lookupByName(name) { + var n = normalizeSourceName(name); + if (!n) return null; + var exact = sources.find(function (s) { return normalizeSourceName(s.name) === n; }); + if (exact) return exact; + return sources.find(function (s) { + var sn = normalizeSourceName(s.name); + return sn && (sn.indexOf(n) !== -1 || n.indexOf(sn) !== -1); + }) || null; + } + + var cards = []; + for (var i = 0; i < lines.length; i++) { + var m = bulletRe.exec(lines[i]); + if (!m) continue; + var date = String(m[1]).padStart(2, '0') + '.' + String(m[2]).padStart(2, '0') + '.'; + var time = m[3]; + var body = m[4]; + + var pills = ''; + var t = trailingRe.exec(body); + if (t) { + body = body.replace(trailingRe, '').trim(); + var items = t[1].split(',').map(function (n) { return n.trim(); }).filter(Boolean); + var seen = {}; + pills = items.map(function (item) { + var pipeIdx = item.indexOf('|'); + var itemName = pipeIdx >= 0 ? item.slice(0, pipeIdx).trim() : item.trim(); + var itemUrl = pipeIdx >= 0 ? item.slice(pipeIdx + 1).trim() : ''; + if (!itemName || junkRe.test(itemName)) return ''; + var key = normalizeSourceName(itemName); + if (seen[key]) return ''; + seen[key] = true; + if (itemUrl) { + return buildPill({ name: itemName, url: itemUrl }, itemName); + } + return buildPill(lookupByName(itemName), itemName); + }).filter(Boolean).join(''); + } + if (!pills) { + var nums = []; + var cm; + while ((cm = citationRe.exec(body)) !== null) { + if (nums.indexOf(cm[1]) === -1) nums.push(cm[1]); + } + citationRe.lastIndex = 0; + if (nums.length) { + body = body.replace(citationRe, '').replace(/\s+/g, ' ').trim(); + pills = nums.map(function (num) { + var src = sources.find(function (s) { return String(s.nr) === num || Number(s.nr) === Number(num); }); + return src ? buildPill(src, src.name) : ''; + }).filter(Boolean).join(''); + } + } + + var head = '
' + + '' + pills + '' + + '' + htmlEscape(time) + ' \u00b7 ' + htmlEscape(date) + '' + + '
'; + cards.push('
' + head + '
' + htmlEscape(body) + '
'); + } + + if (!cards.length) return null; + return '
Neueste Entwicklungen
' + + '
' + cards.join('') + '
'; + } + + /* ==================== SIMPLE MARKDOWN ==================== */ +function mdToHtml(md) { + if (!md) return ''; + var lines = md.split('\n'); + var html = ''; + var inList = false; + for (var i = 0; i < lines.length; i++) { + var line = lines[i].trim(); + if (!line) { + if (inList) { html += ''; inList = false; } + continue; + } + line = line.replace(/\[(\d+[a-z]?)\]/g, ''); + line = line.replace(/\*\*(.+?)\*\*/g, '$1'); + if (/^## /.test(line)) { + if (inList) { html += ''; inList = false; } + html += '

' + line.replace(/^## /, '') + '

'; + } else if (/^### /.test(line)) { + if (inList) { html += ''; inList = false; } + html += '

' + line.replace(/^### /, '') + '

'; + } else if (/^- /.test(line)) { + if (!inList) { html += '
    '; inList = true; } + html += '
  • ' + line.replace(/^- /, '') + '
  • '; + } else { + if (inList) { html += '
'; inList = false; } + html += '

' + line + '

'; + } + } + if (inList) html += ''; + return html; + } + + /* ==================== COUNT-UP ANIMATION ==================== */ + function countUp(el, target) { + if (!el) return; + if (!target) { el.textContent = '0'; return; } + var duration = 1200; + var startTime = null; + function step(ts) { + if (!startTime) startTime = ts; + var progress = Math.min((ts - startTime) / duration, 1); + var ease = 1 - Math.pow(1 - progress, 3); + el.textContent = Math.floor(ease * target).toLocaleString('de-DE'); + if (progress < 1) requestAnimationFrame(step); + } + requestAnimationFrame(step); + } + + /* ==================== LIVE DATA ==================== */ + function timeAgo(dateStr) { + var diffMin = Math.floor((Date.now() - new Date(dateStr).getTime()) / 60000); + if (diffMin < 1) return 'Gerade eben aktualisiert'; + if (diffMin < 60) return 'Aktualisiert vor ' + diffMin + ' Min.'; + var diffH = Math.floor(diffMin / 60); + if (diffH < 24) return 'Aktualisiert vor ' + diffH + ' Std.'; + var diffD = Math.floor(diffH / 24); + return 'Aktualisiert vor ' + diffD + (diffD === 1 ? ' Tag' : ' Tagen'); + } + + function loadLiveData() { + fetch('/lagen/iran-konflikt/data/summary.json?t=' + Date.now()) + .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); }) + .then(function (data) { + var inc = data.incident || {}; + // summary.json has flat structure + + var ea = document.getElementById('stat-articles'); + var es = document.getElementById('stat-sources'); + var ef = document.getElementById('stat-factchecks'); + countUp(ea, inc.article_count); + countUp(es, inc.source_count); + countUp(ef, inc.factcheck_count); + + // Excerpt: pre-extracted in summary.json + var excerptEl = document.getElementById('excerpt-text'); + if (excerptEl && data.zusammenfassung) { + excerptEl.innerHTML = mdToHtml(data.zusammenfassung); + } + + // Store data and init map + lageData['iran-konflikt'] = { + locations: data.locations || [], + category_labels: data.category_labels || {}, + article_count: inc.article_count || 0, + source_count: inc.source_count || 0, + factcheck_count: inc.factcheck_count || 0 + }; + dataLoaded = true; + createMap(); + var mapSection = document.getElementById('map-section'); + if (mapSection) mapSection.classList.remove('map-hidden'); + showMarkers(data.locations || [], data.category_labels || {}); + }) + .catch(function () { + }); + } + + /* ==================== LEAFLET MAP ==================== */ + function clearMarkers() { + if (markerLayer) { mapInstance.removeLayer(markerLayer); markerLayer = null; } + if (legendControl && mapInstance) { mapInstance.removeControl(legendControl); legendControl = null; } + } + + function createMap() { + if (mapInstance) return; + var mapEl = document.getElementById('map-container'); + if (!mapEl || typeof L === 'undefined') return; + + mapInstance = L.map(mapEl, { + center: [33.0, 48.0], zoom: 5, zoomControl: true, scrollWheelZoom: true, + minZoom: 2, maxBounds: [[-85, -180], [85, 180]], maxBoundsViscosity: 1.0 + }); + + L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap', + maxZoom: 19, noWrap: true + }).addTo(mapInstance); + + setTimeout(function () { mapInstance.invalidateSize(); }, 500); + } + + function pulseIcon(color) { + return L.divIcon({ + className: '', + html: '
' + + '
' + + '
' + + '
' + + '
', + iconSize: [20, 20], iconAnchor: [10, 10], popupAnchor: [0, -12] + }); + } + + function buildPopup(loc) { + var html = '' + (loc.name || '') + ''; + if (loc.country_code) html += ' (' + loc.country_code + ')'; + html += '
' + (loc.article_count || 0) + ' Artikel'; + if (loc.top_articles && loc.top_articles.length > 0) { + html += '
'; + loc.top_articles.forEach(function (a) { + var hl = (a.headline || '').replace(/\*\*/g, ''); + if (hl.length > 60) hl = hl.substring(0, 60) + '\u2026'; + if (a.url) { + html += '' + hl + ''; + } else { + html += '' + hl + ''; + } + html += '' + (a.source || '') + ''; + }); + html += '
'; + } + return html; + } + + function showMarkers(locations, apiLabels) { + if (!mapInstance) createMap(); + clearMarkers(); + + var categoryColors = { + primary: '#ef4444', + secondary: '#f59e0b', + tertiary: '#3b82f6', + mentioned: '#7b7b7b' + }; + + var defaultLabels = { + primary: 'Hauptgeschehen', + secondary: 'Reaktionen', + tertiary: 'Beteiligte', + mentioned: 'Erw\u00e4hnt' + }; + + var categoryLabels = {}; + ['primary', 'secondary', 'tertiary', 'mentioned'].forEach(function (k) { + categoryLabels[k] = (apiLabels && apiLabels[k]) || defaultLabels[k]; + }); + + var clusterGroup = L.markerClusterGroup({ + maxClusterRadius: 50, + spiderfyOnMaxZoom: true, + showCoverageOnHover: false, + zoomToBoundsOnClick: true, + disableClusteringAtZoom: 10 + }); + + var usedCategories = {}; + var bounds = []; + + locations.forEach(function (loc) { + if (!loc.lat || !loc.lon) return; + var cat = loc.category || 'mentioned'; + var color = categoryColors[cat] || '#7b7b7b'; + usedCategories[cat] = true; + + var marker; + if (cat === 'primary' || cat === 'secondary') { + marker = L.marker([loc.lat, loc.lon], { icon: pulseIcon(color) }); + } else { + marker = L.circleMarker([loc.lat, loc.lon], { + radius: 5, fillColor: color, fillOpacity: 0.7, + color: color, weight: 1, opacity: 0.9 + }); + } + marker.bindPopup(buildPopup(loc), { maxWidth: 300 }); + clusterGroup.addLayer(marker); + bounds.push([loc.lat, loc.lon]); + }); + + markerLayer = clusterGroup; + mapInstance.addLayer(markerLayer); + + var legend = L.control({ position: 'bottomright' }); + legend.onAdd = function () { + var div = L.DomUtil.create('div'); + div.style.cssText = 'background:#151D2E;padding:10px 14px;border-radius:4px;border:1px solid #1E2D45;box-shadow:0 2px 8px rgba(0,0,0,0.3);font-size:0.8rem;line-height:1.8;color:#E8ECF4;'; + var html = 'Legende
'; + ['primary', 'secondary', 'tertiary', 'mentioned'].forEach(function (cat) { + if (usedCategories[cat]) { + html += ' ' + categoryLabels[cat] + '
'; + } + }); + div.innerHTML = html; + return div; + }; + legendControl = legend; + legendControl.addTo(mapInstance); + + if (bounds.length > 0) { + mapInstance.fitBounds(bounds, { padding: [30, 30], maxZoom: 7 }); + } + + setTimeout(function () { mapInstance.invalidateSize(); }, 300); + } + + /* ==================== CONTACT MODAL ==================== */ + window.openContactModal = function () { + document.getElementById('contact-modal').style.display = 'flex'; + document.body.style.overflow = 'hidden'; + if (window.umami) umami.track('contact_modal_open'); + }; + + window.closeContactModal = function () { + document.getElementById('contact-modal').style.display = 'none'; + document.body.style.overflow = ''; + }; + + // Close on overlay click + var modalOverlay = document.getElementById('contact-modal'); + if (modalOverlay) { + modalOverlay.addEventListener('click', function (e) { + if (e.target === modalOverlay) closeContactModal(); + }); + } + + // Close on Escape + document.addEventListener('keydown', function (e) { + if (e.key === 'Escape' && modalOverlay && modalOverlay.style.display === 'flex') { + closeContactModal(); + } + }); + + // Form submit -> server-side SMTP + window.submitContact = function (e) { + e.preventDefault(); + var btn = e.target.querySelector('button[type="submit"]'); + if (btn) { btn.disabled = true; btn.textContent = 'Wird gesendet...'; } + + fetch('/api/contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + name: document.getElementById('cf-name').value, + organisation: document.getElementById('cf-org').value, + email: document.getElementById('cf-email').value, + message: document.getElementById('cf-message').value + }) + }) + .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, data: d }; }); }) + .then(function (res) { + if (res.ok) { + document.getElementById('contact-form').style.display = 'none'; + document.getElementById('form-success').style.display = 'block'; + if (window.umami) umami.track('contact_form_success'); + } else { + alert(res.data.error || 'Fehler beim Senden'); + if (btn) { btn.disabled = false; btn.textContent = 'Nachricht senden'; } + } + }) + .catch(function () { + alert('Verbindungsfehler. Bitte versuchen Sie es erneut.'); + if (btn) { btn.disabled = false; btn.textContent = 'Nachricht senden'; } + }); + return false; + }; + + /* ==================== LOAD DEEPFAKES DATA ==================== */ + function loadDeepfakesData() { + fetch('/lagen/deepfakes/data/summary.json?t=' + Date.now()) + .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); }) + .then(function (data) { + var excerptEl = document.getElementById('excerpt-text-deepfakes'); + if (excerptEl && data.zusammenfassung) { + var lines = data.zusammenfassung.split("\n"); + var filtered = lines.filter(function(l) { var t = l.trim(); return !t || t.indexOf("## ") === 0 || t.indexOf("- ") === 0; }); + excerptEl.innerHTML = mdToHtml(filtered.join("\n")); + } + + // Store data for map + lageData['deepfakes'] = { + locations: data.locations || [], + category_labels: data.category_labels || {}, + article_count: (data.incident || {}).article_count || 0, + source_count: (data.incident || {}).source_count || 0, + factcheck_count: (data.incident || {}).factcheck_count || 0 + }; + }) + .catch(function () { + var el = document.getElementById('excerpt-text-deepfakes'); + if (el) el.textContent = 'Daten konnten nicht geladen werden.'; + }); + } + + /* ==================== LOAD CYBERANGRIFFE DATA ==================== */ + function loadCyberangriffeData() { + fetch('/lagen/cyberangriffe/data/summary.json?t=' + Date.now()) + .then(function (r) { if (!r.ok) throw new Error(r.status); return r.json(); }) + .then(function (data) { + var excerptEl = document.getElementById('excerpt-text-cyberangriffe'); + if (excerptEl && data.zusammenfassung) { + excerptEl.innerHTML = mdToHtml(data.zusammenfassung); + } + lageData['cyberangriffe'] = { + locations: data.locations || [], + category_labels: data.category_labels || {}, + article_count: (data.incident || {}).article_count || 0, + source_count: (data.incident || {}).source_count || 0, + factcheck_count: (data.incident || {}).factcheck_count || 0 + }; + }) + .catch(function () { + var el = document.getElementById('excerpt-text-cyberangriffe'); + if (el) el.textContent = 'Daten konnten nicht geladen werden.'; + }); + } + + /* ==================== INIT ==================== */ + loadLiveData(); + loadDeepfakesData(); + loadCyberangriffeData(); +})(); diff --git a/vorschau/videos/hero-slide-1-monitoring.mp4 b/vorschau/videos/hero-slide-1-monitoring.mp4 new file mode 100644 index 0000000..7baa12a --- /dev/null +++ b/vorschau/videos/hero-slide-1-monitoring.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71ce14f65f671112afa1c189851575526405bd07de11301c4027a2df321fb86c +size 2104808 diff --git a/vorschau/videos/hero-slide-2-monitoring.mp4 b/vorschau/videos/hero-slide-2-monitoring.mp4 new file mode 100644 index 0000000..a824257 --- /dev/null +++ b/vorschau/videos/hero-slide-2-monitoring.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b12b05dfda45c4456e827ce9b7ddedda87b057d48865f85c485938267f77240 +size 1654509 diff --git a/vorschau/videos/hero-slide-3-monitoring.mp4 b/vorschau/videos/hero-slide-3-monitoring.mp4 new file mode 100644 index 0000000..2509a22 --- /dev/null +++ b/vorschau/videos/hero-slide-3-monitoring.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5a920c978fc6acad08ddfd422077bf7546545e2a1505fb77a77f364f054611b +size 1955473 diff --git a/vorschau/videos/hero-slide-4-monitoring.mp4 b/vorschau/videos/hero-slide-4-monitoring.mp4 new file mode 100644 index 0000000..8d3474b --- /dev/null +++ b/vorschau/videos/hero-slide-4-monitoring.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:febd8b5e77ca59b021cbe35a9f8786953293bf93b47ce72841845fe3e07eb68d +size 1807961 diff --git a/vorschau/videos/hero-slide-5-monitoring.mp4 b/vorschau/videos/hero-slide-5-monitoring.mp4 new file mode 100644 index 0000000..8bb5d92 --- /dev/null +++ b/vorschau/videos/hero-slide-5-monitoring.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a93ef2065623750e2ca7632de90840791bf524dd7579af7f0570e883eb978d74 +size 952619