This commit is contained in:
2026-02-06 09:29:33 +01:00
parent 8ff96d2583
commit 197537e0a5
149 changed files with 0 additions and 15572 deletions

View File

@@ -1,653 +0,0 @@
<?php
/**
* Profice Web API Handler - Centralized Configuration
* ALL SENSITIVE DATA STORED HERE - NOT VISIBLE TO CLIENT
* Handles webhooks, API, tokens, cookie consent, tracking
*/
// ==========================================
// SECURITY HEADERS
// ==========================================
header('Content-Type: application/json');
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
// CORS - Restrict to your domain in production
$allowedOrigins = [
'https://profice.de',
'https://www.profice.de',
'http://localhost',
'http://127.0.0.1'
];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins)) {
header("Access-Control-Allow-Origin: $origin");
} else {
header('Access-Control-Allow-Origin: *'); // Development fallback
}
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Access-Control-Max-Age: 86400');
// Handle preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
// ==========================================
// SENSITIVE CONFIGURATION - HIDDEN FROM CLIENT
// ==========================================
// Environment
define('USE_PRODUCTION', true); // Use production webhook for live server
define('DEBUG_MODE', false); // Disable debug for production
// N8N Webhooks
define('WEBHOOK_TEST', 'https://n8n.profice.de/webhook-test/d94ef798-3f43-46dd-8207-1e335e64518f');
define('WEBHOOK_PROD', 'https://n8n.profice.de/webhook/d94ef798-3f43-46dd-8207-1e335e64518f');
define('WEBHOOK_URL', USE_PRODUCTION ? WEBHOOK_PROD : WEBHOOK_TEST);
// Google Analytics
define('GA_MEASUREMENT_ID', 'G-XXXXXXXXXX'); // Replace with your actual ID
define('GA_API_SECRET', ''); // For server-side tracking
// Google Tag Manager
define('GTM_CONTAINER_ID', 'GTM-XXXXXXX'); // Replace with your actual ID
// Facebook Pixel
define('FB_PIXEL_ID', ''); // Replace with your actual ID
define('FB_ACCESS_TOKEN', ''); // For Conversions API
// Google Ads
define('GADS_CONVERSION_ID', 'AW-XXXXXXXXXX');
define('GADS_CONVERSION_LABEL', '');
// LinkedIn Insight
define('LINKEDIN_PARTNER_ID', '');
// API Keys
define('API_SECRET_KEY', 'your-secret-key-here'); // For API authentication
// Rate Limiting
define('RATE_LIMIT_REQUESTS', 100);
define('RATE_LIMIT_WINDOW', 3600); // 1 hour
// ==========================================
// HELPER FUNCTIONS
// ==========================================
function sendResponse($success, $message, $data = null, $statusCode = 200) {
http_response_code($statusCode);
$response = [
'success' => $success,
'message' => $message,
'timestamp' => date('c')
];
// Only include data if not null and not in production (security)
if ($data !== null && (!USE_PRODUCTION || DEBUG_MODE)) {
$response['data'] = $data;
} elseif ($data !== null && $success) {
// In production, only return safe data
$response['data'] = filterSafeData($data);
}
echo json_encode($response);
exit();
}
function filterSafeData($data) {
// Remove sensitive fields from response
$sensitiveFields = ['webhook_url', 'ip_address', 'user_agent', 'http_code', 'error'];
if (is_array($data)) {
foreach ($sensitiveFields as $field) {
unset($data[$field]);
}
}
return $data;
}
function getClientIP() {
$ipKeys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
foreach ($ipKeys as $key) {
if (array_key_exists($key, $_SERVER)) {
foreach (explode(',', $_SERVER[$key]) as $ip) {
$ip = trim($ip);
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
return $ip;
}
}
}
}
return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
}
function sanitizeInput($input) {
if (is_array($input)) {
return array_map('sanitizeInput', $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
function validateCSRFToken($token) {
// Implement CSRF validation if needed
return true;
}
function checkRateLimit($ip) {
$rateLimitFile = __DIR__ . '/rate_limits.json';
$limits = [];
if (file_exists($rateLimitFile)) {
$limits = json_decode(file_get_contents($rateLimitFile), true) ?: [];
}
$now = time();
$windowStart = $now - RATE_LIMIT_WINDOW;
// Clean old entries
$limits = array_filter($limits, function($entry) use ($windowStart) {
return $entry['time'] > $windowStart;
});
// Count requests from this IP
$ipRequests = array_filter($limits, function($entry) use ($ip) {
return $entry['ip'] === $ip;
});
if (count($ipRequests) >= RATE_LIMIT_REQUESTS) {
return false;
}
// Add new request
$limits[] = ['ip' => $ip, 'time' => $now];
file_put_contents($rateLimitFile, json_encode($limits));
return true;
}
// ==========================================
// WEBHOOK FUNCTIONS
// ==========================================
function sendToWebhook($data, $webhookUrl = null) {
$url = $webhookUrl ?? WEBHOOK_URL;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: Profice-Web-API/2.0'
],
CURLOPT_TIMEOUT => 30,
CURLOPT_SSL_VERIFYPEER => !DEBUG_MODE, // Disable for debugging
CURLOPT_FOLLOWLOCATION => true
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
error_log("Webhook Error: " . $error);
return ['success' => false, 'error' => $error, 'response' => $response];
}
return [
'success' => $httpCode >= 200 && $httpCode < 300,
'http_code' => $httpCode,
'response' => $response,
'error' => $error
];
}
// ==========================================
// TRACKING FUNCTIONS - SERVER SIDE
// ==========================================
function getTrackingConfig() {
// Return tracking configuration for client
// IDs are loaded from server, not exposed in JS
return [
'analytics_enabled' => !empty(GA_MEASUREMENT_ID) && GA_MEASUREMENT_ID !== 'G-XXXXXXXXXX',
'gtm_enabled' => !empty(GTM_CONTAINER_ID) && GTM_CONTAINER_ID !== 'GTM-XXXXXXX',
'fb_enabled' => !empty(FB_PIXEL_ID),
'gads_enabled' => !empty(GADS_CONVERSION_ID) && GADS_CONVERSION_ID !== 'AW-XXXXXXXXXX',
'linkedin_enabled' => !empty(LINKEDIN_PARTNER_ID)
];
}
function loadTrackingScripts($preferences) {
$scripts = [];
// Google Analytics
if ($preferences['analytics'] && !empty(GA_MEASUREMENT_ID) && GA_MEASUREMENT_ID !== 'G-XXXXXXXXXX') {
$scripts['ga'] = [
'type' => 'analytics',
'src' => 'https://www.googletagmanager.com/gtag/js?id=' . GA_MEASUREMENT_ID,
'config' => [
'id' => GA_MEASUREMENT_ID
]
];
}
// Google Tag Manager
if ($preferences['analytics'] && !empty(GTM_CONTAINER_ID) && GTM_CONTAINER_ID !== 'GTM-XXXXXXX') {
$scripts['gtm'] = [
'type' => 'gtm',
'src' => 'https://www.googletagmanager.com/gtm.js?id=' . GTM_CONTAINER_ID,
'config' => [
'id' => GTM_CONTAINER_ID
]
];
}
// Facebook Pixel
if ($preferences['marketing'] && !empty(FB_PIXEL_ID)) {
$scripts['fb'] = [
'type' => 'pixel',
'src' => 'https://connect.facebook.net/en_US/fbevents.js',
'config' => [
'pixel_id' => FB_PIXEL_ID
]
];
}
// Google Ads
if ($preferences['marketing'] && !empty(GADS_CONVERSION_ID) && GADS_CONVERSION_ID !== 'AW-XXXXXXXXXX') {
$scripts['gads'] = [
'type' => 'conversion',
'src' => 'https://www.googletagmanager.com/gtag/js?id=' . GADS_CONVERSION_ID,
'config' => [
'conversion_id' => GADS_CONVERSION_ID,
'conversion_label' => GADS_CONVERSION_LABEL
]
];
}
// LinkedIn
if ($preferences['marketing'] && !empty(LINKEDIN_PARTNER_ID)) {
$scripts['linkedin'] = [
'type' => 'insight',
'src' => 'https://snap.licdn.com/li.lms-analytics/insight.min.js',
'config' => [
'partner_id' => LINKEDIN_PARTNER_ID
]
];
}
return $scripts;
}
function sendEventToGA($data) {
if (empty(GA_MEASUREMENT_ID) || GA_MEASUREMENT_ID === 'G-XXXXXXXXXX') {
return false;
}
$postData = [
'client_id' => $data['client_id'] ?? uniqid(),
'user_id' => $data['user_id'] ?? null,
'events' => [
[
'name' => $data['event_name'],
'params' => $data['params'] ?? []
]
]
];
$url = 'https://www.google-analytics.com/mp/collect';
$url .= '?measurement_id=' . GA_MEASUREMENT_ID;
$url .= '&api_secret=' . GA_API_SECRET;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($postData),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 10
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode >= 200 && $httpCode < 300;
}
function sendEventToFB($data) {
if (empty(FB_PIXEL_ID) || empty(FB_ACCESS_TOKEN)) {
return false;
}
$postData = [
'data' => [
[
'event_name' => $data['event_name'],
'event_time' => time(),
'action_source' => 'website',
'user_data' => $data['user_data'] ?? [],
'custom_data' => $data['custom_data'] ?? []
]
]
];
$url = 'https://graph.facebook.com/v18.0/' . FB_PIXEL_ID . '/events';
$url .= '?access_token=' . FB_ACCESS_TOKEN;
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($postData),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 10
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode >= 200 && $httpCode < 300;
}
// ==========================================
// STORAGE FUNCTIONS
// ==========================================
function storeLead($data) {
$leadFile = __DIR__ . '/data/leads.json';
$dir = dirname($leadFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$leads = [];
if (file_exists($leadFile)) {
$leads = json_decode(file_get_contents($leadFile), true) ?: [];
}
$lead = [
'id' => uniqid('lead_', true),
'datum' => date('d.m.Y'),
'dienstleistung' => $data['service'] ?? 'Allgemein',
'status' => 'open',
'statusText' => 'Offen',
'description' => $data['description'] ?? '',
'name' => $data['name'] ?? '',
'contact' => $data['contact'] ?? '',
'organisation' => $data['organisation'] ?? '',
'timestamp' => date('c'),
'ip_address' => getClientIP()
];
array_unshift($leads, $lead);
$leads = array_slice($leads, 0, 100);
file_put_contents($leadFile, json_encode($leads, JSON_PRETTY_PRINT));
return $lead['id'];
}
function storeCookieConsent($data) {
$consentFile = __DIR__ . '/data/cookie_consent.json';
$dir = dirname($consentFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$consents = [];
if (file_exists($consentFile)) {
$consents = json_decode(file_get_contents($consentFile), true) ?: [];
}
$consent = [
'id' => uniqid('consent_', true),
'timestamp' => date('c'),
'preferences' => $data['preferences'] ?? [],
'ip_address' => getClientIP(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
];
array_unshift($consents, $consent);
$consents = array_slice($consents, 0, 1000);
file_put_contents($consentFile, json_encode($consents, JSON_PRETTY_PRINT));
return $consent['id'];
}
function getCookieConsent() {
$consentFile = __DIR__ . '/data/cookie_consent.json';
if (!file_exists($consentFile)) {
return null;
}
$consents = json_decode(file_get_contents($consentFile), true) ?: [];
return $consents[0] ?? null;
}
// ==========================================
// REQUEST HANDLERS
// ==========================================
function handleContactForm($data) {
$required = ['name', 'contact'];
foreach ($required as $field) {
if (empty($data[$field])) {
sendResponse(false, "Pflichtfeld fehlt: {$field}", null, 400);
}
}
$formData = [
'name' => sanitizeInput($data['name']),
'organisation' => sanitizeInput($data['organisation'] ?? ''),
'contact' => sanitizeInput($data['contact']),
'service' => sanitizeInput($data['service'] ?? ''),
'budget' => sanitizeInput($data['budget'] ?? ''),
'description' => sanitizeInput($data['description'] ?? ''),
'timestamp' => date('c'),
'source' => 'contact_form',
'form_type' => 'offer',
'ip_address' => getClientIP()
];
// Store lead locally first
$leadId = storeLead($formData);
// Send to webhook
$webhookResult = sendToWebhook($formData);
// Always return success to user, but include webhook info in debug mode
$debugData = DEBUG_MODE ? [
'lead_id' => $leadId,
'webhook_result' => $webhookResult,
'webhook_url' => WEBHOOK_URL,
'form_data' => $formData
] : null;
if ($webhookResult['success']) {
sendResponse(true, 'Formular erfolgreich gesendet', $debugData);
} else {
// Still return success to user but log the webhook error
error_log('Webhook failed but form was stored locally: ' . json_encode($webhookResult));
sendResponse(true, 'Formular erfolgreich gesendet (Webhook-Fehler protokolliert)', $debugData);
}
}
function handleLeadForm($data) {
// Handle lead form submissions
$leadId = storeLead($data);
sendResponse(true, 'Lead gespeichert', ['lead_id' => $leadId]);
}
function handleCookieConsent($data) {
$preferences = $data['preferences'] ?? [];
$consentId = storeCookieConsent(['preferences' => $preferences]);
// Load tracking scripts based on preferences
$scripts = loadTrackingScripts($preferences);
sendResponse(true, 'Cookie-Einstellungen gespeichert', [
'consent_id' => $consentId,
'scripts' => $scripts
]);
}
function handleGetCookieConsent($data) {
$consent = getCookieConsent();
if ($consent) {
sendResponse(true, 'Cookie-Einstellungen gefunden', $consent);
} else {
sendResponse(false, 'Keine Cookie-Einstellungen gefunden', null, 404);
}
}
function handleGetTrackingConfig($data) {
$config = getTrackingConfig();
sendResponse(true, 'Tracking-Konfiguration', $config);
}
function handleChatMessage($data) {
$message = sanitizeInput($data['message'] ?? '');
$sessionId = sanitizeInput($data['session_id'] ?? uniqid('chat_'));
if (empty($message)) {
sendResponse(false, 'Nachricht darf nicht leer sein', null, 400);
}
$chatData = [
'type' => 'chat_message',
'session_id' => $sessionId,
'message' => $message,
'timestamp' => date('c'),
'source' => 'website_chat',
'ip_address' => getClientIP(),
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? ''
];
// Send to webhook
$webhookResult = sendToWebhook($chatData);
$debugData = DEBUG_MODE ? [
'session_id' => $sessionId,
'webhook_result' => $webhookResult,
'webhook_url' => WEBHOOK_URL
] : null;
if ($webhookResult['success']) {
// Try to parse response from webhook
$response = json_decode($webhookResult['response'], true);
$aiResponse = $response['message'] ?? 'Vielen Dank für Ihre Nachricht. Ich melde mich so schnell wie möglich bei Ihnen.';
sendResponse(true, 'Nachricht gesendet', array_merge($debugData, [
'session_id' => $sessionId,
'ai_response' => $aiResponse,
'timestamp' => date('c')
]));
} else {
error_log('Chat webhook failed: ' . json_encode($webhookResult));
sendResponse(false, 'Chat-Service derzeit nicht verfügbar. Bitte versuchen Sie es später erneut.', $debugData, 503);
}
}
function handleTrackEvent($data) {
$eventName = $data['event_name'] ?? '';
$eventData = $data['event_data'] ?? [];
// Send to Google Analytics
$gaSuccess = sendEventToGA([
'event_name' => $eventName,
'params' => $eventData
]);
// Send to Facebook
$fbSuccess = sendEventToFB([
'event_name' => $eventName,
'custom_data' => $eventData
]);
sendResponse(true, 'Event gesendet', [
'ga_success' => $gaSuccess,
'fb_success' => $fbSuccess
]);
}
// ==========================================
// MAIN REQUEST HANDLER
// ==========================================
// Validate request method
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
sendResponse(false, 'Nur POST-Anfragen erlaubt', null, 405);
}
// Rate limiting
$clientIP = getClientIP();
if (!checkRateLimit($clientIP)) {
sendResponse(false, 'Zu viele Anfragen. Bitte später erneut versuchen.', null, 429);
}
// Get JSON input
$jsonInput = file_get_contents('php://input');
$data = json_decode($jsonInput, true);
if (!$data) {
$data = $_POST;
}
if (!$data || empty($data)) {
sendResponse(false, 'Keine Daten empfangen', null, 400);
}
$requestType = $data['type'] ?? 'contact';
try {
switch ($requestType) {
case 'contact':
handleContactForm($data);
break;
case 'lead':
handleLeadForm($data);
break;
case 'cookie_consent':
handleCookieConsent($data);
break;
case 'get_cookie_consent':
handleGetCookieConsent($data);
break;
case 'get_tracking_config':
handleGetTrackingConfig($data);
break;
case 'track_event':
handleTrackEvent($data);
break;
case 'chat':
handleChatMessage($data);
break;
default:
sendResponse(false, 'Ungültiger Anfragetyp', null, 400);
}
} catch (Exception $e) {
error_log('API Error: ' . $e->getMessage());
error_log('Stack trace: ' . $e->getTraceAsString());
sendResponse(false, 'Interner Serverfehler', null, 500);
}
?>

View File

@@ -1,13 +0,0 @@
<?php
// Simple test file to verify PHP is working
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'message' => 'PHP is working correctly',
'timestamp' => date('c'),
'server_info' => [
'php_version' => phpversion(),
'server_software' => $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown'
]
]);
?>

View File

@@ -1,429 +0,0 @@
/**
* KI Chat Widget - Functional Chat Interface
* Integrates with existing chat widget in KI Chat card
* Connects to N8N webhook via send.php
*/
class KIChat {
constructor() {
this.sessionId = this.generateSessionId();
this.isTyping = false;
this.messages = [];
this.init();
}
init() {
this.bindEvents();
this.loadChatHistory();
this.setupExistingChat();
}
generateSessionId() {
return 'chat_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
bindEvents() {
// Chat button clicks
const chatBtn = document.getElementById('chatBtn');
const interactionBtn = document.querySelector('#chatInteraction .interaction-btn');
if (chatBtn) {
chatBtn.addEventListener('click', () => this.scrollToChat());
}
if (interactionBtn) {
interactionBtn.addEventListener('click', () => this.focusInput());
}
// Send message on Enter in existing chat
const existingInput = document.querySelector('#chatInteraction .chat-input');
if (existingInput) {
existingInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.sendMessage();
}
});
}
// Send button click
const sendBtn = document.querySelector('#chatInteraction .chat-send');
if (sendBtn) {
sendBtn.addEventListener('click', () => this.sendMessage());
}
}
setupExistingChat() {
const chatMessages = document.querySelector('#chatInteraction .chat-messages');
const chatInput = document.querySelector('#chatInteraction .chat-input');
const sendBtn = document.querySelector('#chatInteraction .chat-send');
// Add click event listener to the send button
if (sendBtn) {
sendBtn.addEventListener('click', () => this.sendMessage());
}
// Add welcome message
if (this.messages.length === 0 && chatMessages) {
this.addMessage('ki', 'Hallo! Ich bin Ihr KI-Assistent. Wie kann ich Ihnen helfen?');
}
this.updateChatDisplay();
}
scrollToChat() {
const chatInteraction = document.getElementById('chatInteraction');
if (chatInteraction) {
chatInteraction.scrollIntoView({ behavior: 'smooth', block: 'center' });
setTimeout(() => this.focusInput(), 500);
}
}
focusInput() {
const inputField = document.querySelector('#chatInteraction .chat-input');
if (inputField) {
inputField.focus();
}
}
addMessage(sender, text, timestamp = Date.now()) {
this.messages.push({
sender: sender,
text: text,
timestamp: timestamp
});
// Keep only last 50 messages
if (this.messages.length > 50) {
this.messages = this.messages.slice(-50);
}
this.saveChatHistory();
this.updateChatDisplay();
}
updateChatDisplay() {
const chatMessages = document.querySelector('#chatInteraction .chat-messages');
if (chatMessages) {
// Clear existing messages except typing indicator
const typingIndicator = chatMessages.querySelector('.typing-indicator');
chatMessages.innerHTML = '';
// Add all messages
this.messages.forEach(msg => {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${msg.sender}`;
messageDiv.innerHTML = `
<div class="message-content">${this.escapeHtml(msg.text)}</div>
<div class="message-time">${this.formatTime(msg.timestamp)}</div>
`;
chatMessages.appendChild(messageDiv);
});
// Re-add typing indicator if needed
if (typingIndicator && this.isTyping) {
chatMessages.appendChild(typingIndicator);
}
this.scrollToBottom();
}
}
async sendMessage() {
const inputField = document.querySelector('#chatInteraction .chat-input');
const message = inputField?.value?.trim();
if (!message || this.isTyping) return;
// Add user message
this.addMessage('user', message);
inputField.value = '';
// Show typing indicator
this.showTypingIndicator();
this.isTyping = true;
try {
const response = await fetch('scripts/add/send.php', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'chat',
message: message,
session_id: this.sessionId
})
});
const data = await response.json();
if (data.success) {
this.addMessage('ki', data.data.ai_response, data.data.timestamp);
} else {
this.addMessage('ki', 'Entschuldigung, es gab ein Problem. Bitte versuchen Sie es später erneut.');
}
} catch (error) {
console.error('Chat error:', error);
this.addMessage('ki', 'Verbindung zum Server fehlgeschlagen. Bitte überprüfen Sie Ihre Internetverbindung.');
} finally {
this.hideTypingIndicator();
this.isTyping = false;
}
}
showTypingIndicator() {
const chatMessages = document.querySelector('#chatInteraction .chat-messages');
if (chatMessages && !chatMessages.querySelector('.typing-indicator')) {
const typingHTML = `
<div class="chat-message ki typing">
<div class="message-content">
<div class="typing-indicator">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
`;
chatMessages.insertAdjacentHTML('beforeend', typingHTML);
this.scrollToBottom();
}
}
hideTypingIndicator() {
const typingIndicator = document.querySelector('#chatInteraction .typing');
if (typingIndicator) {
typingIndicator.remove();
}
}
scrollToBottom() {
const chatMessages = document.querySelector('#chatInteraction .chat-messages');
if (chatMessages) {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}
saveChatHistory() {
try {
localStorage.setItem(`ki_chat_${this.sessionId}`, JSON.stringify(this.messages));
} catch (e) {
console.warn('Could not save chat history:', e);
}
}
loadChatHistory() {
try {
const saved = localStorage.getItem(`ki_chat_${this.sessionId}`);
if (saved) {
this.messages = JSON.parse(saved);
}
} catch (e) {
console.warn('Could not load chat history:', e);
}
}
formatTime(timestamp) {
const date = new Date(timestamp);
return date.toLocaleTimeString('de-DE', {
hour: '2-digit',
minute: '2-digit'
});
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Initialize chat when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
window.kiChat = new KIChat();
});
// Add CSS for enhanced chat functionality
const chatStyles = `
#chatInteraction {
display: flex;
flex-direction: column;
gap: 15px;
}
#chatInteraction h3 {
margin: 0;
font-size: 24px;
color: var(--primary-dark);
text-align: center;
}
#chatInteraction p {
margin: 0;
color: var(--primary-mid);
text-align: center;
font-size: 16px;
line-height: 1.5;
}
#chatInteraction .card-visual {
height: 400px;
}
#chatInteraction .chat-window {
width: 100%;
height: 400px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(79, 71, 71, 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
}
#chatInteraction .chat-header {
background: linear-gradient(135deg, var(--accent-teal), var(--accent-green));
color: white;
padding: 15px 20px;
font-weight: 600;
font-size: 16px;
}
#chatInteraction .chat-messages {
flex: 1;
padding: 20px;
overflow-y: auto !important;
background: transparent;
max-height: 300px !important;
display: flex;
flex-direction: column;
gap: 12px;
min-height: 0;
align-items: flex-start;
justify-content: flex-start;
}
#chatInteraction .chat-message {
display: flex;
width: 100%;
background: transparent;
}
#chatInteraction .chat-message.user {
justify-content: flex-end;
}
#chatInteraction .chat-message.ki {
justify-content: flex-start;
}
#chatInteraction .message-content {
background: transparent;
padding: 12px 16px;
border-radius: 16px;
box-shadow: none;
word-wrap: break-word;
line-height: 1.4;
max-width: 70%;
display: inline-block;
text-align: left;
}
#chatInteraction .chat-message.user .message-content {
background: var(--accent-green);
color: white;
}
#chatInteraction .chat-message.ki .message-content {
background: var(--accent-green);
color: white;
}
#chatInteraction .message-time {
font-size: 10px;
color: #666;
margin-top: 4px;
padding: 0 16px;
}
#chatInteraction .chat-message.ki .message-time {
text-align: left;
}
#chatInteraction .chat-message.user .message-time {
text-align: right;
}
#chatInteraction .chat-input-container .interaction-btn {
flex-shrink: 0;
padding: 12px 20px;
font-size: 14px;
}
#chatInteraction .chat-send {
background: var(--accent-teal);
color: white;
border: none;
border-radius: 50%;
width: 45px;
height: 45px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
flex-shrink: 0;
padding: 0;
margin-left: 8px;
box-shadow: 0 2px 8px rgba(38, 166, 154, 0.3);
}
#chatInteraction .chat-send:hover {
background: #1e8e82;
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(38, 166, 154, 0.4);
}
#chatInteraction .chat-send:active {
transform: scale(0.95);
}
#chatInteraction .chat-send img {
width: 22px;
height: 22px;
filter: brightness(0) invert(1);
object-fit: contain;
}
/* Responsive adjustments */
@media (max-width: 768px) {
#chatInteraction .card-visual {
min-height: 350px;
}
#chatInteraction .chat-messages {
min-height: 200px;
padding: 15px;
}
#chatInteraction .chat-input-container {
padding: 12px 15px;
}
#chatInteraction .chat-input {
padding: 10px 14px;
}
#chatInteraction .chat-send {
padding: 10px 16px;
font-size: 13px;
}
}
`;
// Inject CSS
const styleSheet = document.createElement('style');
styleSheet.textContent = chatStyles;
document.head.appendChild(styleSheet);

View File

@@ -1,227 +0,0 @@
// Simple cursor.js - Fixed version with better line effects
document.addEventListener("DOMContentLoaded", function () {
// Check if touch device
if (window.matchMedia("(pointer: coarse)").matches) return;
const toggleBtn = document.getElementById('cursorToggle');
const body = document.body;
// Start with system cursor
let isCustomCursor = false;
function updateCursorState() {
if (isCustomCursor) {
body.classList.remove('system-cursor');
if (toggleBtn) {
toggleBtn.classList.add('active');
}
} else {
body.classList.add('system-cursor');
if (toggleBtn) {
toggleBtn.classList.remove('active');
}
}
}
// Initialize cursor state
updateCursorState();
// Toggle button click handler
if (toggleBtn) {
toggleBtn.addEventListener('click', () => {
isCustomCursor = !isCustomCursor;
localStorage.setItem('customCursor', isCustomCursor);
updateCursorState();
});
}
// Create canvas for custom cursor
const canvas = document.createElement("canvas");
canvas.id = "custom-cursor";
canvas.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 999999;
display: none;
`;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const tentacles = [];
const mouse = { x: 0, y: 0 };
const oldMouse = { x: 0, y: 0 };
// Mouse move handler
document.addEventListener("mousemove", (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
if (isCustomCursor) {
canvas.style.display = 'block';
} else {
canvas.style.display = 'none';
}
});
// Window resize handler
window.addEventListener("resize", () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
// Tentacle class with better line effects
class Tentacle {
constructor(x, y, targetX, targetY) {
this.startX = x;
this.startY = y;
this.endX = targetX;
this.endY = targetY;
this.life = 1.0;
this.decay = 0.015;
this.growth = 0;
this.maxLength = 0;
}
update() {
// Grow the tentacle
if (this.growth < 1.0) {
this.growth += 0.1;
}
// Calculate actual end point based on growth
const currentEndX = this.startX + (this.endX - this.startX) * this.growth;
const currentEndY = this.startY + (this.endY - this.startY) * this.growth;
this.currentEndX = currentEndX;
this.currentEndY = currentEndY;
// Decay over time
this.life -= this.decay;
}
draw(ctx) {
if (this.life <= 0) return;
const opacity = this.life * this.growth;
// Draw main line with gradient effect
const gradient = ctx.createLinearGradient(
this.startX, this.startY,
this.currentEndX, this.currentEndY
);
gradient.addColorStop(0, `rgba(20, 20, 20, ${opacity * 0.8})`);
gradient.addColorStop(1, `rgba(20, 20, 20, ${opacity * 0.2})`);
ctx.strokeStyle = gradient;
ctx.lineWidth = 3 * opacity * this.growth;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(this.startX, this.startY);
ctx.lineTo(this.currentEndX, this.currentEndY);
ctx.stroke();
// Draw glowing endpoint
const glowSize = 6 * opacity;
const glowGradient = ctx.createRadialGradient(
this.currentEndX, this.currentEndY, 0,
this.currentEndX, this.currentEndY, glowSize
);
glowGradient.addColorStop(0, `rgba(20, 20, 20, ${opacity})`);
glowGradient.addColorStop(1, `rgba(20, 20, 20, 0)`);
ctx.fillStyle = glowGradient;
ctx.beginPath();
ctx.arc(this.currentEndX, this.currentEndY, glowSize, 0, Math.PI * 2);
ctx.fill();
// Draw connection dots along the line
const dotCount = 3;
for (let i = 1; i <= dotCount; i++) {
const t = i / (dotCount + 1);
const dotX = this.startX + (this.currentEndX - this.startX) * t;
const dotY = this.startY + (this.currentEndY - this.startY) * t;
const dotOpacity = opacity * (1 - t) * 0.5;
ctx.fillStyle = `rgba(20, 20, 20, ${dotOpacity})`;
ctx.beginPath();
ctx.arc(dotX, dotY, 2, 0, Math.PI * 2);
ctx.fill();
}
}
}
// Animation loop
function animate() {
if (!isCustomCursor) {
requestAnimationFrame(animate);
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Create new tentacles on movement
const dx = mouse.x - oldMouse.x;
const dy = mouse.y - oldMouse.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 3) {
// Create multiple tentacles in movement direction
const angle = Math.atan2(dy, dx);
const spread = 0.3;
for (let i = 0; i < 4; i++) {
const spreadAngle = angle + (Math.random() - 0.5) * spread;
const length = 60 + Math.random() * 80;
const targetX = mouse.x + Math.cos(spreadAngle) * length;
const targetY = mouse.y + Math.sin(spreadAngle) * length;
tentacles.push(new Tentacle(mouse.x, mouse.y, targetX, targetY));
}
oldMouse.x = mouse.x;
oldMouse.y = mouse.y;
}
// Update and draw tentacles
for (let i = tentacles.length - 1; i >= 0; i--) {
const tentacle = tentacles[i];
tentacle.update();
if (tentacle.life <= 0) {
tentacles.splice(i, 1);
} else {
tentacle.draw(ctx);
}
}
// Limit tentacle count
if (tentacles.length > 15) {
tentacles.splice(0, tentacles.length - 15);
}
// Draw cursor point with glow
const cursorGradient = ctx.createRadialGradient(
mouse.x, mouse.y, 0,
mouse.x, mouse.y, 8
);
cursorGradient.addColorStop(0, 'rgba(20, 20, 20, 0.8)');
cursorGradient.addColorStop(1, 'rgba(20, 20, 20, 0)');
ctx.fillStyle = cursorGradient;
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 6, 0, Math.PI * 2);
ctx.fill();
// Add central bright point
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
ctx.beginPath();
ctx.arc(mouse.x, mouse.y, 2, 0, Math.PI * 2);
ctx.fill();
requestAnimationFrame(animate);
}
animate();
});

