Compare commits

...

14 Commits

Author SHA1 Message Date
5dc2a9fd37 chore: bump version to 2.2.41
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 37s
Build & Deploy / 🧪 QA (push) Successful in 1m41s
Build & Deploy / 🏗️ Build (push) Successful in 2m55s
Build & Deploy / 🚀 Deploy (push) Successful in 38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m9s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-23 01:04:43 +02:00
57e5431e2a Fix: Remove conflicting blurDataURL from HeroVideo and fix ReferencesSlider syntax error 2026-06-23 01:04:43 +02:00
a13b033b7d chore: bump version to 2.2.40
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 37s
Build & Deploy / 🧪 QA (push) Failing after 1m26s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-23 00:51:34 +02:00
93714d2683 Fix: Add unoptimized to hero poster to fix 4.8s LCP delay and restore optimizeCss 2026-06-23 00:51:34 +02:00
b0fae48e65 chore: bump version to 2.2.39
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 40s
Build & Deploy / 🧪 QA (push) Failing after 1m31s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-06-23 00:49:50 +02:00
60391b5a3f Fix: Replace JS padding logic with flawless pure CSS full-bleed container grid calculation 2026-06-23 00:49:49 +02:00
6d4e080d17 chore: bump version to 2.2.38
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 40s
Build & Deploy / 🧪 QA (push) Successful in 1m43s
Build & Deploy / 🏗️ Build (push) Successful in 3m13s
Build & Deploy / 🚀 Deploy (push) Successful in 37s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m11s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-23 00:27:21 +02:00
2a4c605a77 Fix: Add robust flex spacer for carousel padding, remove optimizeCss which broke LCP, and fix video opacity overlay 2026-06-23 00:27:20 +02:00
da9d1954d6 chore: bump version to 2.2.37
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 37s
Build & Deploy / 🧪 QA (push) Successful in 1m49s
Build & Deploy / 🏗️ Build (push) Successful in 3m4s
Build & Deploy / 🚀 Deploy (push) Successful in 40s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m12s
Build & Deploy / 🔔 Notify (push) Successful in 3s
2026-06-23 00:06:46 +02:00
4fc00098f2 Fix: Perfectly align carousel left padding with text container using JS calculated offset 2026-06-23 00:06:44 +02:00
2df7aece5c chore: bump version to 2.2.36
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 38s
Build & Deploy / 🧪 QA (push) Successful in 1m41s
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
2026-06-22 23:59:14 +02:00
d65be95e95 Fix: Add base64 placeholder for hero video and squash all massive photo assets down to max 1920px 2026-06-22 23:59:09 +02:00
e83d3c0933 2.2.35
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 37s
Build & Deploy / 🧪 QA (push) Successful in 1m39s
Build & Deploy / 🏗️ Build (push) Successful in 3m22s
Build & Deploy / 🚀 Deploy (push) Successful in 39s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m11s
Build & Deploy / 🔔 Notify (push) Successful in 2s
2026-06-22 23:39:30 +02:00
f69d1977ad fix(ui): resolve lighthouse a11y contrast and css blocking issues 2026-06-22 23:39:30 +02:00
82 changed files with 128 additions and 22 deletions

View File

