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">
|
<Badge variant="accent" className="mb-4 md:mb-6">
|
||||||
{t('badge')}
|
{t('badge')}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Heading level={1} className="text-white mb-0">
|
<Heading level={1} variant="white" className="mb-0">
|
||||||
{pageData.frontmatter.title}
|
{pageData.frontmatter.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,7 +139,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Main Content Area */}
|
{/* 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">
|
<div className="max-w-4xl mx-auto">
|
||||||
{/* Excerpt/Lead paragraph if available */}
|
{/* Excerpt/Lead paragraph if available */}
|
||||||
{pageData.frontmatter.excerpt && (
|
{pageData.frontmatter.excerpt && (
|
||||||
@@ -180,7 +180,7 @@ export default async function Page(props: { params: Promise<{ locale: string; sl
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { cookies } from 'next/headers';
|
|||||||
import { TransitionProvider } from '@/components/providers/TransitionProvider';
|
import { TransitionProvider } from '@/components/providers/TransitionProvider';
|
||||||
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
|
import { PageTransitionShutter } from '@/components/providers/PageTransitionShutter';
|
||||||
import { InitialLoader } from '@/components/providers/InitialLoader';
|
import { InitialLoader } from '@/components/providers/InitialLoader';
|
||||||
|
import { CorporateBackground } from '@/components/decorations/CorporateBackground';
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
subsets: ['latin'],
|
subsets: ['latin'],
|
||||||
@@ -175,9 +176,10 @@ export default async function Layout(props: {
|
|||||||
{/* Preload critical hero video */}
|
{/* Preload critical hero video */}
|
||||||
<link rel="preload" as="video" href="/assets/videos/web/hero-bohrung.mp4" type="video/mp4" />
|
<link rel="preload" as="video" href="/assets/videos/web/hero-bohrung.mp4" type="video/mp4" />
|
||||||
</head>
|
</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}>
|
<NextIntlClientProvider messages={clientMessages} locale={safeLocale}>
|
||||||
<TransitionProvider>
|
<TransitionProvider>
|
||||||
|
<CorporateBackground />
|
||||||
<InitialLoader shouldShowLoader={!hasSeenLoader} />
|
<InitialLoader shouldShowLoader={!hasSeenLoader} />
|
||||||
<PageTransitionShutter />
|
<PageTransitionShutter />
|
||||||
<SkipLink />
|
<SkipLink />
|
||||||
|
|||||||
@@ -15,6 +15,17 @@
|
|||||||
.hide-scrollbar::-webkit-scrollbar {
|
.hide-scrollbar::-webkit-scrollbar {
|
||||||
display: none; /* Chrome, Safari and Opera */
|
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 */
|
/* Base Styles */
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { useState, useEffect } from 'react';
|
|||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
import { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';
|
||||||
|
|
||||||
|
import { Container } from '@/components/ui/Container';
|
||||||
|
|
||||||
interface CompetenceBentoGridProps {
|
interface CompetenceBentoGridProps {
|
||||||
badge?: string;
|
badge?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -58,7 +60,7 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
|
|||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
return (
|
return (
|
||||||
<section className="py-24 bg-neutral text-neutral-dark overflow-hidden relative">
|
<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="flex flex-col md:flex-row justify-between items-end mb-12">
|
||||||
<div className="max-w-2xl">
|
<div className="max-w-2xl">
|
||||||
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
|
<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]">
|
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[220px]">
|
||||||
{/* Simplified static grid */}
|
{/* Simplified static grid */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="py-24 bg-neutral text-neutral-dark relative overflow-hidden">
|
<section className="py-24 bg-neutral text-neutral-dark relative overflow-hidden">
|
||||||
<div className="container relative z-10 px-4">
|
<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">
|
<div className="flex flex-col md:flex-row justify-between items-end mb-12 px-4 md:px-0">
|
||||||
<div className="max-w-2xl">
|
<div className="max-w-2xl">
|
||||||
<h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>
|
<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>
|
<h3 className="font-heading text-4xl md:text-5xl font-extrabold text-neutral-dark mb-4">{title}</h3>
|
||||||
</div>
|
</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}
|
{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>
|
<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>
|
</Button>
|
||||||
@@ -166,7 +168,7 @@ export function CompetenceBentoGrid(props: CompetenceBentoGridProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export function HeroVideo(props: HeroVideoProps) {
|
|||||||
|
|
||||||
{videoUrl && (
|
{videoUrl && (
|
||||||
<video
|
<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"
|
className="absolute inset-0 w-full h-full object-cover z-1 pointer-events-none filter contrast-125 saturate-110 brightness-90"
|
||||||
src={videoUrl}
|
src={videoUrl}
|
||||||
autoPlay
|
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
|
<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
|
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-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'
|
: 'rounded-none bg-transparent py-6 px-4 md:px-8 border border-transparent'
|
||||||
|
|||||||
Reference in New Issue
Block a user