View File

@@ -1,256 +0,0 @@
// cursor.js
document.addEventListener("DOMContentLoaded", function () {
if (window.matchMedia("(pointer: coarse)").matches) return;
// --- SETTINGS ---
const CONFIG = {
tentacleCount: 8,
triggerDist: 10,
maxLength: 300,
connectionDist: 150,
thickness: 1,
color: "rgba(20, 20, 20, 1)",
prediction: 3.5
};
const toggleBtn = document.getElementById('cursorToggle');
const body = document.body;
// Default to system cursor (disabled custom cursor)
let isCursorDisabled = localStorage.getItem('venomCursorDisabled') !== 'false';
function updateCursorState() {
if (isCursorDisabled) {
// System cursor (default) - show spidy icon
body.classList.add('system-cursor');
if (toggleBtn) {
toggleBtn.classList.remove('active');
let icon = toggleBtn.querySelector('.cursor-icon');
if (icon) {
// Replace img with spidy.png if needed
if (icon.tagName !== 'IMG') {
const newIcon = document.createElement('img');
newIcon.className = 'cursor-icon';
newIcon.alt = 'Spider Cursor';
// Check if we're on a page in the sites/ folder
const currentPath = window.location.pathname;
const isInSitesFolder = currentPath.includes('/sites/');
const imagePath = isInSitesFolder ? '../images/additional/spidy.png' : 'images/additional/spidy.png';
newIcon.src = imagePath;
icon.parentNode.replaceChild(newIcon, icon);
} else {
// Update existing img
const currentPath = window.location.pathname;
const isInSitesFolder = currentPath.includes('/sites/');
const imagePath = isInSitesFolder ? '../images/additional/spidy.png' : 'images/additional/spidy.png';
icon.src = imagePath;
}
}
}
} else {
// Custom cursor (secondary) - show standard cursor icon
body.classList.remove('system-cursor');
if (toggleBtn) {
toggleBtn.classList.add('active');
let icon = toggleBtn.querySelector('.cursor-icon');
if (icon) {
// Replace img with cursor.png if needed
if (icon.tagName !== 'IMG') {
const newIcon = document.createElement('img');
newIcon.className = 'cursor-icon';
newIcon.alt = 'Custom Cursor';
// Check if we're on a page in the sites/ folder
const currentPath = window.location.pathname;
const isInSitesFolder = currentPath.includes('/sites/');
const imagePath = isInSitesFolder ? '../images/additional/cursor.png' : 'images/additional/cursor.png';
newIcon.src = imagePath;
icon.parentNode.replaceChild(newIcon, icon);
} else {
// Update existing img
const currentPath = window.location.pathname;
const isInSitesFolder = currentPath.includes('/sites/');
const imagePath = isInSitesFolder ? '../images/additional/cursor.png' : 'images/additional/cursor.png';
icon.src = imagePath;
}
}
}
}
}
updateCursorState();
if (toggleBtn) {
toggleBtn.addEventListener('click', () => {
isCursorDisabled = !isCursorDisabled;
localStorage.setItem('venomCursorDisabled', isCursorDisabled);
updateCursorState();
});
}
function initCursor() {
const canvas = document.createElement("canvas");
canvas.id = "venom-cursor";
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");
const width = window.innerWidth;
const height = window.innerHeight;
canvas.width = width;
canvas.height = height;
const tentacles = [];
const mouse = { x: 0, y: 0 };
const oldMouse = { x: 0, y: 0 };
document.addEventListener("mousemove", (e) => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
window.addEventListener("resize", () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
return { canvas, ctx, tentacles, mouse, oldMouse };
}
const cursorElements = initCursor();
const canvas = cursorElements.canvas;
const ctx = cursorElements.ctx;
const tentacles = cursorElements.tentacles;
const mouse = cursorElements.mouse;
const oldMouse = cursorElements.oldMouse;
class Tentacle {
constructor(mx, my, targetX, targetY) {
this.dead = false;
this.anchor = { x: targetX, y: targetY };
this.currentDist = 0; // For calculating connection opacity
}
update(currentMouse) {
const dx = currentMouse.x - this.anchor.x;
const dy = currentMouse.y - this.anchor.y;
this.currentDist = Math.sqrt(dx*dx + dy*dy);
if (this.currentDist > CONFIG.maxLength) {
this.dead = true;
}
}
draw(ctx, currentMouse) {
if (this.dead) return;
// Tension (0..1)
const tension = Math.min(this.currentDist / CONFIG.maxLength, 1);
const dynamicThickness = CONFIG.thickness * (1 - tension * 0.9);
// Draw main line (Cursor -> Anchor)
ctx.beginPath();
ctx.moveTo(currentMouse.x, currentMouse.y);
ctx.lineTo(this.anchor.x, this.anchor.y);
ctx.lineWidth = Math.max(0.2, dynamicThickness);
ctx.strokeStyle = CONFIG.color;
ctx.lineCap = "butt";
ctx.stroke();
// Draw anchor point
ctx.beginPath();
ctx.arc(this.anchor.x, this.anchor.y, 1.5 * (1 - tension), 0, Math.PI * 2);
ctx.fillStyle = CONFIG.color;
ctx.fill();
}
}
function render() {
if (isCursorDisabled) {
requestAnimationFrame(render);
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 1. Create new tentacles on movement
const distMoved = Math.hypot(mouse.x - oldMouse.x, mouse.y - oldMouse.y);
if (distMoved > CONFIG.triggerDist) {
const vx = mouse.x - oldMouse.x;
const vy = mouse.y - oldMouse.y;
// "Spread" of shots increased slightly for better geometry
const targetX = mouse.x + vx * CONFIG.prediction + (Math.random() - 0.5) * 60;
const targetY = mouse.y + vy * CONFIG.prediction + (Math.random() - 0.5) * 60;
tentacles.push(new Tentacle(mouse.x, mouse.y, targetX, targetY));
oldMouse.x = mouse.x;
oldMouse.y = mouse.y;
}
// Remove old ones (FIFO)
if (tentacles.length > CONFIG.tentacleCount) {
tentacles.shift();
}
// 2. Draw main tentacles
for (let i = tentacles.length - 1; i >= 0; i--) {
const t = tentacles[i];
t.update(mouse);
if (t.dead) {
tentacles.splice(i, 1);
} else {
t.draw(ctx, mouse);
}
}
// 3. DRAW CONNECTIONS BETWEEN ANCHORS (New logic)
// Iterate through all pairs of active tentacles
ctx.beginPath(); // Begin common path for optimization
ctx.lineWidth = 0.5; // Connections are always thin
for (let i = 0; i < tentacles.length; i++) {
for (let j = i + 1; j < tentacles.length; j++) {
const t1 = tentacles[i];
const t2 = tentacles[j];
// Calculate distance between ends of two tentacles
const dx = t1.anchor.x - t2.anchor.x;
const dy = t1.anchor.y - t2.anchor.y;
const dist = Math.sqrt(dx*dx + dy*dy);
// If they are close to each other — connect
if (dist < CONFIG.connectionDist) {
// Opacity depends on how far apart they are
// And how much the tentacles themselves are stretched
const alpha = (1 - dist / CONFIG.connectionDist) * 0.6;
ctx.beginPath(); // New path for each to control opacity
ctx.strokeStyle = `rgba(20, 20, 20, ${alpha})`;
ctx.moveTo(t1.anchor.x, t1.anchor.y);
ctx.lineTo(t2.anchor.x, t2.anchor.y);
ctx.stroke();
}
}
}
// 4. Cursor (Rhombus)
ctx.beginPath();
ctx.fillStyle = CONFIG.color;
ctx.moveTo(mouse.x, mouse.y - 5);
ctx.lineTo(mouse.x + 5, mouse.y);
ctx.lineTo(mouse.x, mouse.y + 5);
ctx.lineTo(mouse.x - 5, mouse.y);
ctx.fill();
requestAnimationFrame(render);
}
render();
});

File diff suppressed because one or more lines are too long

View File

@@ -1,75 +0,0 @@
// leads.js
// ==========================================
// 1. MENU TOGGLE
// ==========================================
const menuToggle = document.getElementById('menuToggle');
const slideMenu = document.getElementById('slideMenu');
const overlay = document.getElementById('overlay');
if (menuToggle && slideMenu && overlay) {
function toggleMenu() {
menuToggle.classList.toggle('active');
slideMenu.classList.toggle('active');
overlay.classList.toggle('active');
}
menuToggle.addEventListener('click', toggleMenu);
overlay.addEventListener('click', toggleMenu);
}
// ==========================================
// 2. LEADS TABLE LOGIC
// ==========================================
function getLeads() {
const storedLeads = localStorage.getItem('myLeads');
if (storedLeads) {
return JSON.parse(storedLeads);
} else {
return [];
}
}
function populateLeadsTable() {
const tableBody = document.getElementById('leadsTableBody');
const leadsData = getLeads();
if (!tableBody) return;
if (leadsData.length === 0) {
tableBody.innerHTML = `
<tr>
<td colspan="4" class="empty-state">
<p>Keine Anfragen vorhanden</p>
</td>
</tr>
`;
return;
}
tableBody.innerHTML = leadsData.map(lead => `
<tr>
<td>${lead.datum}</td>
<td>${lead.dienstleistung}</td>
<td>
<span class="status-badge ${lead.status}">${lead.statusText}</span>
</td>
<td>
<a href="#" class="action-btn" data-id="${lead.id}">Details ansehen</a>
</td>
</tr>
`).join('');
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
const leadId = this.getAttribute('data-id');
// Redirect to lead details page
window.location.href = `lead-details.html?id=${leadId}`;
});
});
}
document.addEventListener('DOMContentLoaded', populateLeadsTable);

