2
This commit is contained in:
@@ -53,6 +53,11 @@ define('WEBHOOK_TEST', 'https://n8n.profice.de/webhook-test/d94ef798-3f43-46dd-8
|
||||
define('WEBHOOK_PROD', 'https://n8n.profice.de/webhook/d94ef798-3f43-46dd-8207-1e335e64518f');
|
||||
define('WEBHOOK_URL', USE_PRODUCTION ? WEBHOOK_PROD : WEBHOOK_TEST);
|
||||
|
||||
// KI Chat Webhook
|
||||
define('KI_CHAT_WEBHOOK_TEST', 'https://n8n.profice.de/webhook-test/8a25bce2-ff83-4676-a3a2-a0e1174fcffe');
|
||||
define('KI_CHAT_WEBHOOK_PROD', 'https://n8n.profice.de/webhook/8a25bce2-ff83-4676-a3a2-a0e1174fcffe');
|
||||
define('KI_CHAT_WEBHOOK_URL', USE_PRODUCTION ? KI_CHAT_WEBHOOK_PROD : KI_CHAT_WEBHOOK_TEST);
|
||||
|
||||
// Google Analytics
|
||||
define('GA_MEASUREMENT_ID', 'G-XXXXXXXXXX'); // Replace with your actual ID
|
||||
define('GA_API_SECRET', ''); // For server-side tracking
|
||||
|
||||
277
Profice WebSite/scripts/hex-background.js
Normal file
277
Profice WebSite/scripts/hex-background.js
Normal file
@@ -0,0 +1,277 @@
|
||||
/**
|
||||
* Hexagonal Magnetism Background
|
||||
* Rigid hexagon grid with mouse-based magnetism effect
|
||||
* Hexagons move as whole units - no vertex deformation
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Configuration
|
||||
const CONFIG = {
|
||||
hexSize: 30,
|
||||
lineWidth: 1,
|
||||
maxLineWidth: 2.5,
|
||||
baseOpacity: 0.2,
|
||||
maxOpacity: 0.7,
|
||||
magnetRadius: 180,
|
||||
maxDisplacement: 12,
|
||||
returnSpeed: 0.1,
|
||||
// Colors (teal to orange gradient based on proximity)
|
||||
tealR: 38, tealG: 166, tealB: 154,
|
||||
orangeR: 245, orangeG: 124, orangeB: 0
|
||||
};
|
||||
|
||||
// State
|
||||
let canvas, ctx;
|
||||
let hexagons = [];
|
||||
let mouse = { x: -1000, y: -1000 };
|
||||
let animationId;
|
||||
let isInitialized = false;
|
||||
|
||||
// Hexagon class - rigid shape, no vertex warping
|
||||
class Hexagon {
|
||||
constructor(originX, originY, size) {
|
||||
this.originX = originX;
|
||||
this.originY = originY;
|
||||
this.currentX = originX;
|
||||
this.currentY = originY;
|
||||
this.size = size;
|
||||
this.opacity = CONFIG.baseOpacity;
|
||||
this.lineWidth = CONFIG.lineWidth;
|
||||
this.colorInfluence = 0;
|
||||
}
|
||||
|
||||
// Calculate vertices relative to current center position
|
||||
getVertices() {
|
||||
const vertices = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
// Pointy-top hexagon: first vertex at top
|
||||
const angle = (Math.PI / 3) * i - Math.PI / 2;
|
||||
vertices.push({
|
||||
x: this.currentX + this.size * Math.cos(angle),
|
||||
y: this.currentY + this.size * Math.sin(angle)
|
||||
});
|
||||
}
|
||||
return vertices;
|
||||
}
|
||||
|
||||
update(mouseX, mouseY) {
|
||||
const dx = mouseX - this.originX;
|
||||
const dy = mouseY - this.originY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < CONFIG.magnetRadius && distance > 0) {
|
||||
// Calculate influence (stronger when closer)
|
||||
const influence = 1 - (distance / CONFIG.magnetRadius);
|
||||
const easedInfluence = easeOutCubic(influence);
|
||||
|
||||
// Calculate displacement toward mouse (limited)
|
||||
const angle = Math.atan2(dy, dx);
|
||||
const displacement = CONFIG.maxDisplacement * easedInfluence;
|
||||
|
||||
// Target position (pulled toward mouse)
|
||||
const targetX = this.originX + Math.cos(angle) * displacement;
|
||||
const targetY = this.originY + Math.sin(angle) * displacement;
|
||||
|
||||
// Smooth interpolation to target
|
||||
this.currentX += (targetX - this.currentX) * 0.15;
|
||||
this.currentY += (targetY - this.currentY) * 0.15;
|
||||
|
||||
// Update visual properties
|
||||
this.opacity = CONFIG.baseOpacity + (CONFIG.maxOpacity - CONFIG.baseOpacity) * easedInfluence;
|
||||
this.lineWidth = CONFIG.lineWidth + (CONFIG.maxLineWidth - CONFIG.lineWidth) * easedInfluence;
|
||||
this.colorInfluence = easedInfluence;
|
||||
} else {
|
||||
// Return to origin with spring effect
|
||||
this.currentX += (this.originX - this.currentX) * CONFIG.returnSpeed;
|
||||
this.currentY += (this.originY - this.currentY) * CONFIG.returnSpeed;
|
||||
|
||||
// Fade back to default
|
||||
this.opacity += (CONFIG.baseOpacity - this.opacity) * CONFIG.returnSpeed;
|
||||
this.lineWidth += (CONFIG.lineWidth - this.lineWidth) * CONFIG.returnSpeed;
|
||||
this.colorInfluence += (0 - this.colorInfluence) * CONFIG.returnSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
const vertices = this.getVertices();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vertices[0].x, vertices[0].y);
|
||||
for (let i = 1; i < vertices.length; i++) {
|
||||
ctx.lineTo(vertices[i].x, vertices[i].y);
|
||||
}
|
||||
ctx.closePath();
|
||||
|
||||
// Interpolate color: base gray -> teal -> orange
|
||||
if (this.colorInfluence > 0.01) {
|
||||
let r, g, b;
|
||||
if (this.colorInfluence < 0.5) {
|
||||
// Gray to teal
|
||||
const t = this.colorInfluence * 2;
|
||||
r = Math.round(119 + (CONFIG.tealR - 119) * t);
|
||||
g = Math.round(119 + (CONFIG.tealG - 119) * t);
|
||||
b = Math.round(100 + (CONFIG.tealB - 100) * t);
|
||||
} else {
|
||||
// Teal to orange
|
||||
const t = (this.colorInfluence - 0.5) * 2;
|
||||
r = Math.round(CONFIG.tealR + (CONFIG.orangeR - CONFIG.tealR) * t);
|
||||
g = Math.round(CONFIG.tealG + (CONFIG.orangeG - CONFIG.tealG) * t);
|
||||
b = Math.round(CONFIG.tealB + (CONFIG.orangeB - CONFIG.tealB) * t);
|
||||
}
|
||||
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${this.opacity})`;
|
||||
} else {
|
||||
ctx.strokeStyle = `rgba(119, 119, 100, ${this.opacity})`;
|
||||
}
|
||||
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// Easing function for smooth animations
|
||||
function easeOutCubic(t) {
|
||||
return 1 - Math.pow(1 - t, 3);
|
||||
}
|
||||
|
||||
// Initialize canvas
|
||||
function init() {
|
||||
canvas = document.getElementById('hexCanvas');
|
||||
if (!canvas) {
|
||||
console.warn('hexCanvas element not found');
|
||||
return;
|
||||
}
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
resize();
|
||||
generateHexagons();
|
||||
|
||||
// Event listeners
|
||||
window.addEventListener('resize', debounce(handleResize, 150));
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseleave', handleMouseLeave);
|
||||
|
||||
isInitialized = true;
|
||||
animate();
|
||||
}
|
||||
|
||||
// Generate hexagon grid with proper pointy-top tessellation
|
||||
function generateHexagons() {
|
||||
hexagons = [];
|
||||
|
||||
// Pointy-top hexagon math:
|
||||
// horizontal spacing = size * sqrt(3)
|
||||
// vertical spacing = size * 1.5
|
||||
// every odd row offset by horizontal_spacing / 2
|
||||
const horizSpacing = CONFIG.hexSize * Math.sqrt(3);
|
||||
const vertSpacing = CONFIG.hexSize * 1.5;
|
||||
|
||||
const cols = Math.ceil(window.innerWidth / horizSpacing) + 3;
|
||||
const rows = Math.ceil(window.innerHeight / vertSpacing) + 3;
|
||||
|
||||
for (let row = -1; row < rows; row++) {
|
||||
for (let col = -1; col < cols; col++) {
|
||||
// Offset every odd row by half horizontal spacing
|
||||
const xOffset = (row % 2 === 1) ? horizSpacing / 2 : 0;
|
||||
const x = col * horizSpacing + xOffset;
|
||||
const y = row * vertSpacing;
|
||||
|
||||
hexagons.push(new Hexagon(x, y, CONFIG.hexSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resize handler
|
||||
function handleResize() {
|
||||
resize();
|
||||
generateHexagons();
|
||||
}
|
||||
|
||||
function resize() {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = window.innerWidth * dpr;
|
||||
canvas.height = window.innerHeight * dpr;
|
||||
canvas.style.width = window.innerWidth + 'px';
|
||||
canvas.style.height = window.innerHeight + 'px';
|
||||
ctx.scale(dpr, dpr);
|
||||
}
|
||||
|
||||
// Mouse handlers
|
||||
function handleMouseMove(e) {
|
||||
mouse.x = e.clientX;
|
||||
mouse.y = e.clientY;
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
mouse.x = -1000;
|
||||
mouse.y = -1000;
|
||||
}
|
||||
|
||||
// Animation loop
|
||||
function animate() {
|
||||
if (!isInitialized) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const updateRadius = CONFIG.magnetRadius + CONFIG.hexSize * 2;
|
||||
|
||||
for (let i = 0; i < hexagons.length; i++) {
|
||||
const hex = hexagons[i];
|
||||
|
||||
// Check if hexagon needs updating (near mouse or returning to origin)
|
||||
const dx = mouse.x - hex.originX;
|
||||
const dy = mouse.y - hex.originY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
const isReturning = Math.abs(hex.currentX - hex.originX) > 0.1 ||
|
||||
Math.abs(hex.currentY - hex.originY) > 0.1;
|
||||
|
||||
if (distance < updateRadius || isReturning) {
|
||||
hex.update(mouse.x, mouse.y);
|
||||
}
|
||||
|
||||
hex.draw(ctx);
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// Debounce utility
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
function destroy() {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
window.removeEventListener('resize', handleResize);
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseleave', handleMouseLeave);
|
||||
isInitialized = false;
|
||||
}
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
// Expose for potential external control
|
||||
window.HexBackground = {
|
||||
init,
|
||||
destroy,
|
||||
config: CONFIG
|
||||
};
|
||||
|
||||
})();
|
||||
Reference in New Issue
Block a user