Compare commits

..

2 Commits

Author SHA1 Message Date
69d2c42ef7 chore: bump version to 2.4.22
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 22s
Build & Deploy / 🧪 QA (push) Successful in 1m19s
Build & Deploy / 🏗️ Build (push) Failing after 22m7s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
2026-07-03 12:22:32 +02:00
6fb46278e0 fix: restore hero animations in tailwind v4 on 2.2.95
Some checks failed
Build & Deploy / 🧪 QA (push) Has been cancelled
Build & Deploy / 🏗️ Build (push) Has been cancelled
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 / 🔍 Prepare (push) Has been cancelled
2026-07-03 12:21:43 +02:00
24 changed files with 757 additions and 15274 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -21,7 +21,8 @@ import { DynamicInitialLoader as InitialLoader } from '@/components/providers/Dy
import { DynamicMobileBottomNav as MobileBottomNav } from '@/components/layout/DynamicMobileBottomNav';
import { DynamicCorporateBackground as CorporateBackground } from '@/components/decorations/DynamicCorporateBackground';
const corporateBackground = null; // Removed to prevent line shifting
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 />
{!hasSeenLoader && <InitialLoader shouldShowLoader={!hasSeenLoader} />}
{/* PageTransitionShutter loaded dynamically in TransitionProvider */}
<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>

View File

@@ -132,12 +132,7 @@ export default async function Home(props: { params: Promise<{ locale: string }>
}
setRequestLocale(locale);
preload('/assets/videos/web/hero-kabelpflug-poster.webp', {
as: 'image',
fetchPriority: 'high',
imageSrcSet: '/assets/videos/web/hero-kabelpflug-poster-mobile.webp 800w, /assets/videos/web/hero-kabelpflug-poster.webp 1920w',
imageSizes: '100vw'
});
preload('/germany-map.svg', { as: 'image', fetchPriority: 'high' });
const mdx = await getMdxContent(locale, 'home');
const allReferences = await getAllReferences(locale);

117
app/globals.css Normal file
View File

@@ -0,0 +1,117 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@config "../tailwind.config.cjs";
/* E-TIB Custom Utilities */
@layer utilities {
.text-balance {
text-wrap: balance;
}
.hide-scrollbar {
-ms-overflow-style: none !important; /* IE and Edge */
scrollbar-width: none !important; /* Firefox */
}
.hide-scrollbar::-webkit-scrollbar {
display: none !important; /* Chrome, Safari and Opera */
width: 0 !important;
height: 0 !important;
}
@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 */
@layer base {
html {
scroll-behavior: smooth;
}
body {
@apply bg-neutral font-sans text-neutral-dark antialiased;
}
/* Interactive elements pointer */
a,
button,
[role="button"],
input[type="button"],
input[type="submit"],
input[type="reset"],
select,
summary,
label[for] {
cursor: pointer;
}
/* Ensure disabled elements don't show pointer */
button:disabled,
[role="button"]:disabled,
input:disabled,
select:disabled,
button[disabled] {
cursor: not-allowed;
}
/* Custom Modern Scrollbar */
::-webkit-scrollbar {
width: 14px;
height: 14px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(14, 122, 92, 0.4);
border-radius: 10px;
border: 4px solid transparent;
background-clip: padding-box;
transition: background-color 0.2s ease;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(14, 122, 92, 0.8);
}
/* Firefox */
html {
scrollbar-color: rgba(14, 122, 92, 0.5) transparent;
scrollbar-width: thin;
}
}
/* trigger rebuild */
@keyframes hero-fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@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);
}
to {
transform: scale(1);
}
}
@utility animate-hero-zoom-out {
animation: hero-zoom-out 1.5s ease-out forwards;
}

View File

@@ -41,7 +41,6 @@ export const HeroSection: React.FC<HeroSectionProps> = (props) => {
objectPosition: `${backgroundImage?.focalX ?? 50}% ${backgroundImage?.focalY ?? 50}%`,
}}
sizes="100vw"
quality={50}
priority
/>
{/* Cinematic Color Grading Overlay */}

View File

@@ -34,7 +34,7 @@ export function HeroVideo(props: HeroVideoProps) {
let defaultPoster = "/assets/photos/DJI_0048.JPG";
if (videoUrl && videoUrl.endsWith('.mp4')) {
defaultPoster = videoUrl.replace('.mp4', '-poster.webp');
defaultPoster = videoUrl.replace('.mp4', '-poster.jpg');
}
const posterSrc = props.backgroundImage?.url || props.posterImage?.url || data?.posterImage?.url || defaultPoster;
@@ -80,14 +80,16 @@ export function HeroVideo(props: HeroVideoProps) {
{/* Background color while video loads */}
<div className="absolute inset-0 z-0 bg-neutral-dark" />
<img
{/* ALWAYS render the Next.js optimized Image as the LCP element */}
<Image
key={`img-${pathname}-${posterSrc}`}
src={posterSrc}
srcSet={posterSrc.includes('-poster.webp') ? `${posterSrc.replace('-poster.webp', '-poster-mobile.webp')} 800w, ${posterSrc} 1920w` : undefined}
sizes={posterSrc.includes('-poster.webp') ? "100vw" : undefined}
alt={posterAlt}
className="absolute inset-0 w-full h-full object-cover z-[1] pointer-events-none"
decoding="sync"
fill
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 */}

View File

@@ -23,7 +23,7 @@ export function EUFundingBadge() {
alt="European Union Background"
fill
className="object-cover object-top"
loading="lazy"
priority
/>
</div>

View File

@@ -0,0 +1,7 @@
'use client';
import dynamic from 'next/dynamic';
export const DynamicFramerMotion = dynamic(
() => import('./FramerMotionProvider').then((mod) => mod.FramerMotionProvider),
{ ssr: true }
);

View 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>
);
}

View File