View File

@@ -1 +0,0 @@
// leads.js // ========================================== // 1. MENU TOGGLE // ========================================== const menuToggle = document.getElementById('menuToggle');const slideMenu = document.getElementById('slideMenu');const overlay = document.getElementById('overlay');if (menuToggle && slideMenu && overlay){function toggleMenu(){menuToggle.classList.toggle('active');slideMenu.classList.toggle('active');overlay.classList.toggle('active');}menuToggle.addEventListener('click', toggleMenu);overlay.addEventListener('click', toggleMenu);}// ========================================== // 2. LEADS TABLE LOGIC // ========================================== function getLeads(){const storedLeads = localStorage.getItem('myLeads');if (storedLeads){return JSON.parse(storedLeads);}else{return [];}}function populateLeadsTable(){const tableBody = document.getElementById('leadsTableBody');const leadsData = getLeads();if (!tableBody) return;if (leadsData.length === 0){tableBody.innerHTML = ` <tr> <td colspan="4" class="empty-state"> <p>Keine Anfragen vorhanden</p> </td> </tr> `;return;}tableBody.innerHTML = leadsData.map(lead => ` <tr> <td>${lead.datum}</td> <td>${lead.dienstleistung}</td> <td> <span class="status-badge ${lead.status}">${lead.statusText}</span> </td> <td> <a href="#" class="action-btn" data-id="${lead.id}">Details ansehen</a> </td> </tr> `).join('');document.querySelectorAll('.action-btn').forEach(btn =>{btn.addEventListener('click', function(e){e.preventDefault();const leadId = this.getAttribute('data-id');// Redirect to lead details page window.location.href = `lead-details.html?id=${leadId}`;});});}document.addEventListener('DOMContentLoaded', populateLeadsTable);

