> e-tib-nextjs@2.4.40 lint /Volumes/Alpha SSD/Coding/e-tib.com
> eslint . --format json
[{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/[slug]/opengraph-image.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/[slug]/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'redirect' is defined but never used.","line":1,"column":20,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":28,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"redirect"},"fix":{"range":[17,27],"text":""},"desc":"Remove unused variable \"redirect\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":64,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":64,"endColumn":17},{"ruleId":"no-empty","severity":2,"message":"Empty block statement.","line":64,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":64,"endColumn":21,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2772,2772],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":75,"column":7,"nodeType":"JSXOpeningElement","endLine":75,"endColumn":102},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":95,"column":24,"nodeType":"JSXOpeningElement","endLine":95,"endColumn":127}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { notFound, redirect } from 'next/navigation';\nimport { getImageProps } from 'next/image';\nimport { Container, Badge, Heading } from '@/components/ui';\nimport { getTranslations, setRequestLocale } from 'next-intl/server';\nimport { Metadata } from 'next';\nimport { getPageBySlug } from '@/lib/pages';\nimport { mapSlugToFileSlug, mapFileSlugToTranslated } from '@/lib/slugs';\nimport { MDXRemote } from 'next-mdx-remote/rsc';\nimport { SITE_URL } from '@/lib/schema';\nimport TrackedLink from '@/components/analytics/TrackedLink';\nimport { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';\nimport { getAllReferences } from '@/lib/references';\nimport { defaultLocations, minorLocations } from '@/lib/map-data';\n// Import components used in MDX\nimport { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';\nimport { SubCompanyTiles as HomeSubCompanyTiles } from '@/components/blocks/SubCompanyTiles';\nimport { CompetenceBentoGrid as HomeCompetenceBentoGrid } from '@/components/blocks/CompetenceBentoGrid';\nimport { ReferencesSlider as HomeReferencesSlider } from '@/components/blocks/ReferencesSlider';\nimport { CompanyTimeline } from '@/components/blocks/CompanyTimeline';\nimport { JobListingBlock } from '@/components/blocks/JobListingBlock';\nimport { ServiceDetailGrid } from '@/components/blocks/ServiceDetailGrid';\nimport { BenefitGrid } from '@/components/blocks/BenefitGrid';\nimport { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';\nimport { FaqBlock } from '@/components/blocks/FaqBlock';\nimport { CertificatesBlock } from '@/components/blocks/CertificatesBlock';\nimport { HeroSection } from '@/components/blocks/HeroSection';\nimport { TeamGrid } from '@/components/blocks/TeamGrid';\nimport { DeepDrillAnimation } from '@/components/blocks/DeepDrillAnimation';\nimport { DataGridPulse } from '@/components/blocks/DataGridPulse';\nimport { GrowthChart } from '@/components/blocks/GrowthChart';\nimport JsonLd from '@/components/JsonLd';\nimport { Button } from '@/components/ui/Button';\nimport { AnimatedCounter } from '@/components/ui';\n\n\nconst mdxComponents = {\n HomeHero,\n HomeSubCompanyTiles,\n HomeCompetenceBentoGrid,\n HomeReferencesSlider,\n CompanyTimeline,\n JobListingBlock,\n ServiceDetailGrid,\n BenefitGrid,\n InteractiveGermanyMap,\n FaqBlock,\n CertificatesBlock,\n HeroSection,\n TeamGrid,\n JsonLd,\n Button,\n Badge,\n Heading,\n AnimatedCounter,\n GrowthChart,\n DeepDrillAnimation,\n DataGridPulse,\n ResponsiveImage: (props: any) => {\n let src = props.src;\n if (typeof src === 'string' && src.startsWith('/_next/image')) {\n try {\n const urlParam = new URLSearchParams(src.split('?')[1]).get('url');\n if (urlParam) src = decodeURIComponent(urlParam);\n } catch (e) {}\n }\n const { props: { srcSet, src: finalSrc, sizes } } = getImageProps({\n src,\n alt: props.alt || '',\n width: 1920,\n height: 1080,\n sizes: '(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 50vw',\n quality: 80,\n });\n return (\n \n );\n },\n // Standard HTML element mapping for consistent E-TIB typography\n h1: (props: any) => , // Hidden because Hero handles H1\n h2: (props: any) => ,\n h3: (props: any) => ,\n h4: (props: any) => ,\n p: (props: any) =>
,\n ul: (props: any) => ,\n ol: (props: any) => ,\n li: (props: any) => (\n \n ),\n a: (props: any) => ,\n strong: (props: any) => ,\n blockquote: (props: any) => (\n \n ),\n hr: (props: any) => ,\n img: (props: any) => ,\n};\n\ninterface PageProps {\n params: Promise<{\n locale: string;\n slug: string;\n }>;\n}\n\nexport async function generateMetadata({ params }: PageProps): Promise {\n const { locale, slug } = await params;\n \n // Guard against invalid locales\n if (locale !== 'de' && locale !== 'en') return {};\n\n const pageData = await getPageBySlug(slug, locale);\n\n if (!pageData) return {};\n\n const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale);\n const deSlug = await mapFileSlugToTranslated(fileSlug, 'de');\n const enSlug = await mapFileSlugToTranslated(fileSlug, 'en');\n\n // Determine correct localized slug based on current locale\n const currentLocaleSlug = locale === 'de' ? deSlug : enSlug;\n\n return {\n title: pageData.frontmatter.title,\n description: pageData.frontmatter.excerpt || '',\n alternates: {\n canonical: `${SITE_URL}/${locale}/${currentLocaleSlug}`,\n languages: {\n de: `${SITE_URL}/de/${deSlug}`,\n en: `${SITE_URL}/en/${enSlug}`,\n 'x-default': `${SITE_URL}/en/${enSlug}`,\n },\n },\n openGraph: {\n title: `${pageData.frontmatter.title} | E-TIB`,\n description: pageData.frontmatter.excerpt || '',\n url: `${SITE_URL}/${locale}/${currentLocaleSlug}`,\n },\n twitter: {\n card: 'summary_large_image',\n title: `${pageData.frontmatter.title} | E-TIB`,\n description: pageData.frontmatter.excerpt || '',\n },\n };\n}\n\nexport default async function Page(props: { params: Promise<{ locale: string; slug: string }> }) {\n const { locale, slug } = await props.params;\n \n // Guard against invalid locales (e.g. favicon.ico) hitting the DB\n if (locale !== 'de' && locale !== 'en') {\n notFound();\n }\n \n setRequestLocale(locale);\n const pageData = await getPageBySlug(slug, locale);\n const t = await getTranslations('StandardPage');\n\n if (!pageData) {\n notFound();\n }\n\n // Redirect logic removed to simplify for now, as slugs match file names\n /*\n const fileSlug = await mapSlugToFileSlug(pageData.slug || slug, locale);\n const correctSlug = await mapFileSlugToTranslated(fileSlug, locale);\n if (correctSlug && correctSlug !== slug) {\n redirect(`/${locale}/${correctSlug}`);\n }\n */\n\n // Determine layout type\n const isFullBleed = pageData.frontmatter.layout === 'fullBleed';\n\n // Fetch references to enrich the map if it's used\n const references = await getAllReferences(locale);\n const allLocations = [...defaultLocations, ...minorLocations];\n const enrichedLocations = allLocations.map(loc => {\n if (loc.type === 'project') {\n const ref = references.find(r => r.slug === loc.id);\n if (ref) {\n return {\n ...loc,\n featuredImage: ref.frontmatter.featuredImage,\n details: [\n ref.frontmatter.client || t('client'),\n ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear().toString(),\n ],\n };\n }\n }\n return loc;\n });\n\n const pageMdxComponents = {\n ...mdxComponents,\n InteractiveGermanyMap: (props: any) => ,\n HomeReferencesSlider: (props: any) => ({\n id: r.slug,\n slug: r.slug,\n title: r.frontmatter.title,\n category: Array.isArray(r.frontmatter.category) ? r.frontmatter.category[0] : (r.frontmatter.category || 'Projekt'),\n image: r.frontmatter.featuredImage\n }))} />,\n };\n\n return (\n \n {/* Generic Hero Section (only for standard pages) */}\n {!isFullBleed && (\n
\n \n \n \n \n {t('badge')}\n \n \n {pageData.frontmatter.title}\n \n
\n \n \n )}\n\n {/* Main Content Area */}\n {isFullBleed ? (\n
\n \n
\n ) : (\n
\n {/* Excerpt/Lead paragraph if available */}\n {pageData.frontmatter.excerpt && (\n \n
\n {pageData.frontmatter.excerpt}\n
\n
\n )}\n\n {/* Main content */}\n \n \n )}\n\n {/* Support Section */}\n
\n \n
\n
\n
{t('needHelp')} \n
{t('supportTeamAvailable')}
\n
\n \n {t('contactUs')}\n \n →\n \n \n \n \n
\n
\n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/blog/[slug]/opengraph-image.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/blog/[slug]/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'redirect' is defined but never used.","line":1,"column":20,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":28,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"redirect"},"fix":{"range":[17,27],"text":""},"desc":"Remove unused variable \"redirect\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":34,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":34,"endColumn":17},{"ruleId":"no-empty","severity":2,"message":"Empty block statement.","line":34,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":34,"endColumn":21,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[1584,1584],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":45,"column":7,"nodeType":"JSXOpeningElement","endLine":45,"endColumn":102},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":60,"column":24,"nodeType":"JSXOpeningElement","endLine":60,"endColumn":127}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { notFound, redirect } from 'next/navigation';\nimport { getImageProps } from 'next/image';\nimport JsonLd from '@/components/JsonLd';\nimport { SITE_URL } from '@/lib/schema';\nimport {\n getPostBySlug,\n getAdjacentPosts,\n getReadingTime,\n getHeadings,\n} from '@/lib/blog';\nimport { Metadata } from 'next';\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport PostNavigation from '@/components/blog/PostNavigation';\nimport PowerCTA from '@/components/blog/PowerCTA';\nimport TableOfContents from '@/components/blog/TableOfContents';\nimport { Heading } from '@/components/ui';\nimport { setRequestLocale } from 'next-intl/server';\nimport BlogEngagementTracker from '@/components/analytics/BlogEngagementTracker';\nimport { MDXRemote } from 'next-mdx-remote/rsc';\n\nconst mdxComponents = {\n Heading,\n h1: (props: any) => , // Hidden because Hero handles H1\n h2: (props: any) => ,\n h3: (props: any) => ,\n h4: (props: any) => ,\n ResponsiveImage: (props: any) => {\n let src = props.src;\n if (typeof src === 'string' && src.startsWith('/_next/image')) {\n try {\n const urlParam = new URLSearchParams(src.split('?')[1]).get('url');\n if (urlParam) src = decodeURIComponent(urlParam);\n } catch (e) {}\n }\n const { props: { srcSet, src: finalSrc, sizes } } = getImageProps({\n src,\n alt: props.alt || '',\n width: 1920,\n height: 1080,\n sizes: '(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 50vw',\n quality: 80,\n });\n return (\n \n );\n },\n p: (props: any) =>
,\n ul: (props: any) => ,\n ol: (props: any) => ,\n li: (props: any) => (\n \n ),\n a: (props: any) => ,\n strong: (props: any) => ,\n blockquote: (props: any) => (\n \n ),\n hr: (props: any) => ,\n img: (props: any) => ,\n};\n\ninterface BlogPostProps {\n params: Promise<{\n locale: string;\n slug: string;\n }>;\n}\n\nexport async function generateMetadata({ params }: BlogPostProps): Promise {\n const { locale, slug } = await params;\n const post = await getPostBySlug(slug, locale);\n\n if (!post) return {};\n\n const description = post.frontmatter.excerpt || '';\n return {\n title: post.frontmatter.title,\n description: description,\n alternates: {\n canonical: `${SITE_URL}/${locale}/blog/${post.slug}`,\n },\n openGraph: {\n title: `${post.frontmatter.title} | E-TIB`,\n description: description,\n type: 'article',\n publishedTime: post.frontmatter.date,\n authors: ['E-TIB'],\n url: `${SITE_URL}/${locale}/blog/${post.slug}`,\n },\n twitter: {\n card: 'summary_large_image',\n title: `${post.frontmatter.title} | E-TIB`,\n description: description,\n },\n };\n}\n\nexport default async function BlogPost({ params }: BlogPostProps) {\n const { locale, slug } = await params;\n setRequestLocale(locale);\n const post = await getPostBySlug(slug, locale);\n\n if (!post) {\n notFound();\n }\n\n const { prev, next, isPrevRandom, isNextRandom } = await getAdjacentPosts(post.slug, locale);\n const headings = getHeadings(post.content);\n const readingTime = getReadingTime(post.content);\n\n return (\n \n \n\n {/* Featured Image Header */}\n {post.frontmatter.featuredImage ? (\n \n
\n \n
\n
\n\n {/* Title overlay on image */}\n
\n
\n
\n {post.frontmatter.category && (\n
\n \n {post.frontmatter.category}\n \n
\n )}\n
\n {post.frontmatter.title}\n \n
\n \n {new Date(post.frontmatter.date).toLocaleDateString(locale || 'de', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n })}\n \n \n {readingTime} min read \n {(new Date(post.frontmatter.date) > new Date() ||\n post.frontmatter.public === false) && (\n <>\n \n \n Draft Preview\n \n >\n )}\n
\n
\n
\n
\n
\n ) : (\n \n \n {post.frontmatter.category && (\n
\n \n {post.frontmatter.category}\n \n
\n )}\n
\n {post.frontmatter.title}\n \n
\n \n {new Date(post.frontmatter.date).toLocaleDateString(locale || 'de', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n })}\n \n \n {readingTime} min read \n {(new Date(post.frontmatter.date) > new Date() ||\n post.frontmatter.public === false) && (\n <>\n \n \n Draft Preview\n \n >\n )}\n
\n
\n \n )}\n\n {/* Main Content Area with Sticky Narrative Layout */}\n \n
\n {/* Left Column: Content */}\n
\n {/* Excerpt/Lead paragraph if available */}\n {post.frontmatter.excerpt && (\n
\n
\n {post.frontmatter.excerpt}\n
\n
\n )}\n\n {/* Main content */}\n
\n \n
\n\n {/* Power CTA */}\n
\n\n {/* Post Navigation */}\n
\n\n {/* Back to blog link */}\n
\n
\n
\n \n \n {locale === 'de' ? 'Zurück zur Übersicht' : 'Back to Overview'}\n \n
\n
\n\n {/* Right Column: Sticky Sidebar - TOC */}\n
\n
\n
\n\n {/* Structured Data */}\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/blog/opengraph-image.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/blog/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/contact/opengraph-image.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/contact/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/error.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'AlertTriangle' is defined but never used.","line":6,"column":30,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":43,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"AlertTriangle"},"fix":{"range":[173,188],"text":""},"desc":"Remove unused variable \"AlertTriangle\"."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport { useEffect } from 'react';\nimport { useTranslations } from 'next-intl';\nimport { Button, Heading } from '@/components/ui';\nimport { Terminal, Activity, AlertTriangle, RefreshCw, Home, ShieldAlert } from 'lucide-react';\nimport { m } from 'framer-motion';\n\nexport default function Error({\n error,\n reset,\n}: {\n error: Error & { digest?: string };\n reset: () => void;\n}) {\n const t = useTranslations('Error');\n\n useEffect(() => {\n // Force standard solid header\n document.body.setAttribute('data-header-variant', 'standard');\n \n // Treat \"Failed to find Server Action\" as a deployment sync issue and reload\n if (error?.message?.includes('Failed to find Server Action')) {\n window.location.reload();\n return;\n }\n\n console.error('Application error caught by boundary', {\n message: error?.message || 'Unknown error',\n stack: error?.stack,\n digest: error?.digest,\n });\n \n return () => {\n document.body.removeAttribute('data-header-variant');\n };\n }, [error]);\n\n return (\n \n {/* Industrial Blueprint Background */}\n
\n\n {/* Decorative Technical Annotations */}\n
\n
\n System_Core_Access :: Restricted\n
\n
\n Protocol_Failure :: Type_500\n
\n
\n
\n
\n \n {/* Background Glow */}\n
\n\n
\n {/* The \"Broken\" Machine Visual */}\n
\n
\n \n \n 500\n {/* Glitch Layers */}\n 500 \n 500 \n \n \n \n {/* Warning Overlay */}\n \n \n \n \n
\n \n
\n\n
\n {/* Main Content */}\n
\n
\n
\n CRITICAL_SYSTEM_OVERRIDE :: ACTIVE\n
\n\n
\n {t('title')}\n \n\n
\n {t('description')}\n
\n\n
\n reset()} \n variant=\"primary\" \n className=\"group h-14 px-10 shadow-[0_0_30px_rgba(var(--color-primary),0.2)] hover:shadow-primary/40 transition-all\"\n >\n \n {t('tryAgain')}\n \n \n \n {t('goHome')}\n \n
\n
\n\n {/* Technical Log Terminal */}\n
\n
\n
\n
\n
[SYSTEM] Initializing recovery protocol...
\n
[ERROR] Connection timeout at node 0x7F
\n
[INFO] Request ID: {error?.digest || '0xDEADBEEF'}
\n
[INFO] Timestamp: {new Date().toISOString()}
\n
[INFO] Trace: {error?.message?.slice(0, 40)}...
\n
_ Waiting for user input...
\n
\n
\n
\n
Status: Handshake_Failed
\n
\n
\n
\n
\n
\n
\n );\n}\n\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/errors/api/relay/route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/kontakt/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/layout.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'cookies' is defined but never used.","line":16,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":16,"endColumn":17,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"cookies"},"fix":{"range":[704,744],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'serverServices' is assigned a value but never used.","line":157,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":157,"endColumn":23},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'feedbackEnabled' is assigned a value but never used.","line":176,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":176,"endColumn":24}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Footer } from '@/components/layout/Footer';\nimport { Header } from '@/components/layout/Header';\nimport JsonLd from '@/components/JsonLd';\nimport SkipLink from '@/components/SkipLink';\nimport AnalyticsShell from '@/components/analytics/AnalyticsShell';\nimport { Metadata, Viewport } from 'next';\nimport { NextIntlClientProvider } from 'next-intl';\nimport { getMessages } from 'next-intl/server';\nimport '../../styles/globals.css';\nimport { SITE_URL } from '@/lib/schema';\nimport FeedbackClientWrapper from '@/components/FeedbackClientWrapper';\n\nimport { setRequestLocale } from 'next-intl/server';\nimport { Inter } from 'next/font/google';\nimport { mapFileSlugToTranslated } from '@/lib/slugs';\nimport { cookies } from 'next/headers';\n\nimport { TransitionProvider } from '@/components/providers/TransitionProvider';\n\nimport { DynamicInitialLoader as InitialLoader } from '@/components/providers/DynamicInitialLoader';\nimport { DynamicMobileBottomNav as MobileBottomNav } from '@/components/layout/DynamicMobileBottomNav';\n\nimport { DynamicCorporateBackground as CorporateBackground } from '@/components/decorations/DynamicCorporateBackground';\nimport { DynamicPageTransitionShutter as PageTransitionShutter } from '@/components/providers/DynamicPageTransitionShutter';\nimport { DynamicFramerMotion as FramerMotionProvider } from '@/components/providers/DynamicFramerMotion';\n\nconst inter = Inter({\n subsets: ['latin'],\n display: 'swap',\n variable: '--font-inter',\n});\n\nexport async function generateMetadata(props: {\n params: Promise<{ locale: string }>;\n}): Promise {\n const params = await props.params;\n const { locale } = params;\n\n const baseUrl = SITE_URL;\n\n return {\n title: {\n template: '%s | E-TIB',\n default: 'E-TIB | Die Experten für Kabelnetzbau',\n },\n description: 'Ihr Partner für Kabelleitungsnetzbau, Horizontalspülbohrungen, Planung und Vermessung in Guben und überregional.',\n metadataBase: new URL(baseUrl),\n manifest: '/manifest.webmanifest',\n alternates: {\n canonical: `/${locale}`,\n languages: {\n 'de': `/de`,\n 'en': `/en`,\n 'x-default': `/de`,\n },\n },\n };\n}\n\nexport const viewport: Viewport = {\n width: 'device-width',\n initialScale: 1,\n maximumScale: 5,\n userScalable: true,\n viewportFit: 'cover',\n themeColor: '#117c61',\n};\n\nexport async function generateStaticParams() {\n return [{ locale: 'de' }, { locale: 'en' }];\n}\n\nexport default async function Layout(props: {\n children: React.ReactNode;\n params: Promise<{ locale: string }>;\n}) {\n const params = await props.params;\n const { locale } = params;\n const { children } = props;\n const supportedLocales = ['en', 'de'];\n const localeStr = (typeof locale === 'string' ? locale : '').trim();\n const safeLocale = supportedLocales.includes(localeStr) ? localeStr : 'en';\n\n setRequestLocale(safeLocale);\n \n let messages: Record = {};\n try {\n messages = await getMessages();\n } catch (err) {\n console.error('[Layout] Failed to load messages:', err);\n messages = {};\n }\n \n const navLinks = [\n { \n label: safeLocale === 'de' ? 'Kompetenzen' : 'Competencies', \n url: `/${safeLocale}/${await mapFileSlugToTranslated('kompetenzen', safeLocale)}` \n },\n { \n label: safeLocale === 'de' ? 'Über uns' : 'About Us', \n url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}`,\n children: [\n { label: safeLocale === 'de' ? 'Firma' : 'Company', url: `/${safeLocale}/${await mapFileSlugToTranslated('ueber-uns', safeLocale)}` },\n { label: safeLocale === 'de' ? 'Unser Team' : 'Our Team', url: `/${safeLocale}/team` },\n { label: safeLocale === 'de' ? 'Zertifikate' : 'Certificates', url: `/${safeLocale}/zertifikate` },\n { label: safeLocale === 'de' ? 'Standorte' : 'Locations', url: `/${safeLocale}/standorte` }\n ]\n },\n {\n label: safeLocale === 'de' ? 'Referenzen' : 'References',\n url: `/${safeLocale}/referenzen`\n },\n { \n label: safeLocale === 'de' ? 'Karriere' : 'Career', \n url: `/${safeLocale}/${safeLocale === 'de' ? 'karriere' : 'career'}` \n },\n { \n label: safeLocale === 'de' ? 'Messen' : 'Events', \n url: `/${safeLocale}/${await mapFileSlugToTranslated('messen', safeLocale)}` \n }\n ];\n \n const companyInfo = {\n contactEmail: 'info@e-tib.com',\n contactPhone: '+49 (0) 3561 / 68577 33',\n address: 'Gewerbestraße 22\\n03172 Guben'\n };\n\n // Pick only the namespaces required by client components to reduce the hydration payload size\n const clientKeys = [\n 'Footer',\n 'Navigation',\n 'Contact',\n 'Products',\n 'Team',\n 'Home',\n 'Error',\n 'StandardPage',\n 'Brochure',\n 'JobListingBlock',\n 'CallToAction',\n 'InteractiveGermanyMap',\n 'ReferencesSlider',\n 'CompanyTimeline',\n 'TeamGrid',\n 'AISearch',\n 'GrowthChart',\n ];\n const clientMessages: Record = {};\n for (const key of clientKeys) {\n if (messages[key]) {\n clientMessages[key] = messages[key];\n }\n }\n\n const { getServerAppServices } = await import('@/lib/services/create-services.server');\n const serverServices = getServerAppServices();\n\n try {\n // Disable analytics in CI to prevent console noise/score penalties\n if (process.env.NEXT_PUBLIC_CI === 'true') {\n // Skip setting server context for analytics in CI\n }\n\n // Server-side analytics tracking removed to prevent duplicate/empty events.\n // Client-side AnalyticsProvider handles all pageviews.\n } catch {\n if (process.env.NODE_ENV !== 'production' || !process.env.CI) {\n console.warn(\n '[Layout] Static generation detected or headers unavailable, skipping server-side analytics context',\n );\n }\n }\n\n // Read directly from process.env — bypasses all abstraction to guarantee correctness\n const feedbackEnabled = process.env.NEXT_PUBLIC_FEEDBACK_ENABLED === 'true';\n const hasSeenLoader = true; // Disabled to allow static generation and instant LCP (Lighthouse 100)\n\n return (\n \n \n \n \n\n \n \n \n \n \n \n \n \n \n \n \n \n \n {children}\n \n\n \n \n \n\n \n \n \n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/not-found.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Terminal' is defined but never used.","line":6,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":18,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Terminal"},"fix":{"range":[155,164],"text":""},"desc":"Remove unused variable \"Terminal\"."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport { useEffect } from 'react';\nimport { useTranslations } from 'next-intl';\nimport { Button, Heading } from '@/components/ui';\nimport { Terminal, Search, Home, Mail, Activity, Cpu } from 'lucide-react';\nimport { m } from 'framer-motion';\nimport ClientNotFoundTracker from '@/components/analytics/ClientNotFoundTracker';\n\nexport default function NotFound() {\n const t = useTranslations('Error.notFound');\n\n useEffect(() => {\n // Force standard solid header\n document.body.setAttribute('data-header-variant', 'standard');\n return () => {\n document.body.removeAttribute('data-header-variant');\n };\n }, []);\n\n return (\n \n
\n \n {/* Industrial Blueprint Background */}\n
\n\n {/* Decorative Technical Annotations */}\n
\n
\n Discovery_Service :: Failure\n
\n
\n Address_Resolution :: 0x404\n
\n
\n
\n
\n \n {/* Background Glow */}\n
\n\n
\n {/* 404 Visual */}\n
\n
\n \n \n 404\n {/* Glitch Layers */}\n 404 \n 404 \n \n \n \n {/* Search Scan Overlay */}\n \n \n \n \n
\n \n
\n\n
\n {/* Main Content */}\n
\n
\n \n OBJECT_NOT_FOUND :: SCAN_COMPLETE\n
\n\n
\n {t('title')}\n \n\n
\n {t('description')}\n
\n\n
\n \n \n {t('cta')}\n \n \n \n Support kontaktieren\n \n
\n
\n\n {/* Technical Log Terminal */}\n
\n
\n
\n
\n
[SCAN] Initializing sector sweep...
\n
[INFO] Indexing available nodes...
\n
[INFO] Searching for: {typeof window !== 'undefined' ? window.location.pathname : 'unknown_resource'}
\n
[WARN] Resource 404: Not found in current branch
\n
[INFO] Redirecting to fallback_strategy...
\n
_ Ready for manual redirect...
\n
\n
\n
\n
Status: Resolution_Failed
\n
\n
\n
\n
\n
\n
\n );\n}\n\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/opengraph-image.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'t' is assigned a value but never used.","line":12,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":12,"endColumn":10}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { ImageResponse } from 'next/og';\nimport { getTranslations } from 'next-intl/server';\nimport { OGImageTemplate } from '@/components/OGImageTemplate';\nimport { getOgFonts, getOgBackground, getOgLogo, OG_IMAGE_SIZE } from '@/lib/og-helper';\n\nexport const size = OG_IMAGE_SIZE;\nexport const contentType = 'image/png';\nexport const runtime = 'nodejs';\n\nexport default async function Image({ params }: { params: Promise<{ locale: string }> }) {\n const { locale } = await params;\n const t = await getTranslations({ locale, namespace: 'Index.meta' });\n\n const fonts = await getOgFonts();\n const bg = getOgBackground();\n const logo = getOgLogo();\n\n return new ImageResponse(\n ,\n {\n ...OG_IMAGE_SIZE,\n fonts,\n },\n );\n}","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":47,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":47,"endColumn":17},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":65,"column":7,"nodeType":"JSXOpeningElement","endLine":72,"endColumn":9},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":82,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":82,"endColumn":17},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":100,"column":7,"nodeType":"JSXOpeningElement","endLine":108,"endColumn":9}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Metadata } from 'next';\nimport { setRequestLocale } from 'next-intl/server';\nimport { notFound } from 'next/navigation';\nimport { getMdxContent } from '@/lib/mdx';\nimport { MDXRemote } from 'next-mdx-remote/rsc';\n\nimport nextDynamic from 'next/dynamic';\nimport { getImageProps } from 'next/image';\n\nimport { HeroVideo as HomeHero } from '@/components/blocks/HeroVideo';\nconst HomeSubCompanyTiles = nextDynamic(() => import('@/components/blocks/SubCompanyTiles').then(mod => mod.SubCompanyTiles));\nconst HomeCompetenceBentoGrid = nextDynamic(() => import('@/components/blocks/CompetenceBentoGrid').then(mod => mod.CompetenceBentoGrid));\nconst HomeReferencesSlider = nextDynamic(() => import('@/components/blocks/ReferencesSlider').then(mod => mod.ReferencesSlider));\nconst FaqBlock = nextDynamic(() => import('@/components/blocks/FaqBlock').then(mod => mod.FaqBlock));\nconst CertificatesBlock = nextDynamic(() => import('@/components/blocks/CertificatesBlock').then(mod => mod.CertificatesBlock));\nimport { HeroSection } from '@/components/blocks/HeroSection';\nimport JsonLd from '@/components/JsonLd';\n\nimport { Button } from '@/components/ui/Button';\nimport { Heading } from '@/components/ui/Heading';\nconst AnimatedCounter = nextDynamic(() => import('@/components/ui').then(mod => mod.AnimatedCounter));\nconst InteractiveGermanyMap = nextDynamic(() => import('@/components/blocks/InteractiveGermanyMap').then(mod => mod.InteractiveGermanyMap));\nconst ScaleOfImpact = nextDynamic(() => import('@/components/blocks/ScaleOfImpact').then(mod => mod.ScaleOfImpact));\nimport { getAllReferences } from '@/lib/references';\n\nconst mdxComponents = (references: any[]) => ({\n HomeHero,\n HomeSubCompanyTiles,\n HomeCompetenceBentoGrid,\n HomeReferencesSlider: (props: any) => ,\n FaqBlock,\n CertificatesBlock,\n HeroSection,\n JsonLd,\n Button,\n Heading,\n AnimatedCounter,\n InteractiveGermanyMap,\n ScaleOfImpact,\n ResponsiveImage: (props: any) => {\n // Parse src if it's already a next image proxy\n let src = props.src;\n if (typeof src === 'string' && src.startsWith('/_next/image')) {\n try {\n const urlParam = new URLSearchParams(src.split('?')[1]).get('url');\n if (urlParam) src = decodeURIComponent(urlParam);\n } catch (e) {\n // Fallback\n }\n }\n \n // Use Next.js getImageProps for responsive srcSet generation\n const {\n props: { srcSet, src: finalSrc, sizes }\n } = getImageProps({\n src,\n alt: props.alt || '',\n width: 1920,\n height: 1080,\n sizes: '(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 50vw',\n quality: 80,\n });\n\n return (\n \n );\n },\n img: (props: any) => {\n // Parse src if it's already a next image proxy\n let src = props.src;\n if (typeof src === 'string' && src.startsWith('/_next/image')) {\n try {\n const urlParam = new URLSearchParams(src.split('?')[1]).get('url');\n if (urlParam) src = decodeURIComponent(urlParam);\n } catch (e) {\n // Fallback\n }\n }\n \n // Use Next.js getImageProps for responsive srcSet generation\n const {\n props: { srcSet, src: finalSrc, sizes }\n } = getImageProps({\n src,\n alt: props.alt || '',\n width: 1920,\n height: 1080,\n sizes: '(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 50vw',\n quality: 80,\n });\n\n return (\n \n );\n }\n});\n\nexport async function generateMetadata(props: any): Promise {\n const { locale } = await props.params;\n if (locale !== 'de' && locale !== 'en') return {};\n \n const mdx = await getMdxContent(locale, 'home');\n \n return {\n title: mdx?.frontmatter?.title || (locale === 'de' ? 'Startseite' : 'Home'),\n description: mdx?.frontmatter?.description,\n };\n}\n\nimport { preload } from 'react-dom';\n\nexport default async function Home(props: { params: Promise<{ locale: string }> }) {\n const { locale } = await props.params;\n \n if (locale !== 'de' && locale !== 'en') {\n notFound();\n }\n \n setRequestLocale(locale);\n preload('/germany-map.svg', { as: 'image', fetchPriority: 'high' });\n\n const mdx = await getMdxContent(locale, 'home');\n const allReferences = await getAllReferences(locale);\n \n // Transform ReferenceData to the format expected by ReferencesSlider\n const sliderReferences = allReferences.map(ref => ({\n id: ref.slug,\n title: ref.frontmatter.title,\n slug: ref.slug,\n category: ref.frontmatter.location, // Using location as category if not specified\n image: ref.frontmatter.featuredImage\n })).slice(0, 6);\n\n if (!mdx) {\n notFound();\n }\n\n return (\n \n \n \n );\n}\n\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/referenzen/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Badge' is defined but never used.","line":1,"column":30,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":35,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Badge"},"fix":{"range":[27,34],"text":""},"desc":"Remove unused variable \"Badge\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'HeroSection' is defined but never used.","line":12,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":12,"endColumn":21,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"HeroSection"},"fix":{"range":[663,726],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'e' is defined but never used.","line":50,"column":16,"nodeType":"Identifier","messageId":"unusedVar","endLine":50,"endColumn":17},{"ruleId":"no-empty","severity":2,"message":"Empty block statement.","line":50,"column":19,"nodeType":"BlockStatement","messageId":"unexpected","endLine":50,"endColumn":21,"suggestions":[{"messageId":"suggestComment","data":{"type":"block"},"fix":{"range":[2380,2380],"text":" /* empty */ "},"desc":"Add comment inside empty block statement."}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":61,"column":7,"nodeType":"JSXOpeningElement","endLine":61,"endColumn":102}],"suppressedMessages":[],"errorCount":1,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Container, Heading, Badge } from '@/components/ui';\nimport { getImageProps } from 'next/image';\nimport { getTranslations, setRequestLocale } from 'next-intl/server';\nimport { Metadata } from 'next';\nimport { getAllReferences } from '@/lib/references';\nimport TrackedLink from '@/components/analytics/TrackedLink';\nimport { MapPin, Calendar, Briefcase, Zap, Activity, ShieldCheck, Wrench, CheckCircle2 } from 'lucide-react';\nimport { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';\nimport { SITE_URL } from '@/lib/schema';\nimport Image from 'next/image';\nimport { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';\nimport { HeroSection } from '@/components/blocks/HeroSection';\nimport { defaultLocations, minorLocations } from '@/lib/map-data';\nimport { MDXRemote } from 'next-mdx-remote/rsc';\n\nconst CustomLi = ({ children, ...props }: any) => {\n let text = '';\n if (typeof children === 'string') text = children;\n else if (Array.isArray(children)) text = children.map((c: any) => typeof c === 'string' ? c : '').join(' ');\n else if (children?.props?.children) text = children.props.children;\n \n const lowerText = String(text).toLowerCase();\n \n let Icon = CheckCircle2;\n if (lowerText.includes('kabel')) Icon = Zap;\n else if (lowerText.includes('bohrung')) Icon = Activity;\n else if (lowerText.includes('rohr')) Icon = ShieldCheck;\n else if (lowerText.includes('montage')) Icon = Wrench;\n else if (lowerText.includes('vlf') || lowerText.includes('otdr')) Icon = Zap;\n \n return (\n \n \n p]:mb-0\">{children} \n \n );\n};\n\nconst mdxComponents = {\n ul: (props: any) => ,\n li: CustomLi,\n p: (props: any) =>
,\n Heading,\n ResponsiveImage: (props: any) => {\n let src = props.src;\n if (typeof src === 'string' && src.startsWith('/_next/image')) {\n try {\n const urlParam = new URLSearchParams(src.split('?')[1]).get('url');\n if (urlParam) src = decodeURIComponent(urlParam);\n } catch (e) {}\n }\n const { props: { srcSet, src: finalSrc, sizes } } = getImageProps({\n src,\n alt: props.alt || '',\n width: 1920,\n height: 1080,\n sizes: '(max-width: 768px) 100vw, (max-width: 1200px) 75vw, 50vw',\n quality: 80,\n });\n return (\n \n );\n },\n};\n\ninterface PageProps {\n params: Promise<{\n locale: string;\n }>;\n}\n\nexport async function generateMetadata({ params }: PageProps): Promise {\n const { locale } = await params;\n if (locale !== 'de' && locale !== 'en') return {};\n\n return {\n title: 'Referenzen | E-TIB Gruppe',\n description: 'Erfolgreich abgeschlossene Projekte der E-TIB Gruppe im Bereich Kabelnetzbau, Spülbohrtechnik und Netzinfrastruktur.',\n alternates: {\n canonical: `${SITE_URL}/${locale}/referenzen`,\n languages: {\n de: `${SITE_URL}/de/referenzen`,\n en: `${SITE_URL}/en/referenzen`,\n 'x-default': `${SITE_URL}/en/referenzen`,\n },\n },\n };\n}\n\nfunction cleanLocation(location?: string): string {\n if (!location) return '';\n // Remove 5 digit postal codes\n let cleaned = location.replace(/\\b\\d{5}\\b/g, '');\n // Clean up multiple spaces, slashes and commas\n cleaned = cleaned.replace(/\\s*,\\s*/g, ', ').replace(/\\s*\\/\\s*/g, ' / ').trim();\n // Remove leading/trailing slash/comma residues\n cleaned = cleaned.replace(/^[,/\\s]+|[,/\\s]+$/g, '').trim();\n return cleaned || location;\n}\n\nexport default async function ReferenzenOverview(props: { params: Promise<{ locale: string }> }) {\n const { locale } = await props.params;\n setRequestLocale(locale);\n const t = await getTranslations('StandardPage');\n\n const references = await getAllReferences(locale);\n \n const allLocations = [...defaultLocations, ...minorLocations];\n const enrichedLocations = allLocations.map(loc => {\n if (loc.type === 'project') {\n const ref = references.find(r => r.slug === loc.id);\n if (ref) {\n return {\n ...loc,\n featuredImage: ref.frontmatter.featuredImage,\n details: [\n ref.frontmatter.client || 'Kunde',\n ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear().toString(),\n ],\n };\n }\n }\n return loc;\n });\n\n return (\n \n {/* Map Section */}\n
\n\n {/* Main Content Area */}\n
\n \n {references.map((ref) => (\n
\n
\n {/* Image Section */}\n
\n {ref.frontmatter.featuredImage ? (\n
\n ) : (\n
\n \n
\n )}\n
\n \n {/* Location Badge */}\n
\n \n \n {cleanLocation(ref.frontmatter.location)}\n \n
\n
\n\n {/* Content Section */}\n
\n
\n {ref.frontmatter.title}\n \n \n {/* Project Details / Content */}\n {ref.content && (\n
\n \n
\n )}\n \n {/* Technical Meta Data */}\n
\n
\n {t('client')} \n \n \n {ref.frontmatter.client || t('client')} \n \n
\n
\n {t('period')} \n \n \n {ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear()} \n \n
\n
\n
\n
\n
\n ))}\n
\n\n {/* Support Section */}\n \n
\n
\n
{t('nextProjectTitle')} \n
{t('nextProjectDesc')}
\n
\n \n {t('contactUs')}\n \n →\n \n \n \n \n
\n
\n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/standorte/[slug]/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Phone' is defined but never used.","line":8,"column":18,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":23,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Phone"},"fix":{"range":[367,374],"text":""},"desc":"Remove unused variable \"Phone\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Mail' is defined but never used.","line":8,"column":25,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":29,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Mail"},"fix":{"range":[374,380],"text":""},"desc":"Remove unused variable \"Mail\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Navigation' is defined but never used.","line":8,"column":31,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":41,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Navigation"},"fix":{"range":[380,392],"text":""},"desc":"Remove unused variable \"Navigation\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'CheckCircle2' is defined but never used.","line":8,"column":43,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":55,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"CheckCircle2"},"fix":{"range":[392,406],"text":""},"desc":"Remove unused variable \"CheckCircle2\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'AnimatedGlossyBorder' is defined but never used.","line":11,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":11,"endColumn":30,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"AnimatedGlossyBorder"},"fix":{"range":[580,657],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":98,"column":13,"nodeType":"JSXOpeningElement","endLine":98,"endColumn":146}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Container, Badge, Heading } from '@/components/ui';\nimport { getTranslations, setRequestLocale } from 'next-intl/server';\nimport { Metadata } from 'next';\nimport { notFound } from 'next/navigation';\nimport { standorteData, getStandortById } from '@/lib/standorte-data';\nimport { SITE_URL } from '@/lib/schema';\nimport Image from 'next/image';\nimport { MapPin, Phone, Mail, Navigation, CheckCircle2, ArrowUpRight } from 'lucide-react';\nimport { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';\nimport TrackedLink from '@/components/analytics/TrackedLink';\nimport { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';\n\ninterface PageProps {\n params: Promise<{\n locale: string;\n slug: string;\n }>;\n}\n\nexport async function generateStaticParams() {\n const locales = ['de', 'en'];\n const params: { locale: string; slug: string }[] = [];\n \n for (const locale of locales) {\n for (const standort of standorteData) {\n params.push({ locale, slug: standort.id });\n }\n }\n \n return params;\n}\n\nexport async function generateMetadata({ params }: PageProps): Promise {\n const { locale, slug } = await params;\n if (locale !== 'de' && locale !== 'en') return {};\n \n const standort = getStandortById(slug);\n if (!standort) return {};\n\n return {\n title: `${standort.name} | E-TIB Gruppe`,\n description: standort.description[locale],\n alternates: {\n canonical: `${SITE_URL}/${locale}/standorte/${slug}`,\n languages: {\n de: `${SITE_URL}/de/standorte/${slug}`,\n en: `${SITE_URL}/en/standorte/${slug}`,\n 'x-default': `${SITE_URL}/en/standorte/${slug}`,\n },\n },\n };\n}\n\nexport default async function StandortDetail(props: { params: Promise<{ locale: string; slug: string }> }) {\n const { locale, slug } = await props.params;\n const safeLocale = locale === 'de' ? 'de' : 'en';\n setRequestLocale(safeLocale);\n const t = await getTranslations('StandardPage');\n \n const standort = getStandortById(slug);\n if (!standort) {\n notFound();\n }\n\n return (\n \n {/* Generic Hero Section to match standard pages */}\n
\n \n \n \n \n \n {standort.type === 'hq' ? (safeLocale === 'de' ? 'Hauptsitz' : 'Headquarters') : (safeLocale === 'de' ? 'Niederlassung' : 'Branch')}\n \n \n {standort.name}\n \n
\n \n \n\n {/* Main Content Area */}\n
\n {/* Excerpt/Lead paragraph */}\n \n
\n {standort.description[safeLocale]}\n
\n
\n\n {/* Main content in prose style */}\n \n
\n \n
\n\n
{safeLocale === 'de' ? 'Unsere Leistungen vor Ort' : 'Our Local Services'} \n
\n {standort.keyFeatures[safeLocale].map((feature, idx) => (\n \n {feature}\n \n ))}\n \n\n
{safeLocale === 'de' ? 'Kontakt & Anfahrt' : 'Contact & Directions'} \n \n
\n {safeLocale === 'de' ? 'Adresse:' : 'Address:'} \n {standort.address.street} \n {standort.address.city}\n
\n
\n \n {safeLocale === 'de' ? 'In Google Maps öffnen' : 'Open in Google Maps'}\n \n \n
\n\n
\n {safeLocale === 'de' ? 'Telefon:' : 'Phone:'} \n \n {standort.contact.phone}\n \n
\n\n
\n Email: \n \n {standort.contact.email}\n \n
\n\n {standort.gallery && standort.gallery.length > 0 && (\n <>\n
Galerie \n
\n {standort.gallery.map((img, idx) => (\n
\n \n
\n ))}\n
\n >\n )}\n
\n
\n \n\n {/* Support Section */}\n
\n \n
\n
\n
{t('needHelp')} \n
{t('supportTeamAvailable')}
\n
\n \n {t('contactUs')}\n \n →\n \n \n \n \n
\n
\n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/standorte/page.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'PageProps' is defined but never used.","line":12,"column":11,"nodeType":"Identifier","messageId":"unusedVar","endLine":12,"endColumn":20}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Container } from '@/components/ui/Container';\nimport { getTranslations, setRequestLocale } from 'next-intl/server';\nimport { Metadata } from 'next';\nimport TrackedLink from '@/components/analytics/TrackedLink';\nimport { MapPin, Navigation, ArrowUpRight } from 'lucide-react';\nimport { getButtonClasses, ButtonOverlay } from '@/components/ui/Button';\nimport { SITE_URL } from '@/lib/schema';\nimport Image from 'next/image';\nimport { standorteData, standorteLocations } from '@/lib/standorte-data';\nimport { InteractiveGermanyMap } from '@/components/blocks/InteractiveGermanyMap';\n\ninterface PageProps {\n params: Promise<{\n locale: string;\n }>;\n}\n\nexport async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise {\n const { locale } = await params;\n const safeLocale = locale === 'de' ? 'de' : 'en';\n\n return {\n title: safeLocale === 'en' ? 'Our Locations' : 'Unsere Standorte',\n description: safeLocale === 'en' ? 'Our operational locations across Germany.' : 'Unsere operativen Standorte deutschlandweit.',\n alternates: {\n canonical: `${SITE_URL}/${safeLocale}/standorte`,\n languages: {\n de: `${SITE_URL}/de/standorte`,\n en: `${SITE_URL}/en/standorte`,\n 'x-default': `${SITE_URL}/en/standorte`,\n },\n },\n };\n}\n\nimport { preload } from 'react-dom';\n\nexport default async function StandorteOverview(props: { params: Promise<{ locale: string }> }) {\n const { locale } = await props.params;\n const safeLocale = locale === 'de' ? 'de' : 'en';\n setRequestLocale(safeLocale);\n const t = await getTranslations('StandardPage');\n \n preload('/germany-map.svg', { as: 'image', fetchPriority: 'high' });\n\n return (\n \n {/* Map Hero Section */}\n
\n\n {/* Main Content Area */}\n
\n \n {standorteData.map((standort) => (\n
\n \n {/* Image Section */}\n
\n
\n
\n \n {/* Location Badge */}\n
\n \n \n {standort.address.city}\n \n
\n
\n\n {/* Content Section */}\n
\n
\n
\n {standort.name}\n \n
\n
\n \n
\n {standort.description[safeLocale]}\n
\n \n {/* Meta Data */}\n
\n
\n \n {safeLocale === 'de' ? 'Adresse' : 'Address'}\n \n \n \n {standort.address.street}, {standort.address.city} \n \n
\n
\n
\n
\n \n ))}\n
\n\n {/* Support Section */}\n \n
\n
\n
{t('nextProjectTitle')} \n
{t('nextProjectDesc')}
\n
\n \n {t('contactUs')}\n \n →\n \n \n \n \n
\n
\n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/template.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/[locale]/test/page.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/actions/contact.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/actions/getAnnotatorAssets.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/actions/submitAnnotations.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/api/feedback/route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/api/save-session/route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/api/whoami/route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/global-error.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/health/route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/manifest.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/robots.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/sitemap.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/app/stats/api/send/route.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/AnnotatorClientWrapper.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ContactForm.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ContactMap.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/DatasheetDownload.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ExcelDownload.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/FeedbackClientWrapper.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/JsonLd.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Graph' is defined but never used.","line":1,"column":30,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":35,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Graph"},"fix":{"range":[27,34],"text":""},"desc":"Remove unused variable \"Graph\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'_' is assigned a value but never used.","line":43,"column":31,"nodeType":"Identifier","messageId":"unusedVar","endLine":43,"endColumn":32},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'_' is assigned a value but never used.","line":48,"column":29,"nodeType":"Identifier","messageId":"unusedVar","endLine":48,"endColumn":30}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { Thing, WithContext, Graph } from 'schema-dts';\nimport { getOrganizationSchema, getLocalBusinessSchema, SITE_URL } from '@/lib/schema';\n\ninterface JsonLdProps {\n id?: string;\n data?: WithContext | WithContext[];\n disableGlobal?: boolean;\n}\n\nexport default function JsonLd({ id, data, disableGlobal = false }: JsonLdProps) {\n let finalSchema: any;\n\n if (disableGlobal && data) {\n // Escape hatch for overriding the global graph\n finalSchema = data;\n } else {\n // Construct the @graph array\n const graphItems: any[] = [];\n\n // Always include Organization, WebSite, and LocalBusiness for strong AI SEO presence\n graphItems.push(getOrganizationSchema());\n graphItems.push({\n '@context': 'https://schema.org',\n '@type': 'WebSite',\n name: 'E-TIB',\n url: SITE_URL,\n potentialAction: {\n '@type': 'SearchAction',\n target: {\n '@type': 'EntryPoint',\n urlTemplate: `${SITE_URL}/search?q={search_term_string}`,\n },\n 'query-input': 'required name=search_term_string',\n },\n });\n graphItems.push(getLocalBusinessSchema());\n\n // Append dynamic data from props\n if (data) {\n if (Array.isArray(data)) {\n // Strip out their individual @context if we are pushing to a graph\n const cleanedData = data.map((d: any) => {\n const { '@context': _, ...rest } = d;\n return rest;\n });\n graphItems.push(...cleanedData);\n } else {\n const { '@context': _, ...rest } = data as any;\n graphItems.push(rest);\n }\n }\n\n finalSchema = {\n '@context': 'https://schema.org',\n '@graph': graphItems,\n };\n }\n\n return (\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/LeafletMap.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/Lightbox.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/OGImageTemplate.tsx","messages":[],"suppressedMessages":[{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":51,"column":11,"nodeType":"JSXOpeningElement","endLine":61,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":165,"column":13,"nodeType":"JSXOpeningElement","endLine":170,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ObfuscatedEmail.tsx","messages":[],"suppressedMessages":[{"ruleId":"react-hooks/set-state-in-effect","severity":1,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\n/Volumes/Alpha SSD/Coding/e-tib.com/components/ObfuscatedEmail.tsx:21:5\n 19 | useEffect(() => {\n 20 | // eslint-disable-next-line react-hooks/set-state-in-effect\n> 21 | setMounted(true);\n | ^^^^^^^^^^ Avoid calling setState() directly within an effect\n 22 | }, []);\n 23 |\n 24 | if (!mounted) {","line":21,"column":5,"nodeType":null,"endLine":21,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ObfuscatedPhone.tsx","messages":[],"suppressedMessages":[{"ruleId":"react-hooks/set-state-in-effect","severity":1,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\n/Volumes/Alpha SSD/Coding/e-tib.com/components/ObfuscatedPhone.tsx:20:5\n 18 | useEffect(() => {\n 19 | // eslint-disable-next-line react-hooks/set-state-in-effect\n> 20 | setMounted(true);\n | ^^^^^^^^^^ Avoid calling setState() directly within an effect\n 21 | }, []);\n 22 |\n 23 | // Format phone number for tel: link (remove spaces, etc.)","line":20,"column":5,"nodeType":null,"endLine":20,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/PDFDownloadBlock.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ProductSidebar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ProductTabs.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ProductTechnicalData.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/RelatedProductLink.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/RequestQuoteForm.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/Reveal.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/Scribble.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/SkipLink.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/AnalyticsProvider.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/AnalyticsShell.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/BlogEngagementTracker.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/ClientNotFoundTracker.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/ProductEngagementTracker.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/ScrollDepthTracker.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/TrackedButton.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/TrackedLink.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/WebVitalsTracker.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/analytics-events.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/analytics/useAnalytics.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/annotator-local/Annotator.tsx","messages":[{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useMemo has an unnecessary dependency: 'updateTick'. Either exclude it or remove the dependency array.","line":234,"column":78,"nodeType":"ArrayExpression","endLine":234,"endColumn":106,"suggestions":[{"desc":"Update the dependencies array to be: [hoveredElement]","fix":{"range":[7454,7482],"text":"[hoveredElement]"}}]},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useMemo has an unnecessary dependency: 'updateTick'. Either exclude it or remove the dependency array.","line":235,"column":80,"nodeType":"ArrayExpression","endLine":235,"endColumn":109,"suggestions":[{"desc":"Update the dependencies array to be: [selectedElement]","fix":{"range":[7564,7593],"text":"[selectedElement]"}}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":447,"column":35,"nodeType":"JSXOpeningElement","endLine":447,"endColumn":125},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":549,"column":27,"nodeType":"JSXOpeningElement","endLine":549,"endColumn":123}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { useState, useEffect, useMemo } from \"react\";\nimport { AnimatePresence, m } from 'framer-motion';\nimport { MessageSquare, X, Check, Image as ImageIcon, Type, StickyNote, Download, Trash2, List } from \"lucide-react\";\nimport { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\nimport { finder } from \"@medv/finder\";\n\nfunction cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n\nexport interface Annotation {\n id: string;\n x: number;\n y: number;\n selector: string;\n type: \"text\" | \"image\" | \"note\";\n originalContent: string;\n newContent: string;\n url: string;\n elementRect: Record | null;\n scrollX: number;\n scrollY: number;\n viewportWidth: number;\n viewportHeight: number;\n userAgent: string;\n timestamp: string;\n}\n\nexport interface AnnotatorProps {\n assets?: string[]; // Array of image URLs for the media browser\n onSubmit?: (annotations: Annotation[]) => Promise;\n}\n\nexport function Annotator({ assets = [], onSubmit }: AnnotatorProps) {\n const [isActive, setIsActive] = useState(false);\n const [hoveredElement, setHoveredElement] = useState(null);\n const [selectedElement, setSelectedElement] = useState(null);\n const [annotations, setAnnotations] = useState(() => {\n if (typeof window !== \"undefined\") {\n const saved = localStorage.getItem(\"mintel-annotations\");\n if (saved) {\n try {\n return JSON.parse(saved);\n } catch (e) {\n console.error(\"Failed to parse saved annotations\", e);\n }\n }\n }\n return [];\n });\n\n useEffect(() => {\n if (typeof window !== \"undefined\") {\n localStorage.setItem(\"mintel-annotations\", JSON.stringify(annotations));\n }\n }, [annotations]);\n \n // Modal state\n const [currentType, setCurrentType] = useState<\"text\" | \"image\" | \"note\">(\"text\");\n const [currentText, setCurrentText] = useState(\"\");\n const [selectedAsset, setSelectedAsset] = useState(null);\n const [showList, setShowList] = useState(false);\n const [searchTerm, setSearchTerm] = useState(\"\");\n const [isSubmitting, setIsSubmitting] = useState(false);\n\n // Disable overlays if needed (e.g. inside iframes)\n const isExcluded = useMemo(() => {\n if (typeof window === \"undefined\") return false;\n return window.self !== window.top;\n }, []);\n\n const getSelector = (el: HTMLElement): string => {\n try {\n return finder(el, {\n root: document.body,\n className: (name) =>\n !name.startsWith(\"annotator-\") &&\n !name.includes(\"[\") &&\n !name.includes(\"/\") &&\n !name.match(/^[a-z]-[0-9]/),\n idName: (name) => !name.startsWith(\"__next\") && !name.includes(\":\"),\n });\n } catch {\n return \"unknown\";\n }\n };\n\n useEffect(() => {\n if (!isActive) {\n setHoveredElement(null);\n return;\n }\n\n const handleMouseMove = (e: MouseEvent) => {\n if (selectedElement) return;\n const target = e.target as HTMLElement;\n if (target.closest(\".annotator-ui-ignore\")) {\n setHoveredElement(null);\n return;\n }\n setHoveredElement(target);\n };\n\n const handleClick = (e: MouseEvent) => {\n if (selectedElement) return;\n const target = e.target as HTMLElement;\n if (target.closest(\".annotator-ui-ignore\")) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n setSelectedElement(target);\n setHoveredElement(null);\n \n // Auto-detect type\n if (target.tagName.toLowerCase() === \"img\") {\n setCurrentType(\"image\");\n } else {\n setCurrentType(\"text\");\n setCurrentText(target.innerText || \"\");\n }\n };\n\n window.addEventListener(\"mousemove\", handleMouseMove);\n window.addEventListener(\"click\", handleClick, true);\n\n return () => {\n window.removeEventListener(\"mousemove\", handleMouseMove);\n window.removeEventListener(\"click\", handleClick, true);\n };\n }, [isActive, selectedElement]);\n\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") {\n if (selectedElement) {\n setSelectedElement(null);\n } else if (isActive) {\n setIsActive(false);\n }\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [isActive, selectedElement]);\n\n const saveAnnotation = () => {\n if (!selectedElement) return;\n\n const rect = selectedElement.getBoundingClientRect();\n const newContent = currentType === \"image\" ? selectedAsset || \"\" : currentText;\n \n if (!newContent) return;\n\n const annotation: Annotation = {\n id: Math.random().toString(36).substring(2, 9),\n x: rect.left + rect.width / 2 + window.scrollX,\n y: rect.top + rect.height / 2 + window.scrollY,\n selector: getSelector(selectedElement),\n type: currentType,\n originalContent: currentType === \"image\" ? (selectedElement as HTMLImageElement).src || \"\" : selectedElement.innerText,\n newContent,\n url: window.location.href,\n elementRect: rect ? {\n x: rect.x, y: rect.y, width: rect.width, height: rect.height,\n top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left\n } : null,\n scrollX: window.scrollX,\n scrollY: window.scrollY,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n\n setAnnotations([...annotations, annotation]);\n setSelectedElement(null);\n setCurrentText(\"\");\n setSelectedAsset(null);\n };\n\n const exportJSON = () => {\n const dataStr = \"data:text/json;charset=utf-8,\" + encodeURIComponent(JSON.stringify(annotations, null, 2));\n const downloadAnchorNode = document.createElement(\"a\");\n downloadAnchorNode.setAttribute(\"href\", dataStr);\n downloadAnchorNode.setAttribute(\"download\", \"corrections.json\");\n document.body.appendChild(downloadAnchorNode);\n downloadAnchorNode.click();\n downloadAnchorNode.remove();\n };\n\n const handleSubmit = async () => {\n if (!onSubmit || annotations.length === 0) return;\n setIsSubmitting(true);\n try {\n await onSubmit(annotations);\n setAnnotations([]);\n setShowList(false);\n } catch (error) {\n console.error(\"Submission failed\", error);\n alert(\"Fehler beim Senden der Korrekturen. Bitte JSON Export nutzen.\");\n } finally {\n setIsSubmitting(false);\n }\n };\n\n const deleteAnnotation = (id: string) => {\n setAnnotations(annotations.filter((a) => a.id !== id));\n };\n\n const [updateTick, setUpdateTick] = useState(0);\n\n useEffect(() => {\n if (!isActive && !selectedElement) return;\n \n let rafId: number;\n const handleScroll = () => {\n cancelAnimationFrame(rafId);\n rafId = requestAnimationFrame(() => setUpdateTick(t => t + 1));\n };\n \n // Use capture phase to catch scrolls on any nested container\n window.addEventListener(\"scroll\", handleScroll, true);\n window.addEventListener(\"resize\", handleScroll);\n \n return () => {\n window.removeEventListener(\"scroll\", handleScroll, true);\n window.removeEventListener(\"resize\", handleScroll);\n cancelAnimationFrame(rafId);\n };\n }, [isActive, selectedElement]);\n\n const hoveredRect = useMemo(() => hoveredElement?.getBoundingClientRect(), [hoveredElement, updateTick]);\n const selectedRect = useMemo(() => selectedElement?.getBoundingClientRect(), [selectedElement, updateTick]);\n\n if (isExcluded) return null;\n\n return (\n \n {/* 1. Global Toolbar */}\n
\n
\n
setIsActive(!isActive)}\n className={cn(\n \"p-2.5 rounded-xl transition-all\",\n isActive\n ? \"bg-blue-500 text-white shadow-lg shadow-blue-500/20\"\n : \"text-white/70 hover:text-white hover:bg-white/10\"\n )}\n title={isActive ? \"Modus beenden\" : \"Korrekturen erfassen\"}\n >\n {isActive ? : }\n \n\n
\n\n
setShowList(!showList)}\n className=\"p-2.5 text-white/70 hover:text-white hover:bg-white/10 rounded-xl relative\"\n title=\"Liste anzeigen\"\n >\n
\n {annotations.length > 0 && (\n \n {annotations.length}\n \n )}\n \n\n {annotations.length > 0 && (\n <>\n
\n
\n \n \n >\n )}\n
\n
\n\n {/* 2. Highlights */}\n
\n {isActive && (\n \n {hoveredRect && (\n \n )}\n\n {selectedRect && (\n \n )}\n
\n )}\n \n\n {/* 3. Action Modal */}\n
\n {selectedElement && (\n \n
\n \n
Änderung erfassen \n setSelectedElement(null)}\n className=\"text-white/40 hover:text-white\"\n >\n \n \n \n\n \n setCurrentType(\"text\")}\n className={cn(\n \"flex-1 py-3 px-4 rounded-xl text-sm font-medium transition-all flex items-center justify-center gap-2\",\n currentType === \"text\"\n ? \"bg-white text-black shadow-lg\"\n : \"bg-white/5 text-white/40 hover:bg-white/10\"\n )}\n >\n Text\n \n setCurrentType(\"image\")}\n className={cn(\n \"flex-1 py-3 px-4 rounded-xl text-sm font-medium transition-all flex items-center justify-center gap-2\",\n currentType === \"image\"\n ? \"bg-white text-black shadow-lg\"\n : \"bg-white/5 text-white/40 hover:bg-white/10\"\n )}\n >\n Bild\n \n setCurrentType(\"note\")}\n className={cn(\n \"flex-1 py-3 px-4 rounded-xl text-sm font-medium transition-all flex items-center justify-center gap-2\",\n currentType === \"note\"\n ? \"bg-white text-black shadow-lg\"\n : \"bg-white/5 text-white/40 hover:bg-white/10\"\n )}\n >\n Notiz\n \n
\n\n \n {currentType === \"text\" && (\n
\n Neuer Text \n
\n )}\n\n {currentType === \"note\" && (\n
\n Anmerkung \n
\n )}\n\n {currentType === \"image\" && (\n
\n
Neues Bild wählen \n {assets.length === 0 ? (\n
\n Keine Assets übergeben. Bitte das prop assets befüllen.\n
\n ) : (\n <>\n \n
\n setSearchTerm(e.target.value)}\n className=\"w-full bg-black/40 border border-white/10 rounded-xl px-4 py-3 text-sm text-white placeholder:text-white/40 focus:outline-none focus:border-blue-500/50 transition-colors\"\n />\n
\n
\n {assets.filter(asset => asset.toLowerCase().includes(searchTerm.toLowerCase())).map((asset) => {\n const filename = asset.split('/').pop() || asset;\n return (\n
setSelectedAsset(asset)}\n className={cn(\n \"relative flex flex-col items-center bg-black/20 rounded-xl overflow-hidden border-2 transition-all p-2 gap-2 w-full\",\n selectedAsset === asset\n ? \"border-blue-500 shadow-[0_0_0_4px_rgba(59,130,246,0.3)] bg-blue-500/10\"\n : \"border-transparent hover:border-white/20 hover:bg-white/5\"\n )}\n style={{ height: '130px' }}\n >\n \n {asset.match(/\\.(mp4|webm)$/i) ? (\n
\n ) : (\n
\n )}\n {selectedAsset === asset && (\n
\n )}\n
\n \n {filename}\n \n \n );\n })}\n
\n >\n )}\n
\n )}\n
\n\n \n \n Speichern\n \n \n
\n )}\n \n\n {/* 4. Annotations List Sidebar */}\n
\n {showList && (\n <>\n setShowList(false)}\n className=\"fixed inset-0 bg-black/60 backdrop-blur-sm z-[10001]\"\n />\n \n \n
\n
Korrekturen \n
{annotations.length} erfasst
\n
\n
setShowList(false)}\n className=\"p-2 text-white/40 hover:text-white bg-white/5 rounded-xl transition-colors\"\n >\n \n \n
\n\n \n {annotations.length === 0 ? (\n
\n
\n
Noch keine Korrekturen vorhanden.
\n
\n ) : (\n annotations.map((ann) => (\n
\n
deleteAnnotation(ann.id)}\n className=\"absolute top-4 right-4 text-white/20 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity\"\n >\n \n \n \n
\n \n {ann.type === \"text\" && }\n {ann.type === \"image\" && }\n {ann.type === \"note\" && }\n {ann.type}\n \n
\n \n {ann.type === \"image\" ? (\n
\n
\n
{ann.newContent}
\n
\n ) : (\n
{ann.newContent}
\n )}\n \n
\n {ann.selector} \n {ann.url && {new URL(ann.url).pathname} }\n
\n
\n {ann.timestamp && (\n \n {new Date(ann.timestamp).toLocaleString()}\n \n )}\n {ann.viewportWidth && (\n \n {ann.viewportWidth}x{ann.viewportHeight}\n \n )}\n {ann.scrollX !== undefined && (\n \n Scroll: {Math.round(ann.scrollX)},{Math.round(ann.scrollY)}\n \n )}\n
\n
\n ))\n )}\n
\n \n {annotations.length > 0 && (\n \n {onSubmit && (\n
\n {isSubmitting ? (\n
\n ) : (\n \n )}\n {isSubmitting ? \"Wird gesendet...\" : \"Korrekturen an Entwickler senden\"}\n \n )}\n
\n \n JSON {onSubmit && \"lokal \"}Exportieren\n \n
{\n if (confirm(\"Möchtest du wirklich alle erfassten Korrekturen löschen?\")) {\n setAnnotations([]);\n }\n }}\n className=\"w-full bg-red-500/10 text-red-500 hover:bg-red-500/20 font-bold py-3 rounded-xl flex items-center justify-center gap-2 transition-all mt-2\"\n >\n \n Alle löschen\n \n
\n )}\n \n >\n )}\n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/annotator-local/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/BenefitGrid.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CertificatesBlock.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Button' is defined but never used.","line":6,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":16,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"Button"},"fix":{"range":[150,199],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'FileText' is defined but never used.","line":9,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":18,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"FileText"},"fix":{"range":[356,365],"text":""},"desc":"Remove unused variable \"FileText\"."}]},{"ruleId":"react-hooks/set-state-in-effect","severity":1,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\n/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CertificatesBlock.tsx:78:5\n 76 |\n 77 | useEffect(() => {\n> 78 | setIsMounted(true);\n | ^^^^^^^^^^^^ Avoid calling setState() directly within an effect\n 79 | }, []);\n 80 |\n 81 | const badgeText = badge || 'Zertifizierungen & Nachweise';","line":78,"column":5,"nodeType":null,"endLine":78,"endColumn":17}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport { useState, useEffect } from 'react';\nimport { m } from 'framer-motion';\nimport { Container } from '@/components/ui/Container';\nimport { Button } from '@/components/ui/Button';\nimport { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';\nimport { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';\nimport { FileText, Award, Download, ShieldCheck } from 'lucide-react';\nimport { LogoArcs } from '@/components/ui/LogoArcs';\n\ninterface Certificate {\n title: string;\n description: string;\n pdfUrl?: string;\n type: 'iso' | 'tax' | 'general';\n date?: string;\n}\n\ninterface CertificatesBlockProps {\n badge?: string;\n title?: string;\n description?: string;\n certificates?: Certificate[];\n hideHeader?: boolean;\n}\n\nconst defaultCertificates: Certificate[] = [\n {\n title: 'ISO 14001:2015',\n description: 'Umweltmanagementsystem',\n pdfUrl: '/assets/certificates/iso14001.pdf',\n type: 'iso',\n date: '14.12.2023',\n },\n {\n title: 'ISO 9001:2015',\n description: 'Qualitätsmanagementsystem',\n pdfUrl: '/assets/certificates/iso9001.pdf',\n type: 'iso',\n date: '14.12.2023',\n },\n {\n title: 'DIN EN ISO 45001:2018',\n description: 'Arbeits- und Gesundheitsschutz',\n pdfUrl: '/assets/certificates/iso45001.pdf',\n type: 'iso',\n date: '05.12.2025',\n },\n {\n title: 'Freistellungsbescheinigung',\n description: 'Nach § 48 b EStG',\n pdfUrl: '/assets/certificates/freistellung.pdf',\n type: 'tax',\n date: '09.02.2024',\n },\n {\n title: 'Nachweis § 13b UStG',\n description: 'Steuerschuldnerschaft des Leistungsempfängers',\n pdfUrl: '/assets/certificates/nachweis13b.pdf',\n type: 'tax',\n date: '09.02.2024',\n },\n {\n title: 'Bescheinigung in Steuersachen',\n description: 'Zertifikat des Finanzamtes',\n pdfUrl: '/assets/certificates/bescheinigung.pdf',\n type: 'tax',\n date: '13.02.2025',\n },\n\n];\n\nexport function CertificatesBlock({ badge, title, description, certificates = defaultCertificates, hideHeader = false }: CertificatesBlockProps) {\n const [isMounted, setIsMounted] = useState(false);\n\n useEffect(() => {\n setIsMounted(true);\n }, []);\n\n const badgeText = badge || 'Zertifizierungen & Nachweise';\n const titleText = title || 'Geprüfte Qualität und Sicherheit';\n const descriptionText = description || 'Transparenz und höchste Standards sind das Fundament unserer Arbeit. Hier finden Sie unsere aktuellen Zertifikate und Bescheinigungen als PDF-Download.';\n\n const containerVariants = {\n hidden: { opacity: 0 },\n show: {\n opacity: 1,\n transition: {\n staggerChildren: 0.1,\n },\n },\n };\n\n const itemVariants = {\n hidden: { opacity: 0, y: 20 },\n show: { opacity: 1, y: 0, transition: { duration: 1.0, ease: [0.16, 1, 0.3, 1] } },\n };\n\n if (!isMounted) {\n return (\n \n \n {!hideHeader && (\n \n
{badgeText} \n
{titleText} \n
{descriptionText}
\n
\n )}\n \n \n );\n }\n\n return (\n \n {/* Background Accents */}\n
\n
\n\n \n {!hideHeader && (\n \n
\n {badgeText} \n {titleText} \n {descriptionText}
\n \n
\n )}\n\n \n {certificates.map((cert, index) => {\n const isIso = cert.type === 'iso';\n const Wrapper = cert.pdfUrl ? m.a : m.div;\n const wrapperProps = cert.pdfUrl ? {\n href: encodeURI(cert.pdfUrl),\n target: \"_blank\",\n rel: \"noopener noreferrer\",\n download: cert.pdfUrl.split('/').pop()\n } : {};\n \n return (\n \n {/* Background Decorative Elements */}\n
\n\n {isIso && (\n \n \n
\n )}\n \n \n \n\n \n
\n
\n {cert.date && (\n
\n {cert.date}\n \n )}\n
\n\n
\n
\n {cert.title}\n \n
\n {cert.description}\n
\n
\n\n
\n {cert.pdfUrl ? (\n <>\n Download PDF \n \n >\n ) : (\n Nachweis liegt vor \n )}\n
\n
\n \n );\n })}\n \n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CompanyTimeline.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CompetenceBentoGrid.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Button' is defined but never used.","line":7,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":7,"endColumn":16,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"Button"},"fix":{"range":[157,206],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"react-hooks/set-state-in-effect","severity":1,"message":"Error: Calling setState synchronously within an effect can trigger cascading renders\n\nEffects are intended to synchronize state between React and external systems such as manually updating the DOM, state management libraries, or other platform APIs. In general, the body of an effect should do one or both of the following:\n* Update external systems with the latest state from React.\n* Subscribe for updates from some external system, calling setState in a callback function when external state changes.\n\nCalling setState synchronously within an effect body causes cascading renders that can hurt performance, and is not recommended. (https://react.dev/learn/you-might-not-need-an-effect).\n\n/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/CompetenceBentoGrid.tsx:53:5\n 51 | \n 52 | useEffect(() => {\n> 53 | setIsMounted(true);\n | ^^^^^^^^^^^^ Avoid calling setState() directly within an effect\n 54 | }, []);\n 55 |\n 56 | const badge = props.badge || data?.badge || 'Leistungsspektrum';","line":53,"column":5,"nodeType":null,"endLine":53,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ctaLabel' is assigned a value but never used.","line":58,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":58,"endColumn":17},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'ctaHref' is assigned a value but never used.","line":59,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":59,"endColumn":16}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport Link from 'next/link';\nimport Image from 'next/image';\nimport { m } from 'framer-motion';\nimport { useState, useEffect } from 'react';\nimport { Button } from '@/components/ui/Button';\nimport { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';\nimport { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';\nimport { Container } from '@/components/ui/Container';\nimport { LogoArcs } from '@/components/ui/LogoArcs';\n\ninterface CompetenceBentoGridProps {\n badge?: string;\n title?: string;\n ctaLabel?: string;\n ctaHref?: string;\n items?: {\n title?: string;\n description?: string;\n tag?: string;\n href?: string;\n image?: {\n url?: string;\n alt?: string;\n } | any;\n size?: 'large' | 'medium' | 'small' | 'accent';\n }[];\n data?: {\n badge?: string;\n title?: string;\n ctaLabel?: string;\n ctaHref?: string;\n items?: {\n title?: string;\n description?: string;\n tag?: string;\n href?: string;\n image?: {\n url?: string;\n alt?: string;\n } | any;\n size?: 'large' | 'medium' | 'small' | 'accent';\n }[];\n };\n}\n\nexport function CompetenceBentoGrid(props: CompetenceBentoGridProps) {\n const { data } = props;\n const [isMounted, setIsMounted] = useState(false);\n \n useEffect(() => {\n setIsMounted(true);\n }, []);\n\n const badge = props.badge || data?.badge || 'Leistungsspektrum';\n const title = props.title || data?.title || 'Umfassende Lösungen für komplexe Netzwerke';\n const ctaLabel = props.ctaLabel || data?.ctaLabel || 'Alle Kompetenzen ansehen';\n const ctaHref = props.ctaHref || data?.ctaHref || '/kompetenzen';\n const items = props.items || data?.items || [];\n\n // Static fallback for SSR\n if (!isMounted) {\n return (\n \n \n \n
\n
{badge} \n {title} \n \n
\n \n {/* Simplified static grid */}\n
\n \n \n );\n }\n\n return (\n \n \n \n
\n
{badge} \n {title} \n \n
\n\n \n {items.map((item, idx) => {\n const isLarge = item.size === 'large';\n const isMedium = item.size === 'medium';\n const isAccent = item.size === 'accent';\n const imgSrc = item.image?.url;\n \n let gridClasses = \"rounded-2xl overflow-hidden relative group select-none \";\n if (isLarge) gridClasses += \"md:col-span-2 md:row-span-2\";\n else if (isMedium) gridClasses += \"md:col-span-2 lg:col-span-2\";\n else if (isAccent) gridClasses += \"md:col-span-1 bg-primary text-white p-6 md:p-8 flex flex-col justify-center border-none shadow-md\";\n else gridClasses += \"md:col-span-1\";\n\n if (isAccent) {\n return (\n
\n {item.href && (\n \n )}\n
\n \n {/* Subtle Background Logo Arcs */}\n \n \n
\n\n \n \n {item.title} \n {item.description && {item.description}
}\n \n );\n }\n\n return (\n
\n {item.href && (\n \n )}\n {!imgSrc && (\n
\n )}\n {imgSrc && (\n <>\n \n \n
\n
\n >\n )}\n \n \n \n \n {/* Subtle Background Logo Arcs */}\n \n \n
\n \n \n {item.tag && (\n
\n {item.tag}\n \n )}\n
\n {item.title}\n \n {item.description && (\n
\n {item.description}\n
\n )}\n
\n \n );\n })}\n
\n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/ContactSection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/DataGridPulse.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/DeepDrillAnimation.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/FaqBlock.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/GrowthChart.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/HeroSection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/HeroVideo.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'placeholders' is defined but never used.","line":25,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":25,"endColumn":20,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"placeholders"},"fix":{"range":[523,575],"text":""},"desc":"Remove unused import declaration."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport { Button } from '@/components/ui/Button';\nimport Image from 'next/image';\nimport { useState, useEffect } from 'react';\nimport { usePathname } from 'next/navigation';\n\ninterface HeroVideoProps {\n data?: any;\n title?: string;\n subtitle?: string;\n description?: string;\n videoUrl?: string;\n posterImage?: any;\n backgroundImage?: any;\n ctaLabel?: string;\n ctaHref?: string;\n linkText?: string;\n linkHref?: string;\n secondaryCtaLabel?: string;\n secondaryCtaHref?: string;\n badge?: string;\n}\n\nimport placeholders from '@/lib/placeholders.json';\n\nexport function HeroVideo(props: HeroVideoProps) {\n const { data } = props;\n const title = props.title || data?.title || 'DIE EXPERTEN FÜR KABELNETZBAU';\n const subtitle = props.subtitle || props.description || data?.subtitle || 'Wir verbinden Infrastruktur mit Präzision. Von Horizontalbohrungen bis zu komplexen Leitungsnetzen.';\n const explicitNoVideo = props.videoUrl === 'none' || props.videoUrl === '';\n const videoUrl = explicitNoVideo ? null : (props.videoUrl || data?.videoUrl || '/assets/dummy-hero.mp4');\n const pathname = usePathname();\n\n let defaultPoster = \"/assets/photos/DJI_0048.JPG\";\n if (videoUrl && videoUrl.endsWith('.mp4')) {\n defaultPoster = videoUrl.replace('.mp4', '-poster.jpg');\n }\n\n const posterSrc = props.backgroundImage?.url || props.posterImage?.url || data?.posterImage?.url || defaultPoster;\n const posterAlt = props.backgroundImage?.alt || data?.posterImage?.alt || \"E-TIB Gruppe Baustelle\";\n\n const ctaLabel = props.ctaLabel || props.linkText || data?.ctaLabel || 'Unternehmen entdecken';\n const ctaHref = props.ctaHref || props.linkHref || data?.ctaHref || '#unternehmen';\n \n const currentLocale = pathname?.startsWith('/en') ? 'en' : 'de';\n\n const secondaryCtaLabel = props.secondaryCtaLabel || data?.secondaryCtaLabel || (currentLocale === 'de' ? 'Projekt anfragen' : 'Inquire Project');\n const secondaryCtaHref = props.secondaryCtaHref || data?.secondaryCtaHref || `/${currentLocale}/contact`;\n\n const [videoSrcLoaded, setVideoSrcLoaded] = useState(false);\n useEffect(() => {\n let interactionTriggered = false;\n\n const handleInteraction = () => {\n if (interactionTriggered) return;\n interactionTriggered = true;\n setVideoSrcLoaded(true);\n \n // Cleanup listeners\n ['scroll', 'mousemove', 'touchstart', 'keydown'].forEach((event) =>\n window.removeEventListener(event, handleInteraction)\n );\n };\n\n // Listen for any user interaction\n ['scroll', 'mousemove', 'touchstart', 'keydown'].forEach((event) =>\n window.addEventListener(event, handleInteraction, { passive: true, once: true })\n );\n\n return () => {\n ['scroll', 'mousemove', 'touchstart', 'keydown'].forEach((event) =>\n window.removeEventListener(event, handleInteraction)\n );\n };\n }, []);\n\n return (\n \n {/* Background color while video loads */}\n
\n \n {/* ALWAYS render the Next.js optimized Image as the LCP element */}\n
\n {/* Fast compositing layer to replace the heavy CSS filters on the image */}\n
\n\n {/* Render video on top if available, but defer src to avoid blocking LCP */}\n {videoUrl && (\n
\n )}\n \n {/* Cinematic Color Grading Overlay */}\n
\n \n {/* Dramatic Vignette & Fade to content */}\n
\n
\n \n {/* Top Fade for Header Navigation Readability */}\n
\n \n
\n
\n
Kabelnetzbau').replace(/KABELNETZBAU|Kabelnetzbau/g, 'Kabelnetzbau ').replace(/\\\\n|\\n/g, ' ') }}\n />\n \n \n {subtitle}\n
\n \n
\n \n {ctaLabel}\n \n \n \n {secondaryCtaLabel}\n \n
\n
\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/InteractiveGermanyMap.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Image' is defined but never used.","line":5,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":13,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"Image"},"fix":{"range":[139,171],"text":""},"desc":"Remove unused import declaration."}]},{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":91,"column":7,"nodeType":"JSXOpeningElement","endLine":97,"endColumn":9}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport React, { useState, useRef, useCallback, useMemo } from 'react';\nimport { MapPin, ArrowUpRight } from 'lucide-react';\nimport Image from 'next/image';\nimport { usePathname } from 'next/navigation';\nimport Link from 'next/link';\nimport dynamic from 'next/dynamic';\nimport { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';\nimport { Location, projectLocations } from '@/lib/map-data';\nimport { standorteLocations } from '@/lib/standorte-data';\nimport { useLocale, useTranslations } from 'next-intl';\n\nconst InteractiveMapPins = dynamic(() => import('./InteractiveMapPins'), {\n ssr: false,\n});\n\ninterface Stat {\n value: string;\n suffix?: string;\n label: string;\n href?: string;\n}\n\ninterface InteractiveGermanyMapProps {\n badge?: string;\n title?: React.ReactNode;\n description?: string;\n stats?: Stat[];\n locations?: Location[];\n isHero?: boolean;\n hideStandorte?: boolean;\n}\n\n\n\nexport function InteractiveGermanyMap({\n badge,\n title,\n description,\n stats,\n locations = projectLocations,\n isHero = false,\n hideStandorte = false\n}: InteractiveGermanyMapProps) {\n const [activeLocation, setActiveLocation] = useState(null);\n const hoverTimeoutRef = useRef(null);\n\n const handleMouseEnter = useCallback((loc: Location) => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current);\n hoverTimeoutRef.current = null;\n }\n setActiveLocation(loc);\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n const timeout = setTimeout(() => {\n setActiveLocation(null);\n }, 200);\n hoverTimeoutRef.current = timeout;\n }, []);\n\n const pathname = usePathname();\n const locale = useLocale();\n const t = useTranslations('InteractiveGermanyMap');\n const tStandard = useTranslations('StandardPage');\n\n const finalStats = stats || [\n { value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },\n { value: '3', suffix: '', label: locale === 'en' ? 'Operational Locations' : 'Operative Standorte', href: `/${locale}/standorte` },\n ];\n\n const finalBadge = badge || tStandard('badge');\n const finalTitle = title || (locale === 'en' ? <>Nationwide in operation for you.> : <>Deutschlandweit für Sie im Einsatz.>);\n const finalDescription = description || (locale === 'en' ? 'From our strategic locations in Guben, Kirchheilingen and Bülstedt, we control and implement complex infrastructure projects nationwide.' : 'Von unseren strategischen Standorten in Guben, Kirchheilingen und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.');\n\n const finalLocations = useMemo(() => {\n if (hideStandorte) return locations;\n const base = [...locations];\n standorteLocations.forEach(sl => {\n if (!base.some(l => l.id === sl.id)) {\n base.push(sl as Location);\n }\n });\n return base;\n }, [locations, hideStandorte]);\n\n const mapBackground = useMemo(() => (\n \n
\n
\n ), [locale]);\n\n return (\n \n
\n {!isHero &&
}\n \n
\n
\n \n
\n \n
\n
\n \n {finalBadge} \n
\n \n {isHero ? (\n
\n {finalTitle}\n \n ) : (\n
\n {finalTitle}\n \n )}\n \n
\n {finalDescription}\n
\n\n
\n {finalStats.map((stat, i) => {\n const StatCard = () => (\n
\n
\n
\n
\n {stat.value}{stat.suffix} \n
\n
\n {stat.label}\n {stat.href &&
}\n
\n
\n
\n );\n\n return stat.href ? (\n
\n
\n \n ) : (\n
\n \n
\n );\n })}\n
\n
\n\n
\n
\n \n {mapBackground}\n\n \n
\n
\n
\n
\n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/InteractiveMapPins.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/JobListingBlock.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/ReferencesSlider.test.tsx","messages":[{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":20,"column":12,"nodeType":"JSXOpeningElement","endLine":20,"endColumn":30}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import * as React from 'react';\nimport { describe, it, expect, vi } from 'vitest';\nimport { render, screen } from '@testing-library/react';\nimport { ReferencesSlider, Reference } from './ReferencesSlider';\n\n// Mock next/link to render standard tag\nvi.mock('next/link', () => ({\n default: vi.fn(({ children, href, onClick, ...props }: any) => {\n return (\n \n {children}\n \n );\n })\n}));\n\n// Mock next/image to render standard img tag\nvi.mock('next/image', () => ({\n default: (props: any) => {\n return ;\n }\n}));\n\n// Mock next-intl\nvi.mock('next-intl', () => ({\n useTranslations: () => (key: string) => key,\n useLocale: () => 'en',\n NextIntlClientProvider: ({ children }: any) => <>{children}>\n}));\n\ndescribe('ReferencesSlider TDD', () => {\n const mockReferences: Reference[] = [\n {\n id: '1',\n title: 'Referenz Projekt Eins',\n slug: 'referenz-projekt-eins',\n category: 'Kabelnetzbau',\n },\n {\n id: '2',\n title: 'Referenz Projekt Zwei',\n slug: 'referenz-projekt-zwei',\n category: 'Bohrtechnik',\n }\n ];\n\n it('renders reference tiles correctly', () => {\n render( );\n \n expect(screen.getByText('Referenz Projekt Eins')).toBeTruthy();\n expect(screen.getByText('Referenz Projekt Zwei')).toBeTruthy();\n });\n\n it('renders each tile as a static block and NOT wrapped in a link', () => {\n render( );\n \n const links = screen.queryAllByTestId('reference-tile-link');\n expect(links).toHaveLength(0);\n\n const tiles = screen.getAllByTestId('reference-tile');\n expect(tiles).toHaveLength(2);\n });\n});\n\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/ReferencesSlider.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/ScaleOfImpact.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/ServiceDetailGrid.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/SubCompanyTiles.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'CardWrapper' is assigned a value but never used.","line":94,"column":19,"nodeType":"Identifier","messageId":"unusedVar","endLine":94,"endColumn":30},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'wrapperProps' is assigned a value but never used.","line":95,"column":19,"nodeType":"Identifier","messageId":"unusedVar","endLine":95,"endColumn":31}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport * as React from 'react';\nimport { m, Variants } from 'framer-motion';\nimport Image from 'next/image';\nimport Link from 'next/link';\nimport { HoverShineOverlay } from '@/components/ui/HoverShineOverlay';\nimport { AnimatedGlossyBorder } from '@/components/ui/AnimatedGlossyBorder';\n\ninterface SubCompanyTilesProps {\n badge?: string;\n title?: string;\n companies?: {\n title?: string;\n url?: string;\n backgroundImage?: string;\n }[];\n data?: any;\n}\n\nconst defaultCompanies = [\n {\n title: 'E-TIB GmbH',\n backgroundImage: '/assets/photos/DJI_0243.JPG',\n },\n {\n title: 'E-TIB Ingenieurgesellschaft mbH',\n url: 'https://www.etib-ing.com/',\n backgroundImage: '/assets/photos/DSC01137.JPG',\n },\n {\n title: 'NEMO GmbH',\n url: 'https://www.nemo-gmbh.de/',\n backgroundImage: '/assets/photos/DJI_0048.JPG',\n }\n];\n\nconst containerVariants: Variants = {\n hidden: { opacity: 0 },\n visible: {\n opacity: 1,\n transition: {\n staggerChildren: 0.15,\n },\n },\n};\n\nconst itemVariants: Variants = {\n hidden: { opacity: 0, y: 30 },\n visible: { opacity: 1, y: 0, transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1] } },\n};\n\nimport { usePathname } from 'next/navigation';\nimport { LogoArcs } from '@/components/ui/LogoArcs';\n\nexport function SubCompanyTiles(props: SubCompanyTilesProps) {\n const { data } = props;\n const badge = props.badge || data?.badge;\n const title = props.title || data?.title;\n const rawCompanies = props.companies || data?.companies;\n const pathname = usePathname();\n const isDe = pathname?.startsWith('/de') ?? true;\n \n const companiesData = rawCompanies?.map((c: any) => ({\n title: c.title,\n url: c.url,\n backgroundImage: c.backgroundImage?.url || c.backgroundImage || '/assets/photos/DJI_0243.JPG',\n })) || defaultCompanies;\n \n return (\n \n \n \n {(badge || title) && (\n
\n {badge &&
{badge} }\n {title && {title} }\n \n )}\n \n
\n
\n
\n {companiesData.map((company: any, index: number) => {\n const isCurrent = company.title.toUpperCase() === 'E-TIB GMBH';\n const hasUrl = !!company.url;\n const isExternal = hasUrl && company.url.startsWith('http');\n const CardWrapper = hasUrl ? (isExternal ? 'a' : Link) : 'div';\n const wrapperProps = hasUrl ? (isExternal ? { href: company.url, target: \"_blank\", rel: \"noopener noreferrer\" } : { href: company.url }) : {};\n \n // Generate logo representation based on title\n const isEtib = company.title.includes('E-TIB');\n const isIng = company.title.includes('Ingenieur');\n const isVerwaltung = company.title.includes('Verwaltung');\n const isBohrtechnik = company.title.includes('Bohrtechnik');\n const isNemo = company.title.includes('NEMO');\n\n const bgPositionClasses = [\n \"-bottom-[30%] -right-[30%]\",\n \"-top-[30%] -left-[30%]\",\n \"-bottom-[30%] -left-[30%]\",\n \"-top-[30%] -right-[30%]\"\n ];\n const animationClasses = [\n \"animate-[spin_60s_linear_infinite]\",\n \"animate-[spin_80s_linear_infinite]\",\n \"animate-[spin_100s_linear_infinite]\",\n \"animate-[spin_120s_linear_infinite]\"\n ];\n const positionClass = bgPositionClasses[index % bgPositionClasses.length];\n const animationClass = animationClasses[index % animationClasses.length];\n \n const CardContent = (\n <>\n \n \n
\n \n {/* Heavy dark gradient overlay to make logos pop */}\n
\n\n {/* Subtle Background Logo Arcs */}\n \n \n
\n \n {/* Premium Shine Sweep Effect (Industrial Reflection) */}\n \n \n {/* Glossy Animated Border */}\n \n \n {/* Current Site Indicator Badge */}\n {isCurrent && (\n \n
\n \n \n \n \n \n {isDe ? 'Aktuell hier' : 'You are here'}\n \n
\n
\n )}\n \n \n \n {/* LOGO RENDERING */}\n {isEtib ? (\n
\n
\n \n
\n {isIng && (\n
\n Ingenieurgesellschaft\n
\n )}\n {isVerwaltung && (\n
\n Verwaltung GmbH\n
\n )}\n {isBohrtechnik && (\n
\n Bohrtechnik GmbH\n
\n )}\n
\n ) : isNemo ? (\n
\n
\n NEMO\n
\n
\n GmbH\n
\n
\n ) : (\n
\n {company.title}\n
\n )}\n \n
\n \n {/* Subtle Link Indicator */}\n {company.url && (\n \n
\n {isExternal ? (isDe ? 'Website Besuchen' : 'Visit Website') : (isDe ? 'Zum Standort' : 'View Location')}\n
\n
\n
\n )}\n \n >\n );\n\n const sharedClass = `group relative overflow-hidden aspect-[4/3] flex flex-col justify-center items-center shadow-xl transition-all duration-500 bg-neutral-dark block w-full h-full rounded-2xl select-none ${\n isCurrent \n ? 'border border-white/5 hover:shadow-2xl hover:-translate-y-1 hover:border-white/10 cursor-pointer' \n : 'border border-white/5 hover:shadow-2xl hover:-translate-y-1 hover:border-white/10 cursor-pointer'\n }`;\n\n return (\n \n {hasUrl ? (\n isExternal ? (\n \n {CardContent}\n \n ) : (\n \n {CardContent}\n \n )\n ) : (\n \n {CardContent}\n
\n )}\n \n );\n })}\n \n
\n
\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/SupportCTA.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/TeamGrid.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/TeamGridBlock.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'title' is defined but never used. Allowed unused args must match /^_/u.","line":20,"column":39,"nodeType":"Identifier","messageId":"unusedVar","endLine":20,"endColumn":44},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'department' is defined but never used. Allowed unused args must match /^_/u.","line":20,"column":46,"nodeType":"Identifier","messageId":"unusedVar","endLine":20,"endColumn":56},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'showContact' is assigned a value but never used. Allowed unused args must match /^_/u.","line":20,"column":58,"nodeType":"Identifier","messageId":"unusedVar","endLine":20,"endColumn":69}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import * as React from 'react';\nimport { TeamGrid as TeamGridComponent } from './TeamGrid';\n\nexport interface TeamMember {\n id: string;\n name: string;\n position: string;\n email?: string | null;\n phone?: string | null;\n image?: { url: string; alt?: string } | string | null;\n branch: string;\n}\n\nexport interface TeamGridBlockProps {\n title?: string;\n department?: string;\n showContact?: boolean;\n}\n\nexport const TeamGridBlock = async ({ title, department, showContact = true }: TeamGridBlockProps) => {\n // Static fallback since Payload CMS is removed\n const teamMembers: any[] = [];\n \n // Map docs to TeamGrid expected format\n const members = teamMembers.map((m: any) => ({\n id: m.id,\n name: m.name,\n position: m.role || m.position || '',\n branch: m.department || m.branch || 'e-tib',\n email: m.email,\n phone: m.phone,\n image: m.image,\n }));\n\n return (\n \n {/* Passing data to the client component */}\n \n
\n );\n};\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blocks/TeamProfile.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/AnimatedImage.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/BlogPaginationKeyboardObserver.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/ChatBubble.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/ComparisonGrid.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/HighlightBox.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/PostNavigation.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/PowerCTA.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/ReadingProgressBar.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/ShareButton.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/SplitHeading.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/Stats.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/StickyNarrative.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/TableOfContents.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/TechnicalGrid.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/blog/VisualLinkPreview.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/decorations/CorporateBackground.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/decorations/DynamicCorporateBackground.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/decorations/EUFundingBadge.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/emails/ContactEmail.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/forms/ContactForm.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/CTA.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/Experience.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/GallerySection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/Hero.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'useCallback' is defined but never used.","line":9,"column":39,"nodeType":"Identifier","messageId":"unusedVar","endLine":9,"endColumn":50,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"useCallback"},"fix":{"range":[383,396],"text":""},"desc":"Remove unused variable \"useCallback\"."}]},{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has missing dependencies: 'HERO_PLACEHOLDERS' and 't'. Either include them or remove the dependency array.","line":79,"column":6,"nodeType":"ArrayExpression","endLine":79,"endColumn":19,"suggestions":[{"desc":"Update the dependencies array to be: [HERO_PLACEHOLDERS, searchQuery, t]","fix":{"range":[2401,2414],"text":"[HERO_PLACEHOLDERS, searchQuery, t]"}}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport Scribble from '@/components/Scribble';\nimport { Button, Container, Heading, Section } from '@/components/ui';\nimport { useTranslations, useLocale } from 'next-intl';\nimport dynamic from 'next/dynamic';\nimport { useAnalytics } from '../analytics/useAnalytics';\nimport { AnalyticsEvents } from '../analytics/analytics-events';\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport { ChevronRight } from 'lucide-react';\nimport { usePathname } from 'next/navigation';\nimport { AISearchResults } from '../search/AISearchResults';\nconst HeroIllustration = dynamic(() => import('./HeroIllustration'), { ssr: false });\nconst AIOrb = dynamic(() => import('../search/AIOrb'), { ssr: false });\n\nexport default function Hero({ data }: { data?: any }) {\n const t = useTranslations('Home.hero');\n const locale = useLocale();\n const pathname = usePathname();\n const { trackEvent } = useAnalytics();\n\n const [searchQuery, setSearchQuery] = useState('');\n const [isSearchOpen, setIsSearchOpen] = useState(false);\n const [heroPlaceholder, setHeroPlaceholder] = useState(\n t('searchPlaceholder')\n );\n const typingRef = useRef | null>(null);\n\n const HERO_PLACEHOLDERS = [\n t('placeholder1'),\n t('placeholder2'),\n t('placeholder3'),\n t('placeholder4'),\n t('placeholder5'),\n t('placeholder6'),\n ];\n\n // Typing animation for the hero search placeholder\n useEffect(() => {\n if (searchQuery) {\n setHeroPlaceholder(t('searchPlaceholder'));\n return;\n }\n\n let textIdx = 0;\n let charIdx = 0;\n let deleting = false;\n\n const tick = () => {\n const fullText = HERO_PLACEHOLDERS[textIdx];\n\n if (deleting) {\n charIdx--;\n setHeroPlaceholder(fullText.substring(0, charIdx));\n } else {\n charIdx++;\n setHeroPlaceholder(fullText.substring(0, charIdx));\n }\n\n let delay = deleting ? 30 : 70;\n\n if (!deleting && charIdx === fullText.length) {\n delay = 2500;\n deleting = true;\n } else if (deleting && charIdx === 0) {\n deleting = false;\n textIdx = (textIdx + 1) % HERO_PLACEHOLDERS.length;\n delay = 400;\n }\n\n typingRef.current = setTimeout(tick, delay);\n };\n\n typingRef.current = setTimeout(tick, 1500);\n\n return () => {\n if (typingRef.current) clearTimeout(typingRef.current);\n };\n }, [searchQuery]);\n\n const handleSearchSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n if (searchQuery.trim()) {\n setIsSearchOpen(true);\n }\n };\n\n return (\n <>\n \n \n \n
\n
\n {data?.title ? (\n /g,\n '',\n )\n .replace(\n /<\\/green>/g,\n '
',\n ),\n }}\n />\n ) : (\n t.rich('title', {\n green: (chunks) => (\n \n \n {chunks}\n \n \n \n
\n \n ),\n })\n )}\n \n
\n
\n
\n {data?.subtitle || t('subtitle')}\n
\n
\n
\n\n
\n
\n \n trackEvent(AnalyticsEvents.BUTTON_CLICK, {\n label: data?.ctaLabel || t('cta'),\n location: 'home_hero_primary',\n })\n }\n >\n {data?.ctaLabel || t('cta')}\n \n →\n \n \n
\n
\n \n trackEvent(AnalyticsEvents.BUTTON_CLICK, {\n label: data?.secondaryCtaLabel || t('exploreProducts'),\n location: 'home_hero_secondary',\n })\n }\n >\n {data?.secondaryCtaLabel || t('exploreProducts')}\n \n
\n
\n
\n \n\n \n \n
\n\n \n \n setIsSearchOpen(false)}\n initialQuery={searchQuery}\n triggerSearch={true}\n />\n >\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/HeroIllustration.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/MeetTheTeam.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'teamT' is assigned a value but never used.","line":8,"column":9,"nodeType":"Identifier","messageId":"unusedVar","endLine":8,"endColumn":14}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import React from 'react';\nimport Image from 'next/image';\nimport { useTranslations, useLocale } from 'next-intl';\nimport { Section, Container, Button, Heading } from '../../components/ui';\n\nexport default function MeetTheTeam({ data }: { data?: any }) {\n const t = useTranslations('Home.meetTheTeam');\n const teamT = useTranslations('Team');\n const locale = useLocale();\n\n return (\n \n \n\n \n \n
\n {data?.title || t('title')} \n \n\n
\n
\n
\n \"{data?.description || t('description')}\"\n
\n
\n\n
\n
\n {data?.ctaLabel || t('cta')}\n → \n \n\n
\n
\n
\n {data?.networkLabel || t('andNetwork')}\n \n
\n
\n
\n \n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/ProductCategories.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/RecentPosts.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/VideoSection.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/WhatWeDo.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/home/WhyChooseUs.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/DynamicMobileBottomNav.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/Footer.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Accessibility' is defined but never used.","line":4,"column":35,"nodeType":"Identifier","messageId":"unusedVar","endLine":4,"endColumn":48,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Accessibility"},"fix":{"range":[168,183],"text":""},"desc":"Remove unused variable \"Accessibility\"."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { TransitionLink } from '@/components/ui/TransitionLink';\nimport Image from 'next/image';\nimport { useLocale } from 'next-intl';\nimport { ShieldCheck, Leaf, Lock, Accessibility, Zap, Mail, Phone } from 'lucide-react';\nimport { LanguageSwitcher } from './LanguageSwitcher';\nimport { Tooltip } from '@/components/ui/Tooltip';\nimport { EUFundingBadge } from '@/components/decorations/EUFundingBadge';\ninterface CompanyInfo {\n contactEmail: string;\n contactPhone: string;\n address: string;\n}\n\ninterface FooterProps {\n companyInfo?: CompanyInfo;\n}\n\nexport function Footer({ companyInfo }: FooterProps) {\n const locale = useLocale();\n\n return (\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/Header.test.tsx","messages":[{"ruleId":"@next/next/no-img-element","severity":1,"message":"Using ` ` could result in slower LCP and higher bandwidth. Consider using ` ` from `next/image` or a custom image loader to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element","line":23,"column":45,"nodeType":"JSXOpeningElement","endLine":23,"endColumn":83}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import * as React from 'react';\nimport { describe, it, expect, vi, beforeEach } from 'vitest';\nimport { render, screen, fireEvent, act, waitFor } from '@testing-library/react';\nimport { Header, NavLink } from './Header';\nimport { usePathname } from 'next/navigation';\n\n// Mock next/navigation\nvi.mock('next/navigation', () => ({\n usePathname: vi.fn(),\n}));\n\n// Mock TransitionLink to render standard anchor tag and forward all props\nvi.mock('@/components/ui/TransitionLink', () => ({\n TransitionLink: ({ children, href, onClick, ...props }: any) => (\n \n {children}\n \n ),\n}));\n\n// Mock next/image\nvi.mock('next/image', () => ({\n default: ({ src, alt, ...props }: any) => ,\n}));\n\n// Mock LanguageSwitcher\nvi.mock('./LanguageSwitcher', () => ({\n LanguageSwitcher: () =>
,\n}));\n\ndescribe('Header Dropdown Navigation TDD', () => {\n const mockNavLinks: NavLink[] = [\n {\n label: 'Home',\n url: '/',\n },\n {\n label: 'Leistungen',\n url: '/leistungen',\n children: [\n { label: 'Kabelbau', url: '/leistungen/kabelbau' },\n { label: 'Bohrtechnik', url: '/leistungen/bohrtechnik' },\n ],\n },\n ];\n\n beforeEach(() => {\n vi.clearAllMocks();\n });\n\n it('closes the dropdown menu immediately after a page navigation occurs', async () => {\n // 1. Set initial pathname\n let currentPath = '/de';\n vi.mocked(usePathname).mockImplementation(() => currentPath);\n\n // 2. Render Header\n const { rerender } = render();\n\n // 3. Hover over \"Leistungen\" menu item to open dropdown\n const leistungenNavItem = screen.getByText('Leistungen');\n const navItemContainer = leistungenNavItem.closest('.group');\n expect(navItemContainer).toBeTruthy();\n\n act(() => {\n fireEvent.mouseEnter(navItemContainer!);\n });\n\n // 4. Assert dropdown is open and children are rendered\n expect(screen.getByText('Kabelbau')).toBeTruthy();\n expect(screen.getByText('Bohrtechnik')).toBeTruthy();\n\n // 5. Simulate page navigation by changing the path\n currentPath = '/de/leistungen/kabelbau';\n \n act(() => {\n // Trigger pathname hook change and force rerender\n rerender();\n });\n\n // 6. Assert dropdown is closed (visually hidden via CSS)\n await waitFor(() => {\n const dropdown = screen.getByText('Kabelbau').closest('div[style*=\"perspective\"]');\n expect(dropdown?.className).toContain('invisible');\n expect(dropdown?.className).toContain('opacity-0');\n });\n });\n});\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/Header.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'isMobileMenuOpen' is assigned a value but never used.","line":24,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":24,"endColumn":26}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport * as React from 'react';\nimport { TransitionLink } from '@/components/ui/TransitionLink';\nimport Image from 'next/image';\nimport { usePathname } from 'next/navigation';\nimport { LanguageSwitcher } from './LanguageSwitcher';\n\nexport interface NavLink {\n label: string;\n url: string;\n children?: NavLink[];\n isGroupLabel?: boolean;\n}\n\ninterface HeaderProps {\n navLinks: NavLink[];\n}\n\nexport function Header({ navLinks }: HeaderProps) {\n const [isScrolled, setIsScrolled] = React.useState(false);\n const [forceSolid, setForceSolid] = React.useState(false);\n const [headerVariant, setHeaderVariant] = React.useState<'capsule' | 'standard'>('capsule');\n const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);\n const pathname = usePathname() || '/';\n\n const shineRef = React.useRef(null);\n\n // Determine current locale from pathname\n const currentLocale = pathname.startsWith('/en') ? 'en' : 'de';\n\n React.useEffect(() => {\n let animationFrameId: number;\n const handleScroll = () => {\n setIsScrolled(window.scrollY > 20);\n \n if (shineRef.current) {\n const y = window.scrollY;\n const adjustedY = Math.max(0, y - 20);\n const range = 800;\n const progress = (adjustedY % range) / range;\n const x = -150 + progress * 300;\n \n cancelAnimationFrame(animationFrameId);\n animationFrameId = requestAnimationFrame(() => {\n if (shineRef.current) {\n shineRef.current.style.transform = `translateX(${x}%)`;\n }\n });\n }\n };\n window.addEventListener('scroll', handleScroll);\n \n // Check for forced solid header (used by error pages, legal pages without hero, etc.)\n const checkHeaderState = () => {\n setForceSolid(document.body.getAttribute('data-force-solid-header') === 'true');\n setHeaderVariant((document.body.getAttribute('data-header-variant') as 'capsule' | 'standard') || 'capsule');\n };\n checkHeaderState();\n \n // Set up observer for client-side navigation changes\n const observer = new MutationObserver(checkHeaderState);\n observer.observe(document.body, { attributes: true, attributeFilter: ['data-force-solid-header', 'data-header-variant'] });\n \n // Re-check when pathname changes\n checkHeaderState();\n setIsMobileMenuOpen(false);\n \n return () => {\n window.removeEventListener('scroll', handleScroll);\n observer.disconnect();\n };\n }, [pathname]);\n\n\n const isSolidMode = isScrolled || forceSolid || headerVariant === 'standard';\n const isStandard = headerVariant === 'standard';\n\n return (\n <>\n \n >\n );\n}\n\nfunction NavItem({ link, currentLocale, pathname, isSolidMode }: { link: NavLink, currentLocale: string, pathname: string, isSolidMode: boolean }) {\n const [isHovered, setIsHovered] = React.useState(false);\n\n // Close dropdown on navigation\n React.useEffect(() => {\n setIsHovered(false);\n }, [pathname]);\n\n // Close dropdown on scroll\n React.useEffect(() => {\n const handleScroll = () => {\n if (isHovered) setIsHovered(false);\n };\n window.addEventListener('scroll', handleScroll, { passive: true });\n return () => window.removeEventListener('scroll', handleScroll);\n }, [isHovered]);\n\n const mappedUrl = link.url.startsWith('/') && !link.url.match(/^\\/(en|de)/) \n ? `/${currentLocale}${link.url}` \n : link.url;\n \n const isActive = pathname === mappedUrl || (mappedUrl !== `/${currentLocale}` && pathname?.startsWith(`${mappedUrl}/`));\n\n return (\n setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n
\n {link.label}\n {link.children && (\n \n \n \n )}\n \n {/* Active Indicator */}\n {isActive && !link.children && (\n
\n )}\n \n {/* Subtle hover underline */}\n \n \n\n <>\n {link.children && (\n
\n
\n {/* Top glowing accent line */}\n
\n \n {link.children.map((child, idx) => {\n const childUrl = child.url.startsWith('/') && !child.url.match(/^\\/(en|de)/) \n ? `/${currentLocale}${child.url}` \n : child.url;\n const isChildActive = pathname === childUrl;\n \n if (child.isGroupLabel) {\n return (\n
\n {child.label}\n
\n );\n }\n\n return (\n
setIsHovered(false)}\n className={`group/dropitem relative block px-4 py-3.5 rounded-2xl text-sm font-medium transition-all duration-300 overflow-hidden ${\n isSolidMode\n ? isChildActive ? 'bg-primary/10 text-primary' : 'text-neutral-600 hover:text-primary hover:bg-neutral-50/80'\n : isChildActive ? 'bg-white/10 text-white' : 'text-neutral-300 hover:text-white hover:bg-white/[0.06]'\n }`}\n >\n \n
\n {child.label}\n \n \n
\n \n \n
\n \n );\n })}\n
\n
\n )}\n >\n
\n );\n}\n\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/LanguageSwitcher.test.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/LanguageSwitcher.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/layout/MobileBottomNav.tsx","messages":[{"ruleId":"prefer-const","severity":1,"message":"'links' is never reassigned. Use 'const' instead.","line":86,"column":9,"nodeType":"Identifier","messageId":"useConst","endLine":86,"endColumn":46,"fix":{"range":[2806,2853],"text":"const links: {label: string, url: string}[] = [];"}}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":1,"source":"'use client';\n\nimport * as React from 'react';\nimport { usePathname } from 'next/navigation';\nimport { TransitionLink } from '@/components/ui/TransitionLink';\nimport { Home, Layers, Star, Mail, Menu } from 'lucide-react';\nimport { m, AnimatePresence } from 'framer-motion';\n\nexport interface NavLink {\n label: string;\n url: string;\n children?: NavLink[];\n}\n\ninterface MobileBottomNavProps {\n navLinks: NavLink[];\n currentLocale: string;\n}\n\nexport function MobileBottomNav({ navLinks, currentLocale }: MobileBottomNavProps) {\n const pathname = usePathname() || '/';\n const [isFlyoutOpen, setIsFlyoutOpen] = React.useState(false);\n\n // Scroll lock when flyout is open\n React.useEffect(() => {\n if (isFlyoutOpen) {\n document.body.style.overflow = 'hidden';\n document.documentElement.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = '';\n document.documentElement.style.overflow = '';\n }\n return () => {\n document.body.style.overflow = '';\n document.documentElement.style.overflow = '';\n };\n }, [isFlyoutOpen]);\n\n const items = React.useMemo(() => {\n const homeUrl = `/${currentLocale}`;\n const competenciesUrl = navLinks[0]?.url || `/${currentLocale}/kompetenzen`;\n const referencesUrl = navLinks[2]?.url || `/${currentLocale}/referenzen`;\n const contactUrl = `/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`;\n\n return [\n {\n id: 'home',\n label: currentLocale === 'de' ? 'Start' : 'Home',\n url: homeUrl,\n icon: ,\n exactMatch: true\n },\n {\n id: 'kompetenzen',\n label: navLinks[0]?.label || (currentLocale === 'de' ? 'Kompetenzen' : 'Competencies'),\n url: competenciesUrl,\n icon: \n },\n {\n id: 'referenzen',\n label: navLinks[2]?.label || (currentLocale === 'de' ? 'Referenzen' : 'References'),\n url: referencesUrl,\n icon: \n },\n {\n id: 'kontakt',\n label: currentLocale === 'de' ? 'Kontakt' : 'Contact',\n url: contactUrl,\n icon: \n },\n {\n id: 'menu',\n label: currentLocale === 'de' ? 'Mehr' : 'Menu',\n url: '#',\n icon: ,\n isFlyoutTrigger: true\n }\n ];\n }, [currentLocale, navLinks]);\n\n const flyoutLinks = React.useMemo(() => {\n const aboutNode = navLinks[1];\n const karriereNode = navLinks[3];\n const messenNode = navLinks[4];\n \n let links: {label: string, url: string}[] = [];\n if (aboutNode?.children) {\n links.push(...aboutNode.children.map(c => ({ label: c.label, url: c.url })));\n }\n if (karriereNode) links.push({ label: karriereNode.label, url: karriereNode.url });\n if (messenNode) links.push({ label: messenNode.label, url: messenNode.url });\n return links;\n }, [navLinks]);\n\n return (\n <>\n \n
\n {items.map((item) => {\n const mappedUrl = item.url.startsWith('/') && !item.url.match(/^\\/(en|de)/) \n ? `/${currentLocale}${item.url}` \n : item.url;\n \n let isActive = false;\n if (item.isFlyoutTrigger) {\n isActive = isFlyoutOpen;\n } else {\n isActive = item.exactMatch \n ? pathname === mappedUrl \n : (pathname === mappedUrl || (mappedUrl !== `/${currentLocale}` && pathname?.startsWith(`${mappedUrl}/`)));\n }\n\n const innerContent = (\n <>\n {isActive && (\n \n )}\n \n \n \n {item.icon}\n \n \n {item.label}\n \n
\n >\n );\n\n if (item.isFlyoutTrigger) {\n return (\n setIsFlyoutOpen(!isFlyoutOpen)}\n className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none w-full focus:outline-none ${\n isActive ? 'text-primary-dark' : 'text-neutral-dark hover:text-black'\n }`}\n style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }}\n >\n {innerContent}\n \n );\n }\n\n return (\n setIsFlyoutOpen(false)}\n className={`relative flex flex-col items-center justify-center flex-1 h-[62px] transition-colors duration-300 select-none ${\n isActive ? 'text-primary-dark' : 'text-neutral-dark hover:text-black'\n }`}\n style={{ WebkitTapHighlightColor: 'transparent', touchAction: 'manipulation' }}\n >\n {innerContent}\n \n );\n })}\n \n
\n\n \n {isFlyoutOpen && (\n <>\n setIsFlyoutOpen(false)}\n />\n {\n if (info.offset.y > 50 || info.velocity.y > 100) {\n setIsFlyoutOpen(false);\n }\n }}\n className=\"fixed bottom-0 left-0 right-0 z-[9998] bg-white rounded-t-[32px] pt-4 pb-[calc(env(safe-area-inset-bottom)_+_7rem)] px-6 md:hidden shadow-[0_-20px_40px_rgba(0,0,0,0.1)] border-t border-neutral-100\"\n >\n
\n {currentLocale === 'de' ? 'Über uns & Mehr' : 'About us & More'} \n \n {flyoutLinks.map((link, i) => {\n const childUrl = link.url.startsWith('/') && !link.url.match(/^\\/(en|de)/) \n ? `/${currentLocale}${link.url}` \n : link.url;\n const isChildActive = pathname === childUrl;\n \n return (\n
setIsFlyoutOpen(false)}\n className={`px-4 py-4 rounded-2xl text-[15px] font-semibold transition-all flex items-center justify-between ${\n isChildActive ? 'bg-primary/10 text-primary' : 'text-neutral-700 hover:bg-neutral-50 active:bg-neutral-100'\n }`}\n >\n {link.label}\n \n \n \n \n );\n })}\n
\n \n >\n )}\n \n >\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/DynamicFramerMotion.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/DynamicInitialLoader.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/DynamicPageTransitionShutter.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/FramerMotionProvider.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/InitialLoader.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Image' is defined but never used.","line":5,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":5,"endColumn":13,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"Image"},"fix":{"range":[119,151],"text":""},"desc":"Remove unused import declaration."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport React, { useEffect, useState } from 'react';\nimport { m, AnimatePresence } from 'framer-motion';\nimport Image from 'next/image';\n\n\nexport function InitialLoader({ shouldShowLoader = true }: { shouldShowLoader?: boolean }) {\n const [isLoading, setIsLoading] = useState(shouldShowLoader);\n\n const finishLoading = () => {\n // Set a session cookie so the server knows not to render the loader again\n document.cookie = \"etib_initial_loader_v5=true; path=/; samesite=lax\";\n setIsLoading(false);\n };\n\n useEffect(() => {\n if (!shouldShowLoader) return;\n \n // Just show the animation for a short time to allow UI to mount,\n // we don't preload 10MB videos anymore to save pagespeed!\n const timeout = setTimeout(() => {\n finishLoading();\n }, 1200);\n\n // Cleanup\n return () => clearTimeout(timeout);\n }, [shouldShowLoader]);\n\n // Wenn wir serverseitig rendern und es nicht zeigen sollen, return null\n if (!shouldShowLoader) return null;\n\n return (\n \n {isLoading && (\n \n {/* Subtle grid background */}\n
\n\n {/* Deep Glow */}\n
\n \n \n {/* Single Masked Logo Container to prevent double-logo offset */}\n \n {/* Base logo color (slightly dimmed to make the sweep visible) */}\n
\n \n {/* Sweeping intense white light */}\n
\n
\n \n {/* Elegant Progress Bar */}\n \n
\n System \n Online \n
\n
\n \n \n
\n \n \n )}\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/PageTransitionShutter.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Image' is defined but never used.","line":6,"column":8,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":13,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"Image"},"fix":{"range":[148,180],"text":""},"desc":"Remove unused import declaration."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport React from 'react';\nimport { m, AnimatePresence } from 'framer-motion';\nimport { useTransition } from './TransitionProvider';\nimport Image from 'next/image';\n\nexport function PageTransitionShutter() {\n const { isTransitioning, transitionMessage } = useTransition();\n\n return (\n \n {isTransitioning && (\n \n {/* Subtle grid background */}\n
\n\n {/* Deep Glow inside the Shutter */}\n
\n \n \n {/* Single Masked Logo Container to prevent double-logo offset */}\n \n {/* Base logo color (slightly dimmed to make the sweep visible) */}\n
\n \n {/* Sweeping intense white light */}\n
\n
\n \n {/* Transition Message or Graphical Visualizer */}\n \n {transitionMessage && (\n transitionMessage.startsWith('LANG_SWITCH:') ? (\n (() => {\n const [, fromLang, toLang] = transitionMessage.split(':');\n return (\n \n {/* Old Language Flag - Fading out */}\n \n \n {fromLang} \n
\n \n {/* Animated Arrow connecting them */}\n \n
\n \n \n \n \n \n
\n \n \n \n
\n
\n\n {/* New Language Flag - Intense Glow */}\n \n {toLang} \n \n
\n \n );\n })()\n ) : (\n \n \n \n \n \n \n \n \n {transitionMessage}\n \n \n )\n )}\n \n \n \n )}\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/providers/TransitionProvider.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/search/AIOrb.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'breathe' is assigned a value but never used.","line":153,"column":13,"nodeType":"Identifier","messageId":"unusedVar","endLine":153,"endColumn":20},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'targetExcite' is assigned a value but never used.","line":212,"column":15,"nodeType":"Identifier","messageId":"unusedVar","endLine":212,"endColumn":27}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport React, { useRef, useEffect, useCallback } from 'react';\n\ninterface AIOrbProps {\n isThinking: boolean;\n hasError?: boolean;\n}\n\nfunction lerp(a: number, b: number, t: number) {\n return a + (b - a) * t;\n}\n\n// Simple noise function for organic movement\nfunction noise(x: number, y: number, t: number): number {\n return (\n Math.sin(x * 1.3 + t * 0.7) * Math.cos(y * 0.9 + t * 0.5) * 0.5 +\n Math.sin(x * 2.7 + y * 1.1 + t * 1.3) * 0.25 +\n Math.cos(x * 0.8 - y * 2.3 + t * 0.9) * 0.25\n );\n}\n\n// ── Particle ───────────────────────────────────────────────────\ninterface Particle {\n // Sphere position (target shape)\n theta: number;\n phi: number;\n // Current position\n x: number;\n y: number;\n z: number;\n // Velocity\n vx: number;\n vy: number;\n vz: number;\n // Properties\n size: number;\n baseSize: number;\n hue: number; // 0=blue, 1=green\n brightness: number;\n phase: number;\n orbitSpeed: number;\n noiseScale: number;\n}\n\nfunction createParticles(count: number): Particle[] {\n const particles: Particle[] = [];\n\n for (let i = 0; i < count; i++) {\n // Fibonacci sphere distribution for even spacing\n const golden = Math.PI * (3 - Math.sqrt(5));\n const y = 1 - (i / (count - 1)) * 2;\n const radiusAtY = Math.sqrt(1 - y * y);\n const theta = golden * i;\n const phi = Math.acos(y);\n\n particles.push({\n theta,\n phi,\n x: Math.cos(theta) * radiusAtY,\n y,\n z: Math.sin(theta) * radiusAtY,\n vx: 0,\n vy: 0,\n vz: 0,\n size: 0.4 + Math.random() * 0.8,\n baseSize: 0.4 + Math.random() * 0.8,\n hue: Math.random() > 0.45 ? 0 : 1,\n brightness: 0.5 + Math.random() * 0.5,\n phase: Math.random() * Math.PI * 2,\n orbitSpeed: (0.1 + Math.random() * 0.4) * (Math.random() > 0.5 ? 1 : -1),\n noiseScale: 0.5 + Math.random() * 1.5,\n });\n }\n return particles;\n}\n\nexport default function AIOrb({ isThinking = false, hasError = false }: AIOrbProps) {\n const canvasRef = useRef(null);\n const wrapRef = useRef(null);\n const animRef = useRef(0);\n const particlesRef = useRef([]);\n\n const mouse = useRef({ x: 0.5, y: 0.5, hover: false });\n const state = useRef({\n pulse: 0,\n hover: 0,\n error: 0,\n mouseX: 0.5,\n mouseY: 0.5,\n rotY: 0,\n rotX: 0,\n breathe: 0,\n scatter: 0,\n shake: 0,\n });\n\n const onMove = useCallback((e: React.PointerEvent) => {\n const r = wrapRef.current?.getBoundingClientRect();\n if (!r) return;\n mouse.current.x = (e.clientX - r.left) / r.width;\n mouse.current.y = (e.clientY - r.top) / r.height;\n }, []);\n const onEnter = useCallback(() => {\n mouse.current.hover = true;\n }, []);\n const onLeave = useCallback(() => {\n mouse.current.hover = false;\n mouse.current.x = 0.5;\n mouse.current.y = 0.5;\n }, []);\n\n const draw = useCallback(\n function drawStep() {\n const canvas = canvasRef.current;\n if (!canvas) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n const dpr = window.devicePixelRatio || 1;\n const rect = canvas.getBoundingClientRect();\n const w = rect.width * dpr;\n const h = rect.height * dpr;\n if (canvas.width !== w || canvas.height !== h) {\n canvas.width = w;\n canvas.height = h;\n }\n\n const cx = w / 2;\n const cy = h / 2;\n const minDim = Math.min(w, h);\n // Reduced further to give maximum breathing room for glow + movement\n const sphereR = minDim * 0.16;\n const time = performance.now() / 1000;\n const s = state.current;\n const m = mouse.current;\n\n // ── Interpolate state ──\n s.pulse = lerp(s.pulse, isThinking ? 1 : 0, 0.03);\n s.hover = lerp(s.hover, m.hover ? 1 : 0, 0.12);\n s.error = lerp(s.error, hasError ? 1 : 0, 0.05);\n s.mouseX = lerp(s.mouseX, m.x, 0.12);\n s.mouseY = lerp(s.mouseY, m.y, 0.12);\n s.scatter = lerp(s.scatter, m.hover ? 0.8 : hasError ? 0.5 : 0, 0.06);\n s.shake += 0.15 * s.error;\n\n // Global rotation — ALWAYS rotating + ALWAYS facing cursor\n s.rotY += lerp(0.008, 0.04, Math.max(s.pulse, s.hover));\n const mouseRotY = (s.mouseX - 0.5) * 1.2; // always face cursor\n const mouseRotX = (s.mouseY - 0.5) * 0.8;\n\n s.breathe += lerp(1.2, 3.0, s.pulse) / 60;\n const breathe = Math.sin(s.breathe) * 0.5 + 0.5;\n\n // ── Clear ──\n ctx.clearRect(0, 0, w, h);\n\n // ── Subtle core glow ──\n const shakeX = Math.sin(s.shake * 17) * s.error * minDim * 0.02;\n const glowCX = cx + shakeX;\n const glowCY = cy;\n // Clamp glow radius so it never exceeds ~48% of canvas (leaves padding for movement)\n const glowR = Math.min(\n sphereR * lerp(2.2, 4.0, Math.max(s.pulse, s.hover * 0.8)),\n minDim * 0.48,\n );\n const glowA = lerp(0.1, 0.4, Math.max(s.pulse, s.hover * 0.7, s.error * 0.8));\n const glow = ctx.createRadialGradient(glowCX, glowCY, 0, glowCX, glowCY, glowR);\n // Glow color: blue normally, red on error\n const glowR1 = Math.round(lerp(20, 255, s.error));\n const glowG1 = Math.round(lerp(60, 40, s.error));\n const glowB1 = Math.round(lerp(255, 40, s.error));\n glow.addColorStop(0, `rgba(${glowR1}, ${glowG1}, ${glowB1}, ${glowA * 2})`);\n glow.addColorStop(\n 0.25,\n `rgba(${Math.round(lerp(80, 200, s.error))}, ${Math.round(lerp(140, 50, s.error))}, ${Math.round(lerp(255, 50, s.error))}, ${glowA * 1.2})`,\n );\n glow.addColorStop(0.6, `rgba(${glowR1}, ${glowG1}, ${glowB1}, ${glowA * 0.4})`);\n glow.addColorStop(1, `rgba(${glowR1}, ${glowG1}, ${glowB1}, 0)`);\n ctx.fillStyle = glow;\n ctx.beginPath();\n ctx.arc(glowCX, glowCY, glowR, 0, Math.PI * 2);\n ctx.fill();\n\n // ── Create particles if empty ──\n if (particlesRef.current.length === 0) {\n particlesRef.current = createParticles(350);\n }\n\n // ── Update & draw particles ──\n const cosRY = Math.cos(s.rotY + mouseRotY);\n const sinRY = Math.sin(s.rotY + mouseRotY);\n const cosRX = Math.cos(mouseRotX);\n const sinRX = Math.sin(mouseRotX);\n\n // Sort by z for correct layering\n type ParticleWithScreen = { p: Particle; sx: number; sy: number; sz: number; depth: number };\n const projected: ParticleWithScreen[] = [];\n\n for (const p of particlesRef.current) {\n // Target position: sphere surface + noise displacement\n const n = noise(p.theta * p.noiseScale, p.phi * p.noiseScale, time * 0.5 + p.phase);\n const displacement = 1 + n * lerp(0.12, 0.3, s.pulse);\n\n // Orbit: rotate theta — always moving, faster idle\n const activeTheta = p.theta + time * p.orbitSpeed * lerp(0.35, 0.8, s.pulse);\n\n // Sphere coordinates to cartesian\n const sinPhi = Math.sin(p.phi);\n const tgtX = Math.cos(activeTheta) * sinPhi * displacement;\n // Excitement from hover + pulse + error\n const targetExcite = Math.max(s.hover * 0.9, s.pulse, s.error * 0.8);\n const tgtY = Math.cos(p.phi) * displacement;\n const tgtZ = Math.sin(activeTheta) * sinPhi * displacement;\n\n // Scatter on hover: push particles outward\n const scatterMul = 1 + s.scatter * (0.5 + n * 0.5);\n\n // Spring physics toward target\n const tx = tgtX * scatterMul;\n const ty = tgtY * scatterMul;\n const tz = tgtZ * scatterMul;\n\n p.vx += (tx - p.x) * 0.08;\n p.vy += (ty - p.y) * 0.08;\n p.vz += (tz - p.z) * 0.08;\n p.vx *= 0.88;\n p.vy *= 0.88;\n p.vz *= 0.88;\n p.x += p.vx;\n p.y += p.vy;\n p.z += p.vz;\n\n // 3D rotation (Y then X)\n const rx = p.x * cosRY - p.z * sinRY;\n const rz = p.x * sinRY + p.z * cosRY;\n const ry = p.y * cosRX - rz * sinRX;\n const finalZ = p.y * sinRX + rz * cosRX;\n\n // Project to screen\n const perspective = 3;\n const scale = perspective / (perspective + finalZ);\n const sx = cx + rx * sphereR * scale;\n const sy = cy + ry * sphereR * scale;\n\n projected.push({ p, sx, sy, sz: finalZ, depth: scale });\n }\n\n // Sort back-to-front\n projected.sort((a, b) => a.sz - b.sz);\n\n for (const { p, sx, sy, sz, depth } of projected) {\n // Depth-based alpha and size\n const depthAlpha = 0.25 + (sz + 1) * 0.375; // 0.25 (back) → 1.0 (front)\n const twinkle = 0.75 + 0.25 * Math.sin(time * 3.5 + p.phase);\n\n const alpha =\n depthAlpha * twinkle * p.brightness * lerp(0.8, 1.3, Math.max(s.pulse, s.hover * 0.8));\n\n const drawSize =\n p.baseSize * depth * dpr * lerp(1.0, 2.0, Math.max(s.pulse, s.hover * 0.7));\n\n // Color — shift to red on error\n let r: number, g: number, b: number;\n if (s.error > 0.1) {\n // Error: red family\n if (p.hue === 0) {\n r = Math.round(lerp(40 + sz * 30, 255, s.error));\n g = Math.round(lerp(80 + sz * 40, 40 + sz * 20, s.error));\n b = Math.round(lerp(255, 40, s.error));\n } else {\n r = Math.round(lerp(100 + sz * 30, 230, s.error));\n g = Math.round(lerp(220 + sz * 17, 60, s.error));\n b = Math.round(lerp(20, 20, s.error));\n }\n } else if (p.hue === 0) {\n r = 60 + Math.round(sz * 40);\n g = 100 + Math.round(sz * 50);\n b = 255;\n } else {\n r = 120 + Math.round(sz * 30);\n g = 237 + Math.round(sz * 10);\n b = 30;\n }\n\n // Thinking: shift toward brighter, more saturated\n if (s.pulse > 0.1) {\n r = Math.round(lerp(r, p.hue === 0 ? 100 : 130, s.pulse * 0.3));\n g = Math.round(lerp(g, p.hue === 0 ? 140 : 237, s.pulse * 0.3));\n b = Math.round(lerp(b, p.hue === 0 ? 255 : 32, s.pulse * 0.3));\n }\n\n // Micro glow — always visible, stronger on front\n if (depthAlpha > 0.25) {\n const gSize = drawSize * lerp(4, 7, s.hover);\n const pg = ctx.createRadialGradient(sx, sy, 0, sx, sy, gSize);\n pg.addColorStop(0, `rgba(${r},${g},${b},${alpha * 0.5})`);\n pg.addColorStop(1, `rgba(${r},${g},${b},0)`);\n ctx.fillStyle = pg;\n ctx.beginPath();\n ctx.arc(sx, sy, gSize, 0, Math.PI * 2);\n ctx.fill();\n }\n\n // Core dot — bright\n ctx.fillStyle = `rgba(${Math.min(r + 40, 255)},${Math.min(g + 30, 255)},${b},${Math.min(alpha * 1.6, 1)})`;\n ctx.beginPath();\n ctx.arc(sx, sy, Math.max(drawSize * 0.5, 0.3 * dpr), 0, Math.PI * 2);\n ctx.fill();\n }\n\n // ── Loading rings (thinking) ──\n if (s.pulse > 0.02) {\n ctx.save();\n ctx.translate(cx, cy);\n\n // Spinning arc\n const spinAngle = time * 2;\n const arcLen = Math.PI * lerp(0.3, 1.0, (Math.sin(time * 1.5) + 1) / 2);\n ctx.rotate(spinAngle);\n ctx.strokeStyle = `rgba(130, 237, 32, ${s.pulse * 0.4})`;\n ctx.lineWidth = 1.2 * dpr;\n ctx.lineCap = 'round';\n ctx.beginPath();\n ctx.arc(0, 0, sphereR * 1.25, 0, arcLen);\n ctx.stroke();\n\n // Counter-spinning arc\n ctx.rotate(-spinAngle * 2);\n ctx.strokeStyle = `rgba(1, 29, 255, ${s.pulse * 0.3})`;\n ctx.lineWidth = 0.8 * dpr;\n ctx.beginPath();\n ctx.arc(0, 0, sphereR * 1.35, 0, arcLen * 0.6);\n ctx.stroke();\n\n ctx.restore();\n\n // Expanding pulse\n const pulsePhase = (time * 0.8) % 1;\n const pulseR = sphereR * (1 + pulsePhase * 1.5);\n const pulseA = s.pulse * (1 - pulsePhase) * 0.15;\n ctx.strokeStyle = `rgba(130, 237, 32, ${pulseA})`;\n ctx.lineWidth = 1 * dpr;\n ctx.beginPath();\n ctx.arc(cx, cy, pulseR, 0, Math.PI * 2);\n ctx.stroke();\n }\n\n animRef.current = requestAnimationFrame(drawStep);\n },\n [isThinking, hasError],\n );\n\n useEffect(() => {\n animRef.current = requestAnimationFrame(draw);\n return () => cancelAnimationFrame(animRef.current);\n }, [draw]);\n\n return (\n \n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/search/AISearchResults.test.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/search/AISearchResults.tsx","messages":[{"ruleId":"react-hooks/exhaustive-deps","severity":1,"message":"React Hook useEffect has missing dependencies: 'handleSearch' and 'initialQuery'. Either include them or remove the dependency array.","line":100,"column":6,"nodeType":"ArrayExpression","endLine":100,"endColumn":29,"suggestions":[{"desc":"Update the dependencies array to be: [handleSearch, initialQuery, isOpen, triggerSearch]","fix":{"range":[3050,3073],"text":"[handleSearch, initialQuery, isOpen, triggerSearch]"}}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport { useState, useRef, useEffect, KeyboardEvent } from 'react';\nimport { ArrowUp, X, Sparkles, ChevronRight, RotateCcw, Copy, Check } from 'lucide-react';\nimport Link from 'next/link';\nimport { useAnalytics } from '../analytics/useAnalytics';\nimport { AnalyticsEvents } from '../analytics/analytics-events';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport dynamic from 'next/dynamic';\nimport { useTranslations } from 'next-intl';\nconst AIOrb = dynamic(() => import('./AIOrb'), { ssr: false });\n\ninterface ProductMatch {\n id: string;\n title: string;\n sku: string;\n slug: string;\n}\n\ninterface Message {\n role: 'user' | 'assistant';\n content: string;\n products?: ProductMatch[];\n timestamp: number;\n}\ninterface ComponentProps {\n isOpen: boolean;\n onClose: () => void;\n initialQuery?: string;\n triggerSearch?: boolean;\n}\n\nexport function AISearchResults({\n isOpen,\n onClose,\n initialQuery = '',\n triggerSearch = false,\n}: ComponentProps) {\n const { trackEvent } = useAnalytics();\n\n const [query, setQuery] = useState('');\n const [messages, setMessages] = useState([]);\n const [honeypot, setHoneypot] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState(null);\n const [copiedIndex, setCopiedIndex] = useState(null);\n const [copiedAll, setCopiedAll] = useState(false);\n const t = useTranslations('AISearch');\n\n // We load texts dynamically from translations\n const loadingTexts = t.raw('loadingTexts') as string[];\n const prompts = t.raw('prompts') as string[];\n\n const [loadingText, setLoadingText] = useState(loadingTexts[0]);\n\n const inputRef = useRef(null);\n const modalRef = useRef(null);\n const messagesEndRef = useRef(null);\n const loadingIntervalRef = useRef | null>(null);\n const hasTriggeredRef = useRef(false);\n\n // Dedicated focus effect — polls until the input actually has focus\n useEffect(() => {\n if (!isOpen) return;\n let attempts = 0;\n const focusTimer = setInterval(() => {\n const el = inputRef.current;\n if (el && document.activeElement !== el) {\n el.focus({ preventScroll: true });\n }\n attempts++;\n if (attempts >= 15 || document.activeElement === el) {\n clearInterval(focusTimer);\n }\n }, 100);\n return () => clearInterval(focusTimer);\n }, [isOpen]);\n\n useEffect(() => {\n if (isOpen) {\n document.body.style.overflow = 'hidden';\n\n // Trigger initial search only once\n if (triggerSearch && initialQuery && !hasTriggeredRef.current) {\n hasTriggeredRef.current = true;\n handleSearch(initialQuery);\n }\n } else {\n document.body.style.overflow = 'unset';\n setQuery('');\n setMessages([]);\n setError(null);\n setIsLoading(false);\n hasTriggeredRef.current = false;\n }\n return () => {\n document.body.style.overflow = 'unset';\n };\n }, [isOpen, triggerSearch]);\n\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n }, [messages, isLoading]);\n\n // Global ESC handler\n useEffect(() => {\n if (!isOpen) return;\n const handleEsc = (e: globalThis.KeyboardEvent) => {\n if (e.key === 'Escape') {\n const activeElement = document.activeElement;\n const isInputFocused = activeElement === inputRef.current;\n\n if (query.trim()) {\n // If there's text, clear it but keep chat open\n setQuery('');\n inputRef.current?.focus();\n } else if (!isInputFocused) {\n // If no text and input is not focused, focus it\n inputRef.current?.focus();\n } else {\n // If no text and input IS focused, close the chat\n onClose();\n }\n }\n };\n document.addEventListener('keydown', handleEsc);\n return () => document.removeEventListener('keydown', handleEsc);\n }, [isOpen, onClose, query]);\n\n const handleSearch = async (searchQuery: string = query) => {\n if (!searchQuery.trim() || isLoading) return;\n\n const newUserMessage: Message = { role: 'user', content: searchQuery, timestamp: Date.now() };\n const newMessagesContext = [...messages, newUserMessage];\n\n setMessages(newMessagesContext);\n setQuery(''); // Always clear input after send\n setError(null);\n\n // Give the user message animation 400ms to arrive before showing \"thinking\"\n setTimeout(() => {\n setIsLoading(true);\n // Start rotating loading texts\n let textIdx = Math.floor(Math.random() * loadingTexts.length);\n setLoadingText(loadingTexts[textIdx]);\n loadingIntervalRef.current = setInterval(() => {\n textIdx = (textIdx + 1) % loadingTexts.length;\n setLoadingText(loadingTexts[textIdx]);\n }, 2500);\n }, 400);\n\n trackEvent(AnalyticsEvents.FORM_SUBMIT, {\n type: 'ai_chat',\n query: searchQuery,\n });\n\n try {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 60000);\n\n const res = await fetch('/api/ai-search', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n signal: controller.signal,\n body: JSON.stringify({\n messages: newMessagesContext,\n _honeypot: honeypot,\n }),\n });\n\n clearTimeout(timeout);\n\n const data = await res.json().catch(() => null);\n\n if (!res.ok || !data) {\n throw new Error(data?.error || `Server antwortete mit Status ${res.status}`);\n }\n\n setMessages((prev) => [\n ...prev,\n {\n role: 'assistant',\n content: data.answerText,\n products: data.products,\n timestamp: Date.now(),\n },\n ]);\n\n setTimeout(() => inputRef.current?.focus(), 100);\n } catch (err: any) {\n console.error(err);\n const msg =\n err.name === 'AbortError'\n ? t('timeoutError')\n : err.message || t('genericError');\n\n // Show error as a system message in the chat instead of a separate error banner\n setMessages((prev) => [\n ...prev,\n {\n role: 'assistant',\n content: `⚠️ ${msg}`,\n timestamp: Date.now(),\n },\n ]);\n\n trackEvent(AnalyticsEvents.ERROR, {\n location: 'ai_chat',\n message: err.message,\n query: searchQuery,\n });\n } finally {\n setIsLoading(false);\n if (loadingIntervalRef.current) {\n clearInterval(loadingIntervalRef.current);\n loadingIntervalRef.current = null;\n }\n // Always re-focus the input\n setTimeout(() => inputRef.current?.focus(), 50);\n }\n };\n\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleSearch();\n }\n if (e.key === 'ArrowUp' && !query) {\n // Find the last user message and put it into the input\n const lastUserNav = [...messages].reverse().find((m) => m.role === 'user');\n if (lastUserNav) {\n e.preventDefault();\n setQuery(lastUserNav.content);\n }\n }\n };\n\n const handleCopy = (content: string, index?: number) => {\n navigator.clipboard.writeText(content);\n if (index !== undefined) {\n setCopiedIndex(index);\n setTimeout(() => setCopiedIndex(null), 2000);\n } else {\n setCopiedAll(true);\n setTimeout(() => setCopiedAll(false), 2000);\n }\n };\n\n const handleCopyChat = () => {\n const fullChat = messages\n .map((m) => `${m.role === 'user' ? t('you') : 'Ohm'}:\\n${m.content}`)\n .join('\\n\\n');\n handleCopy(fullChat);\n };\n\n if (!isOpen) return null;\n\n const handleBackdropClick = (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n onClose();\n }\n };\n\n return (\n \n {/* Animated backdrop */}\n
\n\n
\n {/* ── Glassmorphism container ── */}\n
\n {/* ── Header ── */}\n
\n
\n
\n
\n
Ohm \n
\n {isLoading ? t('thinking') : error ? t('errorStatus') : t('online')}\n
\n
\n
\n
\n {messages.length > 0 && (\n \n {copiedAll ? (\n \n ) : (\n \n )}\n {copiedAll ? t('copied') : t('copyChat')} \n \n )}\n \n \n \n
\n
\n\n {/* ── Chat Area ── */}\n
\n {/* Empty state */}\n {messages.length === 0 && !isLoading && !error && (\n
\n
\n
\n
\n {t('howCanIHelp')}\n
\n
\n {t('helpDescription')}\n
\n
\n {/* Quick prompts */}\n
\n {prompts.map(\n (prompt) => (\n handleSearch(prompt)}\n className=\"text-xs text-white/40 hover:text-white/80 border border-white/10 hover:border-white/20 hover:bg-white/5 rounded-full px-4 py-2 transition-all duration-200 cursor-pointer\"\n >\n {prompt}\n \n ),\n )}\n
\n
\n )}\n\n {/* Messages */}\n {messages.map((msg, index) => (\n
\n
\n {/* Copy Button */}\n
handleCopy(msg.content, index)}\n className={`absolute opacity-0 group-hover:opacity-100 transition-opacity p-1.5 rounded-lg cursor-pointer ${\n msg.role === 'user'\n ? 'top-2 right-2 bg-primary/10 hover:bg-primary/20 text-primary/60 hover:text-primary'\n : 'top-2 right-2 bg-white/5 hover:bg-white/10 text-white/40 hover:text-white'\n }`}\n title={t('copyMessage')}\n >\n {copiedIndex === index ? (\n \n ) : (\n \n )}\n \n\n {msg.role === 'assistant' && (\n
\n \n \n Ohm\n \n
\n )}\n
\n {msg.role === 'assistant' ? (\n
{msg.content} \n ) : (\n
{msg.content}
\n )}\n
\n\n {/* Timestamp */}\n {!msg.products?.length && (\n
\n {new Date(msg.timestamp).toLocaleTimeString('de', {\n hour: '2-digit',\n minute: '2-digit',\n })}\n
\n )}\n\n {/* Product cards */}\n {msg.role === 'assistant' && msg.products && msg.products.length > 0 && (\n
\n
\n {t('recommendedProducts')}\n \n
\n {msg.products.map((product, idx) => (\n
{\n onClose();\n trackEvent(AnalyticsEvents.BUTTON_CLICK, {\n target: product.slug,\n location: 'ai_chat',\n });\n }}\n className=\"group flex items-center justify-between bg-white/[0.04] hover:bg-white/[0.08] border border-white/[0.06] hover:border-accent/30 rounded-xl px-4 py-3 transition-all duration-300\"\n style={{ animation: `chatFadeIn 0.3s ease-out ${idx * 0.1}s both` }}\n >\n
\n
\n {product.sku}\n
\n
\n {product.title}\n \n
\n
\n \n ))}\n
\n
\n )}\n
\n
\n ))}\n\n {/* Loading indicator */}\n {isLoading && (\n
\n
\n
\n
\n
\n {loadingText}\n
\n
\n {[0, 1, 2].map((i) => (\n
\n ))}\n
\n
\n
\n
\n )}\n\n {/* Error */}\n {error && (\n
\n
\n
\n
\n
{t('errorTitle')} \n
{error}
\n
{\n setError(null);\n inputRef.current?.focus();\n }}\n className=\"flex items-center gap-1.5 text-[10px] font-bold text-red-300/50 hover:text-red-300 mt-2 transition-colors cursor-pointer\"\n >\n \n {t('tryAgain')}\n \n
\n
\n
\n )}\n\n
\n
\n\n {/* ── Input Area ── */}\n
\n
\n
\n \n {t('footerShortcuts')}\n \n \n ·\n \n \n {t('footerPrivacy')}\n \n
\n
\n
\n
\n\n {/* ── Keyframe animations ── */}\n \n
\n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/team/Gallery.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/AnimatedCounter.tsx","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'useState' is defined but never used.","line":3,"column":28,"nodeType":"Identifier","messageId":"unusedVar","endLine":3,"endColumn":36,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"useState"},"fix":{"range":[40,50],"text":""},"desc":"Remove unused variable \"useState\"."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"'use client';\n\nimport React, { useEffect, useState } from 'react';\nimport { useMotionValue, useTransform, animate, useInView, m } from 'framer-motion';\n\ninterface AnimatedCounterProps {\n value: number;\n duration?: number;\n delay?: number;\n suffix?: string;\n prefix?: string;\n className?: string;\n}\n\nexport function AnimatedCounter({\n value,\n duration = 2,\n delay = 0,\n suffix = '',\n prefix = '',\n className = '',\n}: AnimatedCounterProps) {\n const count = useMotionValue(0);\n const rounded = useTransform(count, (latest) => {\n // Format with thousands separator\n return new Intl.NumberFormat('de-DE').format(Math.round(latest));\n });\n\n const ref = React.useRef(null);\n const inView = useInView(ref, { once: true, margin: \"-100px\" });\n\n useEffect(() => {\n if (inView) {\n const controls = animate(count, value, {\n duration: duration,\n delay: delay,\n ease: 'easeOut',\n });\n return controls.stop;\n }\n }, [inView, value, duration, delay, count]);\n\n return (\n \n {prefix}{rounded} {suffix}\n \n );\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/AnimatedGlossyBorder.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Badge.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Button.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Callout.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Card.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Container.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Heading.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/HoverShineOverlay.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Input.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Label.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/LogoArcs.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Section.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Textarea.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/Tooltip.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/TransitionLink.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/decorations/StratumLines.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/index.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/components/ui/utils.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/i18n/request.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/instrumentation-client.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/instrumentation.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/blog.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'BLOG_DIR' is assigned a value but never used.","line":6,"column":7,"nodeType":"Identifier","messageId":"unusedVar","endLine":6,"endColumn":15},{"ruleId":"prefer-const","severity":1,"message":"'next' is never reassigned. Use 'const' instead.","line":94,"column":7,"nodeType":"Identifier","messageId":"useConst","endLine":94,"endColumn":11,"fix":{"range":[2521,2582],"text":"const next = currentIndex > 0 ? posts[currentIndex - 1] : null;"}},{"ruleId":"prefer-const","severity":1,"message":"'prev' is never reassigned. Use 'const' instead.","line":95,"column":7,"nodeType":"Identifier","messageId":"useConst","endLine":95,"endColumn":11,"fix":{"range":[2585,2661],"text":"const prev = currentIndex < posts.length - 1 ? posts[currentIndex + 1] : null;"}}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":2,"source":"import fs from 'fs';\nimport path from 'path';\nimport matter from 'gray-matter';\nimport { config } from '@/lib/config';\n\nconst BLOG_DIR = path.join(process.cwd(), 'content', 'blog');\n\nexport interface PostFrontmatter {\n title: string;\n date: string;\n excerpt?: string;\n featuredImage?: string | null;\n focalX?: number;\n focalY?: number;\n category?: string;\n public?: boolean;\n}\n\nexport interface PostData {\n slug: string;\n frontmatter: PostFrontmatter;\n content: string;\n}\n\nexport function isPostVisible(post: { frontmatter: { date: string; public?: boolean } }) {\n if (post.frontmatter.public === false && config.isProduction) {\n return false;\n }\n const postDate = new Date(post.frontmatter.date);\n const now = new Date();\n return !(postDate > now && config.isProduction);\n}\n\nexport async function getPostBySlug(slug: string, locale: string): Promise {\n // Check locale-specific directory first\n const localeDir = path.join(process.cwd(), 'content', locale, 'blog');\n const filePath = path.join(localeDir, `${slug}.mdx`);\n\n if (!fs.existsSync(filePath)) return null;\n\n const source = fs.readFileSync(filePath, 'utf8');\n const { content, data } = matter(source);\n\n return {\n slug,\n frontmatter: data as PostFrontmatter,\n content,\n };\n}\n\nexport async function getAllPosts(locale: string): Promise {\n const localeDir = path.join(process.cwd(), 'content', locale, 'blog');\n if (!fs.existsSync(localeDir)) return [];\n\n const files = fs.readdirSync(localeDir).filter(f => f.endsWith('.mdx'));\n \n const posts = await Promise.all(\n files.map(async f => {\n const slug = f.replace('.mdx', '');\n return await getPostBySlug(slug, locale);\n })\n );\n\n return posts\n .filter((p): p is PostData => p !== null)\n .filter(isPostVisible)\n .sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime());\n}\n\nexport async function getAllPostsMetadata(locale: string): Promise[]> {\n const posts = await getAllPosts(locale);\n return posts.map((p) => ({\n slug: p.slug,\n frontmatter: p.frontmatter,\n }));\n}\n\nexport async function getAdjacentPosts(\n slug: string,\n locale: string,\n): Promise<{\n prev: PostData | null;\n next: PostData | null;\n isPrevRandom?: boolean;\n isNextRandom?: boolean;\n}> {\n const posts = await getAllPosts(locale);\n const currentIndex = posts.findIndex((post) => post.slug === slug);\n\n if (currentIndex === -1) {\n return { prev: null, next: null };\n }\n\n let next = currentIndex > 0 ? posts[currentIndex - 1] : null;\n let prev = currentIndex < posts.length - 1 ? posts[currentIndex + 1] : null;\n\n return { prev, next };\n}\n\nexport function getReadingTime(content: string): number {\n const wordsPerMinute = 200;\n const noOfWords = content.split(/\\s/g).length;\n const minutes = noOfWords / wordsPerMinute;\n return Math.ceil(minutes);\n}\n\nexport function generateHeadingId(text: string): string {\n let id = text.toLowerCase();\n id = id.replace(/ä/g, 'ae').replace(/ö/g, 'oe').replace(/ü/g, 'ue').replace(/ß/g, 'ss');\n id = id.replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '');\n return id || 'heading';\n}\n\nexport function getHeadings(content: string): { id: string; text: string; level: number }[] {\n const headingLines = content.split('\\n').filter((line) => line.match(/^#{2,4}\\s/));\n\n return headingLines.map((line) => {\n const level = line.match(/^#+/)?.[0].length || 2;\n const text = line.replace(/^#+\\s/, '').trim();\n const id = generateHeadingId(text);\n return { id, text, level };\n });\n}\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/config.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/datasheets.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/empty-polyfill.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/env.test.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/env.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/framer-features.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/mail/mailer.test.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/mail/mailer.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/map-data.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/mdx.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/metadata.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/og-helper.tsx","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/pages.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/references.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/schema.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/analytics/analytics-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/analytics/noop-analytics-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/analytics/umami-analytics-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/app-services.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/cache/cache-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/cache/memory-cache-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/create-services.server.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/create-services.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/errors/error-reporting-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/errors/glitchtip-error-reporting-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/errors/noop-error-reporting-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/logging/logger-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/logging/noop-logger-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/logging/pino-logger-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/notifications/gotify-notification-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/services/notifications/notification-service.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/slugs.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/standorte-data.ts","messages":[{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'LucideIcon' is defined but never used.","line":1,"column":10,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":20,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"LucideIcon"},"fix":{"range":[9,20],"text":""},"desc":"Remove unused variable \"LucideIcon\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'MapPin' is defined but never used.","line":1,"column":22,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":28,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"MapPin"},"fix":{"range":[19,27],"text":""},"desc":"Remove unused variable \"MapPin\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Building2' is defined but never used.","line":1,"column":30,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":39,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Building2"},"fix":{"range":[27,38],"text":""},"desc":"Remove unused variable \"Building2\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'HardHat' is defined but never used.","line":1,"column":41,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":48,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"HardHat"},"fix":{"range":[38,47],"text":""},"desc":"Remove unused variable \"HardHat\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Phone' is defined but never used.","line":1,"column":50,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":55,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Phone"},"fix":{"range":[47,54],"text":""},"desc":"Remove unused variable \"Phone\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Mail' is defined but never used.","line":1,"column":57,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":61,"suggestions":[{"messageId":"removeUnusedVar","data":{"varName":"Mail"},"fix":{"range":[54,60],"text":""},"desc":"Remove unused variable \"Mail\"."}]},{"ruleId":"@typescript-eslint/no-unused-vars","severity":1,"message":"'Navigation' is defined but never used.","line":1,"column":63,"nodeType":"Identifier","messageId":"unusedVar","endLine":1,"endColumn":73,"suggestions":[{"messageId":"removeUnusedImportDeclaration","data":{"varName":"Navigation"},"fix":{"range":[0,96],"text":""},"desc":"Remove unused import declaration."}]}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"import { LucideIcon, MapPin, Building2, HardHat, Phone, Mail, Navigation } from 'lucide-react';\n\nexport interface ContactPerson {\n name: string;\n role: string;\n phone: string;\n email: string;\n image?: string;\n}\n\nexport interface StandortData {\n id: string;\n name: string;\n shortName: string;\n description: Record<'de' | 'en', string>;\n address: {\n street: string;\n city: string;\n };\n contact: {\n phone: string;\n email: string;\n };\n coordinates: {\n lat: number;\n lng: number;\n };\n keyFeatures: Record<'de' | 'en', string[]>;\n image: string;\n gallery: string[];\n type: 'hq' | 'branch';\n}\n\nexport const standorteData: StandortData[] = [\n {\n id: 'guben',\n name: 'E-TIB GmbH',\n shortName: 'Standort Brandenburg (Hauptsitz)',\n type: 'hq',\n description: {\n de: 'Unser Hauptsitz in Guben bildet das strategische und operative Zentrum der E-TIB Gruppe. Von hier aus steuern wir bundesweit Großprojekte im Kabelleitungsnetzbau und koordinieren unseren hochmodernen Maschinenpark.',\n en: 'Our headquarters in Guben forms the strategic and operational center of the E-TIB Group. From here, we manage large-scale civil engineering projects nationwide and coordinate our state-of-the-art machinery fleet.'\n },\n address: {\n street: 'Gewerbestraße 22',\n city: '03172 Guben'\n },\n contact: {\n phone: '+49 (0) 3561 / 68577 33',\n email: 'info@e-tib.com'\n },\n coordinates: {\n lat: 51.942,\n lng: 14.717\n },\n keyFeatures: {\n de: [\n 'Zentrale strategische Steuerung',\n 'Kabelleitungsnetzbau & Infrastruktur',\n 'Koordination Großmaschinenpark',\n 'Zentrales Projektmanagement'\n ],\n en: [\n 'Central strategic management',\n 'Cable network engineering & infrastructure',\n 'Coordination of heavy machinery',\n 'Central project management'\n ]\n },\n image: '/assets/photos/DSC02114.jpg',\n gallery: [\n '/assets/photos/DSC02114.jpg',\n '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-45.jpg'\n ]\n },\n {\n id: 'kirchheilingen',\n name: 'E-TIB Bohrtechnik GmbH',\n shortName: 'Standort Thüringen',\n type: 'branch',\n description: {\n de: 'Unser spezialisierter Standort für Horizontalspülbohrtechnik in Thüringen. Mit modernsten Bohranlagen realisieren wir grabenlose Leitungsverlegungen für komplexe Infrastrukturprojekte, umweltschonend und hocheffizient.',\n en: 'Our specialized location for horizontal directional drilling in Thuringia. Utilizing state-of-the-art drilling rigs, we implement trenchless cable laying for complex infrastructure projects, environmentally friendly and highly efficient.'\n },\n address: {\n street: 'Bahnhofstraße 180a',\n city: '99947 Kirchheilingen'\n },\n contact: {\n phone: '+49 (0) 3561 / 68577 33', // Placeholder, using central\n email: 'info@e-tib.com'\n },\n coordinates: {\n lat: 51.1852,\n lng: 10.6865\n },\n keyFeatures: {\n de: [\n 'Horizontalspülbohrtechnik (HDD)',\n 'Grabenlose Rohrverlegung',\n 'Felsbohrungen',\n 'Unterquerung von Gewässern & Straßen'\n ],\n en: [\n 'Horizontal Directional Drilling (HDD)',\n 'Trenchless pipe laying',\n 'Rock drilling',\n 'Crossing underneath water bodies & roads'\n ]\n },\n image: '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie_Vermessung_(247_von_80).jpg',\n gallery: [\n '/assets/photos/DSC02089.jpg',\n '/assets/photos/Etib_E-tib_Tiefbau_Guben_Netzanbindung_Energie-6.jpg'\n ]\n },\n {\n id: 'buelstedt',\n name: 'E-TIB Ingenieurgesellschaft mbH',\n shortName: 'Standort Niedersachsen',\n type: 'branch',\n description: {\n de: 'Unsere Ingenieurgesellschaft in Bülstedt ist spezialisiert auf die hochpräzise Planung, Vermessung und Projektierung von Energie- und Telekommunikationsnetzen. Hier verschmelzen technische Expertise und modernes Datenmanagement.',\n en: 'Our engineering firm in Bülstedt specializes in the high-precision planning, surveying, and project management of energy and telecommunication networks. This is where technical expertise and modern data management converge.'\n },\n address: {\n street: 'Dorfstraße 1', // Placeholder\n city: '27412 Bülstedt'\n },\n contact: {\n phone: '+49 (0) 3561 / 68577 33', // Placeholder, using central\n email: 'info@e-tib.com'\n },\n coordinates: {\n lat: 53.2167,\n lng: 9.15\n },\n keyFeatures: {\n de: [\n 'Trassenplanung & Projektierung',\n 'Geodätische Vermessung (GPS/Tachymetrie)',\n 'As-Built Dokumentation (GIS/CAD)',\n 'Genehmigungsmanagement'\n ],\n en: [\n 'Route planning & project management',\n 'Geodetic surveying (GPS/Tacheometry)',\n 'As-built documentation (GIS/CAD)',\n 'Permit management'\n ]\n },\n image: '/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-256.jpg',\n gallery: [\n '/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-294.jpg',\n '/assets/photos/etib_ingenieure_tiefbau_solar_pv_planung_vermessung_gis-298.jpg'\n ]\n }\n];\n\nexport function getStandortById(id: string): StandortData | undefined {\n return standorteData.find(s => s.id === id);\n}\n\n// Convert StandortData to the Location format required by the map\nexport const standorteLocations = standorteData.map(standort => ({\n id: standort.id,\n name: standort.shortName,\n type: standort.type,\n x: standort.id === 'kirchheilingen' ? 52 : standort.id === 'buelstedt' ? 37 : 85,\n y: standort.id === 'kirchheilingen' ? 55 : standort.id === 'buelstedt' ? 25.33 : 41.1,\n description: standort.name,\n href: `/standorte/${standort.id}`,\n details: standort.keyFeatures.de.slice(0, 3)\n}));\n","usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/strapi.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/lib/utils/technical.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/proxy.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/scratch/test-slugs.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/sentry.edge.config.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/sentry.server.config.ts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/vitest.config.mts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]},{"filePath":"/Volumes/Alpha SSD/Coding/e-tib.com/vitest.e2e.config.mts","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[]}]
ELIFECYCLE Command failed with exit code 1.