765
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* Hexagonal Magnetism Background
|
||||
* Rigid hexagon grid with mouse-based magnetism effect
|
||||
* Hexagons move as whole units - no vertex deformation
|
||||
* Hexagonal Magnetism Background - OPTIMIZED
|
||||
* Only renders visible hexagons, uses viewport culling
|
||||
*/
|
||||
|
||||
(function() {
|
||||
@@ -14,210 +13,128 @@
|
||||
maxLineWidth: 2.5,
|
||||
baseOpacity: 0.15,
|
||||
maxOpacity: 0.6,
|
||||
magnetRadius: 350, // Larger radius for 16:9/16:10 monitors
|
||||
maxDisplacement: 15,
|
||||
returnSpeed: 0.35, // Faster return to reset colors quickly
|
||||
// Base color (neutral gray)
|
||||
magnetRadius: 280,
|
||||
maxDisplacement: 12,
|
||||
returnSpeed: 0.4,
|
||||
// Pre-calculated colors
|
||||
baseR: 119, baseG: 119, baseB: 100,
|
||||
// Colors for proximity effect (teal to orange)
|
||||
tealR: 38, tealG: 166, tealB: 154,
|
||||
orangeR: 245, orangeG: 124, orangeB: 0
|
||||
};
|
||||
|
||||
// Pre-calculate hex geometry
|
||||
const SQRT3 = Math.sqrt(3);
|
||||
const horizSpacing = CONFIG.hexSize * SQRT3;
|
||||
const vertSpacing = CONFIG.hexSize * 1.5;
|
||||
|
||||
// Pre-calculate hex vertices angles (pointy-top)
|
||||
const HEX_ANGLES = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const angle = (Math.PI / 3) * i - Math.PI / 2;
|
||||
HEX_ANGLES.push({ cos: Math.cos(angle), sin: Math.sin(angle) });
|
||||
}
|
||||
|
||||
// State
|
||||
let canvas, ctx;
|
||||
let hexagons = [];
|
||||
let mouse = { x: -1000, y: -1000 };
|
||||
let scrollY = 0;
|
||||
let animationId;
|
||||
let isInitialized = false;
|
||||
let canvasWidth = 0;
|
||||
let canvasHeight = 0;
|
||||
|
||||
// Active hexagons (only those being animated)
|
||||
let activeHexagons = new Map();
|
||||
|
||||
// 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 && mouseX > -500 && mouseY > -500) {
|
||||
// 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 - use faster speed for reset
|
||||
const resetSpeed = CONFIG.returnSpeed * 1.5;
|
||||
this.currentX += (this.originX - this.currentX) * resetSpeed;
|
||||
this.currentY += (this.originY - this.currentY) * resetSpeed;
|
||||
|
||||
// Fade back to default - faster color reset
|
||||
this.opacity += (CONFIG.baseOpacity - this.opacity) * resetSpeed;
|
||||
this.lineWidth += (CONFIG.lineWidth - this.lineWidth) * resetSpeed;
|
||||
this.colorInfluence += (0 - this.colorInfluence) * resetSpeed;
|
||||
|
||||
// Force reset if very close to default
|
||||
if (this.colorInfluence < 0.005) this.colorInfluence = 0;
|
||||
if (Math.abs(this.opacity - CONFIG.baseOpacity) < 0.005) this.opacity = CONFIG.baseOpacity;
|
||||
if (Math.abs(this.lineWidth - CONFIG.lineWidth) < 0.01) this.lineWidth = CONFIG.lineWidth;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
// Smooth color interpolation: gray -> teal -> orange based on proximity
|
||||
let r, g, b;
|
||||
if (this.colorInfluence < 0.01) {
|
||||
// Base gray color
|
||||
r = CONFIG.baseR;
|
||||
g = CONFIG.baseG;
|
||||
b = CONFIG.baseB;
|
||||
} else if (this.colorInfluence < 0.6) {
|
||||
// Gray to teal (smooth transition)
|
||||
const t = this.colorInfluence / 0.6;
|
||||
r = Math.round(CONFIG.baseR + (CONFIG.tealR - CONFIG.baseR) * t);
|
||||
g = Math.round(CONFIG.baseG + (CONFIG.tealG - CONFIG.baseG) * t);
|
||||
b = Math.round(CONFIG.baseB + (CONFIG.tealB - CONFIG.baseB) * t);
|
||||
} else {
|
||||
// Teal to orange (only very close to cursor)
|
||||
const t = (this.colorInfluence - 0.6) / 0.4;
|
||||
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})`;
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// Easing function for smooth animations
|
||||
// Easing function
|
||||
function easeOutCubic(t) {
|
||||
return 1 - Math.pow(1 - t, 3);
|
||||
return 1 - (1 - t) * (1 - t) * (1 - t);
|
||||
}
|
||||
|
||||
// Initialize canvas
|
||||
// Get hex key from grid position
|
||||
function getHexKey(col, row) {
|
||||
return `${col},${row}`;
|
||||
}
|
||||
|
||||
// Get hex origin from grid position
|
||||
function getHexOrigin(col, row) {
|
||||
const xOffset = (row % 2 === 1) ? horizSpacing / 2 : 0;
|
||||
return {
|
||||
x: col * horizSpacing + xOffset,
|
||||
y: row * vertSpacing
|
||||
};
|
||||
}
|
||||
|
||||
// Draw a single hexagon
|
||||
function drawHex(x, y, opacity, lineWidth, colorInfluence) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
x + CONFIG.hexSize * HEX_ANGLES[0].cos,
|
||||
y + CONFIG.hexSize * HEX_ANGLES[0].sin
|
||||
);
|
||||
for (let i = 1; i < 6; i++) {
|
||||
ctx.lineTo(
|
||||
x + CONFIG.hexSize * HEX_ANGLES[i].cos,
|
||||
y + CONFIG.hexSize * HEX_ANGLES[i].sin
|
||||
);
|
||||
}
|
||||
ctx.closePath();
|
||||
|
||||
// Color calculation
|
||||
let r, g, b;
|
||||
if (colorInfluence < 0.01) {
|
||||
r = CONFIG.baseR; g = CONFIG.baseG; b = CONFIG.baseB;
|
||||
} else if (colorInfluence < 0.6) {
|
||||
const t = colorInfluence / 0.6;
|
||||
r = CONFIG.baseR + (CONFIG.tealR - CONFIG.baseR) * t | 0;
|
||||
g = CONFIG.baseG + (CONFIG.tealG - CONFIG.baseG) * t | 0;
|
||||
b = CONFIG.baseB + (CONFIG.tealB - CONFIG.baseB) * t | 0;
|
||||
} else {
|
||||
const t = (colorInfluence - 0.6) / 0.4;
|
||||
r = CONFIG.tealR + (CONFIG.orangeR - CONFIG.tealR) * t | 0;
|
||||
g = CONFIG.tealG + (CONFIG.orangeG - CONFIG.tealG) * t | 0;
|
||||
b = CONFIG.tealB + (CONFIG.orangeB - CONFIG.tealB) * t | 0;
|
||||
}
|
||||
|
||||
ctx.strokeStyle = `rgba(${r},${g},${b},${opacity})`;
|
||||
ctx.lineWidth = lineWidth;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// Initialize
|
||||
function init() {
|
||||
canvas = document.getElementById('hexCanvas');
|
||||
if (!canvas) {
|
||||
console.warn('hexCanvas element not found');
|
||||
return;
|
||||
}
|
||||
if (!canvas) return;
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
ctx = canvas.getContext('2d', { alpha: true });
|
||||
resize();
|
||||
generateHexagons();
|
||||
|
||||
// Event listeners
|
||||
window.addEventListener('resize', debounce(handleResize, 150));
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
window.addEventListener('resize', debounce(resize, 200));
|
||||
document.addEventListener('mousemove', handleMouseMove, { passive: true });
|
||||
document.addEventListener('mouseleave', handleMouseLeave);
|
||||
window.addEventListener('scroll', handleScroll, { passive: true });
|
||||
|
||||
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;
|
||||
|
||||
// Use full screen dimensions
|
||||
const width = Math.max(window.innerWidth, document.documentElement.clientWidth);
|
||||
const height = Math.max(window.innerHeight, document.documentElement.clientHeight);
|
||||
|
||||
const cols = Math.ceil(width / horizSpacing) + 4;
|
||||
const rows = Math.ceil(height / vertSpacing) + 4;
|
||||
|
||||
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;
|
||||
// Use full document dimensions to cover entire scrollable page
|
||||
const width = Math.max(window.innerWidth, document.documentElement.clientWidth);
|
||||
const height = Math.max(window.innerHeight, document.documentElement.clientHeight);
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2); // Cap DPR at 2
|
||||
canvasWidth = window.innerWidth;
|
||||
canvasHeight = window.innerHeight;
|
||||
|
||||
canvas.width = width * dpr;
|
||||
canvas.height = height * dpr;
|
||||
canvas.style.width = width + 'px';
|
||||
canvas.style.height = height + 'px';
|
||||
canvas.width = canvasWidth * dpr;
|
||||
canvas.height = canvasHeight * dpr;
|
||||
canvas.style.width = canvasWidth + 'px';
|
||||
canvas.style.height = canvasHeight + 'px';
|
||||
canvas.style.position = 'fixed';
|
||||
canvas.style.top = '0';
|
||||
canvas.style.left = '0';
|
||||
|
||||
// Reset transform and apply new scale
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
||||
ctx.scale(dpr, dpr);
|
||||
}
|
||||
|
||||
// Mouse handlers
|
||||
function handleMouseMove(e) {
|
||||
mouse.x = e.clientX;
|
||||
mouse.y = e.clientY;
|
||||
@@ -228,74 +145,134 @@
|
||||
mouse.y = -1000;
|
||||
}
|
||||
|
||||
// Animation loop
|
||||
function handleScroll() {
|
||||
scrollY = window.scrollY;
|
||||
}
|
||||
|
||||
// Main animation loop - optimized
|
||||
function animate() {
|
||||
if (!isInitialized) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
const updateRadius = CONFIG.magnetRadius + CONFIG.hexSize * 2;
|
||||
// Calculate visible grid range (viewport only)
|
||||
const viewTop = scrollY - CONFIG.hexSize;
|
||||
const viewBottom = scrollY + canvasHeight + CONFIG.hexSize;
|
||||
const viewLeft = -CONFIG.hexSize;
|
||||
const viewRight = canvasWidth + CONFIG.hexSize;
|
||||
|
||||
for (let i = 0; i < hexagons.length; i++) {
|
||||
const hex = hexagons[i];
|
||||
const startRow = Math.floor(viewTop / vertSpacing) - 1;
|
||||
const endRow = Math.ceil(viewBottom / vertSpacing) + 1;
|
||||
const startCol = Math.floor(viewLeft / horizSpacing) - 1;
|
||||
const endCol = Math.ceil(viewRight / horizSpacing) + 1;
|
||||
|
||||
// Check if hexagon needs updating (near mouse or returning to origin)
|
||||
// Mouse position in world coordinates
|
||||
const mouseWorldY = mouse.y + scrollY;
|
||||
const magnetRadiusSq = CONFIG.magnetRadius * CONFIG.magnetRadius;
|
||||
|
||||
// Update active hexagons
|
||||
for (const [key, hex] of activeHexagons) {
|
||||
const dx = mouse.x - hex.originX;
|
||||
const dy = mouse.y - hex.originY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Check if position OR color needs to return to default
|
||||
const isReturning = Math.abs(hex.currentX - hex.originX) > 0.05 ||
|
||||
Math.abs(hex.currentY - hex.originY) > 0.05 ||
|
||||
hex.colorInfluence > 0.001 ||
|
||||
hex.opacity > CONFIG.baseOpacity + 0.001;
|
||||
const dy = mouseWorldY - hex.originY;
|
||||
const distSq = dx * dx + dy * dy;
|
||||
|
||||
if (distance < updateRadius || isReturning) {
|
||||
hex.update(mouse.x, mouse.y);
|
||||
if (distSq < magnetRadiusSq && mouse.x > -500) {
|
||||
const distance = Math.sqrt(distSq);
|
||||
const influence = 1 - (distance / CONFIG.magnetRadius);
|
||||
const easedInfluence = easeOutCubic(influence);
|
||||
const angle = Math.atan2(dy, dx);
|
||||
const displacement = CONFIG.maxDisplacement * easedInfluence;
|
||||
|
||||
hex.currentX += (hex.originX + Math.cos(angle) * displacement - hex.currentX) * 0.15;
|
||||
hex.currentY += (hex.originY + Math.sin(angle) * displacement - hex.currentY) * 0.15;
|
||||
hex.opacity = CONFIG.baseOpacity + (CONFIG.maxOpacity - CONFIG.baseOpacity) * easedInfluence;
|
||||
hex.lineWidth = CONFIG.lineWidth + (CONFIG.maxLineWidth - CONFIG.lineWidth) * easedInfluence;
|
||||
hex.colorInfluence = easedInfluence;
|
||||
} else {
|
||||
// Return to origin
|
||||
hex.currentX += (hex.originX - hex.currentX) * CONFIG.returnSpeed;
|
||||
hex.currentY += (hex.originY - hex.currentY) * CONFIG.returnSpeed;
|
||||
hex.opacity += (CONFIG.baseOpacity - hex.opacity) * CONFIG.returnSpeed;
|
||||
hex.lineWidth += (CONFIG.lineWidth - hex.lineWidth) * CONFIG.returnSpeed;
|
||||
hex.colorInfluence *= (1 - CONFIG.returnSpeed);
|
||||
|
||||
// Remove if fully reset
|
||||
if (hex.colorInfluence < 0.001 &&
|
||||
Math.abs(hex.currentX - hex.originX) < 0.1 &&
|
||||
Math.abs(hex.currentY - hex.originY) < 0.1) {
|
||||
activeHexagons.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hex.draw(ctx);
|
||||
// Draw visible hexagons
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
for (let col = startCol; col <= endCol; col++) {
|
||||
const origin = getHexOrigin(col, row);
|
||||
const screenY = origin.y - scrollY;
|
||||
|
||||
// Skip if outside viewport
|
||||
if (screenY < -CONFIG.hexSize || screenY > canvasHeight + CONFIG.hexSize) continue;
|
||||
|
||||
const key = getHexKey(col, row);
|
||||
const activeHex = activeHexagons.get(key);
|
||||
|
||||
if (activeHex) {
|
||||
// Draw animated hex
|
||||
drawHex(
|
||||
activeHex.currentX,
|
||||
activeHex.currentY - scrollY,
|
||||
activeHex.opacity,
|
||||
activeHex.lineWidth,
|
||||
activeHex.colorInfluence
|
||||
);
|
||||
} else {
|
||||
// Check if should become active
|
||||
const dx = mouse.x - origin.x;
|
||||
const dy = mouseWorldY - origin.y;
|
||||
const distSq = dx * dx + dy * dy;
|
||||
|
||||
if (distSq < magnetRadiusSq && mouse.x > -500) {
|
||||
// Add to active hexagons
|
||||
activeHexagons.set(key, {
|
||||
originX: origin.x,
|
||||
originY: origin.y,
|
||||
currentX: origin.x,
|
||||
currentY: origin.y,
|
||||
opacity: CONFIG.baseOpacity,
|
||||
lineWidth: CONFIG.lineWidth,
|
||||
colorInfluence: 0
|
||||
});
|
||||
}
|
||||
|
||||
// Draw static hex
|
||||
drawHex(origin.x, screenY, CONFIG.baseOpacity, CONFIG.lineWidth, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animationId = requestAnimationFrame(animate);
|
||||
}
|
||||
|
||||
// Debounce utility
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
return function(...args) {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
timeout = setTimeout(() => func(...args), wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
function destroy() {
|
||||
if (animationId) {
|
||||
cancelAnimationFrame(animationId);
|
||||
}
|
||||
window.removeEventListener('resize', handleResize);
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseleave', handleMouseLeave);
|
||||
if (animationId) cancelAnimationFrame(animationId);
|
||||
isInitialized = false;
|
||||
activeHexagons.clear();
|
||||
}
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
window.HexBackground = { init, destroy, config: CONFIG };
|
||||
})();
|
||||
|
||||
@@ -18,6 +18,8 @@ body {
|
||||
background-color: var(--primary-light);
|
||||
color: var(--primary-dark);
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Hexagonal Canvas Background */
|
||||
@@ -56,12 +58,8 @@ body {
|
||||
background-color 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
|
||||
/* Performance optimizations */
|
||||
will-change: height, padding, transform;
|
||||
transform: translateZ(0);
|
||||
backface-visibility: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
contain: layout style paint;
|
||||
|
||||
/* Prevent sub-pixel rendering issues */
|
||||
box-shadow: 0 0 0 rgba(0,0,0,0);
|
||||
@@ -128,8 +126,7 @@ body {
|
||||
.logo {
|
||||
height: 50px;
|
||||
width: auto;
|
||||
transition: height 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
will-change: height;
|
||||
transition: height 0.3s ease-out;
|
||||
}
|
||||
|
||||
.top-banner.scrolled .logo {
|
||||
@@ -149,7 +146,6 @@ body {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
will-change: width, height;
|
||||
}
|
||||
|
||||
.top-banner.scrolled .menu-toggle,
|
||||
@@ -166,7 +162,6 @@ body {
|
||||
border-radius: 25px;
|
||||
transition: font-size 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94),
|
||||
padding 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
will-change: font-size, padding;
|
||||
}
|
||||
|
||||
.top-banner.scrolled .opening-hours {
|
||||
@@ -354,7 +349,6 @@ body {
|
||||
padding: 10px 20px;
|
||||
border-radius: 25px;
|
||||
transition: font-size 0.25s cubic-bezier(0.4, 0, 0.2, 1), padding 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
will-change: font-size, padding;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
@@ -371,7 +365,6 @@ body {
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
transition: background 0.3s ease, color 0.3s ease, width 0.25s cubic-bezier(0.4, 0, 0.2, 1), height 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
will-change: width, height;
|
||||
}
|
||||
|
||||
#cursorToggle {
|
||||
@@ -387,7 +380,6 @@ body {
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1), height 0.25s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease, border-color 0.3s ease;
|
||||
will-change: width, height;
|
||||
}
|
||||
|
||||
.cursor-icon {
|
||||
|
||||
@@ -2105,6 +2105,96 @@
|
||||
}
|
||||
|
||||
/* ===== RESPONSIVE DESIGN ===== */
|
||||
|
||||
/* Ultra-wide monitors (16:9, 16:10 - 1920px+) */
|
||||
@media (min-width: 1920px) {
|
||||
.hero-container {
|
||||
max-width: 1600px;
|
||||
gap: 100px;
|
||||
}
|
||||
|
||||
.hero-headline {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.hero-subline,
|
||||
.hero-proof {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.hero-right {
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
.system-graphic {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.node {
|
||||
min-width: 140px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.central-node {
|
||||
min-width: 160px;
|
||||
max-width: 180px;
|
||||
padding: 12px 24px;
|
||||
}
|
||||
|
||||
.section-headline {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.systeme-grid {
|
||||
gap: 50px;
|
||||
max-width: 1800px;
|
||||
}
|
||||
|
||||
.system-card {
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.results-grid {
|
||||
max-width: 1400px;
|
||||
}
|
||||
|
||||
.data-card {
|
||||
padding: 45px;
|
||||
}
|
||||
|
||||
.card-metric {
|
||||
font-size: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra large screens (2560px+) */
|
||||
@media (min-width: 2560px) {
|
||||
.hero-container {
|
||||
max-width: 2000px;
|
||||
gap: 120px;
|
||||
}
|
||||
|
||||
.hero-headline {
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
|
||||
.hero-right {
|
||||
height: 700px;
|
||||
}
|
||||
|
||||
.system-graphic {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1800px;
|
||||
}
|
||||
|
||||
.systeme-grid {
|
||||
max-width: 2200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.hero-container {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user