View File

@@ -1,138 +0,0 @@
/**
* Main Script - Profice Website
* All API calls go through server-side PHP
* Includes fallback for local file access (no server)
*
* @version 2.1.0
*/
document.addEventListener("DOMContentLoaded", function() {
const API_ENDPOINT = 'scripts/add/send.php';
const isLocalFile = window.location.protocol === 'file:';
// ==========================================
// 1. SMOOTH SCROLLING
// ==========================================
function initSmoothScrolling() {
document.querySelectorAll('a[href^="#"]').forEach(link => {
link.addEventListener('click', function(e) {
const targetId = this.getAttribute('href');
if (targetId === '#') return;
const targetElement = document.querySelector(targetId);
if (targetElement) {
e.preventDefault();
const header = document.querySelector('.top-banner');
const headerHeight = header ? header.offsetHeight : 90;
window.scrollTo({
top: targetElement.offsetTop - headerHeight - 20,
behavior: 'smooth'
});
history.pushState(null, null, targetId);
}
});
});
}
initSmoothScrolling();
// ==========================================
// 2. MENU TOGGLE
// ==========================================
const menuToggle = document.getElementById('menuToggle');
const slideMenu = document.getElementById('slideMenu');
const overlay = document.getElementById('overlay');
if (menuToggle && slideMenu && overlay) {
const toggleMenu = () => {
menuToggle.classList.toggle('active');
slideMenu.classList.toggle('active');
overlay.classList.toggle('active');
};
menuToggle.addEventListener('click', toggleMenu);
overlay.addEventListener('click', toggleMenu);
}
// ==========================================
// 3. LOGIN BUTTON (REMOVED)
// ==========================================
// Login button functionality has been removed
// ==========================================
// 4. FORM SUBMISSION
// ==========================================
const contactForm = document.getElementById('contactForm');
const successMessage = document.getElementById('successMessage');
if (contactForm) {
contactForm.addEventListener('submit', async function(e) {
e.preventDefault();
const getValue = (id) => {
const el = document.getElementById(id);
return el ? el.value : '';
};
const serviceSelect = document.getElementById('service');
const selectedServiceText = serviceSelect ?
serviceSelect.options[serviceSelect.selectedIndex].text : 'Dienstleistung';
const formData = {
type: 'contact',
name: getValue('name'),
organisation: getValue('organisation'),
contact: getValue('contact'),
service: getValue('service'),
budget: getValue('budget'),
description: getValue('description')
};
// Always store locally for dashboard
try {
const localLead = {
id: Date.now(),
datum: new Date().toLocaleDateString('de-DE'),
dienstleistung: selectedServiceText,
status: 'open',
statusText: 'Offen',
description: formData.description
};
const existingLeads = JSON.parse(localStorage.getItem('myLeads') || '[]');
existingLeads.unshift(localLead);
localStorage.setItem('myLeads', JSON.stringify(existingLeads.slice(0, 100)));
} catch (err) {}
// Send to API if not local file
if (!isLocalFile) {
try {
const response = await fetch(API_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
const result = await response.json();
if (!result.success) {
alert('Fehler beim Senden: ' + (result.message || 'Unbekannter Fehler'));
return;
}
} catch (error) {
alert('Netzwerkfehler beim Senden des Formulars');
return;
}
}
// Show success
contactForm.style.display = 'none';
if (successMessage) successMessage.classList.add('show');
contactForm.reset();
});
}
});

View File

@@ -1 +0,0 @@
document.addEventListener("DOMContentLoaded", function(){const API_ENDPOINT = 'scripts/add/send.php';const isLocalFile = window.location.protocol === 'file:';// ========================================== // 1. SMOOTH SCROLLING // ========================================== function initSmoothScrolling(){document.querySelectorAll('a[href^="#"]').forEach(link =>{link.addEventListener('click', function(e){const targetId = this.getAttribute('href');if (targetId === '#') return;const targetElement = document.querySelector(targetId);if (targetElement){e.preventDefault();const header = document.querySelector('.top-banner');const headerHeight = header ? header.offsetHeight : 90;window.scrollTo({top: targetElement.offsetTop - headerHeight - 20, behavior: 'smooth'});history.pushState(null, null, targetId);}});});}initSmoothScrolling();// ========================================== // 2. MENU TOGGLE // ========================================== const menuToggle = document.getElementById('menuToggle');const slideMenu = document.getElementById('slideMenu');const overlay = document.getElementById('overlay');if (menuToggle && slideMenu && overlay){const toggleMenu = () =>{menuToggle.classList.toggle('active');slideMenu.classList.toggle('active');overlay.classList.toggle('active');};menuToggle.addEventListener('click', toggleMenu);overlay.addEventListener('click', toggleMenu);}// ========================================== // 3. LOGIN BUTTON (REMOVED) // ========================================== // Login button functionality has been removed // ========================================== // 4. FORM SUBMISSION // ========================================== const contactForm = document.getElementById('contactForm');const successMessage = document.getElementById('successMessage');if (contactForm){contactForm.addEventListener('submit', async function(e){e.preventDefault();const getValue = (id) =>{const el = document.getElementById(id);return el ? el.value : '';};const serviceSelect = document.getElementById('service');const selectedServiceText = serviceSelect ? serviceSelect.options[serviceSelect.selectedIndex].text : 'Dienstleistung';const formData ={type: 'contact', name: getValue('name'), organisation: getValue('organisation'), contact: getValue('contact'), service: getValue('service'), budget: getValue('budget'), description: getValue('description')};// Always store locally for dashboard try{const localLead ={id: Date.now(), datum: new Date().toLocaleDateString('de-DE'), dienstleistung: selectedServiceText, status: 'open', statusText: 'Offen', description: formData.description};const existingLeads = JSON.parse(localStorage.getItem('myLeads') || '[]');existingLeads.unshift(localLead);localStorage.setItem('myLeads', JSON.stringify(existingLeads.slice(0, 100)));}catch (err){}// Send to API if not local file if (!isLocalFile){try{const response = await fetch(API_ENDPOINT,{method: 'POST', headers:{'Content-Type': 'application/json'}, body: JSON.stringify(formData)});const result = await response.json();if (!result.success){alert('Fehler beim Senden: ' + (result.message || 'Unbekannter Fehler'));return;}}catch (error){alert('Netzwerkfehler beim Senden des Formulars');return;}}// Show success contactForm.style.display = 'none';if (successMessage) successMessage.classList.add('show');contactForm.reset();});}});

View File

@@ -1,170 +0,0 @@
/**
* Ultra-Smooth Scroll Header - Butter Performance
* Uses advanced techniques for maximum smoothness
*/
document.addEventListener("DOMContentLoaded", function() {
const topBanner = document.querySelector('.top-banner');
const slideMenu = document.querySelector('.slide-menu');
if (!topBanner) return;
// Configuration for ultra-smooth performance
const scrollThreshold = 50;
const rafDelay = 8; // 120fps for ultra-smooth
const transitionDuration = 400; // Slightly longer for smoother feel
// State tracking with performance optimization
let isScrolled = false;
let lastScrollY = 0;
let scrollDirection = 'down';
let rafId = null;
let lastUpdateTime = 0;
let scrollVelocity = 0;
let lastScrollTime = 0;
// Calculate scroll velocity for smoother transitions
function calculateVelocity(currentScrollY, currentTime) {
if (lastScrollTime === 0) {
lastScrollTime = currentTime;
return 0;
}
const timeDelta = currentTime - lastScrollTime;
const scrollDelta = Math.abs(currentScrollY - lastScrollY);
const velocity = scrollDelta / timeDelta;
lastScrollTime = currentTime;
return velocity;
}
// Ultra-smooth header state update with velocity-based easing
function updateHeaderState(scrolled, velocity = 0) {
if (scrolled === isScrolled) return;
// Add velocity-based class for different transition speeds
if (velocity > 5) {
topBanner.classList.add('fast-scroll');
} else {
topBanner.classList.remove('fast-scroll');
}
// Use requestAnimationFrame for smooth DOM updates
requestAnimationFrame(() => {
if (scrolled) {
topBanner.classList.add('scrolled');
if (slideMenu) {
slideMenu.style.transition = 'top 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
slideMenu.style.top = '80px';
}
} else {
topBanner.classList.remove('scrolled');
if (slideMenu) {
slideMenu.style.transition = 'top 0.3s cubic-bezier(0.4, 0, 0.2, 1)';
slideMenu.style.top = '110px';
}
}
});
isScrolled = scrolled;
}
// Advanced scroll handler with velocity detection
function handleScroll(currentTime) {
// Ultra-high frequency throttling
if (currentTime - lastUpdateTime < rafDelay) {
rafId = requestAnimationFrame(handleScroll);
return;
}
const currentScrollY = window.pageYOffset || document.documentElement.scrollTop;
const velocity = calculateVelocity(currentScrollY, currentTime);
// Detect scroll direction with hysteresis for stability
const scrollDelta = currentScrollY - lastScrollY;
if (Math.abs(scrollDelta) > 1) {
scrollDirection = scrollDelta > 0 ? 'down' : 'up';
}
// Apply hysteresis to prevent flickering
let shouldScroll;
if (scrollDirection === 'down') {
shouldScroll = currentScrollY > scrollThreshold + 10;
} else {
shouldScroll = currentScrollY > scrollThreshold - 10;
}
updateHeaderState(shouldScroll, velocity);
lastScrollY = currentScrollY;
lastUpdateTime = currentTime;
rafId = null;
}
// Optimized scroll listener with passive event
function onScroll() {
if (!rafId) {
rafId = requestAnimationFrame(handleScroll);
}
}
// Add scroll listener with maximum performance
window.addEventListener('scroll', onScroll, {
passive: true,
capture: false
});
// Handle resize with debouncing
let resizeTimeout;
function onResize() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
lastScrollY = window.pageYOffset || document.documentElement.scrollTop;
updateHeaderState(lastScrollY > scrollThreshold);
}, 100);
}
window.addEventListener('resize', onResize, { passive: true });
// Handle visibility change to pause/resume animations
function onVisibilityChange() {
if (document.hidden) {
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
} else {
lastScrollY = window.pageYOffset || document.documentElement.scrollTop;
updateHeaderState(lastScrollY > scrollThreshold);
}
}
document.addEventListener('visibilitychange', onVisibilityChange);
// Initialize with smooth transition
requestAnimationFrame(() => {
updateHeaderState(window.pageYOffset > scrollThreshold);
});
// Add smooth scroll behavior to internal links
document.querySelectorAll('a[href^="#"]').forEach(link => {
link.addEventListener('click', function(e) {
const targetId = this.getAttribute('href');
if (targetId === '#') return;
const targetElement = document.querySelector(targetId);
if (targetElement) {
e.preventDefault();
// Smooth scroll with header offset
const headerHeight = topBanner.offsetHeight;
const targetPosition = targetElement.offsetTop - headerHeight - 20;
window.scrollTo({
top: targetPosition,
behavior: 'smooth'
});
}
});
});
});

