Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 531e1a49f8 | |||
| 324cfb0ca3 | |||
| 69d2c42ef7 | |||
| 6fb46278e0 | |||
| 73886406a4 | |||
| b2701d6bcd | |||
| 4f46825fb4 |
@@ -47,14 +47,7 @@ jobs:
|
||||
run: |
|
||||
echo "=== System Disk Usage ==="
|
||||
df -h || true
|
||||
echo "=== Tool Cache Usage ==="
|
||||
du -sh /opt/hostedtoolcache/* || true
|
||||
du -sh /opt/hostedtoolcache/.[!.]* || true
|
||||
echo "Purging old tool caches skipped to prevent runner corruption."
|
||||
echo "=== Host Disk Usage ==="
|
||||
docker run --rm -v /:/host-root alpine df -h || true
|
||||
echo "=== Host Inode Usage ==="
|
||||
docker run --rm -v /:/host-root alpine df -i || true
|
||||
echo "=== Running Host Containers ==="
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Image}}" || true
|
||||
echo "=== Host Docker Info ==="
|
||||
|
||||
@@ -22,6 +22,7 @@ import { DynamicMobileBottomNav as MobileBottomNav } from '@/components/layout/D
|
||||
|
||||
import { DynamicCorporateBackground as CorporateBackground } from '@/components/decorations/DynamicCorporateBackground';
|
||||
import { DynamicPageTransitionShutter as PageTransitionShutter } from '@/components/providers/DynamicPageTransitionShutter';
|
||||
import { DynamicFramerMotion as FramerMotionProvider } from '@/components/providers/DynamicFramerMotion';
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
@@ -190,26 +191,28 @@ export default async function Layout(props: {
|
||||
<body className="relative flex flex-col min-h-screen font-sans antialiased overflow-x-hidden selection:bg-primary/90 selection:text-white" suppressHydrationWarning>
|
||||
<NextIntlClientProvider messages={clientMessages} locale={safeLocale}>
|
||||
<TransitionProvider>
|
||||
<CorporateBackground />
|
||||
<InitialLoader shouldShowLoader={!hasSeenLoader} />
|
||||
<PageTransitionShutter />
|
||||
<SkipLink />
|
||||
<Header navLinks={navLinks} />
|
||||
<MobileBottomNav navLinks={navLinks} currentLocale={safeLocale} />
|
||||
|
||||
<main
|
||||
id="main-content"
|
||||
className="flex-grow overflow-visible"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
<FramerMotionProvider>
|
||||
<CorporateBackground />
|
||||
<InitialLoader shouldShowLoader={!hasSeenLoader} />
|
||||
<PageTransitionShutter />
|
||||
<SkipLink />
|
||||
<Header navLinks={navLinks} />
|
||||
<MobileBottomNav navLinks={navLinks} currentLocale={safeLocale} />
|
||||
|
||||
<main
|
||||
id="main-content"
|
||||
className="flex-grow overflow-visible"
|
||||
tabIndex={-1}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<Footer companyInfo={companyInfo} />
|
||||
<JsonLd />
|
||||
<AnalyticsShell />
|
||||
<Footer companyInfo={companyInfo} />
|
||||
<JsonLd />
|
||||
<AnalyticsShell />
|
||||
|
||||
<FeedbackClientWrapper />
|
||||
<FeedbackClientWrapper />
|
||||
</FramerMotionProvider>
|
||||
</TransitionProvider>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
|
||||
@@ -7,8 +7,7 @@ import { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';
|
||||
import { SITE_URL } from '@/lib/schema';
|
||||
import Image from 'next/image';
|
||||
import { standorteData, standorteLocations } from '@/lib/standorte-data';
|
||||
import nextDynamic from 'next/dynamic';
|
||||
const InteractiveGermanyMap = nextDynamic(() => import('@/components/blocks/InteractiveGermanyMap').then(mod => mod.InteractiveGermanyMap));
|
||||
import { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';
|
||||
|
||||
interface PageProps {
|
||||
params: Promise<{
|
||||
|
||||
@@ -88,34 +88,30 @@
|
||||
}
|
||||
/* trigger rebuild */
|
||||
|
||||
@layer utilities {
|
||||
@keyframes hero-fade-in-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
@keyframes hero-fade-in-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
.animate-hero-fade-in-up {
|
||||
animation: hero-fade-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
@keyframes hero-zoom-out {
|
||||
from {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
@utility animate-hero-fade-in-up {
|
||||
animation: hero-fade-in-up 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
@keyframes hero-zoom-out {
|
||||
from {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.animate-hero-zoom-out {
|
||||
animation: hero-zoom-out 1.5s ease-out forwards;
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@utility animate-hero-zoom-out {
|
||||
animation: hero-zoom-out 1.5s ease-out forwards;
|
||||
}
|
||||
|
||||
@@ -60,26 +60,26 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
|
||||
className={`max-w-4xl ${alignment === 'center' ? 'mx-auto' : ''}`}
|
||||
>
|
||||
{badge && (
|
||||
<div className="animate-hero-fade-in-up" style={{ animationDelay: '0.1s', opacity: 0, animationFillMode: 'forwards' }}>
|
||||
<div className="animate-hero-fade-in-up" style={{ animationDelay: '0.1s', animationFillMode: 'both' }}>
|
||||
<Badge variant="saturated" className="mb-4 md:mb-8 shadow-lg">
|
||||
{badge}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
<div className="animate-hero-fade-in-up" style={{ animationDelay: badge ? '0.25s' : '0.1s', opacity: 0, animationFillMode: 'forwards' }}>
|
||||
<div className="animate-hero-fade-in-up" style={{ animationDelay: badge ? '0.25s' : '0.1s', animationFillMode: 'both' }}>
|
||||
<Heading level={1} size="section" variant="white" align={alignment || 'left'} className="mb-4 md:mb-8">
|
||||
{title}
|
||||
</Heading>
|
||||
</div>
|
||||
{subtitle && (
|
||||
<div className="animate-hero-fade-in-up" style={{ animationDelay: badge ? '0.4s' : '0.25s', opacity: 0, animationFillMode: 'forwards' }}>
|
||||
<div className="animate-hero-fade-in-up" style={{ animationDelay: badge ? '0.4s' : '0.25s', animationFillMode: 'both' }}>
|
||||
<p className={`text-lg md:text-2xl text-white/70 font-medium leading-relaxed max-w-2xl ${alignment === 'center' ? 'mx-auto' : ''}`}>
|
||||
{subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{ctaLabel && ctaHref && (
|
||||
<div className={`mt-8 animate-hero-fade-in-up ${alignment === 'center' ? 'flex flex-wrap justify-center gap-4' : 'flex flex-wrap gap-4'}`} style={{ animationDelay: badge ? '0.55s' : '0.4s', opacity: 0, animationFillMode: 'forwards' }}>
|
||||
<div className={`mt-8 animate-hero-fade-in-up ${alignment === 'center' ? 'flex flex-wrap justify-center gap-4' : 'flex flex-wrap gap-4'}`} style={{ animationDelay: badge ? '0.55s' : '0.4s', animationFillMode: 'both' }}>
|
||||
<Button
|
||||
href={ctaHref}
|
||||
variant="accent"
|
||||
|
||||
@@ -86,12 +86,14 @@ export function HeroVideo(props: HeroVideoProps) {
|
||||
src={posterSrc}
|
||||
alt={posterAlt}
|
||||
fill
|
||||
className="object-cover z-[1] pointer-events-none filter contrast-125 saturate-110 brightness-90"
|
||||
className="object-cover z-[1] pointer-events-none"
|
||||
sizes="100vw"
|
||||
quality={25}
|
||||
priority
|
||||
fetchPriority="high"
|
||||
/>
|
||||
{/* Fast compositing layer to replace the heavy CSS filters on the image */}
|
||||
<div className="absolute inset-0 bg-black/20 z-[1] pointer-events-none mix-blend-multiply" />
|
||||
|
||||
{/* Render video on top if available, but defer src to avoid blocking LCP */}
|
||||
{videoUrl && (
|
||||
|
||||
7
components/providers/DynamicFramerMotion.tsx
Normal file
7
components/providers/DynamicFramerMotion.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
|
||||
export const DynamicFramerMotion = dynamic(
|
||||
() => import('./FramerMotionProvider').then((mod) => mod.FramerMotionProvider),
|
||||
{ ssr: true }
|
||||
);
|
||||
11
components/providers/FramerMotionProvider.tsx
Normal file
11
components/providers/FramerMotionProvider.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
import { LazyMotion } from 'framer-motion';
|
||||
const loadFeatures = () => import('@/lib/framer-features').then(res => res.default);
|
||||
|
||||
export function FramerMotionProvider({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<LazyMotion features={loadFeatures} strict>
|
||||
{children}
|
||||
</LazyMotion>
|
||||
);
|
||||
}
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
|
||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||
import { LazyMotion } from 'framer-motion';
|
||||
|
||||
const loadFeatures = () => import('@/lib/framer-features').then(res => res.default);
|
||||
|
||||
interface TransitionContextType {
|
||||
isTransitioning: boolean;
|
||||
@@ -71,9 +68,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
|
||||
setTransitionMessage={setTransitionMessage}
|
||||
/>
|
||||
</React.Suspense>
|
||||
<LazyMotion features={loadFeatures}>
|
||||
{children}
|
||||
</LazyMotion>
|
||||
{children}
|
||||
</TransitionContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ import { join } from 'path';
|
||||
* Loads the Inter fonts for use in Satori (Next.js OG Image generation).
|
||||
* Since we are using runtime = 'nodejs', we can read them from the filesystem.
|
||||
*/
|
||||
let cachedFonts: any[] | null = null;
|
||||
export async function getOgFonts() {
|
||||
if (cachedFonts) return cachedFonts;
|
||||
const boldFontPath = join(process.cwd(), 'public/fonts/Inter-Bold.woff');
|
||||
const regularFontPath = join(process.cwd(), 'public/fonts/Inter-Regular.woff');
|
||||
|
||||
@@ -28,7 +30,7 @@ export async function getOgFonts() {
|
||||
`[OG] Fonts loaded successfully (${boldFont.byteLength} and ${regularFont.byteLength} bytes)`,
|
||||
);
|
||||
|
||||
return [
|
||||
cachedFonts = [
|
||||
{
|
||||
name: 'Inter',
|
||||
data: boldFont,
|
||||
@@ -42,28 +44,35 @@ export async function getOgFonts() {
|
||||
style: 'normal' as const,
|
||||
},
|
||||
];
|
||||
return cachedFonts;
|
||||
} catch (error) {
|
||||
console.error(`[OG] Failed to load fonts from ${process.cwd()}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
let cachedBackground: string | null = null;
|
||||
export function getOgBackground() {
|
||||
if (cachedBackground) return cachedBackground;
|
||||
try {
|
||||
const bgPath = join(process.cwd(), 'public/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-100.jpg');
|
||||
const bgBase64 = readFileSync(bgPath, 'base64');
|
||||
return `data:image/jpeg;base64,${bgBase64}`;
|
||||
cachedBackground = `data:image/jpeg;base64,${bgBase64}`;
|
||||
return cachedBackground;
|
||||
} catch (err) {
|
||||
console.error('[OG] Failed to load background', err);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
let cachedLogo: string | null = null;
|
||||
export function getOgLogo() {
|
||||
if (cachedLogo) return cachedLogo;
|
||||
try {
|
||||
const logoPath = join(process.cwd(), 'public/assets/logo-white.png');
|
||||
const logoBase64 = readFileSync(logoPath, 'base64');
|
||||
return `data:image/png;base64,${logoBase64}`;
|
||||
cachedLogo = `data:image/png;base64,${logoBase64}`;
|
||||
return cachedLogo;
|
||||
} catch (err) {
|
||||
console.error('[OG] Failed to load logo', err);
|
||||
return undefined;
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
"prepare": "husky",
|
||||
"preinstall": "npx only-allow pnpm"
|
||||
},
|
||||
"version": "2.2.92",
|
||||
"version": "2.4.24",
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
|
||||
Reference in New Issue
Block a user