update
This commit is contained in:
@@ -48,8 +48,8 @@ define('USE_PRODUCTION', false); // Use test webhook for debugging
|
||||
define('DEBUG_MODE', true); // Enable debug to see errors
|
||||
|
||||
// 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_TEST', 'https://n8n.profice.de/webhook-test/8658d57e-2348-4046-90a5-7551708f8d50');
|
||||
define('WEBHOOK_PROD', 'https://n8n.profice.de/webhook/8658d57e-2348-4046-90a5-7551708f8d50');
|
||||
define('WEBHOOK_URL', USE_PRODUCTION ? WEBHOOK_PROD : WEBHOOK_TEST);
|
||||
|
||||
// Google Analytics
|
||||
@@ -492,30 +492,6 @@ function handleContactForm($data) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleLoginForm($data) {
|
||||
$required = ['email', 'password'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($data[$field])) {
|
||||
sendResponse(false, "Pflichtfeld fehlt: {$field}", null, 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Add your login logic here
|
||||
sendResponse(true, 'Login erfolgreich');
|
||||
}
|
||||
|
||||
function handleRegisterForm($data) {
|
||||
$required = ['name', 'email', 'password'];
|
||||
foreach ($required as $field) {
|
||||
if (empty($data[$field])) {
|
||||
sendResponse(false, "Pflichtfeld fehlt: {$field}", null, 400);
|
||||
}
|
||||
}
|
||||
|
||||
// Add your registration logic here
|
||||
sendResponse(true, 'Registrierung erfolgreich');
|
||||
}
|
||||
|
||||
function handleLeadForm($data) {
|
||||
// Handle lead form submissions
|
||||
$leadId = storeLead($data);
|
||||
@@ -549,6 +525,49 @@ function handleGetTrackingConfig($data) {
|
||||
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'] ?? [];
|
||||
@@ -605,12 +624,6 @@ try {
|
||||
case 'contact':
|
||||
handleContactForm($data);
|
||||
break;
|
||||
case 'login':
|
||||
handleLoginForm($data);
|
||||
break;
|
||||
case 'register':
|
||||
handleRegisterForm($data);
|
||||
break;
|
||||
case 'lead':
|
||||
handleLeadForm($data);
|
||||
break;
|
||||
@@ -626,6 +639,9 @@ try {
|
||||
case 'track_event':
|
||||
handleTrackEvent($data);
|
||||
break;
|
||||
case 'chat':
|
||||
handleChatMessage($data);
|
||||
break;
|
||||
default:
|
||||
sendResponse(false, 'Ungültiger Anfragetyp', null, 400);
|
||||
}
|
||||
|
||||
429
Profice WebSite/scripts/chat.js
Normal file
429
Profice WebSite/scripts/chat.js
Normal file
@@ -0,0 +1,429 @@
|
||||
/**
|
||||
* 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);
|
||||
@@ -1,423 +0,0 @@
|
||||
/**
|
||||
* Cookie Consent Management System
|
||||
* All sensitive data (tracking IDs, URLs) are loaded from server-side PHP
|
||||
* Includes fallback for local file access (no server)
|
||||
*
|
||||
* @version 2.1.0
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// ==========================================
|
||||
// CONFIGURATION
|
||||
// ==========================================
|
||||
|
||||
const cookieCategories = ['necessary', 'analytics', 'marketing'];
|
||||
const API_ENDPOINT = 'scripts/add/send.php';
|
||||
|
||||
// Detect if running from file:// protocol (no server)
|
||||
const isLocalFile = window.location.protocol === 'file:';
|
||||
|
||||
let consentState = {
|
||||
hasConsented: false,
|
||||
preferences: {}
|
||||
};
|
||||
|
||||
let previousPreferences = {};
|
||||
let trackingScripts = {};
|
||||
|
||||
// ==========================================
|
||||
// DATALAYER INTEGRATION
|
||||
// ==========================================
|
||||
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
|
||||
function pushConsentToDataLayer(preferences) {
|
||||
window.dataLayer.push({
|
||||
'event': 'consent_update',
|
||||
'consent': {
|
||||
'analytics_storage': preferences.analytics ? 'granted' : 'denied',
|
||||
'ad_storage': preferences.marketing ? 'granted' : 'denied',
|
||||
'functionality_storage': preferences.necessary ? 'granted' : 'denied',
|
||||
'personalization_storage': preferences.marketing ? 'granted' : 'denied',
|
||||
'security_storage': 'granted'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setDefaultConsent() {
|
||||
window.dataLayer.push({
|
||||
'event': 'consent_default',
|
||||
'consent': {
|
||||
'analytics_storage': 'denied',
|
||||
'ad_storage': 'denied',
|
||||
'functionality_storage': 'granted',
|
||||
'personalization_storage': 'denied',
|
||||
'security_storage': 'granted',
|
||||
'wait_for_update': 500
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// STORAGE (API + LOCALSTORAGE FALLBACK)
|
||||
// ==========================================
|
||||
|
||||
async function saveConsentToStorage(preferences) {
|
||||
// Always save to localStorage as backup
|
||||
const consentData = {
|
||||
hasConsented: true,
|
||||
preferences: preferences,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
localStorage.setItem('cookieConsent', JSON.stringify(consentData));
|
||||
|
||||
// Try API if not local file
|
||||
if (!isLocalFile) {
|
||||
try {
|
||||
const response = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'cookie_consent', preferences })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success && result.data && result.data.scripts) {
|
||||
trackingScripts = result.data.scripts;
|
||||
}
|
||||
return result.success;
|
||||
} catch (e) {
|
||||
// API failed, localStorage already saved
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function getConsentFromStorage() {
|
||||
// Try localStorage first (faster)
|
||||
const localConsent = localStorage.getItem('cookieConsent');
|
||||
if (localConsent) {
|
||||
try {
|
||||
return JSON.parse(localConsent);
|
||||
} catch (e) {
|
||||
localStorage.removeItem('cookieConsent');
|
||||
}
|
||||
}
|
||||
|
||||
// Try API if not local file
|
||||
if (!isLocalFile) {
|
||||
try {
|
||||
const response = await fetch(API_ENDPOINT, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ type: 'get_cookie_consent' })
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success && result.data) {
|
||||
if (result.data.scripts) {
|
||||
trackingScripts = result.data.scripts;
|
||||
}
|
||||
// Cache in localStorage
|
||||
localStorage.setItem('cookieConsent', JSON.stringify(result.data));
|
||||
return result.data;
|
||||
}
|
||||
} catch (e) {
|
||||
// API unavailable
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// SCRIPT LOADING
|
||||
// ==========================================
|
||||
|
||||
function loadTrackingScripts() {
|
||||
if (!trackingScripts || Object.keys(trackingScripts).length === 0) return;
|
||||
|
||||
if (trackingScripts.ga && consentState.preferences.analytics) {
|
||||
loadGoogleAnalytics(trackingScripts.ga);
|
||||
}
|
||||
if (trackingScripts.gtm && consentState.preferences.analytics) {
|
||||
loadGTM(trackingScripts.gtm);
|
||||
}
|
||||
if (trackingScripts.fb && consentState.preferences.marketing) {
|
||||
loadFacebookPixel(trackingScripts.fb);
|
||||
}
|
||||
if (trackingScripts.gads && consentState.preferences.marketing) {
|
||||
loadGoogleAds(trackingScripts.gads);
|
||||
}
|
||||
if (trackingScripts.linkedin && consentState.preferences.marketing) {
|
||||
loadLinkedIn(trackingScripts.linkedin);
|
||||
}
|
||||
}
|
||||
|
||||
function loadScript(src, callback) {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.async = true;
|
||||
if (callback) script.onload = callback;
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
|
||||
function loadGoogleAnalytics(config) {
|
||||
if (!config.src || !config.config || !config.config.id) return;
|
||||
loadScript(config.src, function() {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
window.gtag = gtag;
|
||||
gtag('js', new Date());
|
||||
gtag('config', config.config.id);
|
||||
});
|
||||
}
|
||||
|
||||
function loadGTM(config) {
|
||||
if (!config.id) return;
|
||||
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer',config.id);
|
||||
}
|
||||
|
||||
function loadFacebookPixel(config) {
|
||||
if (!config.id) return;
|
||||
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
||||
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
|
||||
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
|
||||
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
|
||||
document,'script','https://connect.facebook.net/en_US/fbevents.js');
|
||||
fbq('init', config.id);
|
||||
fbq('track', 'PageView');
|
||||
}
|
||||
|
||||
function loadGoogleAds(config) {
|
||||
if (!config.id) return;
|
||||
loadScript('https://www.googletagmanager.com/gtag/js?id=' + config.id, function() {
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', config.id);
|
||||
});
|
||||
}
|
||||
|
||||
function loadLinkedIn(config) {
|
||||
if (!config.id) return;
|
||||
window._linkedin_partner_id = config.id;
|
||||
window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];
|
||||
window._linkedin_data_partner_ids.push(config.id);
|
||||
loadScript('https://snap.licdn.com/li.lms-analytics/insight.min.js');
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// COOKIE DELETION
|
||||
// ==========================================
|
||||
|
||||
const cookiePatterns = {
|
||||
analytics: ['_ga', '_gid', '_gat', '__utma', '__utmb', '__utmc', '__utmz'],
|
||||
marketing: ['_fbp', '_fbc', 'fr', '_gcl_au', '_gcl_aw', 'IDE', 'DSID', 'NID']
|
||||
};
|
||||
|
||||
function deleteCookie(name) {
|
||||
const paths = ['/', window.location.pathname];
|
||||
const domains = ['', window.location.hostname, '.' + window.location.hostname];
|
||||
|
||||
paths.forEach(path => {
|
||||
domains.forEach(domain => {
|
||||
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}${domain ? '; domain=' + domain : ''}`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function deleteCookiesForCategory(category) {
|
||||
const patterns = cookiePatterns[category] || [];
|
||||
const allCookies = document.cookie.split(';');
|
||||
|
||||
allCookies.forEach(cookie => {
|
||||
const cookieName = cookie.split('=')[0].trim();
|
||||
patterns.forEach(pattern => {
|
||||
if (cookieName.startsWith(pattern)) {
|
||||
deleteCookie(cookieName);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handlePreferenceChanges(newPreferences) {
|
||||
Object.keys(cookiePatterns).forEach(category => {
|
||||
if (previousPreferences[category] === true && newPreferences[category] === false) {
|
||||
deleteCookiesForCategory(category);
|
||||
window.dataLayer.push({ 'event': 'consent_revoked', 'consent_category': category });
|
||||
}
|
||||
});
|
||||
previousPreferences = { ...newPreferences };
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// DOM ELEMENTS
|
||||
// ==========================================
|
||||
|
||||
const cookieBanner = document.getElementById('cookieBanner');
|
||||
const cookieModal = document.getElementById('cookieModal');
|
||||
const acceptAllBtn = document.getElementById('cookieAcceptAll');
|
||||
const rejectAllBtn = document.getElementById('cookieRejectAll');
|
||||
const settingsBtn = document.getElementById('cookieSettings');
|
||||
const modalCloseBtn = document.getElementById('cookieModalClose');
|
||||
const savePreferencesBtn = document.getElementById('cookieSavePreferences');
|
||||
|
||||
// ==========================================
|
||||
// CORE FUNCTIONS
|
||||
// ==========================================
|
||||
|
||||
async function initCookieConsent() {
|
||||
setDefaultConsent();
|
||||
|
||||
const savedConsent = await getConsentFromStorage();
|
||||
|
||||
if (savedConsent && savedConsent.hasConsented) {
|
||||
consentState = {
|
||||
hasConsented: true,
|
||||
preferences: savedConsent.preferences || {}
|
||||
};
|
||||
previousPreferences = { ...consentState.preferences };
|
||||
applyConsentPreferences();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show banner after short delay
|
||||
setTimeout(() => {
|
||||
if (cookieBanner) cookieBanner.classList.add('show');
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function applyConsentPreferences() {
|
||||
pushConsentToDataLayer(consentState.preferences);
|
||||
loadTrackingScripts();
|
||||
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consentState.preferences }));
|
||||
}
|
||||
|
||||
async function saveConsent(preferences) {
|
||||
handlePreferenceChanges(preferences);
|
||||
await saveConsentToStorage(preferences);
|
||||
|
||||
consentState = {
|
||||
hasConsented: true,
|
||||
preferences: preferences,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
applyConsentPreferences();
|
||||
}
|
||||
|
||||
function hideBanner() {
|
||||
if (cookieBanner) cookieBanner.classList.remove('show');
|
||||
}
|
||||
|
||||
function showSettingsModal() {
|
||||
if (cookieModal) {
|
||||
cookieModal.classList.add('show');
|
||||
populateSettingsModal();
|
||||
}
|
||||
}
|
||||
|
||||
function hideSettingsModal() {
|
||||
if (cookieModal) cookieModal.classList.remove('show');
|
||||
}
|
||||
|
||||
function populateSettingsModal() {
|
||||
cookieCategories.forEach(key => {
|
||||
const toggle = document.getElementById(`cookie-toggle-${key}`);
|
||||
if (!toggle) return;
|
||||
|
||||
const isEnabled = consentState.preferences[key] || false;
|
||||
const isRequired = key === 'necessary';
|
||||
|
||||
if (isRequired) {
|
||||
toggle.classList.add('active', 'disabled');
|
||||
toggle.setAttribute('aria-checked', 'true');
|
||||
toggle.setAttribute('aria-disabled', 'true');
|
||||
} else {
|
||||
toggle.classList.toggle('active', isEnabled);
|
||||
toggle.setAttribute('aria-checked', isEnabled ? 'true' : 'false');
|
||||
toggle.onclick = () => {
|
||||
toggle.classList.toggle('active');
|
||||
toggle.setAttribute('aria-checked', toggle.classList.contains('active') ? 'true' : 'false');
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getModalPreferences() {
|
||||
const preferences = {};
|
||||
cookieCategories.forEach(key => {
|
||||
const toggle = document.getElementById(`cookie-toggle-${key}`);
|
||||
preferences[key] = key === 'necessary' ? true : (toggle ? toggle.classList.contains('active') : false);
|
||||
});
|
||||
return preferences;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// EVENT LISTENERS
|
||||
// ==========================================
|
||||
|
||||
if (acceptAllBtn) {
|
||||
acceptAllBtn.addEventListener('click', () => {
|
||||
const preferences = {};
|
||||
cookieCategories.forEach(key => preferences[key] = true);
|
||||
saveConsent(preferences);
|
||||
hideBanner();
|
||||
});
|
||||
}
|
||||
|
||||
if (rejectAllBtn) {
|
||||
rejectAllBtn.addEventListener('click', () => {
|
||||
saveConsent({ necessary: true, analytics: false, marketing: false });
|
||||
hideBanner();
|
||||
});
|
||||
}
|
||||
|
||||
if (settingsBtn) {
|
||||
settingsBtn.addEventListener('click', showSettingsModal);
|
||||
}
|
||||
|
||||
if (modalCloseBtn) {
|
||||
modalCloseBtn.addEventListener('click', hideSettingsModal);
|
||||
}
|
||||
|
||||
if (savePreferencesBtn) {
|
||||
savePreferencesBtn.addEventListener('click', () => {
|
||||
saveConsent(getModalPreferences());
|
||||
hideSettingsModal();
|
||||
hideBanner();
|
||||
});
|
||||
}
|
||||
|
||||
if (cookieModal) {
|
||||
cookieModal.addEventListener('click', (e) => {
|
||||
if (e.target === cookieModal) hideSettingsModal();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && cookieModal && cookieModal.classList.contains('show')) {
|
||||
hideSettingsModal();
|
||||
}
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// PUBLIC API
|
||||
// ==========================================
|
||||
|
||||
window.CookieConsent = {
|
||||
getConsent: () => consentState,
|
||||
hasConsent: (category) => consentState.preferences[category] === true,
|
||||
updateConsent: saveConsent,
|
||||
showSettings: showSettingsModal
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// INITIALIZE
|
||||
// ==========================================
|
||||
|
||||
initCookieConsent();
|
||||
});
|
||||
227
Profice WebSite/scripts/cursor-simple.js
Normal file
227
Profice WebSite/scripts/cursor-simple.js
Normal file
@@ -0,0 +1,227 @@
|
||||
// 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();
|
||||
});
|
||||
@@ -27,27 +27,57 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
body.classList.add('system-cursor');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.classList.remove('active');
|
||||
const icon = toggleBtn.querySelector('.cursor-icon');
|
||||
let icon = toggleBtn.querySelector('.cursor-icon');
|
||||
if (icon) {
|
||||
// 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';
|
||||
icon.src = imagePath;
|
||||
// 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 cursor icon
|
||||
// Custom cursor (secondary) - show standard cursor icon
|
||||
body.classList.remove('system-cursor');
|
||||
if (toggleBtn) {
|
||||
toggleBtn.classList.add('active');
|
||||
const icon = toggleBtn.querySelector('.cursor-icon');
|
||||
let icon = toggleBtn.querySelector('.cursor-icon');
|
||||
if (icon) {
|
||||
// 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';
|
||||
icon.src = imagePath;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,29 +93,40 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
});
|
||||
}
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.id = "venom-cursor";
|
||||
document.body.appendChild(canvas);
|
||||
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 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;
|
||||
});
|
||||
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) {
|
||||
@@ -130,7 +171,12 @@ document.addEventListener("DOMContentLoaded", function () {
|
||||
}
|
||||
|
||||
function render() {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
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);
|
||||
|
||||
1
Profice WebSite/scripts/cursor.min.js
vendored
Normal file
1
Profice WebSite/scripts/cursor.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
Profice WebSite/scripts/leads.min.js
vendored
Normal file
1
Profice WebSite/scripts/leads.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
// 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);
|
||||
@@ -1,217 +0,0 @@
|
||||
// login.js
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const loginBtn = document.getElementById('loginSubmit');
|
||||
const successMessage = document.getElementById('successMessage');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const errorText = document.getElementById('errorText');
|
||||
const registerBtn = document.getElementById('registerBtn');
|
||||
|
||||
// Register button functionality
|
||||
if (registerBtn) {
|
||||
registerBtn.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
window.location.href = 'register.html';
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user is already logged in
|
||||
checkExistingSession();
|
||||
|
||||
// Login form submission
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const email = document.getElementById('email').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const remember = document.getElementById('remember').checked;
|
||||
|
||||
// Simple validation
|
||||
if (!email || !password) {
|
||||
showError('Bitte füllen Sie alle Felder aus.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
showError('Bitte geben Sie eine gültige E-Mail-Adresse ein.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
setLoadingState(true);
|
||||
hideMessages();
|
||||
|
||||
// Send login data to PHP API
|
||||
const loginData = {
|
||||
type: 'login',
|
||||
email: email,
|
||||
password: password,
|
||||
remember: remember
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('../scripts/add/send.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(loginData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Store session (for demo purposes - in real app, this would come from server)
|
||||
const sessionData = {
|
||||
user: { email: email },
|
||||
loginTime: new Date().toISOString(),
|
||||
remember: remember
|
||||
};
|
||||
|
||||
if (remember) {
|
||||
localStorage.setItem('userSession', JSON.stringify(sessionData));
|
||||
} else {
|
||||
sessionStorage.setItem('userSession', JSON.stringify(sessionData));
|
||||
}
|
||||
|
||||
// Show success message
|
||||
showSuccess('Anmeldung erfolgreich! Sie werden weitergeleitet...');
|
||||
|
||||
// Redirect after delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '../index.html';
|
||||
}, 2000);
|
||||
} else {
|
||||
showError('Anmeldung fehlgeschlagen: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
showError('Netzwerkfehler bei der Anmeldung. Bitte versuchen Sie es später erneut.');
|
||||
}
|
||||
|
||||
setLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Loading state management
|
||||
function setLoadingState(loading) {
|
||||
if (loginBtn) {
|
||||
const btnText = loginBtn.querySelector('.btn-text');
|
||||
const btnLoading = loginBtn.querySelector('.btn-loading');
|
||||
|
||||
if (loading) {
|
||||
loginBtn.disabled = true;
|
||||
if (btnText) btnText.style.display = 'none';
|
||||
if (btnLoading) btnLoading.style.display = 'inline-block';
|
||||
} else {
|
||||
loginBtn.disabled = false;
|
||||
if (btnText) btnText.style.display = 'inline-block';
|
||||
if (btnLoading) btnLoading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message display functions
|
||||
function showSuccess(message) {
|
||||
if (successMessage) {
|
||||
const messageElement = successMessage.querySelector('p');
|
||||
if (messageElement) {
|
||||
messageElement.textContent = message;
|
||||
}
|
||||
successMessage.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
if (errorMessage && errorText) {
|
||||
errorText.textContent = message;
|
||||
errorMessage.classList.add('show');
|
||||
|
||||
// Hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
errorMessage.classList.remove('show');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function hideMessages() {
|
||||
if (successMessage) successMessage.classList.remove('show');
|
||||
if (errorMessage) errorMessage.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Check existing session - DISABLED auto-redirect
|
||||
function checkExistingSession() {
|
||||
const sessionData = localStorage.getItem('userSession') || sessionStorage.getItem('userSession');
|
||||
|
||||
if (sessionData) {
|
||||
try {
|
||||
const session = JSON.parse(sessionData);
|
||||
const loginTime = new Date(session.loginTime);
|
||||
const now = new Date();
|
||||
const sessionAge = (now - loginTime) / (1000 * 60 * 60); // hours
|
||||
|
||||
// Auto-logout after 24 hours
|
||||
if (sessionAge < 24) {
|
||||
// User is still logged in - just log it, don't redirect
|
||||
console.log('User already logged in');
|
||||
// DISABLED: window.location.href = '../index.html';
|
||||
} else {
|
||||
// Session expired, remove it
|
||||
console.log('Session expired, removing...');
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session parsing error:', error);
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Logout function (can be called from other pages)
|
||||
function logout() {
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
window.location.href = 'sites/login.html';
|
||||
}
|
||||
|
||||
// Get current user (can be called from other pages)
|
||||
function getCurrentUser() {
|
||||
const sessionData = localStorage.getItem('userSession') || sessionStorage.getItem('userSession');
|
||||
|
||||
if (sessionData) {
|
||||
try {
|
||||
const session = JSON.parse(sessionData);
|
||||
const loginTime = new Date(session.loginTime);
|
||||
const now = new Date();
|
||||
const sessionAge = (now - loginTime) / (1000 * 60 * 60); // hours
|
||||
|
||||
if (sessionAge < 24) {
|
||||
return session.user;
|
||||
} else {
|
||||
// Session expired
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session parsing error:', error);
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if user is logged in (can be called from other pages)
|
||||
function isLoggedIn() {
|
||||
return getCurrentUser() !== null;
|
||||
}
|
||||
@@ -1,272 +0,0 @@
|
||||
// register.js
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
const registerBtn = document.getElementById('registerSubmit');
|
||||
const successMessage = document.getElementById('successMessage');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
const errorText = document.getElementById('errorText');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const confirmPasswordInput = document.getElementById('confirmPassword');
|
||||
const passwordStrength = document.getElementById('passwordStrength');
|
||||
|
||||
// Check if user is already logged in
|
||||
checkExistingSession();
|
||||
|
||||
// Password strength checker
|
||||
if (passwordInput && passwordStrength) {
|
||||
passwordInput.addEventListener('input', function() {
|
||||
const password = this.value;
|
||||
const strength = checkPasswordStrength(password);
|
||||
updatePasswordStrength(strength);
|
||||
});
|
||||
}
|
||||
|
||||
// Password confirmation checker
|
||||
if (confirmPasswordInput && passwordInput) {
|
||||
confirmPasswordInput.addEventListener('input', function() {
|
||||
const password = passwordInput.value;
|
||||
const confirmPassword = this.value;
|
||||
|
||||
if (confirmPassword && password !== confirmPassword) {
|
||||
this.setCustomValidity('Passwörter stimmen nicht überein');
|
||||
} else {
|
||||
this.setCustomValidity('');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Registration form submission
|
||||
if (registerForm) {
|
||||
registerForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
const formData = {
|
||||
type: 'register',
|
||||
name: `${document.getElementById('firstName').value} ${document.getElementById('lastName').value}`,
|
||||
email: document.getElementById('email').value,
|
||||
phone: document.getElementById('phone').value,
|
||||
company: document.getElementById('company').value,
|
||||
password: passwordInput.value,
|
||||
confirmPassword: confirmPasswordInput.value,
|
||||
terms: document.getElementById('terms').checked,
|
||||
newsletter: document.getElementById('newsletter').checked,
|
||||
registrationTime: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Validation
|
||||
const validation = validateRegistrationForm(formData);
|
||||
if (!validation.valid) {
|
||||
showError(validation.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
setLoadingState(true);
|
||||
hideMessages();
|
||||
|
||||
// Send registration data to PHP API
|
||||
try {
|
||||
const response = await fetch('../scripts/add/send.php', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Store user data (for demo purposes)
|
||||
const userData = {
|
||||
firstName: document.getElementById('firstName').value,
|
||||
lastName: document.getElementById('lastName').value,
|
||||
email: formData.email,
|
||||
phone: formData.phone,
|
||||
company: formData.company,
|
||||
registrationTime: formData.registrationTime,
|
||||
newsletter: formData.newsletter
|
||||
};
|
||||
|
||||
// Store in localStorage (for demo purposes)
|
||||
localStorage.setItem('userData', JSON.stringify(userData));
|
||||
localStorage.setItem('userRegistered', 'true');
|
||||
|
||||
// Show success message
|
||||
showSuccess('Registrierung erfolgreich! Sie werden weitergeleitet...');
|
||||
|
||||
// Redirect after delay
|
||||
setTimeout(() => {
|
||||
window.location.href = '../index.html';
|
||||
}, 2000);
|
||||
} else {
|
||||
showError('Registrierung fehlgeschlagen: ' + result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
showError('Netzwerkfehler bei der Registrierung. Bitte versuchen Sie es später erneut.');
|
||||
}
|
||||
|
||||
setLoadingState(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Password strength checker function
|
||||
function checkPasswordStrength(password) {
|
||||
let strength = 0;
|
||||
|
||||
// Length check
|
||||
if (password.length >= 8) strength++;
|
||||
if (password.length >= 12) strength++;
|
||||
|
||||
// Character variety checks
|
||||
if (/[a-z]/.test(password)) strength++; // lowercase
|
||||
if (/[A-Z]/.test(password)) strength++; // uppercase
|
||||
if (/[0-9]/.test(password)) strength++; // numbers
|
||||
if (/[^a-zA-Z0-9]/.test(password)) strength++; // special characters
|
||||
|
||||
return strength;
|
||||
}
|
||||
|
||||
// Update password strength indicator
|
||||
function updatePasswordStrength(strength) {
|
||||
const strengthBar = passwordStrength.querySelector('.strength-bar');
|
||||
const strengthText = passwordStrength.querySelector('.strength-text');
|
||||
|
||||
// Remove all strength classes
|
||||
passwordStrength.classList.remove('strength-weak', 'strength-medium', 'strength-strong');
|
||||
|
||||
if (strength <= 2) {
|
||||
passwordStrength.classList.add('strength-weak');
|
||||
strengthText.textContent = 'Schwach';
|
||||
} else if (strength <= 4) {
|
||||
passwordStrength.classList.add('strength-medium');
|
||||
strengthText.textContent = 'Mittel';
|
||||
} else {
|
||||
passwordStrength.classList.add('strength-strong');
|
||||
strengthText.textContent = 'Stark';
|
||||
}
|
||||
}
|
||||
|
||||
// Form validation
|
||||
function validateRegistrationForm(data) {
|
||||
// Required fields check
|
||||
if (!data.firstName || !data.lastName || !data.email || !data.password) {
|
||||
return { valid: false, message: 'Bitte füllen Sie alle Pflichtfelder aus.' };
|
||||
}
|
||||
|
||||
// Name validation
|
||||
if (data.firstName.length < 2 || data.lastName.length < 2) {
|
||||
return { valid: false, message: 'Vorname und Nachname müssen mindestens 2 Zeichen lang sein.' };
|
||||
}
|
||||
|
||||
// Email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(data.email)) {
|
||||
return { valid: false, message: 'Bitte geben Sie eine gültige E-Mail-Adresse ein.' };
|
||||
}
|
||||
|
||||
// Password validation
|
||||
if (data.password.length < 8) {
|
||||
return { valid: false, message: 'Das Passwort muss mindestens 8 Zeichen lang sein.' };
|
||||
}
|
||||
|
||||
// Password confirmation
|
||||
if (data.password !== data.confirmPassword) {
|
||||
return { valid: false, message: 'Die Passwörter stimmen nicht überein.' };
|
||||
}
|
||||
|
||||
// Terms acceptance
|
||||
if (!data.terms) {
|
||||
return { valid: false, message: 'Sie müssen die Nutzungsbedingungen akzeptieren.' };
|
||||
}
|
||||
|
||||
// Phone validation (if provided)
|
||||
if (data.phone) {
|
||||
const phoneRegex = /^[\d\s\-\+\(\)]+$/;
|
||||
if (!phoneRegex.test(data.phone)) {
|
||||
return { valid: false, message: 'Bitte geben Sie eine gültige Telefonnummer ein.' };
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true, message: 'Validierung erfolgreich' };
|
||||
}
|
||||
|
||||
// Loading state management
|
||||
function setLoadingState(loading) {
|
||||
if (registerBtn) {
|
||||
const btnText = registerBtn.querySelector('.btn-text');
|
||||
const btnLoading = registerBtn.querySelector('.btn-loading');
|
||||
|
||||
if (loading) {
|
||||
registerBtn.disabled = true;
|
||||
if (btnText) btnText.style.display = 'none';
|
||||
if (btnLoading) btnLoading.style.display = 'inline-block';
|
||||
} else {
|
||||
registerBtn.disabled = false;
|
||||
if (btnText) btnText.style.display = 'inline-block';
|
||||
if (btnLoading) btnLoading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Message display functions
|
||||
function showSuccess(message) {
|
||||
if (successMessage) {
|
||||
const messageElement = successMessage.querySelector('p');
|
||||
if (messageElement) {
|
||||
messageElement.textContent = message;
|
||||
}
|
||||
successMessage.classList.add('show');
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
if (errorMessage && errorText) {
|
||||
errorText.textContent = message;
|
||||
errorMessage.classList.add('show');
|
||||
|
||||
// Hide after 5 seconds
|
||||
setTimeout(() => {
|
||||
errorMessage.classList.remove('show');
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function hideMessages() {
|
||||
if (successMessage) successMessage.classList.remove('show');
|
||||
if (errorMessage) errorMessage.classList.remove('show');
|
||||
}
|
||||
});
|
||||
|
||||
// Check existing session (same as login.js) - DISABLED auto-redirect
|
||||
function checkExistingSession() {
|
||||
const sessionData = localStorage.getItem('userSession') || sessionStorage.getItem('userSession');
|
||||
|
||||
if (sessionData) {
|
||||
try {
|
||||
const session = JSON.parse(sessionData);
|
||||
const loginTime = new Date(session.loginTime);
|
||||
const now = new Date();
|
||||
const sessionAge = (now - loginTime) / (1000 * 60 * 60); // hours
|
||||
|
||||
// Auto-logout after 24 hours
|
||||
if (sessionAge < 24) {
|
||||
// User is still logged in - just log it, don't redirect
|
||||
console.log('User already logged in');
|
||||
// DISABLED: window.location.href = '../index.html';
|
||||
} else {
|
||||
// Session expired, remove it
|
||||
console.log('Session expired, removing...');
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session parsing error:', error);
|
||||
localStorage.removeItem('userSession');
|
||||
sessionStorage.removeItem('userSession');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,23 +59,10 @@ document.addEventListener("DOMContentLoaded", function() {
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. LOGIN BUTTON
|
||||
// 3. LOGIN BUTTON (REMOVED)
|
||||
// ==========================================
|
||||
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
if (loginBtn) {
|
||||
const currentPath = window.location.pathname;
|
||||
let loginPath = 'sites/login.html';
|
||||
|
||||
if (currentPath.includes('/sites/')) {
|
||||
loginPath = currentPath.includes('login.html') ? 'register.html' : 'login.html';
|
||||
}
|
||||
|
||||
loginBtn.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
window.location.href = loginPath;
|
||||
};
|
||||
}
|
||||
// Login button functionality has been removed
|
||||
|
||||
// ==========================================
|
||||
// 4. FORM SUBMISSION
|
||||
|
||||
1
Profice WebSite/scripts/script.min.js
vendored
Normal file
1
Profice WebSite/scripts/script.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
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();});}});
|
||||
@@ -1,83 +1,170 @@
|
||||
// scroll-header.js
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Scroll threshold to trigger the shrink effect
|
||||
// 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 isTransitioning = false;
|
||||
let lastScrollY = 0;
|
||||
let scrollDirection = 'down';
|
||||
let rafId = null;
|
||||
let lastUpdateTime = 0;
|
||||
let scrollVelocity = 0;
|
||||
let lastScrollTime = 0;
|
||||
|
||||
function updateHeaderState(scrolled) {
|
||||
if (isTransitioning) return;
|
||||
|
||||
if (scrolled && !isScrolled) {
|
||||
isTransitioning = true;
|
||||
topBanner.classList.add('scrolled');
|
||||
isScrolled = true;
|
||||
|
||||
if (slideMenu) {
|
||||
slideMenu.style.top = '80px';
|
||||
}
|
||||
|
||||
// Reset transition flag after animation completes
|
||||
setTimeout(() => {
|
||||
isTransitioning = false;
|
||||
}, 250);
|
||||
|
||||
} else if (!scrolled && isScrolled) {
|
||||
isTransitioning = true;
|
||||
topBanner.classList.remove('scrolled');
|
||||
isScrolled = false;
|
||||
|
||||
if (slideMenu) {
|
||||
slideMenu.style.top = '110px';
|
||||
}
|
||||
|
||||
// Reset transition flag after animation completes
|
||||
setTimeout(() => {
|
||||
isTransitioning = false;
|
||||
}, 250);
|
||||
// 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;
|
||||
}
|
||||
|
||||
function handleScroll() {
|
||||
// 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
|
||||
if (currentScrollY > lastScrollY) {
|
||||
scrollDirection = 'down';
|
||||
} else if (currentScrollY < lastScrollY) {
|
||||
scrollDirection = 'up';
|
||||
// Detect scroll direction with hysteresis for stability
|
||||
const scrollDelta = currentScrollY - lastScrollY;
|
||||
if (Math.abs(scrollDelta) > 1) {
|
||||
scrollDirection = scrollDelta > 0 ? 'down' : 'up';
|
||||
}
|
||||
|
||||
// Only update when crossing threshold in the right direction
|
||||
if (scrollDirection === 'down' && currentScrollY > scrollThreshold && !isScrolled) {
|
||||
updateHeaderState(true);
|
||||
} else if (scrollDirection === 'up' && currentScrollY <= scrollThreshold && isScrolled) {
|
||||
updateHeaderState(false);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Use requestAnimationFrame for smooth scroll handling
|
||||
let ticking = false;
|
||||
function requestTick() {
|
||||
if (!ticking) {
|
||||
requestAnimationFrame(handleScroll);
|
||||
ticking = true;
|
||||
setTimeout(() => { ticking = false; }, 16); // ~60fps
|
||||
// Optimized scroll listener with passive event
|
||||
function onScroll() {
|
||||
if (!rafId) {
|
||||
rafId = requestAnimationFrame(handleScroll);
|
||||
}
|
||||
}
|
||||
|
||||
// Optimized scroll event listener
|
||||
window.addEventListener('scroll', requestTick, { passive: true, capture: false });
|
||||
// Add scroll listener with maximum performance
|
||||
window.addEventListener('scroll', onScroll, {
|
||||
passive: true,
|
||||
capture: false
|
||||
});
|
||||
|
||||
// Initial check
|
||||
handleScroll();
|
||||
// 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'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
1
Profice WebSite/scripts/scroll-header.min.js
vendored
Normal file
1
Profice WebSite/scripts/scroll-header.min.js
vendored
Normal file
@@ -0,0 +1 @@
|
||||
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'});}});});});
|
||||
595
Profice WebSite/scripts/tech-onepager.js
Normal file
595
Profice WebSite/scripts/tech-onepager.js
Normal file
@@ -0,0 +1,595 @@
|
||||
/**
|
||||
* 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');
|
||||
});
|
||||
1
Profice WebSite/scripts/tech-onepager.min.js
vendored
Normal file
1
Profice WebSite/scripts/tech-onepager.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user