View File

@@ -1 +0,0 @@
document.addEventListener("DOMContentLoaded",function(){const topBanner=document.querySelector('.top-banner');const slideMenu=document.querySelector('.slide-menu');if(!topBanner)return;const scrollThreshold=50;const rafDelay=8;let isScrolled=false;let lastScrollY=0;let scrollDirection='down';let rafId=null;let lastUpdateTime=0;let scrollVelocity=0;let lastScrollTime=0;function calculateVelocity(currentScrollY,currentTime){if(lastScrollTime===0){lastScrollTime=currentTime;return 0;}const timeDelta=currentTime-lastScrollTime;const scrollDelta=Math.abs(currentScrollY-lastScrollY);const velocity=scrollDelta/timeDelta;lastScrollTime=currentTime;return velocity;}function updateHeaderState(scrolled,velocity=0){if(scrolled===isScrolled)return;if(velocity>5){topBanner.classList.add('fast-scroll');}else{topBanner.classList.remove('fast-scroll');}requestAnimationFrame(()=>{if(scrolled){topBanner.classList.add('scrolled');if(slideMenu){slideMenu.style.transition='top 0.3s cubic-bezier(0.4, 0, 0.2, 1)';slideMenu.style.top='80px';}}else{topBanner.classList.remove('scrolled');if(slideMenu){slideMenu.style.transition='top 0.3s cubic-bezier(0.4, 0, 0.2, 1)';slideMenu.style.top='110px';}}});isScrolled=scrolled;}function handleScroll(currentTime){if(currentTime-lastUpdateTime<rafDelay){rafId=requestAnimationFrame(handleScroll);return;}const currentScrollY=window.pageYOffset||document.documentElement.scrollTop;const velocity=calculateVelocity(currentScrollY,currentTime);const scrollDelta=currentScrollY-lastScrollY;if(Math.abs(scrollDelta)>1){scrollDirection=scrollDelta>0?'down':'up';}let shouldScroll;if(scrollDirection==='down'){shouldScroll=currentScrollY>scrollThreshold+10;}else{shouldScroll=currentScrollY>scrollThreshold-10;}updateHeaderState(shouldScroll,velocity);lastScrollY=currentScrollY;lastUpdateTime=currentTime;rafId=null;}function onScroll(){if(!rafId){rafId=requestAnimationFrame(handleScroll);}}window.addEventListener('scroll',onScroll,{passive:true,capture:false});let resizeTimeout;function onResize(){clearTimeout(resizeTimeout);resizeTimeout=setTimeout(()=>{lastScrollY=window.pageYOffset||document.documentElement.scrollTop;updateHeaderState(lastScrollY>scrollThreshold);},100);}window.addEventListener('resize',onResize,{passive:true});function onVisibilityChange(){if(document.hidden){if(rafId){cancelAnimationFrame(rafId);rafId=null;}}else{lastScrollY=window.pageYOffset||document.documentElement.scrollTop;updateHeaderState(lastScrollY>scrollThreshold);}}document.addEventListener('visibilitychange',onVisibilityChange);requestAnimationFrame(()=>{updateHeaderState(window.pageYOffset>scrollThreshold);});document.querySelectorAll('a[href^="#"]').forEach(link=>{link.addEventListener('click',function(e){const targetId=this.getAttribute('href');if(targetId==='#')return;const targetElement=document.querySelector(targetId);if(targetElement){e.preventDefault();const headerHeight=topBanner.offsetHeight;const targetPosition=targetElement.offsetTop-headerHeight-20;window.scrollTo({top:targetPosition,behavior:'smooth'});}});});});

