feat: unified container widths and added magnetic mouse pull to corporate background arcs
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m20s
Build & Deploy / 🏗️ Build (push) Successful in 2m59s
Build & Deploy / 🚀 Deploy (push) Successful in 43s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m0s
Build & Deploy / 🔔 Notify (push) Successful in 1s

This commit is contained in:
2026-05-12 19:08:23 +02:00
parent 55d633a33c
commit 7d6a52e1c6
7 changed files with 232 additions and 11 deletions

View File

@@ -131,7 +131,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
<Badge variant="accent" className="mb-4 md:mb-6">
{t('badge')}
</Badge>
<Heading level={1} className="text-white mb-0">
<Heading level={1} variant="white" className="mb-0">
{pageData.frontmatter.title}
</Heading>
</div>
@@ -139,7 +139,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
</section>
{/* Main Content Area */}
<div className="container mx-auto px-4 py-16 md:py-24">
<Container className="py-16 md:py-24">
<div className="max-w-4xl mx-auto">
{/* Excerpt/Lead paragraph if available */}
{pageData.frontmatter.excerpt && (
@@ -180,7 +180,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
</div>
</div>
</div>
</div>
</Container>
</div>
);
}

View File

@@ -18,6 +18,7 @@ import { cookies } from 'next/headers';
import { TransitionProvider } from '@/components/providers/TransitionProvider';
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
import { InitialLoader } from '@/components/providers/InitialLoader';
import { CorporateBackground } from '@/components/decorations/CorporateBackground';
const inter = Inter({
subsets: ['latin'],
@@ -175,9 +176,10 @@ export default async function Layout(props: {
{/* Preload critical hero video */}
<link rel="preload" as="video" href="/assets/videos/web/hero-bohrung.mp4" type="video/mp4" />
</head>
<body className="flex flex-col min-h-screen font-sans antialiased overflow-x-hidden selection:bg-primary/90 selection:text-white">
<body className="relative flex flex-col min-h-screen font-sans antialiased overflow-x-hidden selection:bg-primary/90 selection:text-white">
<NextIntlClientProvider messages={clientMessages} locale={safeLocale}>
<TransitionProvider>
<CorporateBackground />
<InitialLoader shouldShowLoader={!hasSeenLoader} />
<PageTransitionShutter />
<SkipLink />

View File

@@ -15,6 +15,17 @@
.hide-scrollbar::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
@keyframes bg-pulse-15 {
0%, 90%, 100% { opacity: 0.04; }
95% { opacity: 0.10; filter: brightness(1.2); }
}
@keyframes bg-pulse-20 {
0%, 90%, 100% { opacity: 0.07; }
95% { opacity: 0.14; filter: brightness(1.2); }
}
.animate-bg-pulse-15 { animation: bg-pulse-15 12s infinite ease-in-out; }
.animate-bg-pulse-20 { animation: bg-pulse-20 12s infinite ease-in-out; }
}
/* Base Styles */

View File

@@ -7,6 +7,8 @@ import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/Button';
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
import { Container } from '@/components/ui/Container';
interface CompetenceBentoGridProps {
badge?: string;
title?: string;
@@ -58,7 +60,7 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
if (!isMounted) {
return (
<section className="py-24 bg-neutral text-neutral-dark overflow-hidden relative">
<div className="container">
<Container>
<div className="flex flex-col md:flex-row justify-between items-end mb-12">
<div className="max-w-2xl">
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
@@ -68,20 +70,20 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[220px]">
{/* Simplified static grid */}
</div>
</div>
</Container>
</section>
);
}
return (
<section className="py-24 bg-neutral text-neutral-dark relative overflow-hidden">
<div className="container relative z-10 px-4">
<div className="flex flex-col md:flex-row justify-between items-end mb-12">
<Container className="relative z-10 px-0 md:px-12 lg:px-16">
<div className="flex flex-col md:flex-row justify-between items-end mb-12 px-4 md:px-0">
<div className="max-w-2xl">
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
<h3 className="font-heading text-4xl md:text-5xl font-extrabold text-neutral-dark mb-4">{title}</h3>
</div>
<Button href={ctaHref} variant="ghost" className="mt-6 md:mt-0 bg-primary/5 hover:bg-primary/10">
<Button href={ctaHref} variant="ghost" className="mt-6 md:mt-0 bg-primary/5 hover:bg-primary/10 mx-4 md:mx-0">
{ctaLabel}
<svg xmlns="http://www.w3.org/2000/svg" className="w-5 h-5 ml-2 transform group-hover:translate-x-1 transition-transform" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</Button>
@@ -166,7 +168,7 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
);
})}
</div>
</div>
</Container>
</section>
);
}