@@ -184,7 +184,6 @@ export default async function Layout(props: {
data-scroll-behavior="smooth"
>
<head>
<link rel="preconnect" href="https://img.infra.mintel.me" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="apple-touch-icon" href="/apple-icon.png" sizes="180x180" />

View File

@@ -22,6 +22,8 @@ interface HeroVideoProps {
badge?: string;
}
import placeholders from '@/lib/placeholders.json';
export function HeroVideo(props: HeroVideoProps) {
const { data } = props;
const title = props.title || data?.title || 'DIE EXPERTEN FÜR KABELNETZBAU';
@@ -84,6 +86,7 @@ export function HeroVideo(props: HeroVideoProps) {
src={posterSrc}
alt={posterAlt}
fill
unoptimized
className="object-cover z-[1] pointer-events-none filter contrast-125 saturate-110 brightness-90"
sizes="100vw"
priority
@@ -93,7 +96,7 @@ export function HeroVideo(props: HeroVideoProps) {
{videoUrl && (
<video
key={`${pathname}-${videoUrl}`}
className="absolute inset-0 w-full h-full object-cover z-[2] pointer-events-none filter contrast-125 saturate-110 brightness-90"
className={`absolute inset-0 w-full h-full object-cover z-[2] pointer-events-none filter contrast-125 saturate-110 brightness-90 transition-opacity duration-1000 ${videoSrcLoaded ? 'opacity-100' : 'opacity-0'}`}
src={videoSrcLoaded ? videoUrl : undefined}
autoPlay
muted

View File

@@ -107,17 +107,27 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
</div>
</div>
<div className="relative z-10">
<div className="container relative z-10">
<div className="absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 md:hidden pointer-events-none" />
<div
ref={containerRef}
onMouseDown={onMouseDown}
onMouseLeave={onMouseLeave}
onMouseUp={onMouseUp}
onMouseMove={onMouseMove}
className={`select-none flex gap-6 overflow-x-auto pb-8 pt-4 px-6 md:px-[max(2rem,calc((100vw_-_1280px)_/_2))] lg:px-[max(2.5rem,calc((100vw_-_1280px)_/_2))] [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
>
{references.map((ref, i) => {
{/* Pure CSS Full-Bleed Alignment Container */}
<div style={{ '--offset': 'calc(50vw - 50%)' } as React.CSSProperties}>
<div
ref={containerRef}
onMouseDown={onMouseDown}
onMouseLeave={onMouseLeave}
onMouseUp={onMouseUp}
onMouseMove={onMouseMove}
style={{
marginLeft: 'calc(-1 * var(--offset))',
marginRight: 'calc(-1 * var(--offset))',
paddingLeft: 'var(--offset)',
paddingRight: 'var(--offset)',
scrollPaddingLeft: 'var(--offset)',
}}
className={`select-none flex overflow-x-auto pb-8 pt-4 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ${isDragging ? 'cursor-grabbing snap-none' : 'cursor-grab snap-x snap-mandatory'}`}
>
{references.map((ref, i) => {
const imgSrc = ref.image
? (typeof ref.image === 'string' ? ref.image : ref.image.url)
: fallbacks[i % fallbacks.length];
@@ -129,7 +139,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, margin: "-50px" }}
transition={{ delay: i * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}
className="flex-shrink-0 w-[320px] md:w-[480px] snap-start group pointer-events-auto"
className="flex-shrink-0 w-[320px] md:w-[480px] snap-start group pointer-events-auto mr-6"
>
<Link
href={`/${locale}/referenzen#${ref.slug}`}
@@ -164,6 +174,7 @@ export function ReferencesSlider(props: ReferencesSliderProps) {
);
})}
</div>
</div>
</div>
<div className="container relative z-10 mt-4 flex justify-center">

View File

@@ -84,19 +84,19 @@ export function ScaleOfImpact() {
{/* Static Data Grid (No jumping text) */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-y-4 gap-x-2 md:gap-12 w-full max-w-5xl mt-4 md:mt-12">
<div className="flex flex-col items-center">
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">200<span className="text-primary">+</span></div>
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">200<span className="text-emerald-400">+</span></div>
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Projects' : 'Projekte'}</div>
</div>
<div className="flex flex-col items-center">
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">974<span className="text-primary text-xl md:text-2xl ml-1">km</span></div>
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">974<span className="text-emerald-400 text-xl md:text-2xl ml-1">km</span></div>
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Cable Laying' : 'Kabelverlegung'}</div>
</div>
<div className="flex flex-col items-center">
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">300<span className="text-primary text-xl md:text-2xl ml-1">km</span></div>
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">300<span className="text-emerald-400 text-xl md:text-2xl ml-1">km</span></div>
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'Open Trenching' : 'Offener Tiefbau'}</div>
</div>
<div className="flex flex-col items-center">
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">160<span className="text-primary text-xl md:text-2xl ml-1">km</span></div>
<div className="font-mono text-3xl md:text-5xl font-black text-white tracking-tighter mb-2">160<span className="text-emerald-400 text-xl md:text-2xl ml-1">km</span></div>
<div className="text-xs font-bold uppercase tracking-widest text-white/50">{locale === 'en' ? 'HDD Drilling' : 'Spülbohrung'}</div>
</div>
</div>

View File

@@ -73,7 +73,7 @@ export function SubCompanyTiles(props: SubCompanyTilesProps) {
{(badge || title) && (
<div className="text-center md:text-left max-w-3xl mx-auto md:mx-0 mb-16">
{badge && <h2 className="text-primary font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>}
{badge && <h2 className="text-emerald-400 font-bold tracking-wider uppercase text-sm mb-3">{badge}</h2>}
{title && <h3 className="font-heading text-3xl md:text-5xl font-extrabold text-white">{title}</h3>}
</div>
)}

View File

@@ -129,7 +129,7 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
{item.icon}
</motion.div>
<motion.span
animate={{ opacity: isActive ? 1 : 0.7, y: isActive ? 0 : 2 }}
animate={{ y: isActive ? 0 : 2 }}
className="text-[9px] font-bold mt-1 tracking-wide"
>
{item.label}
@@ -144,7 +144,7 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
key={item.label}
onClick={() => setIsFlyoutOpen(!isFlyoutOpen)}
className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none w-full focus:outline-none ${
isActive ? 'text-primary' : 'text-neutral-500 hover:text-neutral-900'
isActive ? 'text-primary' : 'text-neutral-600 hover:text-neutral-900'
}`}
style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }}
>
@@ -159,7 +159,7 @@ export function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProp
href={mappedUrl}
onClick={() => setIsFlyoutOpen(false)}
className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none ${
isActive ? 'text-primary' : 'text-neutral-500 hover:text-neutral-900'
isActive ? 'text-primary' : 'text-neutral-600 hover:text-neutral-900'
}`}
style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }}
>

7
lib/placeholders.json Normal file
View File

@@ -0,0 +1,7 @@
{
"/assets/photos/DJI_0048.JPG": "data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAADwAQCdASoKAAcABUB8JYwCdH8AGDNIbaAA92kbyv6vgOVMTIAAAA==",
"/assets/videos/web/hero-bahnkreuzung-poster.jpg": "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoKAAYABUB8JaQAA3AA/vDUoAA=",
"/assets/videos/web/hero-bohrung-poster.jpg": "data:image/webp;base64,UklGRjIAAABXRUJQVlA4ICYAAACwAQCdASoKAAYABUB8JZACdAEOO2gAAN5Rr3co8j7CDYNKNCAAAA==",
"/assets/videos/web/hero-kabelpflug-poster.jpg": "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoKAAYABUB8JaQAA3AA/vDUoAA=",
"/assets/videos/web/hero-messe-poster.jpg": "data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoKAAYABUB8JaQAA3AA/vDUoAA="
}

View File

@@ -15,6 +15,7 @@ const nextConfig = {
pagesBufferLength: 2,
},
experimental: {
optimizeCss: true,
staleTimes: {
dynamic: 0,
static: 30,

View File

@@ -138,7 +138,7 @@
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
},
"version": "2.2.34",
"version": "2.2.41",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",

BIN
public/assets/photos/2025-09-23_12.11.43.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

After

Width:  |  Height:  |  Size: 1007 KiB

BIN
public/assets/photos/2025-09-23_12.12.37.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

After

Width:  |  Height:  |  Size: 777 KiB

BIN
public/assets/photos/DJI_0037.JPG Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 MiB

After

Width:  |  Height:  |  Size: 522 KiB

BIN
public/assets/photos/DJI_0048.JPG Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 MiB

After

Width:  |  Height:  |  Size: 412 KiB

BIN
public/assets/photos/DJI_0243.JPG Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 MiB

After

Width:  |  Height:  |  Size: 488 KiB

BIN
public/assets/photos/DJI_0301.JPG Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 MiB

After

Width:  |  Height:  |  Size: 574 KiB

BIN
public/assets/photos/DSC00373.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

After

Width:  |  Height:  |  Size: 304 KiB

BIN
public/assets/photos/DSC00375.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 304 KiB

BIN
public/assets/photos/DSC00388.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

After

Width:  |  Height:  |  Size: 342 KiB

BIN
public/assets/photos/DSC00400.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

After

Width:  |  Height:  |  Size: 322 KiB

BIN
public/assets/photos/DSC00430.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 MiB

After

Width:  |  Height:  |  Size: 540 KiB

BIN
public/assets/photos/DSC00440.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 358 KiB

BIN
public/assets/photos/DSC00452.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 MiB

After

Width:  |  Height:  |  Size: 618 KiB

BIN
public/assets/photos/DSC02028.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 314 KiB

BIN
public/assets/photos/DSC02057.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

After

Width:  |  Height:  |  Size: 345 KiB

BIN
public/assets/photos/DSC02069.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 MiB

After

Width:  |  Height:  |  Size: 597 KiB

BIN
public/assets/photos/DSC02083.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 MiB

After

Width:  |  Height:  |  Size: 493 KiB

BIN
public/assets/photos/DSC02089.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 MiB

After

Width:  |  Height:  |  Size: 371 KiB

BIN
public/assets/photos/DSC02114.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 240 KiB

BIN
public/assets/photos/DSC02135.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 MiB

After

Width:  |  Height:  |  Size: 325 KiB

BIN
public/assets/photos/DSC02150.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 MiB

After

Width:  |  Height:  |  Size: 629 KiB

BIN
public/assets/photos/DSC02152.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 MiB

After

Width:  |  Height:  |  Size: 659 KiB

BIN
public/assets/photos/DSC02156.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 MiB

After

Width:  |  Height:  |  Size: 664 KiB

BIN
public/assets/photos/DSC02164.jpg Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 MiB

After

Width:  |  Height:  |  Size: 648 KiB

BIN
public/assets/photos/DSCF8687.JPG Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 MiB

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

After

Width:  |  Height:  |  Size: 355 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 336 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 MiB

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 MiB

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

After

Width:  |  Height:  |  Size: 408 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 MiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 MiB

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 MiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 623 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 774 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 601 KiB

BIN
public/assets/photos/Screenshot_2025-09-30_085237.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 MiB

After

Width:  |  Height:  |  Size: 455 KiB

BIN
public/assets/photos/Screenshot_2025-09-30_085339.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

After

Width:  |  Height:  |  Size: 474 KiB

BIN
public/assets/photos/Screenshot_2025-09-30_085736.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 MiB

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 MiB

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 MiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 MiB

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 MiB

After

Width:  |  Height:  |  Size: 390 KiB

View File

@@ -0,0 +1,44 @@
import fs from 'fs';
import path from 'path';
import sharp from 'sharp';
const PUBLIC_DIR = path.join(process.cwd(), 'public');
const IMAGES_TO_PROCESS = [
'/assets/photos/DJI_0048.JPG',
'/assets/videos/web/hero-bahnkreuzung-poster.jpg',
'/assets/videos/web/hero-bohrung-poster.jpg',
'/assets/videos/web/hero-kabelpflug-poster.jpg',
'/assets/videos/web/hero-messe-poster.jpg',
];
async function generatePlaceholders() {
const placeholders: Record<string, string> = {};
for (const imagePath of IMAGES_TO_PROCESS) {
const fullPath = path.join(PUBLIC_DIR, imagePath);
if (!fs.existsSync(fullPath)) {
console.warn(`File not found: ${fullPath}`);
continue;
}
try {
const buffer = await sharp(fullPath)
.resize(10, 10, { fit: 'inside' })
.blur(2) // Adding slight blur
.webp({ quality: 20 })
.toBuffer();
const base64 = `data:image/webp;base64,${buffer.toString('base64')}`;
placeholders[imagePath] = base64;
console.log(`Generated placeholder for ${imagePath}`);
} catch (e) {
console.error(`Failed to generate placeholder for ${imagePath}`, e);
}
}
const outputPath = path.join(process.cwd(), 'lib', 'placeholders.json');
fs.writeFileSync(outputPath, JSON.stringify(placeholders, null, 2));
console.log(`Saved placeholders to ${outputPath}`);
}
generatePlaceholders();

View File

@@ -0,0 +1,41 @@
import fs from 'fs';
import path from 'path';
import sharp from 'sharp';
const PHOTOS_DIR = path.join(process.cwd(), 'public', 'assets', 'photos');
async function optimizeImages() {
const files = fs.readdirSync(PHOTOS_DIR);
let optimized = 0;
for (const file of files) {
if (!file.match(/\.(jpg|jpeg|png)$/i)) continue;
const filePath = path.join(PHOTOS_DIR, file);
const metadata = await sharp(filePath).metadata();
// If image is larger than 1920px wide, or is a massive file
if ((metadata.width && metadata.width > 1920) || fs.statSync(filePath).size > 1024 * 1024) {
console.log(`Optimizing ${file} (Width: ${metadata.width}, Size: ${Math.round(fs.statSync(filePath).size / 1024)}KB)`);
const tmpPath = `${filePath}.tmp`;
try {
await sharp(filePath)
.resize({ width: 1920, withoutEnlargement: true })
.jpeg({ quality: 80, progressive: true })
.toFile(tmpPath);
fs.renameSync(tmpPath, filePath);
console.log(`✅ Optimized ${file} (New Size: ${Math.round(fs.statSync(filePath).size / 1024)}KB)`);
optimized++;
} catch (err) {
console.error(`Failed to optimize ${file}`, err);
if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
}
}
}
console.log(`Finished optimizing ${optimized} images.`);
}
optimizeImages();