View File

@@ -1,595 +0,0 @@
/**
* Tech-Onepager JavaScript - Interactive Elements and Animations
* System being built aesthetic with smooth micro-animations
*/
document.addEventListener('DOMContentLoaded', function() {
// ===== GLOBAL VARIABLES =====
let currentTooltip = null;
// ===== SYSTEM GRAPHIC ANIMATIONS =====
const systemGraphic = document.getElementById('systemGraphic');
const connections = document.getElementById('connections');
const dataPoints = document.getElementById('dataPoints');
if (systemGraphic && connections) {
initializeSystemGraphic();
}
function initializeSystemGraphic() {
const nodes = systemGraphic.querySelectorAll('.node');
const centralNode = systemGraphic.querySelector('.central-node');
// Draw connection lines
drawConnections();
// Initialize tooltip system
initializeTooltips();
// Add node interactions
nodes.forEach(node => {
node.addEventListener('mouseenter', function() {
activateConnection(this);
});
node.addEventListener('mouseleave', function() {
deactivateConnections();
});
// Special handling for central node
if (node.classList.contains('central-node')) {
node.addEventListener('click', function() {
triggerCentralNode(this);
});
}
});
// Scroll-based activation
setupScrollActivation();
}
// ===== TOOLTIP SYSTEM =====
function initializeTooltips() {
const nodes = systemGraphic.querySelectorAll('.node[data-tooltip]');
nodes.forEach(node => {
let hoverTimeout;
node.addEventListener('mouseenter', function() {
const tooltip = this.getAttribute('data-tooltip');
if (!tooltip) return;
// Clear any existing timeout
clearTimeout(hoverTimeout);
// Set 1-second delay before showing tooltip
hoverTimeout = setTimeout(() => {
showTooltip(this, tooltip);
}, 1000);
});
node.addEventListener('mouseleave', function() {
// Clear the timeout if mouse leaves before 1 second
clearTimeout(hoverTimeout);
// Hide any visible tooltip
hideTooltip();
});
});
}
function showTooltip(node, text) {
// Remove any existing tooltip
hideTooltip();
// Create tooltip element
const tooltip = document.createElement('div');
tooltip.className = 'node-tooltip';
tooltip.textContent = text;
// Position the tooltip
const nodeRect = node.getBoundingClientRect();
const systemGraphicRect = systemGraphic.getBoundingClientRect();
// Calculate position relative to system graphic
let left = nodeRect.left - systemGraphicRect.left + (nodeRect.width / 2) - 140; // Center horizontally
let top = nodeRect.top - systemGraphicRect.top - 60; // Position above node
// Adjust if tooltip goes outside bounds
if (left < 10) left = 10;
if (left + 280 > systemGraphicRect.width - 10) left = systemGraphicRect.width - 290;
if (top < 10) top = nodeRect.top - systemGraphicRect.top + nodeRect.height + 10; // Show below if not enough space above
tooltip.style.left = left + 'px';
tooltip.style.top = top + 'px';
// Add to system graphic
systemGraphic.appendChild(tooltip);
// Trigger show animation
setTimeout(() => {
tooltip.classList.add('show');
}, 10);
currentTooltip = tooltip;
}
function hideTooltip() {
if (currentTooltip) {
currentTooltip.classList.remove('show');
setTimeout(() => {
if (currentTooltip && currentTooltip.parentNode) {
currentTooltip.parentNode.removeChild(currentTooltip);
}
currentTooltip = null;
}, 300);
}
}
// Cleanup function
function cleanupTooltips() {
hideTooltip();
}
// Add cleanup on page unload
window.addEventListener('beforeunload', cleanupTooltips);
function drawConnections() {
const centralNode = systemGraphic.querySelector('.central-node');
const otherNodes = systemGraphic.querySelectorAll('.node:not(.central-node)');
const centralRect = centralNode.getBoundingClientRect();
const graphicRect = systemGraphic.getBoundingClientRect();
const centerX = centralRect.left - graphicRect.left + centralRect.width / 2;
const centerY = centralRect.top - graphicRect.top + centralRect.height / 2;
connections.innerHTML = '';
otherNodes.forEach(node => {
const nodeRect = node.getBoundingClientRect();
const nodeX = nodeRect.left - graphicRect.left + nodeRect.width / 2;
const nodeY = nodeRect.top - graphicRect.top + nodeRect.height / 2;
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', centerX);
line.setAttribute('y1', centerY);
line.setAttribute('x2', nodeX);
line.setAttribute('y2', nodeY);
line.setAttribute('class', 'connection-line');
line.setAttribute('data-target', node.dataset.node);
connections.appendChild(line);
});
}
function triggerCentralNode(node) {
// Add triggered class for growth animation
node.classList.add('triggered');
// Activate all connections
const lines = connections.querySelectorAll('.connection-line');
lines.forEach((line, index) => {
setTimeout(() => {
line.classList.add('active');
}, index * 100);
});
// Remove triggered class after animation
setTimeout(() => {
node.classList.remove('triggered');
// Deactivate connections
setTimeout(() => {
lines.forEach(line => line.classList.remove('active'));
}, 500);
}, 2000);
}
function activateConnection(node) {
const targetNode = node.dataset.node;
const line = connections.querySelector(`[data-target="${targetNode}"]`);
if (line) {
line.classList.add('active');
}
}
function deactivateConnections() {
const lines = connections.querySelectorAll('.connection-line');
lines.forEach(line => line.classList.remove('active'));
}
function setupScrollActivation() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const lines = connections.querySelectorAll('.connection-line');
lines.forEach((line, index) => {
setTimeout(() => {
line.classList.add('active');
setTimeout(() => {
line.classList.remove('active');
}, 2000);
}, index * 200);
});
}
});
}, { threshold: 0.5 });
observer.observe(systemGraphic);
}
// ===== PROCESS LINE ANIMATION =====
const processLine = document.getElementById('processLine');
const processSteps = document.querySelectorAll('.process-step');
const processConnectors = document.querySelectorAll('.process-connector');
const stepDetails = document.querySelectorAll('.step-detail');
if (processLine) {
setupProcessAnimation();
}
function setupProcessAnimation() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
animateProcessLine();
observer.unobserve(entry.target);
}
});
}, { threshold: 0.3 });
observer.observe(processLine);
}
function animateProcessLine() {
// Animate connectors sequentially
processConnectors.forEach((connector, index) => {
setTimeout(() => {
connector.classList.add('active');
}, 500 + (index * 500));
});
// Activate steps sequentially
processSteps.forEach((step, index) => {
setTimeout(() => {
step.classList.add('active');
activateStepDetail(index + 1);
}, 200 + (index * 500));
});
}
function activateStepDetail(stepNumber) {
const detail = document.querySelector(`[data-step-detail="${stepNumber}"]`);
if (detail) {
setTimeout(() => {
detail.classList.add('active');
}, 300);
}
}
// Step click interactions
processSteps.forEach((step, index) => {
step.addEventListener('click', () => {
// Reset all steps
processSteps.forEach(s => s.classList.remove('active'));
processConnectors.forEach(c => c.classList.remove('active'));
stepDetails.forEach(d => d.classList.remove('active'));
// Activate up to clicked step
for (let i = 0; i <= index; i++) {
processSteps[i].classList.add('active');
activateStepDetail(i + 1);
if (i < processConnectors.length) {
processConnectors[i].classList.add('active');
}
}
});
});
// ===== INTERACTION CARD ANIMATIONS =====
const phoneInteraction = document.getElementById('phoneInteraction');
const chatInteraction = document.getElementById('chatInteraction');
if (phoneInteraction) {
setupPhoneInteraction();
}
if (chatInteraction) {
setupChatInteraction();
}
function setupPhoneInteraction() {
const microphone = phoneInteraction.querySelector('.microphone');
const pulseRing = phoneInteraction.querySelector('.pulse-ring');
const micIcon = phoneInteraction.querySelector('.mic-icon');
if (microphone && pulseRing && micIcon) {
microphone.addEventListener('mouseenter', () => {
pulseRing.style.animation = 'pulse 0.8s infinite';
micIcon.style.transform = 'scale(1.1)';
});
microphone.addEventListener('mouseleave', () => {
pulseRing.style.animation = 'pulse 2s infinite';
micIcon.style.transform = 'scale(1)';
});
microphone.addEventListener('click', () => {
// Enhanced click animation
microphone.style.transform = 'scale(0.9)';
micIcon.style.transform = 'scale(0.8)';
// Create ripple effect
const ripple = document.createElement('div');
ripple.style.cssText = `
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
background: rgba(255, 255, 255, 0.5);
border-radius: 50%;
animation: rippleEffect 0.6s ease-out;
pointer-events: none;
`;
microphone.appendChild(ripple);
setTimeout(() => {
microphone.style.transform = 'scale(1.15)';
micIcon.style.transform = 'scale(1.1)';
setTimeout(() => {
microphone.style.transform = 'scale(1)';
micIcon.style.transform = 'scale(1)';
ripple.remove();
}, 200);
}, 100);
// Show feedback
showInteractionFeedback('KI Telefon wird verbunden...');
});
}
}
function setupChatInteraction() {
const chatWindow = chatInteraction.querySelector('.chat-window');
const typingIndicator = chatInteraction.querySelector('.typing-indicator');
const chatHeader = chatInteraction.querySelector('.chat-header');
if (chatWindow && typingIndicator && chatHeader) {
chatInteraction.addEventListener('mouseenter', () => {
// Enhanced typing animation
const spans = typingIndicator.querySelectorAll('span');
spans.forEach((span, index) => {
span.style.animation = 'typing 1.2s infinite ease-in-out';
span.style.animationDelay = `${-0.32 + (index * 0.16)}s`;
span.style.background = 'var(--accent-teal)';
});
// Animate chat window
chatWindow.style.transform = 'scale(1.02)';
chatHeader.style.background = 'linear-gradient(135deg, var(--accent-green), var(--accent-teal))';
});
chatInteraction.addEventListener('mouseleave', () => {
const spans = typingIndicator.querySelectorAll('span');
spans.forEach(span => {
span.style.background = 'var(--primary-mid)';
});
chatWindow.style.transform = 'scale(1)';
chatHeader.style.background = 'linear-gradient(135deg, var(--accent-teal), var(--accent-green))';
});
const chatBtn = chatInteraction.querySelector('.interaction-btn.secondary');
if (chatBtn) {
chatBtn.addEventListener('click', () => {
// Enhanced click animation
chatWindow.style.transform = 'scale(0.95)';
setTimeout(() => {
chatWindow.style.transform = 'scale(1.05)';
setTimeout(() => {
chatWindow.style.transform = 'scale(1)';
}, 200);
}, 100);
showInteractionFeedback('KI Chat wird gestartet...');
});
}
}
}
function showInteractionFeedback(message) {
// Create temporary feedback element
const feedback = document.createElement('div');
feedback.style.cssText = `
position: fixed;
top: 100px;
left: 50%;
transform: translateX(-50%);
background: var(--accent-teal);
color: white;
padding: 16px 24px;
border-radius: 8px;
font-weight: 600;
z-index: 10000;
box-shadow: 0 8px 30px rgba(38, 166, 154, 0.4);
animation: slideDown 0.3s ease;
`;
feedback.textContent = message;
document.body.appendChild(feedback);
setTimeout(() => {
feedback.style.animation = 'slideUp 0.3s ease';
setTimeout(() => {
feedback.remove();
}, 300);
}, 2000);
}
// ===== HERO BUTTON INTERACTIONS =====
const kiPhoneBtn = document.getElementById('kiPhoneBtn');
const chatBtn = document.getElementById('chatBtn');
if (kiPhoneBtn) {
kiPhoneBtn.addEventListener('click', () => {
showInteractionFeedback('KI Telefon wird verbunden...');
// Scroll to interaction section
document.getElementById('interaction')?.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
});
}
if (chatBtn) {
chatBtn.addEventListener('click', () => {
showInteractionFeedback('KI Chat wird gestartet...');
// Scroll to interaction section
document.getElementById('interaction')?.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
});
}
// ===== SYSTEM CARD HOVER EFFECTS =====
const systemCards = document.querySelectorAll('.system-card');
systemCards.forEach(card => {
card.addEventListener('mouseenter', function() {
// Add subtle glow effect
this.style.boxShadow = '0 25px 50px rgba(38, 166, 154, 0.2)';
// Animate internal components
const flowItems = this.querySelectorAll('.flow-item, .phone-icon, .ki-processor, .crm-output');
flowItems.forEach((item, index) => {
setTimeout(() => {
item.style.transform = 'translateY(-2px)';
setTimeout(() => {
item.style.transform = 'translateY(0)';
}, 200);
}, index * 100);
});
});
card.addEventListener('mouseleave', function() {
this.style.boxShadow = '0 12px 30px rgba(79, 71, 71, 0.1)';
});
});
// ===== DATA CARD ANIMATIONS =====
const dataCards = document.querySelectorAll('.data-card');
const dataCardObserver = new IntersectionObserver((entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
setTimeout(() => {
entry.target.style.animation = 'fadeInUp 0.6s ease forwards';
}, index * 100);
dataCardObserver.unobserve(entry.target);
}
});
}, { threshold: 0.1 });
dataCards.forEach(card => {
dataCardObserver.observe(card);
});
// ===== SMOOTH SCROLL FOR INTERNAL LINKS =====
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute('href'));
if (target) {
target.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
});
});
// ===== UTILITY ANIMATIONS =====
// Add CSS animations dynamically
const style = document.createElement('style');
style.textContent = `
@keyframes slideDown {
from {
opacity: 0;
transform: translate(-50%, -20px);
}
to {
opacity: 1;
transform: translate(-50%, 0);
}
}
@keyframes slideUp {
from {
opacity: 1;
transform: translate(-50%, 0);
}
to {
opacity: 0;
transform: translate(-50%, -20px);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes rippleEffect {
from {
width: 20px;
height: 20px;
opacity: 0.5;
}
to {
width: 100px;
height: 100px;
opacity: 0;
}
}
@keyframes shimmer {
0% { transform: translateX(-100%) translateY(-100%) rotate(45deg); opacity: 0; }
50% { opacity: 1; }
100% { transform: translateX(100%) translateY(100%) rotate(45deg); opacity: 0; }
}
`;
document.head.appendChild(style);
// ===== PERFORMANCE OPTIMIZATION =====
// Throttle scroll events
let ticking = false;
function requestTick() {
if (!ticking) {
requestAnimationFrame(updateScrollEffects);
ticking = true;
setTimeout(() => { ticking = false; }, 16);
}
}
function updateScrollEffects() {
// Add scroll-based effects here if needed
// Parallax, fade-ins, etc.
}
window.addEventListener('scroll', requestTick, { passive: true });
// ===== INITIALIZATION COMPLETE =====
console.log('Tech-Onepager initialized successfully');
});

File diff suppressed because one or more lines are too long