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
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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
205
components/decorations/CorporateBackground.tsx
Normal file
205
components/decorations/CorporateBackground.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user