View File

@@ -55,6 +55,7 @@ export function HeroVideo(props: HeroVideoProps) {
{videoUrl && (
<video
key={videoUrl}
className="absolute inset-0 w-full h-full object-cover z-1 pointer-events-none filter contrast-125 saturate-110 brightness-90"
src={videoUrl}
autoPlay

View File

@@ -0,0 +1,205 @@
'use client';
import React, { useRef, useEffect } from 'react';
import { motion, useScroll, useTransform, useSpring } from 'framer-motion';
// Reusable SVG component that precisely mimics the overlapping, sweeping arcs of the E-TIB logo.
const LogoArcs = ({ className }: { className?: string }) => (
<svg
className={className}
viewBox="0 0 200 200"
fill="none"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
>
{/* Outer ring */}
<circle cx="100" cy="100" r="90" strokeWidth="1.5" />
{/* Middle thicker ring */}
<circle cx="92" cy="108" r="78" strokeWidth="3.5" />
{/* Inner ring */}
<circle cx="84" cy="116" r="66" strokeWidth="1.5" />
</svg>
);
function MagneticRing({
children,
className,
pullStrength = 30, // How strong the pull is (higher = moves closer to mouse)
pullRadius = 600 // Distance from center at which it starts pulling
}: {
children: React.ReactNode,
className?: string,
pullStrength?: number,
pullRadius?: number
}) {
const ref = useRef<HTMLDivElement>(null);
// Spring physics for extremely smooth, organic movement
const x = useSpring(0, { stiffness: 40, damping: 25, mass: 1 });
const y = useSpring(0, { stiffness: 40, damping: 25, mass: 1 });
useEffect(() => {
// Only run on client with mouse
if (window.matchMedia("(pointer: coarse)").matches) return;
const handleMouseMove = (e: MouseEvent) => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distX = e.clientX - centerX;
const distY = e.clientY - centerY;
const distance = Math.sqrt(distX * Math.pow(distX, 2) + Math.pow(distY, 2)); // Fix: correct pythagoras
// Calculate actual distance correctly
const actualDistance = Math.sqrt(distX * distX + distY * distY);
if (actualDistance < pullRadius) {
// Exponential falloff for natural magnetic feel
const pullFactor = Math.pow(1 - (actualDistance / pullRadius), 2);
// Target offset (fraction of the distance based on strength)
x.set(distX * pullFactor * (pullStrength / 100));
y.set(distY * pullFactor * (pullStrength / 100));
} else {
// Return to origin smoothly
x.set(0);
y.set(0);
}
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, [x, y, pullRadius, pullStrength]);
return (
<motion.div
ref={ref}
style={{ x, y }}
className={className}
>
{children}
</motion.div>
);
}
export function CorporateBackground() {
const { scrollYProgress } = useScroll();
// Stronger parallax offsets for visible movement
const y1 = useTransform(scrollYProgress, [0, 1], [0, -400]);
const y2 = useTransform(scrollYProgress, [0, 1], [0, 500]);
const y3 = useTransform(scrollYProgress, [0, 1], [0, -600]);
const y4 = useTransform(scrollYProgress, [0, 1], [0, 350]);
const y5 = useTransform(scrollYProgress, [0, 1], [0, -500]);
return (
<div className="absolute inset-0 z-[50] overflow-hidden pointer-events-none" aria-hidden="true">
{/* 1. Top Right - Medium, rotating moderately */}
<motion.div
style={{
y: y1,
animationDelay: '0s',
maskImage: 'radial-gradient(circle at center, black 10%, transparent 60%)',
WebkitMaskImage: 'radial-gradient(circle at center, black 10%, transparent 60%)'
}}
className="absolute top-[0%] right-0 translate-x-[40%] w-[400px] h-[400px] md:w-[700px] md:h-[700px] animate-bg-pulse-20"
>
<MagneticRing pullRadius={700} pullStrength={40} className="w-full h-full">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 40, repeat: Infinity, ease: "linear" }}
className="w-full h-full text-primary"
>
<LogoArcs className="w-full h-full" />
</motion.div>
</MagneticRing>
</motion.div>
{/* 2. Quarter way down left - Smaller, rotating fast reverse */}
<motion.div
style={{
y: y2,
animationDelay: '3s',
maskImage: 'radial-gradient(circle at center, black 15%, transparent 65%)',
WebkitMaskImage: 'radial-gradient(circle at center, black 15%, transparent 65%)'
}}
className="absolute top-[20%] left-0 -translate-x-[40%] w-[300px] h-[300px] md:w-[500px] md:h-[500px] animate-bg-pulse-15"
>
<MagneticRing pullRadius={500} pullStrength={50} className="w-full h-full">
<motion.div
animate={{ rotate: -360 }}
transition={{ duration: 30, repeat: Infinity, ease: "linear" }}
className="w-full h-full text-secondary"
>
<LogoArcs className="w-full h-full -rotate-45" />
</motion.div>
</MagneticRing>
</motion.div>
{/* 3. Halfway down right - Medium, rotating slow */}
<motion.div
style={{
y: y3,
animationDelay: '6s',
maskImage: 'radial-gradient(circle at center, black 10%, transparent 60%)',
WebkitMaskImage: 'radial-gradient(circle at center, black 10%, transparent 60%)'
}}
className="absolute top-[45%] right-0 translate-x-[30%] w-[350px] h-[350px] md:w-[600px] md:h-[600px] animate-bg-pulse-15"
>
<MagneticRing pullRadius={600} pullStrength={35} className="w-full h-full">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 60, repeat: Infinity, ease: "linear" }}
className="w-full h-full text-primary"
>
<LogoArcs className="w-full h-full rotate-90" />
</motion.div>
</MagneticRing>
</motion.div>
{/* 4. Bottom left - Larger, rotating moderately */}
<motion.div
style={{
y: y4,
animationDelay: '9s',
maskImage: 'radial-gradient(circle at center, black 5%, transparent 60%)',
WebkitMaskImage: 'radial-gradient(circle at center, black 5%, transparent 60%)'
}}
className="absolute top-[70%] left-0 -translate-x-[40%] w-[500px] h-[500px] md:w-[800px] md:h-[800px] animate-bg-pulse-20"
>
<MagneticRing pullRadius={800} pullStrength={30} className="w-full h-full">
<motion.div
animate={{ rotate: -360 }}
transition={{ duration: 50, repeat: Infinity, ease: "linear" }}
className="w-full h-full text-secondary"
>
<LogoArcs className="w-full h-full rotate-180" />
</motion.div>
</MagneticRing>
</motion.div>
{/* 5. Deep bottom right - Smallest, very fast */}
<motion.div
style={{
y: y5,
animationDelay: '12s',
maskImage: 'radial-gradient(circle at center, black 20%, transparent 70%)',
WebkitMaskImage: 'radial-gradient(circle at center, black 20%, transparent 70%)'
}}
className="absolute top-[88%] right-0 translate-x-[50%] w-[250px] h-[250px] md:w-[400px] md:h-[400px] animate-bg-pulse-15"
>
<MagneticRing pullRadius={400} pullStrength={60} className="w-full h-full">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 25, repeat: Infinity, ease: "linear" }}
className="w-full h-full text-primary"
>
<LogoArcs className="w-full h-full -rotate-12" />
</motion.div>
</MagneticRing>
</motion.div>
</div>
);
}

View File

@@ -71,7 +71,7 @@ export function Header({ navLinks }: HeaderProps) {
}`}
>
<div
className={`relative overflow-hidden w-full max-w-[1400px] mx-auto transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] flex items-center justify-between ${
className={`relative overflow-hidden w-full max-w-7xl mx-auto transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] flex items-center justify-between ${
isSolidMode
? 'rounded-full bg-white/50 backdrop-blur-xl shadow-[0_8px_32px_rgba(0,0,0,0.08)] border border-white/40 py-2.5 px-6 md:px-8'
: 'rounded-none bg-transparent py-6 px-4 md:px-8 border border-transparent'