@@ -72,8 +72,8 @@ export function InitialLoader({ shouldShowLoader = true }: { shouldShowLoader?:
{/* Sweeping intense white light */}
<m.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent skew-x-[-25deg]"
initial={{ x: '-150%' }}
animate={{ x: '250%' }}
initial={{ left: '-150%' }}
animate={{ left: '250%' }}
transition={{ duration: 2.5, ease: "easeInOut", repeat: Infinity, repeatDelay: 0.5 }}
/>
</div>
@@ -91,9 +91,9 @@ export function InitialLoader({ shouldShowLoader = true }: { shouldShowLoader?:
className="h-[2px] w-full bg-white/5 overflow-hidden relative rounded-full"
>
<m.div
className="absolute inset-y-0 left-0 w-full bg-gradient-to-r from-primary-dark via-primary to-primary-light origin-left"
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
className="absolute inset-y-0 left-0 bg-gradient-to-r from-primary-dark via-primary to-primary-light"
initial={{ width: "0%" }}
animate={{ width: "100%" }}
transition={{ duration: 3.8, ease: [0.16, 1, 0.3, 1] }}
/>
</m.div>

View File

@@ -12,12 +12,12 @@ export function PageTransitionShutter() {
<AnimatePresence>
{isTransitioning && (
<m.div
key="page-transition-shutter"
key="shutter"
initial={{ y: '-100%' }}
animate={{ y: 0 }}
animate={{ y: '0%' }}
exit={{ y: '100%' }}
transition={{ duration: 0.65, ease: [0.76, 0, 0.24, 1] }}
className="fixed left-0 right-0 z-[9998] bg-[#050B14] flex flex-col items-center justify-center overflow-hidden"
transition={{ duration: 0.9, ease: [0.16, 1, 0.3, 1] }}
className="fixed left-0 w-full z-[9998] bg-[#050B14] pointer-events-none flex flex-col items-center justify-center overflow-visible"
style={{
height: '100vh',
top: '0',
@@ -56,8 +56,8 @@ export function PageTransitionShutter() {
{/* Sweeping intense white light */}
<m.div
className="absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent skew-x-[-25deg]"
initial={{ x: '-150%' }}
animate={{ x: '250%' }}
initial={{ left: '-150%' }}
animate={{ left: '250%' }}
transition={{ duration: 1.5, ease: "easeInOut", repeat: Infinity, repeatDelay: 0.2 }}
/>
</div>

View File

@@ -2,16 +2,6 @@
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import { LazyMotion } from 'framer-motion';
import dynamic from 'next/dynamic';
const loadFeatures = () => import('@/lib/framer-features').then(res => res.default);
// Defer loading the shutter to eliminate AnimatePresence from the initial bundle
const DynamicPageTransitionShutter = dynamic(
() => import('./PageTransitionShutter').then(mod => mod.PageTransitionShutter),
{ ssr: false }
);
interface TransitionContextType {
isTransitioning: boolean;
@@ -24,12 +14,8 @@ const TransitionContext = createContext<TransitionContextType | undefined>(undef
export function TransitionProvider({ children }: { children: ReactNode }) {
const [isTransitioning, setIsTransitioning] = useState(false);
const [transitionMessage, setTransitionMessage] = useState<string | null>(null);
const [hasMountedShutter, setHasMountedShutter] = useState(false);
const router = useRouter();
// We no longer eagerly load the shutter in the background.
// It will be loaded precisely when the user initiates a transition.
// We use a ref to track transition state without triggering the route change effect when it turns true
const isTransitioningRef = React.useRef(isTransitioning);
useEffect(() => {
@@ -55,7 +41,6 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
setTransitionMessage(message);
setIsTransitioning(true);
setHasMountedShutter(true);
// Die Animation des Shutters abwarten (ca. 700ms), dann pushen
setTimeout(() => {
@@ -83,10 +68,7 @@ export function TransitionProvider({ children }: { children: ReactNode }) {
setTransitionMessage={setTransitionMessage}
/>
</React.Suspense>
<LazyMotion features={loadFeatures}>
{hasMountedShutter && <DynamicPageTransitionShutter />}
{children}
</LazyMotion>
{children}
</TransitionContext.Provider>
);
}

View File

@@ -118,7 +118,7 @@ description: "Die E-TIB GmbH ist Ihr zuverlässiger Partner für komplexe Kabelt
items={[
{
title: "Kabelleitungsnetzbau",
description: "Kabelleitungsnetzbau (Nieder-/Mittel- und Hochspannung bis 110 kV)",
description: "Kabelleitungsnetzbau (Hoch-/Mittel- und Niederspannung) sowie Kabelmontagen bis 110 kV",
tag: "Energie",
size: "large",
href: "/de/kabeltiefbau",

View File

@@ -8,7 +8,7 @@ layout: "fullBleed"
<HeroSection
badge="Kernkompetenz"
title="Kabelleitungsnetzbau"
subtitle="Komplette Infrastruktur-Lösungen für Nieder-, Mittel- und Hochspannungsnetze bis 110 kV"
subtitle="Komplette Infrastruktur-Lösungen für Hoch-, Mittel- und Niederspannungsnetze sowie Kabelmontagen bis 110 kV"
backgroundImage={{ url: '/assets/photos/DSC01123.JPG' }}
alignment="center"
ctaLabel="Projekt anfragen"

View File

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

View File

@@ -1,6 +1,5 @@
{
"name": "e-tib-nextjs",
"version": "2.4.9",
"type": "module",
"private": true,
"packageManager": "pnpm@10.18.3",
@@ -12,7 +11,6 @@
"@mintel/next-utils": "1.9.5",
"@next/mdx": "^16.1.6",
"@react-email/components": "1.0.8",
"@react-pdf/renderer": "^4.5.1",
"@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.5.0",
"@sentry/nextjs": "^10.39.0",
@@ -141,6 +139,7 @@
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
},
"version": "2.4.22",
"pnpm": {
"onlyBuiltDependencies": [
"@parcel/watcher",

278
pnpm-lock.yaml generated
View File

@@ -34,9 +34,6 @@ importers:
'@react-email/components':
specifier: 1.0.8
version: 1.0.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@react-pdf/renderer':
specifier: ^4.5.1
version: 4.5.1(react@19.2.4)
'@react-three/drei':
specifier: ^10.7.7
version: 10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.14)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2))(@types/react@19.2.14)(@types/three@0.183.1)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2)
@@ -2213,49 +2210,6 @@ packages:
react: ^18.0.0
react-dom: ^18.0.0
'@react-pdf/fns@3.1.3':
resolution: {integrity: sha512-0I7pApDr1/RLAKbizuLy/IHTEa93LSPy/bEwYniboC3Xqnp6Od8xFJKbKEzGw2wh/5zKFFwl00g4t9RwgIMc3w==}
'@react-pdf/font@4.0.8':
resolution: {integrity: sha512-deNd+emtZAJho1IlzKL9bRoLAGv/6oXOIKO2oZfs4RuXUrK1onLHbJO7e2YoVLPFP/sQxisRTnzdJFtd35iKwA==}
'@react-pdf/image@3.1.0':
resolution: {integrity: sha512-ks7Ry8v711r8NvKWSELehj0BXBNPRihSnWsM09nDD8Ur175zbWBCK217LLwQMKDNYDVpkZaipdoJPom1LGaE9g==}
'@react-pdf/layout@4.6.1':
resolution: {integrity: sha512-gN6PmWoEffvlIkifLfEhMsVucRywVMyH3rnxdyOVOhGy0nWJKKGpHyPc4plbDdpP6EfZ0r8prHXujDSkIG2nSA==}
'@react-pdf/pdfkit@5.1.1':
resolution: {integrity: sha512-wNcdSsNlNYyGHGAgIdt453egBF7fiF9UxpRlklUfVvu8OWCrUppG9xiUrPLVoKiqWet5tMi0w6LmuFUJuYqjEg==}
'@react-pdf/primitives@4.3.0':
resolution: {integrity: sha512-nYXoZ36pvwNzbc54+DbL8RCn15jU7woJ9D/svnh5tpUXekJ+CbI4mZLo6boSv24CvJgychOu6h7gxX03B4ps0A==}
'@react-pdf/reconciler@2.0.0':
resolution: {integrity: sha512-7zaPRujpbHSmCpIrZ+b9HSTJHthcVZzX0Wx7RzvQGsGBUbHP4p6s5itXrAIOuQuPvDepoHGNOvf6xUuMVvdoyw==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@react-pdf/render@4.5.1':
resolution: {integrity: sha512-IW/N4HWJWtioBXCf7n02IR24VJJ8gbdS3jGypf+vW/rSErEx3/URRzh9UK6Ma8Fpog9+T/W6GE2NHJ5AAKHhVA==}
'@react-pdf/renderer@4.5.1':
resolution: {integrity: sha512-5r1VQrE6FRLXX5wWUxwZzM24E2BJMo6g8AQWuS8WyPs9ugu5yMnb2g8/RpPYka/Z6J+RUEWc32wty2NoUJF42Q==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@react-pdf/stylesheet@6.2.1':
resolution: {integrity: sha512-2+UEk+7e+z8baaWi2l5kPLWmwtJeOI+T5wW9GGeN3iDH7vd3kbTqOpN1yt9mmfNVZFxQsnDHpznFb5v5UF983A==}
'@react-pdf/svg@1.1.0':
resolution: {integrity: sha512-cTIHXiz9x1HrbfqzfxfZP3FRdDwUXG77QWF6Fb5MP/lV3ONxR+g0Z3hwtBatCS9HeGBQCpxX/Lzb8wHE+co1PA==}
'@react-pdf/textkit@6.3.0':
resolution: {integrity: sha512-v6+V8nAcVwm7s2s1jIG2MD3Iw//x/k+XrH1foWOELBE4b32pyDgKyPXN/6KJE0dnX7+fVy27uctLNCLNMvzKzQ==}
'@react-pdf/types@2.11.1':
resolution: {integrity: sha512-i9xQgfaDU9QoeNnbp6rltXCWg1huEh195rpOuN8cE4BZ2FuLdQrsIcb2dhFF9aOxXf+XBA6LOSpIW051MDD/bw==}
'@react-three/drei@10.7.7':
resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==}
peerDependencies:
@@ -3430,9 +3384,6 @@ packages:
resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
abs-svg-path@0.1.1:
resolution: {integrity: sha512-d8XPSGjfyzlXC3Xx891DJRyZfqk5JU0BJrDQcsWomFIV1/BIzPW5HDH5iDdWpqWaav0YVIEzT1RHTwWr0FFshA==}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@@ -3768,9 +3719,6 @@ packages:
brotli@1.3.3:
resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==}
browserify-zlib@0.2.0:
resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==}
browserslist@4.28.2:
resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
@@ -3965,14 +3913,6 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-name@2.1.0:
resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==}
engines: {node: '>=12.20'}
color-string@2.1.4:
resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==}
engines: {node: '>=18'}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
@@ -4424,9 +4364,6 @@ packages:
electron-to-chromium@1.5.330:
resolution: {integrity: sha512-jFNydB5kFtYUobh4IkWUnXeyDbjf/r9gcUEXe1xcrcUxIGfTdzPXA+ld6zBRbwvgIGVzDll/LTIiDztEtckSnA==}
emoji-regex-xs@1.0.0:
resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==}
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
@@ -5150,12 +5087,6 @@ packages:
resolution: {integrity: sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==}
engines: {node: '>= 6.0.0'}
hsl-to-hex@1.0.0:
resolution: {integrity: sha512-K6GVpucS5wFf44X0h2bLVRDsycgJmf9FF2elg+CrqD8GcFU8c6vYhgXn8NjUkFCwj+xDFb70qgLbTUm6sxwPmA==}
hsl-to-rgb-for-reals@1.1.1:
resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==}
html-encoding-sniffer@6.0.0:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
@@ -5237,9 +5168,6 @@ packages:
engines: {node: '>=18'}
hasBin: true
hyphen@1.14.1:
resolution: {integrity: sha512-kvL8xYl5QMTh+LwohVN72ciOxC0OEV79IPdJSTwEXok9y9QHebXGdFgrED4sWfiax/ODx++CAMk3hMy4XPJPOw==}
i18next-fs-backend@2.6.3:
resolution: {integrity: sha512-/5aW996nbolGh/qVU9urjAJ2QiXpiyGuhG8x/ZvvZQ3bMlrK+ouUdD6whS4e+sU9CCdUIzl7x/heEjsBh/3xtw==}
@@ -5531,9 +5459,6 @@ packages:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
is-url@1.2.4:
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
is-weakmap@2.0.2:
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
engines: {node: '>= 0.4'}
@@ -5575,9 +5500,6 @@ packages:
jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jay-peg@1.1.1:
resolution: {integrity: sha512-D62KEuBxz/ip2gQKOEhk/mx14o7eiFRaU+VNNSP4MOiIkwb/D6B3G1Mfas7C/Fit8EsSV2/IWjZElx/Gs6A4ww==}
jest-worker@27.5.1:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'}
@@ -6011,9 +5933,6 @@ packages:
mdn-data@2.27.1:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
media-engine@1.0.3:
resolution: {integrity: sha512-aa5tG6sDoK+k70B9iEX1NeyfT8ObCKhNDs6lJVpwF6r8vhUfuKMslIcirq6HIUYuuUYLefcEQOn9bSBOvawtwg==}
media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
@@ -6354,9 +6273,6 @@ packages:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
normalize-svg-path@1.1.0:
resolution: {integrity: sha512-r9KHKG2UUeB5LoTouwDzBy2VxXlHsiM6fyLQvnJa0S5hrhzqElH/CH7TUGhT1fVvIYBIKf3OpY4YJ4CK+iaqHg==}
npm-run-path@4.0.1:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
@@ -6503,9 +6419,6 @@ packages:
pako@0.2.9:
resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==}
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -6524,9 +6437,6 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
parse-svg-path@0.1.2:
resolution: {integrity: sha512-JyPSBnkTJ0AI8GGJLfMXvKq42cj5c006fnLz6fXy6zfoVjJizi8BNTpu8on8ziI1cKy9d9DGNuY17Ce7wuejpQ==}
parse5-htmlparser2-tree-adapter@7.1.0:
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
@@ -6650,9 +6560,6 @@ packages:
png-js@1.0.0:
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
png-js@2.0.0:
resolution: {integrity: sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA==}
po-parser@2.1.1:
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
@@ -6797,9 +6704,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
queue@6.0.2:
resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
@@ -7093,9 +6997,6 @@ packages:
resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
engines: {node: '>=v12.22.7'}
scheduler@0.25.0-rc-603e6108-20241029:
resolution: {integrity: sha512-pFwF6H1XrSdYYNLfOcGlM28/j8CGLu8IvdrxqhjWULe2bPcKiKW4CV+OWqR/9fT52mywx65l7ysNkjLKBda7eA==}
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
@@ -7369,9 +7270,6 @@ packages:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'}
string_decoder@1.3.0:
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
stringify-entities@4.0.4:
resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==}
@@ -7457,9 +7355,6 @@ packages:
peerDependencies:
react: '>=17.0'
svg-arc-to-cubic-bezier@3.2.0:
resolution: {integrity: sha512-djbJ/vZKZO+gPoSDThGNpKDO+o+bAeA4XQKovvkNCqnIS2t+S4qnLAGQhyyrulhCFRl1WWzAp0wUDV8PpTVU3g==}
svg-to-pdfkit@0.1.8:
resolution: {integrity: sha512-QItiGZBy5TstGy+q8mjQTMGRlDDOARXLxH+sgVm1n/LYeo0zFcQlcCh8m4zi8QxctrxB9Kue/lStc/RD5iLadQ==}
@@ -7843,10 +7738,6 @@ packages:
victory-vendor@37.3.6:
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
vite-compatible-readable-stream@3.6.1:
resolution: {integrity: sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==}
engines: {node: '>= 6'}
vite@8.0.3:
resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -8185,9 +8076,6 @@ packages:
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
engines: {node: '>=18'}
yoga-layout@3.2.1:
resolution: {integrity: sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==}
zod-validation-error@4.0.2:
resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
engines: {node: '>=18.0.0'}
@@ -10110,110 +9998,6 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@react-pdf/fns@3.1.3': {}
'@react-pdf/font@4.0.8':
dependencies:
'@react-pdf/pdfkit': 5.1.1
'@react-pdf/types': 2.11.1
fontkit: 2.0.4
is-url: 1.2.4
'@react-pdf/image@3.1.0':
dependencies:
'@react-pdf/svg': 1.1.0
jay-peg: 1.1.1
png-js: 2.0.0
'@react-pdf/layout@4.6.1':
dependencies:
'@react-pdf/fns': 3.1.3
'@react-pdf/image': 3.1.0
'@react-pdf/primitives': 4.3.0
'@react-pdf/stylesheet': 6.2.1
'@react-pdf/textkit': 6.3.0
'@react-pdf/types': 2.11.1
emoji-regex-xs: 1.0.0
queue: 6.0.2
yoga-layout: 3.2.1
'@react-pdf/pdfkit@5.1.1':
dependencies:
'@babel/runtime': 7.29.2
'@noble/ciphers': 1.3.0
'@noble/hashes': 1.8.0
browserify-zlib: 0.2.0
fontkit: 2.0.4
jay-peg: 1.1.1
js-md5: 0.8.3
linebreak: 1.1.0
png-js: 2.0.0
vite-compatible-readable-stream: 3.6.1
'@react-pdf/primitives@4.3.0': {}
'@react-pdf/reconciler@2.0.0(react@19.2.4)':
dependencies:
object-assign: 4.1.1
react: 19.2.4
scheduler: 0.25.0-rc-603e6108-20241029
'@react-pdf/render@4.5.1':
dependencies:
'@babel/runtime': 7.29.2
'@react-pdf/fns': 3.1.3
'@react-pdf/primitives': 4.3.0
'@react-pdf/textkit': 6.3.0
'@react-pdf/types': 2.11.1
abs-svg-path: 0.1.1
color-string: 2.1.4
normalize-svg-path: 1.1.0
parse-svg-path: 0.1.2
svg-arc-to-cubic-bezier: 3.2.0
'@react-pdf/renderer@4.5.1(react@19.2.4)':
dependencies:
'@babel/runtime': 7.29.2
'@react-pdf/fns': 3.1.3
'@react-pdf/font': 4.0.8
'@react-pdf/layout': 4.6.1
'@react-pdf/pdfkit': 5.1.1
'@react-pdf/primitives': 4.3.0
'@react-pdf/reconciler': 2.0.0(react@19.2.4)
'@react-pdf/render': 4.5.1
'@react-pdf/types': 2.11.1
events: 3.3.0
object-assign: 4.1.1
prop-types: 15.8.1
queue: 6.0.2
react: 19.2.4
'@react-pdf/stylesheet@6.2.1':
dependencies:
'@react-pdf/fns': 3.1.3
'@react-pdf/types': 2.11.1
color-string: 2.1.4
hsl-to-hex: 1.0.0
media-engine: 1.0.3
postcss-value-parser: 4.2.0
'@react-pdf/svg@1.1.0':
dependencies:
'@react-pdf/primitives': 4.3.0
'@react-pdf/textkit@6.3.0':
dependencies:
'@react-pdf/fns': 3.1.3
bidi-js: 1.0.3
hyphen: 1.14.1
unicode-properties: 1.4.1
'@react-pdf/types@2.11.1':
dependencies:
'@react-pdf/font': 4.0.8
'@react-pdf/primitives': 4.3.0
'@react-pdf/stylesheet': 6.2.1
'@react-three/drei@10.7.7(@react-three/fiber@9.5.0(@types/react@19.2.14)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2))(@types/react@19.2.14)(@types/three@0.183.1)(immer@11.1.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(three@0.183.2)':
dependencies:
'@babel/runtime': 7.29.2
@@ -11392,8 +11176,6 @@ snapshots:
abbrev@2.0.0: {}
abs-svg-path@0.1.1: {}
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
@@ -11714,10 +11496,6 @@ snapshots:
dependencies:
base64-js: 1.5.1
browserify-zlib@0.2.0:
dependencies:
pako: 1.0.11
browserslist@4.28.2:
dependencies:
baseline-browser-mapping: 2.10.13
@@ -11932,12 +11710,6 @@ snapshots:
color-name@1.1.4: {}
color-name@2.1.0: {}
color-string@2.1.4:
dependencies:
color-name: 2.1.0
colorette@2.0.20: {}
combined-stream@1.0.8:
@@ -12415,8 +12187,6 @@ snapshots:
electron-to-chromium@1.5.330: {}
emoji-regex-xs@1.0.0: {}
emoji-regex@10.6.0: {}
emoji-regex@8.0.0: {}
@@ -13469,12 +13239,6 @@ snapshots:
hoopy@0.1.4: {}
hsl-to-hex@1.0.0:
dependencies:
hsl-to-rgb-for-reals: 1.1.1
hsl-to-rgb-for-reals@1.1.1: {}
html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0):
dependencies:
'@exodus/bytes': 1.15.0(@noble/hashes@1.8.0)
@@ -13573,8 +13337,6 @@ snapshots:
husky@9.1.7: {}
hyphen@1.14.1: {}
i18next-fs-backend@2.6.3: {}
i18next@25.10.10(typescript@5.9.3):
@@ -13863,8 +13625,6 @@ snapshots:
is-unicode-supported@2.1.0: {}
is-url@1.2.4: {}
is-weakmap@2.0.2: {}
is-weakref@1.1.1:
@@ -13915,10 +13675,6 @@ snapshots:
optionalDependencies:
'@pkgjs/parseargs': 0.11.0
jay-peg@1.1.1:
dependencies:
restructure: 3.0.2
jest-worker@27.5.1:
dependencies:
'@types/node': 22.19.15
@@ -14467,8 +14223,6 @@ snapshots:
mdn-data@2.27.1: {}
media-engine@1.0.3: {}
media-typer@0.3.0: {}
meow@13.2.0: {}
@@ -14929,10 +14683,6 @@ snapshots:
normalize-path@3.0.0: {}
normalize-svg-path@1.1.0:
dependencies:
svg-arc-to-cubic-bezier: 3.2.0
npm-run-path@4.0.1:
dependencies:
path-key: 3.1.1
@@ -15143,8 +14893,6 @@ snapshots:
pako@0.2.9: {}
pako@1.0.11: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -15172,8 +14920,6 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
parse-svg-path@0.1.2: {}
parse5-htmlparser2-tree-adapter@7.1.0:
dependencies:
domhandler: 5.0.3
@@ -15316,10 +15062,6 @@ snapshots:
png-js@1.0.0: {}
png-js@2.0.0:
dependencies:
fflate: 0.8.2
po-parser@2.1.1: {}
possible-typed-array-names@1.1.0: {}
@@ -15477,10 +15219,6 @@ snapshots:
queue-microtask@1.2.3: {}
queue@6.0.2:
dependencies:
inherits: 2.0.4
quick-format-unescaped@4.0.4: {}
range-parser@1.2.1: {}
@@ -15899,8 +15637,6 @@ snapshots:
dependencies:
xmlchars: 2.2.0
scheduler@0.25.0-rc-603e6108-20241029: {}
scheduler@0.27.0: {}
schema-dts@1.1.5: {}
@@ -16289,10 +16025,6 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
string_decoder@1.3.0:
dependencies:
safe-buffer: 5.2.1
stringify-entities@4.0.4:
dependencies:
character-entities-html4: 2.1.0
@@ -16363,8 +16095,6 @@ snapshots:
dependencies:
react: 19.2.4
svg-arc-to-cubic-bezier@3.2.0: {}
svg-to-pdfkit@0.1.8:
dependencies:
pdfkit: 0.18.0
@@ -16833,12 +16563,6 @@ snapshots:
d3-time: 3.1.0
d3-timer: 3.0.1
vite-compatible-readable-stream@3.6.1:
dependencies:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
vite@8.0.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)(@types/node@22.19.15)(esbuild@0.27.4)(jiti@2.6.1)(sass@1.98.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3):
dependencies:
lightningcss: 1.32.0
@@ -17170,8 +16894,6 @@ snapshots:
yoctocolors@2.1.2: {}
yoga-layout@3.2.1: {}
zod-validation-error@4.0.2(zod@3.25.76):
dependencies:
zod: 3.25.76

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View File

@@ -1,267 +1,378 @@
@import "tailwindcss";
@import 'tailwindcss';
@config "../tailwind.config.cjs";
@theme {
/* Breakpoints */
--breakpoint-xs: 475px;
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
--breakpoint-2xl: 1400px;
--breakpoint-3xl: 1600px;
/* Brand Colors */
--color-primary: #0e7a5c;
--color-primary-dark: #0a5742;
--color-primary-light: #149d76;
--color-secondary: #194f3b;
--color-secondary-light: #20654c;
--color-secondary-dark: #13392a;
--color-accent: #0e7a5c;
--color-accent-light: #149d76;
--color-neutral: #fafafa;
--color-neutral-light: #ffffff;
--color-neutral-dark: #333333;
--color-neutral-black: #1a1a1a;
/* Text Colors */
--color-text-primary: #1a1a1a;
--color-text-secondary: #6c757d;
--color-text-light: #adb5bd;
/* Semantic Colors */
--color-success: #28a745;
--color-warning: #ffc107;
--color-danger: #dc3545;
--color-info: #17a2b8;
/* Fonts */
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-sans:
var(--font-inter), system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif;
--font-heading: 'Inter', system-ui, sans-serif;
--font-body: 'Inter', system-ui, sans-serif;
/* Typography */
--text-xs: 0.75rem;
--text-xs--line-height: 1.6;
--text-sm: 0.875rem;
--text-sm--line-height: 1.6;
--text-base: 1rem;
--text-base--line-height: 1.7;
--text-lg: 1.125rem;
--text-lg--line-height: 1.7;
--text-xl: 1.25rem;
--text-xl--line-height: 1.6;
--text-2xl: 1.5rem;
--text-2xl--line-height: 1.5;
--text-3xl: 1.875rem;
--text-3xl--line-height: 1.4;
--text-4xl: 2.25rem;
--text-4xl--line-height: 1.3;
--text-5xl: 3rem;
--text-5xl--line-height: 1.25;
--text-6xl: 3.75rem;
--text-6xl--line-height: 1.2;
--text-7xl: 4.5rem;
--text-7xl--line-height: 1.15;
--text-8xl: 6rem;
--text-8xl--line-height: 1.1;
--color-primary: #0e7a5c;
/* Deep Teal/Green */
--color-primary-dark: #084c39;
--color-primary-light: #e6f5f0;
/* Font Weights */
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--font-weight-extrabold: 800;
--color-saturated: #10a379;
/* Saturated Teal Accent */
/* Border Radius */
--radius-sm: 0.25rem;
--radius: 0.5rem;
--radius-lg: 0.75rem;
--radius-xl: 1rem;
--radius-2xl: 1.5rem;
--color-secondary: #003d82;
--color-secondary-light: #0056b3;
/* Box Shadow */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--color-accent: #10a379;
/* Brand Aligned Accent */
--color-accent-dark: #0e7a5c;
--color-accent-light: #e6f5f0;
--color-neutral: #f8f9fa;
--color-neutral-light: #ffffff;
--color-neutral-dark: #0a0a0a;
--color-neutral-medium: #e5e7eb;
--color-text-primary: #111827;
--color-text-secondary: #4b5563;
--color-text-light: #9ca3af;
--color-success: #10b981;
--color-warning: #f59e0b;
--color-danger: #ef4444;
--color-destructive: #ef4444;
--color-destructive-foreground: #ffffff;
--color-info: #3b82f6;
--animate-fade-in: fade-in 0.5s ease-out;
--animate-slide-up: slide-up 0.6s ease-out;
--animate-slow-zoom: slow-zoom 20s linear infinite;
--animate-reveal: reveal 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards;
--animate-slight-fade-in-from-bottom: slight-fade-in-from-bottom 0.8s
cubic-bezier(0.16, 1, 0.3, 1) forwards;
--animate-gradient-x: gradient-x 15s ease infinite;
--animate-draw-stroke: draw-stroke 1.8s ease-in-out 0.5s forwards;
@keyframes draw-stroke {
from {
stroke-dasharray: 1;
stroke-dashoffset: 1;
}
to {
stroke-dasharray: 1;
stroke-dashoffset: 0;
}
}
@keyframes gradient-x {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slow-zoom {
from {
transform: scale(1);
}
to {
transform: scale(1.1);
}
}
@keyframes reveal {
from {
opacity: 0;
transform: translateY(20px);
filter: blur(8px);
}
to {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
}
@keyframes slight-fade-in-from-bottom {
from {
opacity: 0;
transform: translateY(10px);
filter: blur(4px);
}
to {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
}
@keyframes float {
0% {
transform: translate(0, 0) scale(1);
}
33% {
transform: translate(2%, 4%) scale(1.1);
}
66% {
transform: translate(-3%, 2%) scale(0.9);
}
100% {
transform: translate(0, 0) scale(1);
}
}
@keyframes spin-slow {
to {
transform: rotate(360deg);
}
}
@keyframes flow {
to {
stroke-dashoffset: 0;
}
}
@keyframes solar-pulse {
0%,
100% {
fill-opacity: 0.2;
}
50% {
fill-opacity: 0.5;
}
}
}
/* Custom Utilities (from JS plugins) */
@layer base {
html {
/* Scale text down slightly on mobile (87.5% of 16px = 14px) */
font-size: 87.5%;
}
@media (min-width: 768px) {
html {
/* Normal scaling on tablet and desktop (100% = 16px) */
font-size: 100%;
}
}
.bg-primary a,
.bg-primary-dark a {
@apply text-white/90 hover:text-white transition-colors;
}
body {
@apply text-base md:text-lg antialiased;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
line-height: 1.7;
}
*:focus-visible {
outline: 3px solid var(--color-primary);
outline-offset: 3px;
border-radius: 2px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
@apply font-heading font-bold tracking-tight;
}
/* Enhanced Mobile-first typography hierarchy with fluid sizing */
h1 {
@apply text-3xl md:text-5xl lg:text-6xl;
}
h2 {
@apply text-2xl md:text-4xl lg:text-5xl;
}
h3 {
@apply text-xl md:text-2xl lg:text-3xl;
}
h4 {
@apply text-lg md:text-xl lg:text-2xl;
}
h5 {
@apply text-base md:text-lg;
}
h6 {
@apply text-sm md:text-base;
}
/* Paragraph and text styles */
p {
@apply mb-4 leading-relaxed;
}
/* Link styles */
a {
@apply transition-all duration-200;
}
/* List styles */
ul,
ol {
@apply my-4 ml-6;
}
li {
@apply mb-2 leading-relaxed;
}
/* Small text */
small {
@apply text-sm md:text-base;
}
/* Strong and emphasis */
strong {
@apply font-bold;
}
em {
@apply italic;
}
/* Blockquote */
blockquote {
@apply border-l-4 pl-6 my-6 italic;
}
/* Code */
code {
@apply px-2 py-1 rounded font-mono text-sm;
}
/* Horizontal rule */
hr {
@apply my-8;
}
}
@layer components {
.glass-panel {
@apply bg-white/80 backdrop-blur-md border border-white/20 shadow-lg;
}
.image-overlay-gradient {
@apply absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent;
}
.premium-card {
@apply bg-white rounded-3xl shadow-sm transition-all duration-500 hover:shadow-2xl hover:-translate-y-1;
}
.sticky-narrative-container {
@apply grid grid-cols-1 lg:grid-cols-12 gap-12 lg:gap-20;
}
.sticky-narrative-sidebar {
@apply lg:col-span-4 lg:sticky lg:top-32 h-fit;
}
.sticky-narrative-content {
@apply lg:col-span-8;
}
}
/* Custom Utilities */
@utility touch-target {
min-height: 44px;
min-width: 44px;
min-height: 48px;
/* Increased for better touch-first feel */
min-width: 48px;
display: inline-flex;
align-items: center;
justify-content: center;
}
@utility mobile-hidden {
@media (max-width: 767px) {
display: none !important;
@utility reveal-on-scroll {
opacity: 0;
transform: translateY(20px);
filter: blur(8px);
transition:
opacity 0.6s ease-out,
transform 0.8s cubic-bezier(0.16, 1, 0.3, 1),
filter 0.8s cubic-bezier(0.16, 1, 0.3, 1);
&.is-visible {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
@media (prefers-reduced-motion: reduce) {
opacity: 1;
transform: none !important;
filter: none !important;
transition: opacity 0.3s ease-out;
}
}
@utility tablet-hidden {
@media (min-width: 768px) and (max-width: 1023px) {
display: none !important;
}
}
@utility desktop-hidden {
@media (min-width: 1024px) {
display: none !important;
}
}
@utility mobile-only {
@media (min-width: 768px) {
display: none !important;
}
}
@utility tablet-only {
@media (max-width: 767px), (min-width: 1024px) {
display: none !important;
}
}
@utility desktop-only {
@media (max-width: 1023px) {
display: none !important;
@utility hover-effect {
@media (hover: hover) {
@apply transition-all duration-300 hover:scale-105;
}
}
@utility fluid-spacing {
padding: clamp(1rem, 3vw, 3rem);
margin: clamp(0.5rem, 2vw, 2rem);
}
@utility text-start-mobile {
@media (max-width: 767px) {
text-align: left;
}
}
@utility text-center-mobile {
@media (max-width: 767px) {
text-align: center;
}
}
@utility text-end-mobile {
@media (max-width: 767px) {
text-align: right;
}
padding: clamp(1.5rem, 5vw, 5rem);
margin: clamp(1rem, 3vw, 3rem);
}
@utility grid-mobile-stacked {
@media (max-width: 767px) {
display: flex;
flex-direction: column;
gap: 1rem;
}
@apply grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8 lg:gap-12;
}
@utility safe-area-p {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-top: max(1rem, env(safe-area-inset-top));
padding-bottom: max(1rem, env(safe-area-inset-bottom));
padding-left: max(1rem, env(safe-area-inset-left));
padding-right: max(1rem, env(safe-area-inset-right));
}
/* E-TIB Custom Utilities (from previous globals.css) */
@layer utilities {
.text-balance {
text-wrap: balance;
}
.hide-scrollbar {
-ms-overflow-style: none !important; /* IE and Edge */
scrollbar-width: none !important; /* Firefox */
}
.hide-scrollbar::-webkit-scrollbar {
display: none !important; /* Chrome, Safari and Opera */
width: 0 !important;
height: 0 !important;
}
@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; }
@keyframes hero-fade-in-up {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.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); }
to { transform: scale(1); }
}
.animate-hero-zoom-out {
animation: hero-zoom-out 1.5s ease-out forwards;
}
}
/* Base Styles */
@layer base {
html {
scroll-behavior: smooth;
}
body {
@apply bg-neutral font-sans text-neutral-dark antialiased;
}
/* Interactive elements pointer */
a, button, [role="button"], input[type="button"], input[type="submit"], input[type="reset"], select, summary, label[for] {
cursor: pointer;
}
/* Ensure disabled elements don't show pointer */
button:disabled, [role="button"]:disabled, input:disabled, select:disabled, button[disabled] {
cursor: not-allowed;
}
/* Custom Modern Scrollbar */
::-webkit-scrollbar {
width: 14px;
height: 14px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(14, 122, 92, 0.4);
border-radius: 10px;
border: 4px solid transparent;
background-clip: padding-box;
transition: background-color 0.2s ease;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(14, 122, 92, 0.8);
}
/* Firefox */
html {
scrollbar-color: rgba(14, 122, 92, 0.5) transparent;
scrollbar-width: thin;
}
@utility content-visibility-auto {
content-visibility: auto;
contain-intrinsic-size: 1px 1000px;
}

226
tailwind.config.cjs Normal file
View File

@@ -0,0 +1,226 @@
/* eslint-disable no-undef */
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./content/**/*.{mdx,md}',
'./node_modules/@mintel/annotator/dist/**/*.{js,ts,jsx,tsx,mjs}',
'./node_modules/.pnpm/@mintel+annotator*/node_modules/@mintel/annotator/dist/**/*.{js,ts,jsx,tsx,mjs}'
],
theme: {
extend: {
// Custom Breakpoints for KLZ Cables
screens: {
'xs': '475px',
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1400px',
'3xl': '1600px',
},
colors: {
// Brand Colors
primary: {
DEFAULT: '#0e7a5c',
dark: '#0a5742',
light: '#149d76',
},
secondary: {
DEFAULT: '#194f3b',
light: '#20654c',
dark: '#13392a',
},
accent: {
DEFAULT: '#0e7a5c',
light: '#149d76',
},
neutral: {
DEFAULT: '#fafafa',
light: '#ffffff',
dark: '#333333',
black: '#1a1a1a',
},
// Text Colors
text: {
primary: '#1a1a1a',
secondary: '#6c757d',
light: '#adb5bd',
},
// Semantic Colors
success: '#28a745',
warning: '#ffc107',
danger: '#dc3545',
info: '#17a2b8',
},
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif'],
heading: ['Inter', 'system-ui', 'sans-serif'],
body: ['Inter', 'system-ui', 'sans-serif'],
},
// Standard Fluid Typography
// Responsive scaling is handled globally via the html selector percentage in globals.css
fontSize: {
'xs': ['0.75rem', { lineHeight: '1.6' }],
'sm': ['0.875rem', { lineHeight: '1.6' }],
'base': ['1rem', { lineHeight: '1.7' }],
'lg': ['1.125rem', { lineHeight: '1.7' }],
'xl': ['1.25rem', { lineHeight: '1.6' }],
'2xl': ['1.5rem', { lineHeight: '1.5' }],
'3xl': ['1.875rem', { lineHeight: '1.4' }],
'4xl': ['2.25rem', { lineHeight: '1.3' }],
'5xl': ['3rem', { lineHeight: '1.25' }],
'6xl': ['3.75rem', { lineHeight: '1.2' }],
'7xl': ['4.5rem', { lineHeight: '1.15' }],
'8xl': ['6rem', { lineHeight: '1.1' }],
},
fontWeight: {
regular: '400',
medium: '500',
semibold: '600',
bold: '700',
extrabold: '800',
},
borderRadius: {
'sm': '0.25rem',
'DEFAULT': '0.5rem',
'lg': '0.75rem',
'xl': '1rem',
'2xl': '1.5rem',
},
boxShadow: {
'sm': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
'DEFAULT': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
'md': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
'xl': '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
},
container: {
center: true,
padding: {
DEFAULT: '1rem',
sm: '1.5rem',
md: '2rem',
lg: '2.5rem',
xl: '3rem',
'2xl': '4rem',
},
screens: {
xs: '475px',
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1400px',
'3xl': '1600px',
},
},
maxWidth: {
'xs': '20rem',
'sm': '24rem',
'md': '28rem',
'lg': '32rem',
'xl': '36rem',
'2xl': '42rem',
'3xl': '48rem',
'4xl': '56rem',
'5xl': '64rem',
'6xl': '72rem',
},
zIndex: {
'0': '0',
'10': '10',
'20': '20',
'30': '30',
'40': '40',
'50': '50',
'auto': 'auto',
},
},
},
plugins: [
// Custom plugin for responsive utilities
function ({ addUtilities }) {
const newUtilities = {
// Touch target utilities
'.touch-target': {
minHeight: '44px',
minWidth: '44px',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
},
// Mobile-first responsive utilities
'.mobile-hidden': {
'@media (max-width: 767px)': {
display: 'none !important',
},
},
'.tablet-hidden': {
'@media (min-width: 768px) and (max-width: 1023px)': {
display: 'none !important',
},
},
'.desktop-hidden': {
'@media (min-width: 1024px)': {
display: 'none !important',
},
},
'.mobile-only': {
'@media (min-width: 768px)': {
display: 'none !important',
},
},
'.tablet-only': {
'@media (max-width: 767px), (min-width: 1024px)': {
display: 'none !important',
},
},
'.desktop-only': {
'@media (max-width: 1023px)': {
display: 'none !important',
},
},
// Fluid spacing utilities
'.fluid-spacing': {
padding: 'clamp(1rem, 3vw, 3rem)',
margin: 'clamp(0.5rem, 2vw, 2rem)',
},
// Responsive text alignment
'.text-start-mobile': {
'@media (max-width: 767px)': {
textAlign: 'left',
},
},
'.text-center-mobile': {
'@media (max-width: 767px)': {
textAlign: 'center',
},
},
'.text-end-mobile': {
'@media (max-width: 767px)': {
textAlign: 'right',
},
},
// Mobile-optimized grid
'.grid-mobile-stacked': {
'@media (max-width: 767px)': {
display: 'flex',
flexDirection: 'column',
gap: '1rem',
},
},
// Safe area padding for mobile notch
'.safe-area-p': {
paddingTop: 'env(safe-area-inset-top)',
paddingBottom: 'env(safe-area-inset-bottom)',
paddingLeft: 'env(safe-area-inset-left)',
paddingRight: 'env(safe-area-inset-right)',
},
};
addUtilities(newUtilities, ['responsive', 'hover', 'focus']);
},
],
}

View File

@@ -39,7 +39,7 @@ describe('Task 6 Content Updates', () => {
// DE Home
expect(deContent).toContain('title: "Kabelleitungsnetzbau"');
expect(deContent).toContain('description: "Kabelleitungsnetzbau (Nieder-/Mittel- und Hochspannung bis 110 kV)"');
expect(deContent).toContain('description: "Kabelleitungsnetzbau (Hoch-/Mittel- und Niederspannung) sowie Kabelmontagen bis 110 kV"');
expect(deContent).toContain('100+');
expect(deContent).toContain('Mitarbeitende');
@@ -58,7 +58,7 @@ describe('Task 6 Content Updates', () => {
expect(deContent).toContain('title: "Kabelleitungsnetzbau"');
expect(deContent).toContain('excerpt: "Professioneller Kabelleitungsnetzbau');
expect(deContent).toContain('Kabelmontagen bis 110 kV');
expect(deContent).toContain('subtitle="Komplette Infrastruktur-Lösungen für Nieder-, Mittel- und Hochspannungsnetze bis 110 kV"');
expect(deContent).toContain('subtitle="Komplette Infrastruktur-Lösungen für Hoch-, Mittel- und Niederspannungsnetze sowie Kabelmontagen bis 110 kV"');
expect(deContent).toContain('"Kabelmontagen bis 110 kV"');
// EN Kabelnetzbau