Files
e-tib.com/lint-results.json

7 lines
362 KiB
JSON
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> 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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 <img {...props} src={finalSrc} srcSet={srcSet} sizes={sizes} loading=\"lazy\" decoding=\"async\" />\n );\n },\n // Standard HTML element mapping for consistent E-TIB typography\n h1: (props: any) => <Heading level={1} size=\"2\" className=\"hidden\" {...props} />, // Hidden because Hero handles H1\n h2: (props: any) => <Heading level={2} size=\"3\" className=\"mt-16 mb-6 border-b border-neutral-100 pb-4\" {...props} />,\n h3: (props: any) => <Heading level={3} size=\"4\" className=\"mt-12 mb-4 text-primary\" {...props} />,\n h4: (props: any) => <Heading level={4} size=\"5\" className=\"mt-8 mb-4 uppercase tracking-widest text-neutral-500\" {...props} />,\n p: (props: any) => <p className=\"text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl\" {...props} />,\n ul: (props: any) => <ul className=\"list-none mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl\" {...props} />,\n ol: (props: any) => <ol className=\"list-decimal pl-6 mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl\" {...props} />,\n li: (props: any) => (\n <li className=\"relative pl-6 before:content-[''] before:absolute before:left-0 before:top-[0.6em] before:w-2 before:h-2 before:bg-primary/50 before:rounded-sm\" {...props} />\n ),\n a: (props: any) => <a className=\"text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold\" {...props} />,\n strong: (props: any) => <strong className=\"font-bold text-neutral-900\" {...props} />,\n blockquote: (props: any) => (\n <blockquote className=\"border-l-4 border-primary pl-6 py-3 my-10 italic bg-neutral-50 rounded-r-2xl text-neutral-700 font-medium max-w-3xl shadow-sm\" {...props} />\n ),\n hr: (props: any) => <hr className=\"my-16 border-t-2 border-neutral-100 max-w-3xl\" {...props} />,\n img: (props: any) => <img className=\"rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100\" {...props} />,\n};\n\ninterface PageProps {\n params: Promise<{\n locale: string;\n slug: string;\n }>;\n}\n\nexport async function generateMetadata({ params }: PageProps): Promise<Metadata> {\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) => <InteractiveGermanyMap locations={enrichedLocations} {...props} />,\n HomeReferencesSlider: (props: any) => <HomeReferencesSlider {...props} references={references.map(r => ({\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 <div className={`flex flex-col min-h-screen ${isFullBleed ? '' : 'bg-white'}`}>\n {/* Generic Hero Section (only for standard pages) */}\n {!isFullBleed && (\n <section key={`hero-section-${slug}`} className=\"bg-primary-dark text-white pt-28 pb-10 md:pt-56 md:pb-16 min-h-[30vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden\">\n <div className=\"absolute inset-0 opacity-20 pointer-events-none\">\n <div className=\"absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent\" />\n </div>\n <Container className=\"relative z-10\">\n <div className=\"max-w-4xl\">\n <Badge variant=\"accent\" className=\"mb-4 md:mb-6 shadow-sm\">\n {t('badge')}\n </Badge>\n <Heading level={1} variant=\"white\" className=\"mb-0 drop-shadow-sm\">\n {pageData.frontmatter.title}\n </Heading>\n </div>\n </Container>\n </section>\n )}\n\n {/* Main Content Area */}\n {isFullBleed ? (\n <div className=\"w-full\">\n <MDXRemote source={pageData.content} components={pageMdxComponents} />\n </div>\n ) : (\n <Container className=\"py-16 md:py-24\">\n {/* Excerpt/Lead paragraph if available */}\n {pageData.frontmatter.excerpt && (\n <div className=\"mb-16 max-w-4xl mx-auto\">\n <p className=\"text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic\">\n {pageData.frontmatter.excerpt}\n </p>\n </div>\n )}\n\n {/* Main content */}\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"prose prose-lg md:prose-xl prose-neutral max-w-none prose-headings:font-heading prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-4xl prose-h2:text-3xl prose-h2:mt-12 prose-h2:border-b prose-h2:border-neutral-200 prose-h2:pb-4 prose-h3:text-2xl prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:font-medium prose-a:underline prose-a:underline-offset-4 prose-a:decoration-primary/30 hover:prose-a:decoration-primary prose-strong:text-text-primary prose-strong:font-semibold prose-li:text-text-secondary prose-ul:list-disc prose-ol:list-decimal\">\n <MDXRemote source={pageData.content} components={pageMdxComponents} />\n </div>\n </div>\n </Container>\n )}\n\n {/* Support Section */}\n <Container>\n <div className=\"mt-8 mb-8 md:mt-24 md:mb-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group animate-slight-fade-in-from-bottom\">\n <div className=\"absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3\" />\n <div className=\"relative z-10 max-w-2xl\">\n <h3 className=\"text-2xl md:text-3xl font-bold mb-4\">{t('needHelp')}</h3>\n <p className=\"text-lg text-white/70 mb-8\">{t('supportTeamAvailable')}</p>\n <TrackedLink\n href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}\n className={getButtonClasses('accent', 'lg')}\n eventProperties={{\n location: 'generic_page_support_cta',\n page_slug: slug,\n }}\n >\n <span className=\"relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark\">\n {t('contactUs')}\n <span className=\"ml-2 transition-transform group-hover/btn:translate-x-1\">\n &rarr;\n </span>\n </span>\n <ButtonOverlay variant=\"accent\" />\n </TrackedLink>\n </div>\n </div>\n </Container>\n </div>\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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) => <Heading level={1} size=\"2\" className=\"hidden\" {...props} />, // Hidden because Hero handles H1\n h2: (props: any) => <Heading level={2} size=\"3\" className=\"mt-16 mb-6 border-b border-neutral-100 pb-4\" {...props} />,\n h3: (props: any) => <Heading level={3} size=\"4\" className=\"mt-12 mb-4 text-primary\" {...props} />,\n h4: (props: any) => <Heading level={4} size=\"5\" className=\"mt-8 mb-4 uppercase tracking-widest text-neutral-500\" {...props} />,\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 <img {...props} src={finalSrc} srcSet={srcSet} sizes={sizes} loading=\"lazy\" decoding=\"async\" />\n );\n },\n p: (props: any) => <p className=\"text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-normal max-w-3xl\" {...props} />,\n ul: (props: any) => <ul className=\"list-none mb-8 space-y-3 text-base md:text-lg text-text-secondary font-normal max-w-3xl\" {...props} />,\n ol: (props: any) => <ol className=\"list-decimal pl-6 mb-8 space-y-3 text-base md:text-lg text-text-secondary font-normal max-w-3xl\" {...props} />,\n li: (props: any) => (\n <li className=\"relative pl-6 before:content-[''] before:absolute before:left-0 before:top-[0.6em] before:w-2 before:h-2 before:bg-primary/50 before:rounded-sm\" {...props} />\n ),\n a: (props: any) => <a className=\"text-text-secondary underline decoration-primary decoration-2 underline-offset-4 hover:text-primary transition-all font-bold\" {...props} />,\n strong: (props: any) => <strong className=\"font-bold text-neutral-900\" {...props} />,\n blockquote: (props: any) => (\n <blockquote className=\"border-l-4 border-primary pl-6 py-3 my-10 italic bg-neutral-50 rounded-r-2xl text-neutral-700 font-medium max-w-3xl shadow-sm\" {...props} />\n ),\n hr: (props: any) => <hr className=\"my-16 border-t-2 border-neutral-100 max-w-3xl\" {...props} />,\n img: (props: any) => <img className=\"rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100\" {...props} />,\n};\n\ninterface BlogPostProps {\n params: Promise<{\n locale: string;\n slug: string;\n }>;\n}\n\nexport async function generateMetadata({ params }: BlogPostProps): Promise<Metadata> {\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 <article className=\"bg-white min-h-screen font-sans selection:bg-primary/10 selection:text-primary\">\n <BlogEngagementTracker\n title={post.frontmatter.title}\n slug={slug}\n category={post.frontmatter.category}\n readingTime={readingTime}\n />\n\n {/* Featured Image Header */}\n {post.frontmatter.featuredImage ? (\n <div className=\"relative w-full h-[50vh] md:h-[70vh] min-h-[350px] md:min-h-[500px] overflow-hidden group\">\n <div className=\"absolute inset-0 transition-transform duration-[3s] ease-out scale-110 group-hover:scale-100\">\n <Image\n src={post.frontmatter.featuredImage.split('?')[0]}\n alt={post.frontmatter.title}\n fill\n priority\n quality={100}\n className=\"object-cover\"\n sizes=\"100vw\"\n style={{\n objectPosition: `${post.frontmatter.focalX ?? 50}% ${post.frontmatter.focalY ?? 50}%`,\n }}\n />\n </div>\n <div className=\"absolute inset-0 bg-gradient-to-t from-neutral-dark/90 via-neutral-dark/70 to-neutral-dark/30\" />\n\n {/* Title overlay on image */}\n <div className=\"absolute inset-0 flex flex-col justify-end pb-16 md:pb-24\">\n <div className=\"container mx-auto max-w-7xl px-4\">\n <div className=\"max-w-4xl bg-neutral-dark/40 backdrop-blur-md p-6 rounded-2xl border border-white/10\">\n {post.frontmatter.category && (\n <div className=\"overflow-hidden mb-6\">\n <span className=\"inline-block px-4 py-1.5 bg-accent text-neutral-dark text-xs font-bold uppercase tracking-[0.2em] rounded-sm\">\n {post.frontmatter.category}\n </span>\n </div>\n )}\n <Heading level={1} className=\"text-white mb-8 drop-shadow-md\">\n {post.frontmatter.title}\n </Heading>\n <div className=\"flex flex-wrap items-center gap-6 text-white text-sm md:text-base font-medium drop-shadow-sm\">\n <time dateTime={post.frontmatter.date} suppressHydrationWarning>\n {new Date(post.frontmatter.date).toLocaleDateString(locale || 'de', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n })}\n </time>\n <span className=\"w-1 h-1 bg-white/50 rounded-full\" />\n <span>{readingTime} min read</span>\n {(new Date(post.frontmatter.date) > new Date() ||\n post.frontmatter.public === false) && (\n <>\n <span className=\"w-1 h-1 bg-white/30 rounded-full\" />\n <span className=\"px-2 py-0.5 border border-white/40 text-white/80 rounded uppercase tracking-widest text-[10px] md:text-xs font-bold\">\n Draft Preview\n </span>\n </>\n )}\n </div>\n </div>\n </div>\n </div>\n </div>\n ) : (\n <header className=\"pt-32 pb-16 bg-neutral-50 border-b border-neutral-100\">\n <div className=\"container mx-auto px-4 max-w-4xl\">\n {post.frontmatter.category && (\n <div className=\"mb-6\">\n <span className=\"inline-block px-4 py-1.5 bg-primary/10 text-primary text-xs font-bold uppercase tracking-[0.2em] rounded-sm\">\n {post.frontmatter.category}\n </span>\n </div>\n )}\n <Heading level={1} className=\"mb-8\">\n {post.frontmatter.title}\n </Heading>\n <div className=\"flex items-center gap-6 text-text-primary/80 font-medium\">\n <time dateTime={post.frontmatter.date} suppressHydrationWarning>\n {new Date(post.frontmatter.date).toLocaleDateString(locale || 'de', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n })}\n </time>\n <span className=\"w-1 h-1 bg-neutral-400 rounded-full\" />\n <span>{readingTime} min read</span>\n {(new Date(post.frontmatter.date) > new Date() ||\n post.frontmatter.public === false) && (\n <>\n <span className=\"w-1 h-1 bg-neutral-300 rounded-full\" />\n <span className=\"px-2 py-0.5 border border-orange-500/50 text-orange-600 rounded uppercase tracking-widest text-[10px] md:text-xs font-bold\">\n Draft Preview\n </span>\n </>\n )}\n </div>\n </div>\n </header>\n )}\n\n {/* Main Content Area with Sticky Narrative Layout */}\n <div className=\"container mx-auto max-w-7xl px-4 py-16 md:py-24\">\n <div className=\"sticky-narrative-container\">\n {/* Left Column: Content */}\n <div className=\"sticky-narrative-content\">\n {/* Excerpt/Lead paragraph if available */}\n {post.frontmatter.excerpt && (\n <div className=\"mb-16\">\n <p className=\"text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic\">\n {post.frontmatter.excerpt}\n </p>\n </div>\n )}\n\n {/* Main content */}\n <div className=\"w-full [&_a]:text-primary [&_a:hover]:text-primary-dark [&_a]:underline [&_a]:decoration-primary [&_a]:underline-offset-4 [&_a]:transition-all [&_a]:font-bold [&_a_strong]:text-inherit\">\n <MDXRemote source={post.content} components={mdxComponents} />\n </div>\n\n {/* Power CTA */}\n <div className=\"mt-24 animate-slight-fade-in-from-bottom\">\n <PowerCTA locale={locale} />\n </div>\n\n {/* Post Navigation */}\n <div className=\"mt-16\">\n <PostNavigation\n prev={prev}\n next={next}\n isPrevRandom={isPrevRandom}\n isNextRandom={isNextRandom}\n locale={locale}\n />\n </div>\n\n {/* Back to blog link */}\n <div className=\"mt-16 pt-10 border-t border-neutral-100\">\n <Link\n href={`/${locale}/blog`}\n className=\"inline-flex items-center gap-3 text-text-secondary hover:text-primary font-bold text-sm uppercase tracking-widest transition-all group\"\n >\n <svg\n className=\"w-5 h-5 transition-transform group-hover:-translate-x-2\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M15 19l-7-7 7-7\"\n />\n </svg>\n {locale === 'de' ? 'Zurück zur Übersicht' : 'Back to Overview'}\n </Link>\n </div>\n </div>\n\n {/* Right Column: Sticky Sidebar - TOC */}\n <aside className=\"sticky-narrative-sidebar hidden lg:block\">\n <div className=\"space-y-12 lg:sticky lg:top-32\">\n <TableOfContents headings={headings} locale={locale} />\n </div>\n </aside>\n </div>\n </div>\n\n {/* Structured Data */}\n <JsonLd\n id={`jsonld-${slug}`}\n data={\n {\n '@context': 'https://schema.org',\n '@type': 'BlogPosting',\n headline: post.frontmatter.title,\n datePublished: post.frontmatter.date,\n dateModified: post.frontmatter.date,\n image: post.frontmatter.featuredImage\n ? `${SITE_URL}${post.frontmatter.featuredImage}`\n : undefined,\n author: {\n '@type': 'Organization',\n name: 'E-TIB',\n url: SITE_URL,\n logo: `${SITE_URL}/logo-blue.svg`,\n },\n publisher: {\n '@type': 'Organization',\n name: 'E-TIB',\n logo: {\n '@type': 'ImageObject',\n url: `${SITE_URL}/logo-blue.svg`,\n },\n },\n description: post.frontmatter.excerpt,\n mainEntityOfPage: {\n '@type': 'WebPage',\n '@id': `${SITE_URL}/${locale}/blog/${slug}`,\n },\n articleSection: post.frontmatter.category,\n wordCount: post.content.split(/\\s+/).length,\n timeRequired: `PT${readingTime}M`,\n } as any\n }\n />\n <JsonLd\n id={`breadcrumb-${slug}`}\n data={\n {\n '@context': 'https://schema.org',\n '@type': 'BreadcrumbList',\n itemListElement: [\n {\n '@type': 'ListItem',\n position: 1,\n name: 'Blog',\n item: `${SITE_URL}/${locale}/blog`,\n },\n {\n '@type': 'ListItem',\n position: 2,\n name: post.frontmatter.title,\n item: `${SITE_URL}/${locale}/blog/${slug}`,\n },\n ],\n } as any\n }\n />\n </article>\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 <div className=\"relative min-h-screen flex flex-col items-center justify-center overflow-hidden bg-[#050B14] py-24 px-4\">\n {/* Industrial Blueprint Background */}\n <div className=\"absolute inset-0 opacity-[0.03] pointer-events-none\">\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#ffffff_1px,transparent_1px),linear-gradient(to_bottom,#ffffff_1px,transparent_1px)] bg-[size:100px_100px]\" />\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#ffffff_1px,transparent_1px),linear-gradient(to_bottom,#ffffff_1px,transparent_1px)] bg-[size:20px_20px]\" />\n </div>\n\n {/* Decorative Technical Annotations */}\n <div className=\"absolute inset-0 pointer-events-none overflow-hidden select-none\">\n <div className=\"absolute top-40 left-10 text-[10px] font-mono text-primary/30 uppercase tracking-[0.2em] rotate-90 origin-left\">\n System_Core_Access :: Restricted\n </div>\n <div className=\"absolute bottom-40 right-10 text-[10px] font-mono text-primary/30 uppercase tracking-[0.2em] -rotate-90 origin-right\">\n Protocol_Failure :: Type_500\n </div>\n <div className=\"absolute top-1/4 right-20 w-32 h-px bg-primary/20\" />\n <div className=\"absolute top-1/4 right-20 w-px h-32 bg-primary/20\" />\n </div>\n \n {/* Background Glow */}\n <div className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-red-500/10 blur-[150px] rounded-full pointer-events-none animate-pulse\" />\n\n <div className=\"relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center\">\n {/* The \"Broken\" Machine Visual */}\n <div className=\"relative mb-12\">\n <m.div\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n className=\"relative\"\n >\n <h1 className=\"text-[12rem] md:text-[18rem] font-heading font-black leading-none select-none\">\n <span className=\"relative inline-block text-transparent bg-clip-text bg-gradient-to-b from-white to-white/5 drop-shadow-[0_0_50px_rgba(255,255,255,0.1)]\">\n 500\n {/* Glitch Layers */}\n <span className=\"absolute inset-0 text-red-500/30 translate-x-[2px] -translate-y-[1px] mix-blend-screen animate-pulse pointer-events-none\">500</span>\n <span className=\"absolute inset-0 text-blue-500/30 -translate-x-[2px] translate-y-[1px] mix-blend-screen animate-pulse pointer-events-none\">500</span>\n </span>\n </h1>\n \n {/* Warning Overlay */}\n <div className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center\">\n <m.div\n animate={{ \n opacity: [0.4, 1, 0.4],\n scale: [0.95, 1.05, 0.95]\n }}\n transition={{ duration: 2, repeat: Infinity }}\n className=\"p-4 rounded-2xl bg-red-500/20 border border-red-500/40 backdrop-blur-md shadow-[0_0_30px_rgba(239,68,68,0.3)]\"\n >\n <ShieldAlert className=\"w-12 h-12 text-red-500\" />\n </m.div>\n </div>\n </m.div>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-12 gap-8 w-full items-start\">\n {/* Main Content */}\n <div className=\"md:col-span-7 text-center md:text-left\">\n <div className=\"inline-flex items-center gap-3 px-4 py-2 mb-6 rounded-lg bg-red-500/10 border border-red-500/20 text-[10px] font-mono uppercase tracking-widest text-red-400\">\n <Activity className=\"w-4 h-4 animate-pulse\" />\n CRITICAL_SYSTEM_OVERRIDE :: ACTIVE\n </div>\n\n <Heading level={1} className=\"text-3xl md:text-5xl font-bold mb-6 text-white leading-tight\">\n {t('title')}\n </Heading>\n\n <p className=\"text-white/50 mb-10 max-w-lg text-lg leading-relaxed\">\n {t('description')}\n </p>\n\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center md:justify-start\">\n <Button \n onClick={() => 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 <RefreshCw className=\"w-5 h-5 mr-3 group-hover:rotate-180 transition-transform duration-700\" />\n {t('tryAgain')}\n </Button>\n <Button \n href=\"/\" \n variant=\"outline\" \n className=\"h-14 px-8 border-white/10 text-white hover:bg-white/5\"\n >\n <Home className=\"w-5 h-5 mr-3\" />\n {t('goHome')}\n </Button>\n </div>\n </div>\n\n {/* Technical Log Terminal */}\n <div className=\"md:col-span-5\">\n <div className=\"bg-black/40 backdrop-blur-xl border border-white/10 rounded-2xl overflow-hidden shadow-2xl\">\n <div className=\"bg-white/5 px-4 py-2 border-b border-white/10 flex items-center justify-between\">\n <div className=\"flex gap-1.5\">\n <div className=\"w-2.5 h-2.5 rounded-full bg-red-500/50\" />\n <div className=\"w-2.5 h-2.5 rounded-full bg-yellow-500/50\" />\n <div className=\"w-2.5 h-2.5 rounded-full bg-green-500/50\" />\n </div>\n <div className=\"text-[10px] font-mono text-white/30 tracking-wider\">TERMINAL_OUTPUT</div>\n </div>\n <div className=\"p-6 font-mono text-xs leading-relaxed overflow-hidden h-[240px]\">\n <div className=\"text-primary/70 mb-1 animate-[fade-in_0.5s_ease-out_forwards]\">[SYSTEM] Initializing recovery protocol...</div>\n <div className=\"text-red-500/80 mb-1 animate-[fade-in_0.5s_ease-out_0.5s_forwards] opacity-0\">[ERROR] Connection timeout at node 0x7F</div>\n <div className=\"text-white/40 mb-1 animate-[fade-in_0.5s_ease-out_1s_forwards] opacity-0\">[INFO] Request ID: {error?.digest || '0xDEADBEEF'}</div>\n <div className=\"text-white/40 mb-1 animate-[fade-in_0.5s_ease-out_1.5s_forwards] opacity-0\">[INFO] Timestamp: {new Date().toISOString()}</div>\n <div className=\"text-white/40 mb-1 animate-[fade-in_0.5s_ease-out_2s_forwards] opacity-0\">[INFO] Trace: {error?.message?.slice(0, 40)}...</div>\n <div className=\"text-primary/70 mt-4 animate-[pulse_1.5s_infinite] opacity-0 animate-[fade-in_0.5s_ease-out_2.5s_forwards]\">_ Waiting for user input...</div>\n </div>\n <div className=\"bg-red-500/10 p-3 flex items-center gap-3\">\n <Terminal className=\"w-4 h-4 text-red-400\" />\n <div className=\"text-[10px] font-bold text-red-400 uppercase tracking-tighter\">Status: Handshake_Failed</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\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<Metadata> {\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<string, any> = {};\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<string, any> = {};\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 <html\n lang={safeLocale}\n className={`scroll-smooth overflow-x-hidden ${inter.variable}`}\n data-scroll-behavior=\"smooth\"\n suppressHydrationWarning\n >\n <head>\n <link rel=\"icon\" href=\"/favicon.ico\" sizes=\"any\" />\n <link rel=\"apple-touch-icon\" href=\"/apple-icon.png\" sizes=\"180x180\" />\n\n </head>\n <body className=\"relative flex flex-col min-h-screen font-sans antialiased overflow-x-hidden selection:bg-primary/90 selection:text-white\" suppressHydrationWarning>\n <NextIntlClientProvider messages={clientMessages} locale={safeLocale}>\n <TransitionProvider>\n <FramerMotionProvider>\n <CorporateBackground />\n <InitialLoader shouldShowLoader={!hasSeenLoader} />\n <PageTransitionShutter />\n <SkipLink />\n <Header navLinks={navLinks} />\n <MobileBottomNav navLinks={navLinks} currentLocale={safeLocale} />\n \n <main\n id=\"main-content\"\n className=\"flex-grow overflow-visible\"\n tabIndex={-1}\n >\n {children}\n </main>\n\n <Footer companyInfo={companyInfo} />\n <JsonLd />\n <AnalyticsShell />\n\n <FeedbackClientWrapper />\n </FramerMotionProvider>\n </TransitionProvider>\n </NextIntlClientProvider>\n </body>\n </html>\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 <div className=\"relative min-h-screen flex flex-col items-center justify-center overflow-hidden bg-[#050B14] py-24 px-4\">\n <ClientNotFoundTracker path=\"\" />\n \n {/* Industrial Blueprint Background */}\n <div className=\"absolute inset-0 opacity-[0.03] pointer-events-none\">\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#ffffff_1px,transparent_1px),linear-gradient(to_bottom,#ffffff_1px,transparent_1px)] bg-[size:100px_100px]\" />\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#ffffff_1px,transparent_1px),linear-gradient(to_bottom,#ffffff_1px,transparent_1px)] bg-[size:20px_20px]\" />\n </div>\n\n {/* Decorative Technical Annotations */}\n <div className=\"absolute inset-0 pointer-events-none overflow-hidden select-none\">\n <div className=\"absolute top-40 left-10 text-[10px] font-mono text-primary/30 uppercase tracking-[0.2em] rotate-90 origin-left\">\n Discovery_Service :: Failure\n </div>\n <div className=\"absolute bottom-40 right-10 text-[10px] font-mono text-primary/30 uppercase tracking-[0.2em] -rotate-90 origin-right\">\n Address_Resolution :: 0x404\n </div>\n <div className=\"absolute top-1/4 left-20 w-32 h-px bg-primary/20\" />\n <div className=\"absolute top-1/4 left-20 w-px h-32 bg-primary/20\" />\n </div>\n \n {/* Background Glow */}\n <div className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] h-[600px] bg-primary/10 blur-[150px] rounded-full pointer-events-none animate-pulse\" />\n\n <div className=\"relative z-10 w-full max-w-4xl mx-auto flex flex-col items-center\">\n {/* 404 Visual */}\n <div className=\"relative mb-12\">\n <m.div\n initial={{ opacity: 0, y: 20 }}\n animate={{ opacity: 1, y: 0 }}\n className=\"relative\"\n >\n <h1 className=\"text-[12rem] md:text-[18rem] font-heading font-black leading-none select-none\">\n <span className=\"relative inline-block text-transparent bg-clip-text bg-gradient-to-b from-white to-white/5 drop-shadow-[0_0_50px_rgba(var(--color-primary),0.15)]\">\n 404\n {/* Glitch Layers */}\n <span className=\"absolute inset-0 text-primary/30 translate-x-[2px] -translate-y-[1px] mix-blend-screen animate-pulse pointer-events-none\">404</span>\n <span className=\"absolute inset-0 text-blue-500/20 -translate-x-[2px] translate-y-[1px] mix-blend-screen animate-pulse pointer-events-none\">404</span>\n </span>\n </h1>\n \n {/* Search Scan Overlay */}\n <div className=\"absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col items-center\">\n <m.div\n animate={{ \n scale: [1, 1.1, 1],\n opacity: [0.5, 1, 0.5]\n }}\n transition={{ duration: 3, repeat: Infinity }}\n className=\"p-5 rounded-full bg-primary/20 border border-primary/40 backdrop-blur-md shadow-[0_0_40px_rgba(var(--color-primary),0.3)]\"\n >\n <Search className=\"w-10 h-10 text-primary\" />\n </m.div>\n </div>\n </m.div>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-12 gap-12 w-full items-start\">\n {/* Main Content */}\n <div className=\"md:col-span-7 text-center md:text-left\">\n <div className=\"inline-flex items-center gap-3 px-4 py-2 mb-6 rounded-lg bg-primary/10 border border-primary/20 text-[10px] font-mono uppercase tracking-widest text-primary\">\n <Cpu className=\"w-4 h-4 animate-spin-slow\" />\n OBJECT_NOT_FOUND :: SCAN_COMPLETE\n </div>\n\n <Heading level={1} className=\"text-3xl md:text-5xl font-bold mb-6 text-white leading-tight\">\n {t('title')}\n </Heading>\n\n <p className=\"text-white/50 mb-10 max-w-lg text-lg leading-relaxed\">\n {t('description')}\n </p>\n\n <div className=\"flex flex-col sm:flex-row gap-4 justify-center md:justify-start\">\n <Button \n href=\"/\" \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 <Home className=\"w-5 h-5 mr-3\" />\n {t('cta')}\n </Button>\n <Button \n href=\"/kontakt\" \n variant=\"outline\" \n className=\"h-14 px-8 border-white/10 text-white hover:bg-white/5\"\n >\n <Mail className=\"w-5 h-5 mr-3\" />\n Support kontaktieren\n </Button>\n </div>\n </div>\n\n {/* Technical Log Terminal */}\n <div className=\"md:col-span-5\">\n <div className=\"bg-black/40 backdrop-blur-xl border border-white/10 rounded-2xl overflow-hidden shadow-2xl\">\n <div className=\"bg-white/5 px-4 py-2 border-b border-white/10 flex items-center justify-between\">\n <div className=\"flex gap-1.5\">\n <div className=\"w-2.5 h-2.5 rounded-full bg-primary/50\" />\n <div className=\"w-2.5 h-2.5 rounded-full bg-white/20\" />\n <div className=\"w-2.5 h-2.5 rounded-full bg-white/10\" />\n </div>\n <div className=\"text-[10px] font-mono text-white/30 tracking-wider\">DISCOVERY_LOG</div>\n </div>\n <div className=\"p-6 font-mono text-xs leading-relaxed overflow-hidden h-[220px]\">\n <div className=\"text-primary/70 mb-1\">[SCAN] Initializing sector sweep...</div>\n <div className=\"text-white/40 mb-1\">[INFO] Indexing available nodes...</div>\n <div className=\"text-primary/50 mb-1 animate-pulse\">[INFO] Searching for: {typeof window !== 'undefined' ? window.location.pathname : 'unknown_resource'}</div>\n <div className=\"text-red-400/80 mb-1 mt-4\">[WARN] Resource 404: Not found in current branch</div>\n <div className=\"text-white/30 mb-1\">[INFO] Redirecting to fallback_strategy...</div>\n <div className=\"text-primary/70 mt-4 animate-[pulse_1.5s_infinite]\">_ Ready for manual redirect...</div>\n </div>\n <div className=\"bg-primary/10 p-3 flex items-center gap-3\">\n <Activity className=\"w-4 h-4 text-primary\" />\n <div className=\"text-[10px] font-bold text-primary uppercase tracking-tighter\">Status: Resolution_Failed</div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\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 <OGImageTemplate\n title=\"Die Experten für Kabelnetzbau\"\n label=\"Kabelnetzbau & Infrastruktur\"\n image={bg}\n logo={logo}\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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) => <HomeReferencesSlider {...props} references={props.references || references} />,\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 <img \n {...props} \n src={finalSrc} \n srcSet={srcSet}\n sizes={sizes}\n loading=\"lazy\" \n decoding=\"async\" \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 <img \n {...props} \n src={finalSrc} \n srcSet={srcSet}\n sizes={sizes}\n loading=\"lazy\" \n decoding=\"async\" \n style={{ width: '100%', height: 'auto', ...props.style }}\n />\n );\n }\n});\n\nexport async function generateMetadata(props: any): Promise<Metadata> {\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 <main>\n <MDXRemote source={mdx.content} components={mdxComponents(sliderReferences)} />\n </main>\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 <li className=\"flex items-start gap-3 pl-4 pr-6 py-3.5 bg-neutral-50/50 border border-neutral-100 rounded-xl\" {...props}>\n <Icon className=\"w-5 h-5 text-primary shrink-0 mt-0.5\" />\n <span className=\"text-sm font-medium text-neutral-700 leading-snug [&>p]:mb-0\">{children}</span>\n </li>\n );\n};\n\nconst mdxComponents = {\n ul: (props: any) => <ul className=\"grid grid-cols-1 gap-2 mb-6\" {...props} />,\n li: CustomLi,\n p: (props: any) => <p className=\"text-neutral-600 text-sm mb-4 leading-relaxed\" {...props} />,\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 <img {...props} src={finalSrc} srcSet={srcSet} sizes={sizes} loading=\"lazy\" decoding=\"async\" />\n );\n },\n};\n\ninterface PageProps {\n params: Promise<{\n locale: string;\n }>;\n}\n\nexport async function generateMetadata({ params }: PageProps): Promise<Metadata> {\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 <div className=\"flex flex-col min-h-screen bg-neutral-50 pb-8 md:pb-24\">\n {/* Map Section */}\n <InteractiveGermanyMap\n isHero={true}\n badge={locale === 'en' ? 'Our References' : 'Unsere Referenzen'}\n title={locale === 'en' ? 'Successfully realized projects.' : 'Erfolgreich umgesetzte Projekte.'}\n description={locale === 'en' ? 'From broadband expansion to complex 110kV lines: A selection of our nationwide projects where we have created infrastructure for the future.' : 'Vom Breitbandausbau bis zur komplexen 110kV-Trasse: Ein Auszug unserer bundesweiten Projekte, bei denen wir Infrastruktur für die Zukunft geschaffen haben.'}\n locations={enrichedLocations}\n stats={[\n { value: '100', suffix: '%', label: locale === 'en' ? 'Nationwide Reach' : 'Überregionale Reichweite' },\n { value: '200', suffix: '+', label: locale === 'en' ? 'Completed Projects' : 'Abgeschlossene Projekte' }\n ]}\n />\n\n {/* Main Content Area */}\n <Container className=\"relative z-20 mt-16 md:mt-24\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8\">\n {references.map((ref) => (\n <div\n key={ref.slug}\n id={ref.slug}\n className=\"flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm block scroll-mt-32\"\n >\n <div className=\"flex flex-col h-full relative\">\n {/* Image Section */}\n <div className=\"relative h-64 w-full bg-[#f4f4f5] overflow-hidden shrink-0 border-b border-neutral-100\">\n {ref.frontmatter.featuredImage ? (\n <Image\n src={ref.frontmatter.featuredImage}\n alt={ref.frontmatter.title}\n fill\n className=\"object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%]\"\n />\n ) : (\n <div className=\"absolute inset-0 bg-[#f4f4f5] flex items-center justify-center\">\n <Image src=\"/assets/logo.png\" alt=\"E-TIB Logo\" width={80} height={80} className=\"opacity-20 grayscale\" />\n </div>\n )}\n <div className=\"absolute inset-0 bg-black/5\" />\n \n {/* Location Badge */}\n <div className=\"absolute top-4 left-4 z-10\">\n <span className=\"bg-white/95 backdrop-blur-md text-neutral-dark px-3 py-1.5 rounded-sm text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 shadow-sm border border-neutral-200\">\n <MapPin className=\"w-3 h-3 text-primary\" />\n {cleanLocation(ref.frontmatter.location)}\n </span>\n </div>\n </div>\n\n {/* Content Section */}\n <div className=\"flex flex-col flex-grow p-6 md:p-8 bg-white\">\n <h3 className=\"text-xl md:text-2xl font-bold font-heading text-neutral-dark mb-6 leading-[1.2]\">\n {ref.frontmatter.title}\n </h3>\n \n {/* Project Details / Content */}\n {ref.content && (\n <div className=\"flex-grow mb-6\">\n <MDXRemote source={ref.content} components={mdxComponents} />\n </div>\n )}\n \n {/* Technical Meta Data */}\n <div className=\"flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10\">\n <div className=\"min-w-0\">\n <span className=\"block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1\">{t('client')}</span>\n <span className=\"flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight\">\n <Briefcase className=\"w-3.5 h-3.5 text-primary shrink-0 mt-[2px]\" />\n <span className=\"line-clamp-2\">{ref.frontmatter.client || t('client')}</span>\n </span>\n </div>\n <div className=\"min-w-0\">\n <span className=\"block text-[10px] uppercase tracking-widest text-neutral-400 font-bold mb-1\">{t('period')}</span>\n <span className=\"flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight\">\n <Calendar className=\"w-3.5 h-3.5 text-primary shrink-0 mt-[2px]\" />\n <span className=\"line-clamp-2\">{ref.frontmatter.dateString || new Date(ref.frontmatter.date).getFullYear()}</span>\n </span>\n </div>\n </div>\n </div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Support Section */}\n <div className=\"mt-8 md:mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group\">\n <div className=\"absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3\" />\n <div className=\"relative z-10 max-w-2xl\">\n <h3 className=\"text-2xl md:text-3xl font-bold mb-4\">{t('nextProjectTitle')}</h3>\n <p className=\"text-lg text-white/70 mb-8\">{t('nextProjectDesc')}</p>\n <TrackedLink\n href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}\n className={getButtonClasses('accent', 'lg')}\n eventProperties={{\n location: 'referenzen_support_cta',\n }}\n >\n <span className=\"relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark\">\n {t('contactUs')}\n <span className=\"ml-2 transition-transform group-hover/btn:translate-x-1\">\n &rarr;\n </span>\n </span>\n <ButtonOverlay variant=\"accent\" />\n </TrackedLink>\n </div>\n </div>\n </Container>\n </div>\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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<Metadata> {\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 <div className=\"flex flex-col min-h-screen bg-white\">\n {/* Generic Hero Section to match standard pages */}\n <section key={`standort-hero-${slug}`} className=\"bg-primary-dark text-white pt-28 pb-10 md:pt-56 md:pb-16 min-h-[30vh] md:min-h-[45vh] flex flex-col justify-end relative overflow-hidden\">\n <div className=\"absolute inset-0 opacity-20 pointer-events-none\">\n <div className=\"absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-accent/50 via-transparent to-transparent\" />\n </div>\n <Container className=\"relative z-10\">\n <div className=\"max-w-4xl\">\n <Badge variant=\"accent\" className=\"mb-4 md:mb-6 shadow-sm flex inline-flex items-center gap-2\">\n <MapPin className=\"w-4 h-4\" />\n {standort.type === 'hq' ? (safeLocale === 'de' ? 'Hauptsitz' : 'Headquarters') : (safeLocale === 'de' ? 'Niederlassung' : 'Branch')}\n </Badge>\n <Heading level={1} variant=\"white\" className=\"mb-0 drop-shadow-sm\">\n {standort.name}\n </Heading>\n </div>\n </Container>\n </section>\n\n {/* Main Content Area */}\n <Container className=\"py-16 md:py-24\">\n {/* Excerpt/Lead paragraph */}\n <div className=\"mb-16 max-w-4xl mx-auto\">\n <p className=\"text-xl md:text-2xl text-text-primary leading-relaxed font-medium border-l-4 border-primary pl-8 py-2 italic\">\n {standort.description[safeLocale]}\n </p>\n </div>\n\n {/* Main content in prose style */}\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"prose prose-lg md:prose-xl prose-neutral max-w-none prose-headings:font-heading prose-headings:font-bold prose-headings:tracking-tight prose-h1:text-4xl prose-h2:text-3xl prose-h2:mt-12 prose-h2:border-b prose-h2:border-neutral-200 prose-h2:pb-4 prose-h3:text-2xl prose-p:text-text-secondary prose-p:leading-relaxed prose-a:text-primary prose-a:font-medium prose-a:underline prose-a:underline-offset-4 prose-a:decoration-primary/30 hover:prose-a:decoration-primary prose-strong:text-text-primary prose-strong:font-semibold prose-li:text-text-secondary prose-ul:list-disc prose-ol:list-decimal\">\n \n <img className=\"rounded-2xl shadow-2xl my-12 max-w-full h-auto border border-neutral-100\" src={standort.image} alt={standort.name} />\n\n <h2 className=\"mt-16 mb-6 border-b border-neutral-100 pb-4\">{safeLocale === 'de' ? 'Unsere Leistungen vor Ort' : 'Our Local Services'}</h2>\n <ul className=\"list-none mb-8 space-y-3 text-base md:text-lg text-text-secondary font-medium max-w-3xl\">\n {standort.keyFeatures[safeLocale].map((feature, idx) => (\n <li key={idx} className=\"relative pl-6 before:content-[''] before:absolute before:left-0 before:top-[0.6em] before:w-2 before:h-2 before:bg-primary/50 before:rounded-sm\">\n {feature}\n </li>\n ))}\n </ul>\n\n <h2 className=\"mt-16 mb-6 border-b border-neutral-100 pb-4\">{safeLocale === 'de' ? 'Kontakt & Anfahrt' : 'Contact & Directions'}</h2>\n \n <p className=\"text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl\">\n <strong>{safeLocale === 'de' ? 'Adresse:' : 'Address:'}</strong><br/>\n {standort.address.street}<br/>\n {standort.address.city}\n </p>\n <p className=\"text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl\">\n <TrackedLink \n href={`https://maps.google.com/?q=${encodeURIComponent(`${standort.address.street}, ${standort.address.city}`)}`}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold flex items-center gap-1 w-fit\"\n eventProperties={{ location: 'standort_maps_link', slug: standort.id }}\n >\n {safeLocale === 'de' ? 'In Google Maps öffnen' : 'Open in Google Maps'}\n <ArrowUpRight className=\"w-4 h-4 inline-block\" />\n </TrackedLink>\n </p>\n\n <p className=\"text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl\">\n <strong>{safeLocale === 'de' ? 'Telefon:' : 'Phone:'}</strong><br/>\n <a href={`tel:${standort.contact.phone.replace(/[^0-9+]/g, '')}`} className=\"text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold\">\n {standort.contact.phone}\n </a>\n </p>\n\n <p className=\"text-base md:text-lg text-text-secondary leading-[1.8] mb-6 font-medium max-w-3xl\">\n <strong>Email:</strong><br/>\n <a href={`mailto:${standort.contact.email}`} className=\"text-primary hover:text-primary-dark underline decoration-primary/30 underline-offset-4 hover:decoration-primary transition-all font-bold\">\n {standort.contact.email}\n </a>\n </p>\n\n {standort.gallery && standort.gallery.length > 0 && (\n <>\n <h2 className=\"mt-16 mb-6 border-b border-neutral-100 pb-4\">Galerie</h2>\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-6 not-prose my-12\">\n {standort.gallery.map((img, idx) => (\n <div key={idx} className=\"relative aspect-[4/3] rounded-2xl overflow-hidden bg-neutral-100 border border-neutral-200 shadow-md hover:shadow-xl transition-shadow duration-300\">\n <Image src={img} alt={`Gallery ${idx + 1}`} fill className=\"object-cover transition-transform duration-700 hover:scale-105\" />\n </div>\n ))}\n </div>\n </>\n )}\n </div>\n </div>\n </Container>\n\n {/* Support Section */}\n <Container>\n <div className=\"mt-8 mb-8 md:mt-24 md:mb-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group animate-slight-fade-in-from-bottom\">\n <div className=\"absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3\" />\n <div className=\"relative z-10 max-w-2xl\">\n <h3 className=\"text-2xl md:text-3xl font-bold mb-4\">{t('needHelp')}</h3>\n <p className=\"text-lg text-white/70 mb-8\">{t('supportTeamAvailable')}</p>\n <TrackedLink\n href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}\n className={getButtonClasses('accent', 'lg')}\n eventProperties={{\n location: 'standort_page_support_cta',\n page_slug: slug,\n }}\n >\n <span className=\"relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark\">\n {t('contactUs')}\n <span className=\"ml-2 transition-transform group-hover/btn:translate-x-1\">\n &rarr;\n </span>\n </span>\n <ButtonOverlay variant=\"accent\" />\n </TrackedLink>\n </div>\n </div>\n </Container>\n </div>\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<Metadata> {\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 <div className=\"flex flex-col min-h-screen bg-neutral-50 pb-16 md:pb-24\">\n {/* Map Hero Section */}\n <InteractiveGermanyMap\n isHero={true}\n locations={standorteLocations}\n badge={safeLocale === 'en' ? 'Our Locations' : 'Unsere Standorte'}\n title={safeLocale === 'en' ? 'Nationwide operational for you.' : 'Deutschlandweit für Sie im Einsatz.'}\n description={safeLocale === 'en' \n ? 'From our strategic locations in Guben, Kirchheilingen and Bülstedt, we control and implement complex infrastructure projects nationwide.' \n : 'Von unseren strategischen Standorten in Guben, Kirchheilingen und Bülstedt steuern und realisieren wir komplexe Infrastrukturprojekte im gesamten Bundesgebiet.'}\n />\n\n {/* Main Content Area */}\n <Container className=\"relative z-20 mt-16 md:mt-24\">\n <div className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-8\">\n {standorteData.map((standort) => (\n <TrackedLink\n key={standort.id}\n href={`/${safeLocale}/standorte/${standort.id}`}\n eventProperties={{ location: 'standorte_list_card', slug: standort.id }}\n className=\"group flex flex-col bg-white border border-neutral-100 rounded-3xl overflow-hidden shadow-sm hover:shadow-2xl transition-all duration-300 transform hover:-translate-y-1 cursor-pointer block\"\n >\n <div className=\"flex flex-col h-full relative\">\n {/* Image Section */}\n <div className=\"relative h-64 w-full bg-[#050B14] overflow-hidden shrink-0 border-b border-neutral-100\">\n <Image\n src={standort.image}\n alt={standort.name}\n fill\n className=\"object-cover transition-transform duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] filter grayscale-[20%] group-hover:scale-105 group-hover:grayscale-0\"\n sizes=\"(max-width: 768px) 200px, 384px\"\n />\n <div className=\"absolute inset-0 bg-black/10 transition-colors duration-500\" />\n \n {/* Location Badge */}\n <div className=\"absolute top-4 left-4 z-10\">\n <span className=\"bg-white/95 backdrop-blur-md text-neutral-dark px-3 py-1.5 rounded-sm text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5 shadow-sm border border-neutral-200\">\n <MapPin className=\"w-3 h-3 text-primary\" />\n {standort.address.city}\n </span>\n </div>\n </div>\n\n {/* Content Section */}\n <div className=\"flex flex-col flex-grow p-6 md:p-8 bg-white\">\n <div className=\"flex items-start justify-between gap-4 mb-4\">\n <h2 className=\"text-xl md:text-2xl font-bold font-heading text-neutral-dark transition-colors leading-[1.2] group-hover:text-primary\">\n {standort.name}\n </h2>\n <ArrowUpRight className=\"w-5 h-5 text-neutral-400 group-hover:text-primary transition-colors shrink-0\" />\n </div>\n \n <p className=\"text-neutral-500 text-sm leading-relaxed mb-8 line-clamp-3\">\n {standort.description[safeLocale]}\n </p>\n \n {/* Meta Data */}\n <div className=\"flex flex-col gap-3 mt-auto border-t border-neutral-100 pt-6 relative z-10\">\n <div className=\"min-w-0\">\n <span className=\"block text-[10px] uppercase tracking-widest text-neutral-500 font-bold mb-1\">\n {safeLocale === 'de' ? 'Adresse' : 'Address'}\n </span>\n <span className=\"flex items-start gap-2 text-sm font-semibold text-neutral-700 leading-tight\">\n <Navigation className=\"w-3.5 h-3.5 text-primary shrink-0 mt-[2px]\" />\n <span className=\"line-clamp-2\">{standort.address.street}, {standort.address.city}</span>\n </span>\n </div>\n </div>\n </div>\n </div>\n </TrackedLink>\n ))}\n </div>\n\n {/* Support Section */}\n <div className=\"mt-12 md:mt-24 p-8 md:p-12 bg-primary-dark rounded-3xl text-white shadow-2xl relative overflow-hidden group\">\n <div className=\"absolute top-0 right-0 w-64 h-full bg-accent/5 -skew-x-12 translate-x-1/2 transition-transform group-hover:translate-x-1/3\" />\n <div className=\"relative z-10 max-w-2xl\">\n <h2 className=\"text-2xl md:text-3xl font-bold mb-4\">{t('nextProjectTitle')}</h2>\n <p className=\"text-lg text-white/70 mb-8\">{t('nextProjectDesc')}</p>\n <TrackedLink\n href={`/${safeLocale}/${safeLocale === 'de' ? 'kontakt' : 'contact'}`}\n className={getButtonClasses('accent', 'lg')}\n eventProperties={{\n location: 'standorte_support_cta',\n }}\n >\n <span className=\"relative z-10 flex items-center justify-center gap-2 transition-colors duration-500 group-hover/btn:text-primary-dark\">\n {t('contactUs')}\n <span className=\"ml-2 transition-transform group-hover/btn:translate-x-1\">\n &rarr;\n </span>\n </span>\n <ButtonOverlay variant=\"accent\" />\n </TrackedLink>\n </div>\n </div>\n </Container>\n </div>\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<Thing> | WithContext<Thing>[];\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 <script\n id={id}\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{\n __html: JSON.stringify(finalSchema),\n }}\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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<string, number> | 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<void>;\n}\n\nexport function Annotator({ assets = [], onSubmit }: AnnotatorProps) {\n const [isActive, setIsActive] = useState(false);\n const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(null);\n const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(null);\n const [annotations, setAnnotations] = useState<Annotation[]>(() => {\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<string | null>(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 <div className=\"annotator-ui-ignore\">\n {/* 1. Global Toolbar */}\n <div className=\"fixed bottom-36 md:bottom-24 right-4 z-[10000] flex flex-col items-end gap-2\">\n <div className=\"bg-black/80 backdrop-blur-xl border border-white/10 p-1.5 rounded-2xl shadow-2xl flex flex-col items-center gap-1\">\n <button\n onClick={() => 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 ? <X size={20} /> : <MessageSquare size={20} />}\n </button>\n\n <div className=\"w-6 h-px bg-white/10 my-1\" />\n\n <button\n onClick={() => 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 <List size={20} />\n {annotations.length > 0 && (\n <span className=\"absolute -top-1 -right-1 w-4 h-4 bg-blue-500 text-[9px] flex items-center justify-center rounded-full text-white font-bold border border-[#1a1a1a]\">\n {annotations.length}\n </span>\n )}\n </button>\n\n {annotations.length > 0 && (\n <>\n <div className=\"w-6 h-px bg-white/10 my-1\" />\n <button\n onClick={exportJSON}\n className=\"p-2.5 rounded-xl text-white/70 hover:text-white hover:bg-white/10 transition-all\"\n title=\"JSON Export\"\n >\n <Download size={20} />\n </button>\n </>\n )}\n </div>\n </div>\n\n {/* 2. Highlights */}\n <AnimatePresence>\n {isActive && (\n <div className=\"fixed inset-0 pointer-events-none z-[9998]\">\n {hoveredRect && (\n <m.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n className=\"absolute border-2 border-blue-400 bg-blue-400/10 rounded-sm transition-all duration-200\"\n style={{\n top: hoveredRect.top,\n left: hoveredRect.left,\n width: hoveredRect.width,\n height: hoveredRect.height,\n }}\n />\n )}\n\n {selectedRect && (\n <m.div\n initial={{ scale: 0.9, opacity: 0 }}\n animate={{ scale: 1, opacity: 1 }}\n className=\"absolute border-2 border-yellow-400 bg-yellow-400/20 rounded-sm\"\n style={{\n top: selectedRect.top,\n left: selectedRect.left,\n width: selectedRect.width,\n height: selectedRect.height,\n }}\n />\n )}\n </div>\n )}\n </AnimatePresence>\n\n {/* 3. Action Modal */}\n <AnimatePresence>\n {selectedElement && (\n <div className=\"fixed inset-0 flex items-center justify-center z-[10000] bg-black/40 backdrop-blur-sm\">\n <m.div\n initial={{ opacity: 0, y: 20, scale: 0.95 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: 20, scale: 0.95 }}\n className=\"bg-[#1c1c1e] border border-white/10 rounded-3xl p-4 sm:p-6 w-[95vw] md:w-[800px] shadow-2xl flex flex-col max-h-[85vh]\"\n >\n <div className=\"flex items-center justify-between mb-6\">\n <h3 className=\"text-white font-bold text-lg\">Änderung erfassen</h3>\n <button\n onClick={() => setSelectedElement(null)}\n className=\"text-white/40 hover:text-white\"\n >\n <X size={20} />\n </button>\n </div>\n\n <div className=\"flex gap-2 mb-6 shrink-0\">\n <button\n onClick={() => 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 <Type size={16} /> Text\n </button>\n <button\n onClick={() => 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 <ImageIcon size={16} /> Bild\n </button>\n <button\n onClick={() => 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 <StickyNote size={16} /> Notiz\n </button>\n </div>\n\n <div className=\"flex-1 overflow-y-auto min-h-[200px] mb-6 hide-scrollbar\" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}>\n {currentType === \"text\" && (\n <div className=\"space-y-2\">\n <label className=\"text-xs font-bold uppercase tracking-wider text-white/40\">Neuer Text</label>\n <textarea\n autoFocus\n value={currentText}\n onChange={(e) => setCurrentText(e.target.value)}\n className=\"w-full h-32 bg-black/40 border border-white/10 rounded-2xl p-4 text-white placeholder:text-white/20 focus:outline-none focus:border-blue-500/50 transition-colors resize-none\"\n />\n </div>\n )}\n\n {currentType === \"note\" && (\n <div className=\"space-y-2\">\n <label className=\"text-xs font-bold uppercase tracking-wider text-white/40\">Anmerkung</label>\n <textarea\n autoFocus\n value={currentText}\n onChange={(e) => setCurrentText(e.target.value)}\n placeholder=\"Was soll hier gemacht werden?\"\n className=\"w-full h-32 bg-black/40 border border-white/10 rounded-2xl p-4 text-white placeholder:text-white/20 focus:outline-none focus:border-blue-500/50 transition-colors resize-none\"\n />\n </div>\n )}\n\n {currentType === \"image\" && (\n <div className=\"space-y-2 h-full\">\n <label className=\"text-xs font-bold uppercase tracking-wider text-white/40\">Neues Bild wählen</label>\n {assets.length === 0 ? (\n <div className=\"text-sm text-white/40 p-4 bg-white/5 rounded-xl border border-white/5 text-center\">\n Keine Assets übergeben. Bitte das prop <code>assets</code> befüllen.\n </div>\n ) : (\n <>\n <style>{`.hide-scrollbar::-webkit-scrollbar { display: none; }`}</style>\n <div className=\"mb-3\">\n <input\n type=\"text\"\n placeholder=\"Suchen nach Dateinamen...\"\n value={searchTerm}\n onChange={(e) => 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 </div>\n <div \n className=\"grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 h-[400px] overflow-y-auto hide-scrollbar\"\n style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}\n >\n {assets.filter(asset => asset.toLowerCase().includes(searchTerm.toLowerCase())).map((asset) => {\n const filename = asset.split('/').pop() || asset;\n return (\n <button\n key={asset}\n title={asset}\n onClick={() => 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 <div className=\"w-full flex-1 rounded-lg overflow-hidden bg-black/40 flex items-center justify-center relative\">\n {asset.match(/\\.(mp4|webm)$/i) ? (\n <video src={asset} className=\"w-full h-full object-cover opacity-80\" />\n ) : (\n <img src={asset} loading=\"lazy\" alt={filename} className=\"w-full h-full object-contain\" />\n )}\n {selectedAsset === asset && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-blue-500/20 backdrop-blur-[2px]\">\n <div className=\"bg-blue-500 text-white rounded-full p-1.5 shadow-lg\">\n <Check size={18} strokeWidth={3} />\n </div>\n </div>\n )}\n </div>\n <span className=\"text-[11px] font-medium text-white/70 truncate w-full text-center shrink-0\" title={filename}>\n {filename}\n </span>\n </button>\n );\n })}\n </div>\n </>\n )}\n </div>\n )}\n </div>\n\n <button\n disabled={(!currentText && currentType !== \"image\") || (currentType === \"image\" && !selectedAsset)}\n onClick={saveAnnotation}\n className=\"w-full bg-blue-500 hover:bg-blue-400 disabled:opacity-50 disabled:cursor-not-allowed text-white font-bold py-4 rounded-2xl flex items-center justify-center gap-2 transition-all shadow-lg shadow-blue-500/20 shrink-0\"\n >\n <Check size={20} />\n Speichern\n </button>\n </m.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* 4. Annotations List Sidebar */}\n <AnimatePresence>\n {showList && (\n <>\n <m.div\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n onClick={() => setShowList(false)}\n className=\"fixed inset-0 bg-black/60 backdrop-blur-sm z-[10001]\"\n />\n <m.div\n initial={{ x: \"100%\" }}\n animate={{ x: 0 }}\n exit={{ x: \"100%\" }}\n transition={{ type: \"spring\", damping: 25, stiffness: 200 }}\n className=\"fixed top-0 right-0 h-full w-full sm:w-[400px] bg-[#1c1c1e] border-l border-white/10 z-[10002] shadow-2xl flex flex-col\"\n >\n <div className=\"p-8 border-b border-white/10 flex items-center justify-between\">\n <div>\n <h2 className=\"text-2xl font-bold text-white mb-1\">Korrekturen</h2>\n <p className=\"text-white/40 text-sm\">{annotations.length} erfasst</p>\n </div>\n <button\n onClick={() => setShowList(false)}\n className=\"p-2 text-white/40 hover:text-white bg-white/5 rounded-xl transition-colors\"\n >\n <X size={20} />\n </button>\n </div>\n\n <div className=\"flex-1 overflow-y-auto p-6 space-y-4\">\n {annotations.length === 0 ? (\n <div className=\"h-full flex flex-col items-center justify-center text-center px-8 opacity-40\">\n <List size={48} className=\"mb-4\" />\n <p>Noch keine Korrekturen vorhanden.</p>\n </div>\n ) : (\n annotations.map((ann) => (\n <div\n key={ann.id}\n className=\"bg-white/5 border border-white/5 rounded-2xl p-4 flex flex-col gap-3 group relative\"\n >\n <button \n onClick={() => 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 <Trash2 size={16} />\n </button>\n \n <div className=\"flex items-center gap-2\">\n <span className={cn(\n \"px-2 py-1 rounded-md text-[10px] font-bold uppercase tracking-wider flex items-center gap-1\",\n ann.type === \"text\" ? \"bg-blue-500/20 text-blue-400\" :\n ann.type === \"image\" ? \"bg-purple-500/20 text-purple-400\" :\n \"bg-orange-500/20 text-orange-400\"\n )}>\n {ann.type === \"text\" && <Type size={12} />}\n {ann.type === \"image\" && <ImageIcon size={12} />}\n {ann.type === \"note\" && <StickyNote size={12} />}\n {ann.type}\n </span>\n </div>\n \n {ann.type === \"image\" ? (\n <div className=\"flex items-center gap-2\">\n <img src={ann.newContent} alt=\"New\" className=\"w-16 h-16 object-cover rounded-lg bg-white/10\" />\n <div className=\"text-xs text-white/60 flex-1 truncate\">{ann.newContent}</div>\n </div>\n ) : (\n <p className=\"text-white text-sm whitespace-pre-wrap\">{ann.newContent}</p>\n )}\n \n <div className=\"text-[10px] text-white/30 truncate mt-2 font-mono flex items-center gap-2\">\n <span>{ann.selector}</span>\n {ann.url && <span className=\"truncate max-w-[100px] text-blue-400\" title={ann.url}>{new URL(ann.url).pathname}</span>}\n </div>\n <div className=\"flex flex-wrap items-center gap-2 mt-2 pt-2 border-t border-white/5\">\n {ann.timestamp && (\n <span className=\"text-[9px] px-1.5 py-0.5 rounded bg-white/5 text-white/40\">\n {new Date(ann.timestamp).toLocaleString()}\n </span>\n )}\n {ann.viewportWidth && (\n <span className=\"text-[9px] px-1.5 py-0.5 rounded bg-white/5 text-white/40\">\n {ann.viewportWidth}x{ann.viewportHeight}\n </span>\n )}\n {ann.scrollX !== undefined && (\n <span className=\"text-[9px] px-1.5 py-0.5 rounded bg-white/5 text-white/40\">\n Scroll: {Math.round(ann.scrollX)},{Math.round(ann.scrollY)}\n </span>\n )}\n </div>\n </div>\n ))\n )}\n </div>\n \n {annotations.length > 0 && (\n <div className=\"p-6 border-t border-white/10 flex flex-col gap-3\">\n {onSubmit && (\n <button\n onClick={handleSubmit}\n disabled={isSubmitting}\n className=\"w-full bg-blue-500 hover:bg-blue-400 text-white font-bold py-4 rounded-2xl flex items-center justify-center gap-2 transition-all shadow-lg shadow-blue-500/20 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {isSubmitting ? (\n <div className=\"w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin\" />\n ) : (\n <Check size={20} />\n )}\n {isSubmitting ? \"Wird gesendet...\" : \"Korrekturen an Entwickler senden\"}\n </button>\n )}\n <button\n onClick={exportJSON}\n className={cn(\n \"w-full font-bold py-3 rounded-2xl flex items-center justify-center gap-2 transition-all\",\n onSubmit ? \"bg-white/5 text-white/70 hover:bg-white/10 hover:text-white\" : \"bg-blue-500 hover:bg-blue-400 text-white shadow-lg shadow-blue-500/20 py-4\"\n )}\n >\n <Download size={onSubmit ? 18 : 20} />\n JSON {onSubmit && \"lokal \"}Exportieren\n </button>\n <button\n onClick={() => {\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 <Trash2 size={18} />\n Alle löschen\n </button>\n </div>\n )}\n </m.div>\n </>\n )}\n </AnimatePresence>\n </div>\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 <section className=\"py-16 md:py-24 lg:py-32 bg-neutral-50 relative overflow-hidden\">\n <Container>\n {!hideHeader && (\n <div className=\"text-center max-w-3xl mx-auto mb-16\">\n <h2 className=\"text-primary font-bold tracking-wider uppercase text-sm mb-3\">{badgeText}</h2>\n <h3 className=\"font-heading text-2xl md:text-5xl font-extrabold text-neutral-dark mb-6\">{titleText}</h3>\n <p className=\"text-lg text-neutral-600\">{descriptionText}</p>\n </div>\n )}\n </Container>\n </section>\n );\n }\n\n return (\n <section className=\"py-12 md:py-24 bg-neutral-50 relative overflow-hidden\">\n {/* Background Accents */}\n <div className=\"absolute top-0 right-0 w-1/3 h-full bg-primary/5 -skew-x-12 translate-x-1/2\" />\n <div className=\"absolute bottom-0 left-0 w-1/4 h-1/2 bg-accent/5 skew-y-12 -translate-x-1/2 blur-3xl rounded-full\" />\n\n <Container className=\"relative z-10\">\n {!hideHeader && (\n <div className=\"text-center max-w-3xl mx-auto mb-16\">\n <m.div\n initial={{ opacity: 0, y: 20 }}\n whileInView={{ opacity: 1, y: 0 }}\n viewport={{ once: true, margin: \"-50px\" }}\n transition={{ duration: 1.0, ease: [0.16, 1, 0.3, 1] }}\n >\n <h2 className=\"text-primary font-bold tracking-wider uppercase text-sm mb-3\">{badgeText}</h2>\n <h3 className=\"font-heading text-2xl md:text-5xl font-extrabold text-neutral-dark mb-6\">{titleText}</h3>\n <p className=\"text-lg text-neutral-600\">{descriptionText}</p>\n </m.div>\n </div>\n )}\n\n <m.div \n className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\"\n variants={containerVariants}\n initial=\"hidden\"\n whileInView=\"show\"\n viewport={{ once: true, margin: \"-50px\" }}\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 <Wrapper\n key={index}\n {...wrapperProps as any}\n variants={itemVariants}\n className={`group relative rounded-2xl p-6 md:p-8 flex flex-col justify-between overflow-hidden transition-all duration-500 hover:shadow-xl hover:-translate-y-1 border ${\n isIso \n ? 'bg-neutral-dark text-white border-neutral-800 shadow-lg' \n : 'bg-white text-neutral-dark border-neutral-200 shadow-md'\n }`}\n >\n {/* Background Decorative Elements */}\n <div className={`absolute top-0 right-0 w-32 h-32 blur-3xl rounded-full -mr-10 -mt-10 transition-colors duration-500 ${\n isIso ? 'bg-primary/20 group-hover:bg-primary/40' : 'bg-primary/10 group-hover:bg-primary/20'\n }`} />\n\n {isIso && (\n <div className=\"absolute -bottom-[20%] -right-[20%] w-[100%] h-[100%] z-0 opacity-10 group-hover:opacity-20 transition-opacity duration-1000 pointer-events-none mix-blend-overlay\">\n <LogoArcs className=\"w-full h-full text-white animate-[spin_60s_linear_infinite]\" />\n </div>\n )}\n \n <HoverShineOverlay shineColor={isIso ? 'via-white/20' : 'via-black/5'} />\n <AnimatedGlossyBorder color={isIso ? 'primary' : 'primary'} className=\"opacity-0 group-hover:opacity-100 transition-opacity duration-700\" borderWidth={2} />\n\n <div className=\"relative z-10 flex flex-col h-full\">\n <div className=\"flex justify-between items-start mb-8\">\n <div className={`p-3 rounded-xl inline-flex ${\n isIso ? 'bg-primary/20 text-primary-light backdrop-blur-md' : 'bg-primary/10 text-primary'\n }`}>\n {isIso ? <Award size={28} /> : <ShieldCheck size={28} />}\n </div>\n {cert.date && (\n <span className={`text-xs font-bold uppercase tracking-wider px-3 py-1 rounded-full ${\n isIso ? 'bg-neutral-800 text-neutral-400 border border-neutral-700' : 'bg-neutral-100 text-neutral-500 border border-neutral-200'\n }`}>\n {cert.date}\n </span>\n )}\n </div>\n\n <div>\n <h4 className={`text-xl font-bold mb-2 ${isIso ? 'text-white' : 'text-neutral-dark'}`}>\n {cert.title}\n </h4>\n <p className={`text-sm mb-6 ${isIso ? 'text-neutral-400' : 'text-neutral-600'}`}>\n {cert.description}\n </p>\n </div>\n\n <div className={`mt-auto inline-flex items-center text-sm font-bold uppercase tracking-wider transition-all ${\n cert.pdfUrl ? 'group-hover:gap-3' : ''\n } ${\n isIso ? 'text-primary-light' : 'text-primary'\n }`}>\n {cert.pdfUrl ? (\n <>\n <span>Download PDF</span>\n <Download size={16} className=\"ml-2 group-hover:translate-y-0.5 transition-transform\" />\n </>\n ) : (\n <span className=\"text-neutral-400\">Nachweis liegt vor</span>\n )}\n </div>\n </div>\n </Wrapper>\n );\n })}\n </m.div>\n </Container>\n </section>\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 <section className=\"py-16 md:py-24 lg:py-32 bg-neutral text-neutral-dark overflow-hidden relative\">\n <Container>\n <div className=\"flex flex-col md:flex-row justify-between items-end mb-12\">\n <div className=\"max-w-2xl\">\n <h2 className=\"text-primary font-bold tracking-wider uppercase text-sm mb-3\">{badge}</h2>\n <h3 className=\"font-heading text-3xl md:text-5xl font-extrabold text-neutral-dark mb-4\">{title}</h3>\n </div>\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[minmax(220px,auto)]\">\n {/* Simplified static grid */}\n </div>\n </Container>\n </section>\n );\n }\n\n return (\n <section className=\"py-12 md:py-24 bg-neutral text-neutral-dark relative overflow-hidden\">\n <Container className=\"relative z-10 px-0 md:px-12 lg:px-16\">\n <div className=\"flex flex-row justify-between items-end mb-12 px-4 md:px-0\">\n <div className=\"max-w-2xl text-left\">\n <h2 className=\"text-primary font-bold tracking-wider uppercase text-sm mb-3\">{badge}</h2>\n <h3 className=\"font-heading text-3xl md:text-5xl font-extrabold text-neutral-dark mb-4\">{title}</h3>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4 auto-rows-[minmax(220px,auto)] px-4 md:px-0\">\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 <m.div \n key={idx}\n initial={{ opacity: 0, y: 20 }}\n whileInView={{ opacity: 1, y: 0 }}\n viewport={{ once: true, margin: \"-50px\" }}\n transition={{ delay: idx * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}\n className={`${gridClasses} ${item.href ? 'cursor-pointer hover:-translate-y-1 transition-transform duration-300' : ''}`}\n >\n {item.href && (\n <Link href={item.href} className=\"absolute inset-0 z-30\" aria-label={item.title} />\n )}\n <div className=\"absolute top-0 right-0 w-32 h-32 bg-primary/30 blur-3xl rounded-full -mr-10 -mt-10 group-hover:bg-primary/50 transition-colors duration-500\" />\n \n {/* Subtle Background Logo Arcs */}\n <div className={`absolute -bottom-[30%] -right-[30%] w-[120%] h-[120%] z-0 opacity-10 group-hover:opacity-20 transition-opacity duration-1000 pointer-events-none mix-blend-overlay`}>\n <LogoArcs className={`w-full h-full text-white animate-[spin_60s_linear_infinite]`} />\n </div>\n\n <HoverShineOverlay shineColor=\"via-white/30\" />\n <AnimatedGlossyBorder color=\"white\" className=\"opacity-0 group-hover:opacity-100 transition-opacity duration-700\" borderWidth={1} />\n <h4 className=\"font-heading font-extrabold text-2xl md:text-3xl relative z-10 leading-snug\">{item.title}</h4>\n {item.description && <p className=\"text-white/80 text-base md:text-lg mt-3 relative z-10 font-medium\">{item.description}</p>}\n </m.div>\n );\n }\n\n return (\n <m.div \n key={idx}\n initial={{ opacity: 0, y: 20, scale: 0.98 }}\n whileInView={{ opacity: 1, y: 0, scale: 1 }}\n viewport={{ once: true, margin: \"-50px\" }}\n transition={{ delay: idx * 0.1, duration: 1.0, ease: [0.16, 1, 0.3, 1] }}\n className={`${gridClasses} ${item.href ? 'cursor-pointer hover:-translate-y-1 transition-transform duration-300' : ''}`}\n >\n {item.href && (\n <Link href={item.href} className=\"absolute inset-0 z-30\" aria-label={item.title} />\n )}\n {!imgSrc && (\n <div className=\"absolute inset-0 bg-neutral-dark z-0 transition-colors duration-500 group-hover:bg-primary-dark\" />\n )}\n {imgSrc && (\n <>\n <div className=\"absolute inset-0 bg-white z-0\">\n <Image\n src={imgSrc}\n alt={item.title || ''}\n fill\n className=\"object-cover opacity-90 group-hover:scale-105 group-hover:opacity-100 transition-all duration-700 ease-in-out\"\n sizes={isLarge ? \"(max-width: 768px) 100vw, 50vw\" : \"(max-width: 768px) 100vw, 25vw\"}\n />\n </div>\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/90 via-black/60 to-black/30 opacity-100 transition-opacity duration-500 z-0 group-hover:from-black/80 group-hover:via-black/50 group-hover:to-black/20\" />\n </>\n )}\n \n <HoverShineOverlay />\n <AnimatedGlossyBorder color=\"white\" className=\"opacity-0 group-hover:opacity-100 transition-opacity duration-700\" borderWidth={1} />\n \n {/* Subtle Background Logo Arcs */}\n <div className=\"absolute -top-[30%] -left-[30%] w-[120%] h-[120%] z-0 opacity-0 group-hover:opacity-[0.05] transition-opacity duration-1000 pointer-events-none mix-blend-overlay\">\n <LogoArcs className={`w-full h-full text-white animate-[spin_80s_linear_infinite]`} />\n </div>\n \n <div className=\"relative z-10 p-8 flex flex-col justify-end h-full text-white\">\n {item.tag && (\n <span className=\"inline-block px-3 py-1 bg-primary/20 backdrop-blur-md rounded-full text-xs font-bold uppercase tracking-wider text-primary-light mb-3 w-fit border border-primary/30\">\n {item.tag}\n </span>\n )}\n <h4 className=\"font-heading font-extrabold mb-2 text-2xl md:text-3xl\">\n {item.title}\n </h4>\n {item.description && (\n <p className=\"text-white/90 text-sm md:text-base max-w-md\">\n {item.description}\n </p>\n )}\n </div>\n </m.div>\n );\n })}\n </div>\n </Container>\n </section>\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 <div className=\"relative w-full h-[85svh] md:h-[100svh] flex items-center justify-center overflow-hidden bg-neutral-dark\">\n {/* Background color while video loads */}\n <div className=\"absolute inset-0 z-0 bg-neutral-dark\" />\n \n {/* ALWAYS render the Next.js optimized Image as the LCP element */}\n <Image\n key={`img-${pathname}-${posterSrc}`}\n src={posterSrc}\n alt={posterAlt}\n fill\n className=\"object-cover z-[1] pointer-events-none\"\n sizes=\"100vw\"\n quality={25}\n priority\n fetchPriority=\"high\"\n />\n {/* Fast compositing layer to replace the heavy CSS filters on the image */}\n <div className=\"absolute inset-0 bg-black/20 z-[1] pointer-events-none mix-blend-multiply\" />\n\n {/* Render video on top if available, but defer src to avoid blocking LCP */}\n {videoUrl && (\n <video\n key={`${pathname}-${videoUrl}`}\n className={`absolute inset-0 w-full h-full object-cover z-[2] pointer-events-none filter contrast-125 saturate-110 brightness-90 transition-opacity duration-1000 ${videoSrcLoaded ? 'opacity-100' : 'opacity-0'}`}\n src={videoSrcLoaded ? videoUrl : undefined}\n autoPlay\n muted\n loop\n playsInline\n preload=\"none\"\n />\n )}\n \n {/* Cinematic Color Grading Overlay */}\n <div className=\"absolute inset-0 bg-[#0a192f]/30 z-[2] mix-blend-multiply\" />\n \n {/* Dramatic Vignette & Fade to content */}\n <div className=\"absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-transparent via-black/20 to-black/80 z-[2]\" />\n <div className=\"absolute inset-0 bg-gradient-to-t from-neutral-dark via-neutral-dark/60 to-transparent z-[3]\" />\n \n {/* Top Fade for Header Navigation Readability */}\n <div className=\"absolute top-0 left-0 w-full h-48 bg-gradient-to-b from-black/70 to-transparent z-[3] pointer-events-none\" />\n \n <div className=\"container mx-auto max-w-7xl relative z-[4] text-center px-4\">\n <div\n key={`hero-content-${pathname}`}\n className=\"relative\"\n >\n <h1 \n className=\"font-heading font-bold text-3xl sm:text-4xl md:text-6xl lg:text-8xl text-white mb-6 uppercase tracking-tight drop-shadow-lg\"\n dangerouslySetInnerHTML={{ __html: title.replace(/KABELNETZBAU|Kabelnetzbau/g, '<span class=\"text-primary-light\">Kabelnetzbau</span>').replace(/KABELNETZBAU|Kabelnetzbau/g, '<span class=\"text-primary-light\">Kabelnetzbau</span>').replace(/\\\\n|\\n/g, '<br />') }}\n />\n \n <p className=\"text-lg md:text-xl lg:text-2xl text-white/90 max-w-3xl mx-auto mb-12 drop-shadow\">\n {subtitle}\n </p>\n \n <div className=\"flex flex-wrap items-center justify-center gap-4 md:gap-6\">\n <Button \n href={ctaHref} \n variant=\"primary\"\n size=\"lg\"\n >\n {ctaLabel}\n </Button>\n \n <Button \n href={secondaryCtaHref} \n variant=\"outline\"\n size=\"lg\"\n className=\"border-white/60 text-white hover:text-primary hover:bg-white\"\n >\n {secondaryCtaLabel}\n </Button>\n </div>\n </div>\n </div>\n </div>\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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<Location | null>(null);\n const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(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<br/>in operation for you.</> : <>Deutschlandweit<br/>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 <div className=\"absolute inset-0 opacity-[0.25] mix-blend-screen drop-shadow-2xl brightness-200 pointer-events-none transform-gpu\">\n <img\n src=\"/germany-map.svg\"\n alt={locale === 'en' ? \"Map of Germany\" : \"Deutschlandkarte\"}\n className=\"w-full h-full object-cover\"\n fetchPriority=\"high\"\n decoding=\"async\"\n />\n </div>\n ), [locale]);\n\n return (\n <div key={isHero ? `hero-map-${pathname}` : undefined} className={isHero ? \"relative w-full\" : \"relative w-full max-w-7xl mx-auto py-12 md:py-24 px-4 sm:px-6 mt-16 md:mt-20\"}>\n <div className={`bg-[#050B14] ${isHero ? 'pt-32 pb-16 md:pt-40 md:pb-24' : 'rounded-[2.5rem] md:rounded-[3.5rem] shadow-2xl border border-white/5'} overflow-visible relative group/map`}>\n {!isHero && <AnimatedGlossyBorder opacity={0.7} className=\"z-30\" />}\n \n <div className={`absolute inset-0 bg-gradient-to-br from-[#050B14] via-[#0A1322] to-[#050B14] ${!isHero && 'rounded-[2.5rem] md:rounded-[3.5rem]'} overflow-hidden`} />\n <div className=\"absolute top-0 right-0 w-[800px] h-[800px] bg-primary/5 rounded-full blur-[120px] pointer-events-none translate-x-1/3 -translate-y-1/3 overflow-hidden\" />\n \n <div className={`relative z-10 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-12 gap-12 items-center min-h-[600px] ${isHero ? 'max-w-7xl mx-auto px-4 sm:px-6' : 'p-8 md:p-12 lg:p-16'}`}>\n \n <div className=\"xl:col-span-5 text-white z-20\">\n <div className=\"inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider mb-8\">\n <MapPin className=\"w-4 h-4\" />\n <span>{finalBadge}</span>\n </div>\n \n {isHero ? (\n <h1 className=\"font-heading text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 text-balance\">\n {finalTitle}\n </h1>\n ) : (\n <h3 className=\"font-heading text-3xl md:text-4xl lg:text-5xl xl:text-6xl font-extrabold mb-6 leading-[1.1] text-transparent bg-clip-text bg-gradient-to-r from-white to-white/70 text-balance\">\n {finalTitle}\n </h3>\n )}\n \n <p className=\"text-white/60 text-lg mb-12 leading-relaxed\">\n {finalDescription}\n </p>\n\n <div className=\"grid grid-cols-2 gap-4\">\n {finalStats.map((stat, i) => {\n const StatCard = () => (\n <div className={`bg-white/5 border border-white/10 rounded-2xl p-6 backdrop-blur-md relative overflow-hidden group h-full ${stat.href ? 'cursor-pointer' : ''}`}>\n <div className=\"absolute inset-0 bg-gradient-to-br from-primary/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500\" />\n <div className=\"relative z-10 flex flex-col h-full justify-between\">\n <div className=\"text-3xl md:text-4xl font-extrabold text-white mb-1 flex items-baseline gap-1\">\n {stat.value}<span className=\"text-xl text-primary\">{stat.suffix}</span>\n </div>\n <div className={`text-sm text-white/50 font-medium flex items-center ${stat.href ? 'group-hover:text-primary transition-colors' : ''}`}>\n {stat.label}\n {stat.href && <ArrowUpRight className=\"w-4 h-4 ml-1 opacity-0 -translate-y-1 translate-x-1 group-hover:opacity-100 group-hover:translate-y-0 group-hover:translate-x-0 transition-all duration-300\" />}\n </div>\n </div>\n </div>\n );\n\n return stat.href ? (\n <Link key={i} href={stat.href} className=\"block group/link hover:-translate-y-1 transition-transform duration-300\">\n <StatCard />\n </Link>\n ) : (\n <div key={i}>\n <StatCard />\n </div>\n );\n })}\n </div>\n </div>\n\n <div className=\"xl:col-span-7 relative flex items-center justify-center min-h-[500px] lg:min-h-[700px] z-10 pointer-events-none\">\n <div className=\"relative w-full max-w-[600px] aspect-square group pointer-events-auto\">\n \n {mapBackground}\n\n <InteractiveMapPins\n locations={finalLocations}\n locale={locale}\n t={t}\n activeLocation={activeLocation}\n handleMouseEnter={handleMouseEnter}\n handleMouseLeave={handleMouseLeave}\n />\n </div>\n </div>\n </div>\n </div>\n </div>\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 <a> tag\nvi.mock('next/link', () => ({\n default: vi.fn(({ children, href, onClick, ...props }: any) => {\n return (\n <a href={href} onClick={onClick} {...props}>\n {children}\n </a>\n );\n })\n}));\n\n// Mock next/image to render standard img tag\nvi.mock('next/image', () => ({\n default: (props: any) => {\n return <img {...props} />;\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(<ReferencesSlider references={mockReferences} />);\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(<ReferencesSlider references={mockReferences} />);\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 <section id=\"unternehmen\" className=\"pt-12 pb-16 md:py-24 lg:py-32 bg-neutral-dark border-b border-neutral-800 relative overflow-hidden\">\n <div className=\"container mx-auto max-w-7xl relative z-10 px-4\">\n \n {(badge || title) && (\n <div className=\"text-center md:text-left max-w-3xl mx-auto md:mx-0 mb-16\">\n {badge && <h2 className=\"text-emerald-400 font-bold tracking-wider uppercase text-sm mb-3\">{badge}</h2>}\n {title && <h3 className=\"font-heading text-3xl md:text-5xl font-extrabold text-white\">{title}</h3>}\n </div>\n )}\n \n <div className=\"relative -mx-4 md:mx-0\">\n <div className=\"absolute right-0 top-0 bottom-0 w-16 bg-gradient-to-l from-neutral-dark to-transparent z-20 md:hidden pointer-events-none\" />\n <m.div \n className=\"flex overflow-x-auto md:overflow-visible snap-x snap-mandatory pb-6 md:pb-0 md:grid md:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 px-4 md:px-0 [scrollbar-width:none] [-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden\"\n variants={containerVariants}\n initial=\"hidden\"\n whileInView=\"visible\"\n viewport={{ once: true, margin: \"-50px\" }}\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 <div className=\"absolute inset-0 bg-[#0a192f] z-0\">\n <Image\n src={company.backgroundImage}\n alt=\"Background\"\n fill\n className={`object-cover transition-all duration-1000 ease-out ${\n isCurrent \n ? 'opacity-30 grayscale-0 scale-105' \n : 'opacity-40 grayscale-[50%] group-hover:opacity-30 group-hover:grayscale-0 group-hover:scale-110'\n }`}\n sizes=\"(max-width: 768px) 200px, 384px\"\n />\n </div>\n \n {/* Heavy dark gradient overlay to make logos pop */}\n <div className={`absolute inset-0 z-0 transition-colors duration-700 mix-blend-multiply ${\n isCurrent \n ? 'bg-gradient-to-t from-[#0a192f]/90 via-[#0a192f]/60 to-[#0a192f]/40' \n : 'bg-gradient-to-t from-[#0a192f]/90 via-[#0a192f]/60 to-[#0a192f]/40 group-hover:from-primary/90 group-hover:to-primary/20'\n }`} />\n\n {/* Subtle Background Logo Arcs */}\n <div className={`absolute ${positionClass} w-[120%] h-[120%] z-10 opacity-[0.02] group-hover:opacity-[0.08] transition-opacity duration-1000 pointer-events-none mix-blend-overlay`}>\n <LogoArcs className={`w-full h-full text-white ${animationClass}`} />\n </div>\n \n {/* Premium Shine Sweep Effect (Industrial Reflection) */}\n <HoverShineOverlay shineColor=\"via-white/30\" />\n \n {/* Glossy Animated Border */}\n <AnimatedGlossyBorder \n color={isCurrent ? \"primary\" : \"white\"} \n className={`transition-opacity duration-700 ${isCurrent ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}`} \n borderWidth={1} \n />\n \n {/* Current Site Indicator Badge */}\n {isCurrent && (\n <div className=\"absolute top-6 right-6 z-20\">\n <div className=\"flex items-center space-x-2 bg-neutral-dark/80 backdrop-blur-md border border-primary/40 px-3 py-1.5 rounded-full shadow-lg\">\n <span className=\"relative flex h-2 w-2\">\n <span className=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-primary opacity-75\"></span>\n <span className=\"relative inline-flex rounded-full h-2 w-2 bg-primary\"></span>\n </span>\n <span className=\"text-[10px] font-bold uppercase tracking-widest text-primary/90\">\n {isDe ? 'Aktuell hier' : 'You are here'}\n </span>\n </div>\n </div>\n )}\n \n <div className=\"relative z-10 flex flex-col items-center justify-center w-full h-full p-8 transform group-hover:scale-105 transition-transform duration-500\">\n \n {/* LOGO RENDERING */}\n {isEtib ? (\n <div className=\"flex flex-col items-center\">\n <div className=\"relative w-32 md:w-40 lg:w-44 h-10 md:h-12 lg:h-14 mb-2 max-w-[85%]\">\n <Image\n src=\"/assets/logo-white.png\"\n alt=\"E-TIB Logo\"\n fill\n className=\"object-contain\"\n />\n </div>\n {isIng && (\n <div className=\"text-white/80 tracking-[0.15em] sm:tracking-[0.2em] text-[9px] sm:text-[10px] lg:text-[11px] font-semibold uppercase mt-1 text-center\">\n Ingenieurgesellschaft\n </div>\n )}\n {isVerwaltung && (\n <div className=\"text-white/80 tracking-[0.15em] sm:tracking-[0.2em] text-[9px] sm:text-[10px] lg:text-[11px] font-semibold uppercase mt-1 text-center\">\n Verwaltung GmbH\n </div>\n )}\n {isBohrtechnik && (\n <div className=\"text-white/80 tracking-[0.15em] sm:tracking-[0.2em] text-[9px] sm:text-[10px] lg:text-[11px] font-semibold uppercase mt-1 text-center\">\n Bohrtechnik GmbH\n </div>\n )}\n </div>\n ) : isNemo ? (\n <div className=\"flex flex-col items-center\">\n <div className=\"text-5xl font-heading font-black text-white tracking-tight\">\n NEMO\n </div>\n <div className=\"text-white/60 tracking-widest text-xs font-semibold uppercase mt-1\">\n GmbH\n </div>\n </div>\n ) : (\n <div className=\"text-2xl font-heading font-bold text-white uppercase text-center\">\n {company.title}\n </div>\n )}\n \n </div>\n \n {/* Subtle Link Indicator */}\n {company.url && (\n <div className=\"absolute bottom-6 left-0 right-0 flex justify-center opacity-0 group-hover:opacity-100 transform translate-y-4 group-hover:translate-y-0 transition-all duration-500 z-20\">\n <div className=\"flex items-center text-white font-bold uppercase text-xs tracking-widest bg-white/10 backdrop-blur-md px-4 py-2 rounded-full border border-white/20\">\n {isExternal ? (isDe ? 'Website Besuchen' : 'Visit Website') : (isDe ? 'Zum Standort' : 'View Location')}\n <svg className=\"w-4 h-4 ml-2\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M5 12h14M12 5l7 7-7 7\"/></svg>\n </div>\n </div>\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 <m.div \n key={index}\n variants={itemVariants}\n className=\"h-full flex-shrink-0 snap-center w-[85%] sm:w-[70%] md:w-full\"\n >\n {hasUrl ? (\n isExternal ? (\n <a href={company.url} target=\"_blank\" rel=\"noopener noreferrer\" className={sharedClass}>\n {CardContent}\n </a>\n ) : (\n <Link href={company.url as string} className={sharedClass}>\n {CardContent}\n </Link>\n )\n ) : (\n <div className={sharedClass}>\n {CardContent}\n </div>\n )}\n </m.div>\n );\n })}\n </m.div>\n </div>\n </div>\n </section>\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 <div className=\"team-grid-block\">\n {/* Passing data to the client component */}\n <TeamGridComponent members={members} />\n </div>\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<ReturnType<typeof setTimeout> | 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 <Section key={`home-hero-${pathname}`} className=\"relative min-h-[75vh] md:min-h-[90vh] flex flex-col items-center justify-center overflow-hidden bg-primary py-12 md:py-0 lg:py-0\">\n <Container className=\"relative z-10 text-center md:text-left text-white w-full order-2 md:order-none\">\n <div className=\"max-w-5xl mx-auto md:mx-0\">\n <div>\n <Heading\n level={1}\n className=\"text-center md:text-left mb-6 md:mb-8 md:max-w-none text-white text-4xl sm:text-5xl md:text-7xl font-extrabold [text-shadow:_-2px_-2px_0_#002b49,_2px_-2px_0_#002b49,_-2px_2px_0_#002b49,_2px_2px_0_#002b49,_-2px_0_0_#002b49,_2px_0_0_#002b49,_0_-2px_0_#002b49,_0_2px_0_#002b49]\"\n >\n {data?.title ? (\n <span\n dangerouslySetInnerHTML={{\n __html: data.title\n .replace(\n /<green>/g,\n '<span class=\"relative inline-block\"><span class=\"relative z-10 text-accent italic inline-block\">',\n )\n .replace(\n /<\\/green>/g,\n '</span><div class=\"w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute -z-10 \" style=\"animation-delay: 500ms;\"><Scribble variant=\"circle\" /></div></span>',\n ),\n }}\n />\n ) : (\n t.rich('title', {\n green: (chunks) => (\n <span className=\"relative inline-block\">\n <span className=\"relative z-10 text-accent italic inline-block\">\n {chunks}\n </span>\n <div\n className=\"w-[140%] h-[140%] -top-[20%] -left-[20%] text-accent/30 hidden md:block absolute -z-10 \"\n style={{ animationDelay: '500ms' }}\n >\n <Scribble variant=\"circle\" />\n </div>\n </span>\n ),\n })\n )}\n </Heading>\n </div>\n <div>\n <p className=\"text-lg md:text-xl text-white leading-relaxed max-w-2xl mb-10 md:mb-12\">\n {data?.subtitle || t('subtitle')}\n </p>\n </div>\n <form\n onSubmit={handleSearchSubmit}\n className=\"w-full max-w-2xl bg-white/10 backdrop-blur-md border border-white/20 rounded-2xl p-2 flex items-center mt-8 mb-10 transition-all focus-within:bg-white/15 focus-within:border-accent shadow-lg relative\"\n >\n <div className=\"absolute left-1 w-20 h-20 flex items-center justify-center z-10 overflow-visible\">\n <AIOrb isThinking={false} />\n </div>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={heroPlaceholder}\n className=\"flex-1 bg-transparent border-none text-white pl-20 pr-2 py-4 placeholder:text-white/50 focus:outline-none text-lg lg:text-xl\"\n autoFocus\n />\n <Button\n type=\"submit\"\n variant=\"accent\"\n size=\"lg\"\n className=\"rounded-xl px-6 py-4 shrink-0 flex items-center shadow-md font-bold cursor-pointer hover:bg-accent hover:brightness-110\"\n >\n {t('ask')}\n <ChevronRight className=\"w-5 h-5 ml-2 -mr-1\" />\n </Button>\n </form>\n\n <div className=\"flex flex-col sm:flex-row justify-center md:justify-start gap-4 md:gap-6\">\n <div>\n <Button\n href={`/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}`}\n variant=\"white\"\n size=\"lg\"\n className=\"group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg hover:scale-105 transition-all outline-none\"\n onClick={() =>\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 <span className=\"transition-transform group-hover/btn:translate-x-1 ml-2\">\n &rarr;\n </span>\n </Button>\n </div>\n <div>\n <Button\n href={`/${locale}/${locale === 'de' ? 'produkte' : 'products'}`}\n variant=\"outline\"\n size=\"lg\"\n className=\"group w-full sm:w-auto h-14 md:h-16 px-8 md:px-10 text-base md:text-lg text-white border-white/30 hover:bg-white/10 hover:border-white transition-all\"\n onClick={() =>\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 </Button>\n </div>\n </div>\n </div>\n </Container>\n\n <div className=\"relative md:absolute inset-0 z-0 w-full h-[35vh] md:h-full order-1 md:order-none mb-[-80px] md:mb-0 mt-[80px] md:mt-0 overflow-visible pointer-events-none \">\n <HeroIllustration />\n </div>\n\n <div\n className=\"absolute bottom-6 md:bottom-10 left-1/2 -translate-x-1/2 hidden sm:block \"\n style={{ animationDelay: '2000ms' }}\n >\n <div className=\"w-6 h-10 border-2 border-white/30 rounded-full flex justify-center p-1\">\n <div className=\"w-1 h-2 bg-white rounded-full animate-bounce\" />\n </div>\n </div>\n </Section>\n <AISearchResults\n isOpen={isSearchOpen}\n onClose={() => 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 <Section className=\"relative overflow-hidden\">\n <div className=\"absolute inset-0 z-0\">\n <Image\n src=\"/uploads/2024/12/DSC08036-Large.webp\"\n alt={data?.subtitle || t('subtitle')}\n fill\n className=\"object-cover scale-105 \"\n sizes=\"100vw\"\n quality={100}\n />\n <div className=\"absolute inset-0 bg-primary/70 mix-blend-multiply\" />\n <div className=\"absolute inset-0 bg-gradient-to-t from-primary via-primary/20 to-transparent\" />\n </div>\n\n <Container className=\"relative z-10\">\n <div className=\"max-w-3xl text-white \">\n <Heading level={2} subtitle={data?.subtitle || t('subtitle')} className=\"text-white mb-8\">\n <span className=\"text-white\">{data?.title || t('title')}</span>\n </Heading>\n\n <div className=\"relative mb-12\">\n <div className=\"absolute -left-8 top-0 bottom-0 w-1 bg-accent rounded-full\" />\n <p className=\"text-lg md:text-2xl leading-relaxed font-medium italic text-white/90 pl-8\">\n \"{data?.description || t('description')}\"\n </p>\n </div>\n\n <div className=\"flex flex-wrap gap-8 items-center\">\n <Button href={`/${locale}/team`} variant=\"accent\" size=\"xl\" className=\"group\">\n {data?.ctaLabel || t('cta')}\n <span className=\"ml-3 transition-transform group-hover:translate-x-2\">&rarr;</span>\n </Button>\n\n <div className=\"flex items-center gap-4\">\n <div className=\"flex -space-x-4\">\n <div className=\"w-14 h-14 rounded-full border-4 border-primary overflow-hidden relative\">\n <Image\n src=\"/uploads/2024/12/DSC07768-Large.webp\"\n alt=\"Team E-TIB\"\n fill\n className=\"object-cover\"\n />\n </div>\n <div className=\"w-14 h-14 rounded-full border-4 border-primary overflow-hidden relative\">\n <Image\n src=\"/uploads/2024/12/DSC07963-Large.webp\"\n alt=\"Experten\"\n fill\n className=\"object-cover\"\n />\n </div>\n </div>\n <span className=\"text-white/60 font-bold text-xs md:text-sm uppercase tracking-widest\">\n {data?.networkLabel || t('andNetwork')}\n </span>\n </div>\n </div>\n </div>\n </Container>\n </Section>\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 <footer className=\"bg-[#050B14] text-neutral-light pt-24 pb-28 md:pt-32 md:pb-16 relative overflow-hidden border-t border-white/5\">\n {/* Subtle background tech grid */}\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:32px_32px] [mask-image:radial-gradient(ellipse_80%_50%_at_50%_0%,#000_70%,transparent_100%)] pointer-events-none\" />\n\n {/* Top glowing edge */}\n <div className=\"absolute top-0 left-0 w-full h-px bg-gradient-to-r from-transparent via-primary/40 to-transparent\" />\n <div className=\"absolute top-0 left-1/2 -translate-x-1/2 w-1/3 h-[200px] bg-primary/20 blur-[100px] rounded-full pointer-events-none opacity-50\" />\n\n <EUFundingBadge />\n\n <div className=\"container mx-auto max-w-7xl relative z-10 grid grid-cols-1 md:grid-cols-4 lg:grid-cols-12 gap-4 md:gap-6 lg:gap-8 mb-4 md:mb-12\">\n {/* Brand Column Bento */}\n <div className=\"col-span-1 md:col-span-4 lg:col-span-5 flex flex-col justify-between bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden\">\n <div className=\"absolute inset-0 bg-gradient-to-br from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700\" />\n\n <div className=\"space-y-4 md:space-y-6 relative z-10\">\n <TransitionLink href={`/${locale}`} className=\"relative block h-12 md:h-14 w-40 md:w-48 mb-4 md:mb-6 group/logo cursor-pointer\">\n <Image\n src=\"/assets/logo-white.png\"\n alt=\"E-TIB Gruppe\"\n fill\n className=\"object-contain object-left transition-transform duration-500 group-hover/logo:scale-105 origin-left\"\n />\n </TransitionLink>\n <p className=\"text-neutral-400 text-base leading-relaxed max-w-sm\">\n {locale === 'de'\n ? 'Die Experten für Kabelnetzbau, Horizontalspülbohrungen und umfassende Infrastrukturprojekte. Qualität und Zuverlässigkeit aus Guben.'\n : 'Experts in cable network construction, horizontal directional drilling, and comprehensive infrastructure projects. Quality and reliability from Guben.'}\n </p>\n </div>\n\n {companyInfo && (\n <div className=\"text-white/70 text-sm space-y-3 md:space-y-4 mt-8 md:mt-12 relative z-10\">\n <div className=\"flex gap-4 p-4 rounded-2xl bg-white/[0.03] border border-white/[0.05] hover:bg-white/[0.05] transition-colors\">\n <div className=\"w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center shrink-0\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" className=\"w-5 h-5 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\" /><path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\" /></svg>\n </div>\n <div>\n <p className=\"text-xs text-neutral-400 uppercase tracking-wider mb-1 font-heading\">{locale === 'de' ? 'Hauptsitz' : 'Headquarters'}</p>\n <p className=\"whitespace-pre-line font-medium text-neutral-200\">{companyInfo.address}</p>\n </div>\n </div>\n <a href={`mailto:${companyInfo.contactEmail}`} className=\"flex gap-4 p-4 rounded-2xl bg-white/[0.03] border border-white/[0.05] hover:bg-white/[0.08] hover:border-primary/30 transition-all group/contact cursor-pointer\">\n <div className=\"w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center shrink-0 group-hover/contact:bg-primary/30 transition-colors\">\n <Mail className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-neutral-400 uppercase tracking-wider mb-1 font-heading\">E-Mail</p>\n <p className=\"font-medium text-neutral-200 group-hover/contact:text-primary transition-colors truncate\">{companyInfo.contactEmail}</p>\n </div>\n </a>\n <a href={`tel:${companyInfo.contactPhone.replace(/\\s+/g, '')}`} className=\"flex gap-4 p-4 rounded-2xl bg-white/[0.03] border border-white/[0.05] hover:bg-white/[0.08] hover:border-primary/30 transition-all group/contact cursor-pointer\">\n <div className=\"w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center shrink-0 group-hover/contact:bg-primary/30 transition-colors\">\n <Phone className=\"w-5 h-5 text-primary\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-xs text-neutral-400 uppercase tracking-wider mb-1 font-heading\">{locale === 'de' ? 'Telefon' : 'Phone'}</p>\n <p className=\"font-medium text-neutral-200 group-hover/contact:text-primary transition-colors truncate\">{companyInfo.contactPhone}</p>\n </div>\n </a>\n <div className=\"flex gap-4 p-4 rounded-2xl bg-white/[0.03] border border-white/[0.05] hover:bg-white/[0.05] transition-colors\">\n <div className=\"w-10 h-10 rounded-full bg-primary/20 flex items-center justify-center shrink-0\">\n <svg className=\"w-5 h-5 text-primary\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><circle cx=\"12\" cy=\"12\" r=\"10\"></circle><polyline points=\"12 6 12 12 16 14\"></polyline></svg>\n </div>\n <div>\n <p className=\"text-xs text-neutral-400 uppercase tracking-wider mb-1 font-heading\">{locale === 'de' ? 'Öffnungszeiten' : 'Opening Hours'}</p>\n <div className=\"text-sm font-medium text-neutral-200\">\n <p>{locale === 'de' ? 'Mo. Fr.' : 'Mon. Fri.'}: 07:00 17:00 {locale === 'de' ? 'Uhr' : ''}</p>\n <p className=\"text-white/50 mt-0.5\">{locale === 'de' ? 'Sa. So.' : 'Sat. Sun.'}: {locale === 'de' ? 'Geschlossen' : 'Closed'}</p>\n </div>\n </div>\n </div>\n </div>\n )}\n </div>\n\n {/* Navigation Bento Grid */}\n <div className=\"col-span-1 md:col-span-4 lg:col-span-7 grid grid-cols-1 sm:grid-cols-2 gap-4 md:gap-6 lg:gap-8\">\n\n {/* Links Column */}\n <div className=\"bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col\">\n <div className=\"absolute inset-0 bg-gradient-to-bl from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none\" />\n \n {/* Desktop View */}\n <div className=\"hidden md:block relative z-10 flex-grow\">\n <h4 className=\"font-heading font-bold uppercase tracking-widest text-sm mb-6 text-white\">\n {locale === 'de' ? 'Navigation' : 'Navigation'}\n <span className=\"block w-8 h-1 bg-primary mt-3 rounded-full\" />\n </h4>\n <ul className=\"grid grid-cols-2 gap-x-8 gap-y-3 mb-8\">\n {[\n { label: locale === 'de' ? 'Unternehmen' : 'Company', href: `/${locale}/${locale === 'de' ? 'ueber-uns' : 'about-us'}` },\n { label: locale === 'de' ? 'Standorte' : 'Locations', href: `/${locale}/standorte` },\n { label: locale === 'de' ? 'Unser Team' : 'Our Team', href: `/${locale}/team` },\n { label: locale === 'de' ? 'Karriere' : 'Career', href: `/${locale}/${locale === 'de' ? 'karriere' : 'career'}` },\n { label: locale === 'de' ? 'Zertifikate' : 'Certificates', href: `/${locale}/zertifikate` },\n { label: locale === 'de' ? 'Kompetenzen' : 'Competencies', href: `/${locale}/${locale === 'de' ? 'kompetenzen' : 'competencies'}` },\n { label: locale === 'de' ? 'Referenzen' : 'References', href: `/${locale}/referenzen` },\n { label: locale === 'de' ? 'Messen & Events' : 'Fairs & Events', href: `/${locale}/${locale === 'de' ? 'messen' : 'trade-fairs'}` },\n { label: locale === 'de' ? 'Kontakt' : 'Contact', href: `/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}` },\n ].map((link) => (\n <li key={link.href}>\n <TransitionLink href={link.href} className=\"group/link flex items-center text-neutral-400 hover:text-white transition-colors py-1 cursor-pointer\">\n <span className=\"w-0 h-px bg-primary mr-0 group-hover/link:w-4 group-hover/link:mr-3 transition-all duration-300\" />\n <span className=\"font-medium\">{link.label}</span>\n </TransitionLink>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Mobile View (Accordions) */}\n <div className=\"md:hidden relative z-10 flex-grow space-y-4\">\n <details className=\"group/acc\">\n <summary className=\"font-heading font-bold uppercase tracking-widest text-sm text-white list-none cursor-pointer flex justify-between items-center py-2 outline-none select-none\">\n {locale === 'de' ? 'Navigation' : 'Navigation'}\n <svg className=\"w-4 h-4 text-white/50 transition-transform group-open/acc:rotate-180\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M19 9l-7 7-7-7\"/></svg>\n </summary>\n <div className=\"pt-2 pb-4\">\n <ul className=\"space-y-3\">\n {[\n { label: locale === 'de' ? 'Unternehmen' : 'Company', href: `/${locale}/${locale === 'de' ? 'ueber-uns' : 'about-us'}` },\n { label: locale === 'de' ? 'Standorte' : 'Locations', href: `/${locale}/standorte` },\n { label: locale === 'de' ? 'Unser Team' : 'Our Team', href: `/${locale}/team` },\n { label: locale === 'de' ? 'Karriere' : 'Career', href: `/${locale}/${locale === 'de' ? 'karriere' : 'career'}` },\n { label: locale === 'de' ? 'Zertifikate' : 'Certificates', href: `/${locale}/zertifikate` },\n { label: locale === 'de' ? 'Kompetenzen' : 'Competencies', href: `/${locale}/${locale === 'de' ? 'kompetenzen' : 'competencies'}` },\n { label: locale === 'de' ? 'Referenzen' : 'References', href: `/${locale}/referenzen` },\n { label: locale === 'de' ? 'Messen & Events' : 'Fairs & Events', href: `/${locale}/${locale === 'de' ? 'messen' : 'trade-fairs'}` },\n { label: locale === 'de' ? 'Kontakt' : 'Contact', href: `/${locale}/${locale === 'de' ? 'kontakt' : 'contact'}` },\n ].map((link) => (\n <li key={link.href}>\n <TransitionLink href={link.href} className=\"flex items-center text-neutral-400 hover:text-white transition-colors py-1\">\n <span className=\"font-medium\">{link.label}</span>\n </TransitionLink>\n </li>\n ))}\n </ul>\n </div>\n </details>\n </div>\n </div>\n\n {/* Legal Column */}\n <div className=\"bg-white/[0.02] border border-white/5 rounded-3xl p-5 md:p-8 lg:p-10 backdrop-blur-md relative group overflow-hidden flex flex-col\">\n <div className=\"absolute inset-0 bg-gradient-to-tr from-white/[0.04] to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-700 pointer-events-none\" />\n \n {/* Desktop View */}\n <div className=\"hidden md:block relative z-10 flex-grow\">\n <h4 className=\"font-heading font-bold uppercase tracking-widest text-sm mb-6 text-white\">\n {locale === 'de' ? 'Rechtliches' : 'Legal'}\n <span className=\"block w-8 h-1 bg-primary mt-3 rounded-full opacity-50\" />\n </h4>\n <ul className=\"space-y-2\">\n {[\n { label: locale === 'de' ? 'Impressum' : 'Imprint', href: `/${locale}/${locale === 'de' ? 'impressum' : 'imprint'}` },\n { label: locale === 'de' ? 'Datenschutz' : 'Privacy Policy', href: `/${locale}/${locale === 'de' ? 'datenschutz' : 'privacy-policy'}` },\n { label: locale === 'de' ? 'AGB' : 'Terms', href: `/${locale}/${locale === 'de' ? 'agb' : 'terms'}` },\n ].map((link) => (\n <li key={link.href}>\n <TransitionLink href={link.href} className=\"group/link flex items-center text-neutral-400 hover:text-white transition-colors py-1 cursor-pointer\">\n <span className=\"w-0 h-px bg-primary mr-0 group-hover/link:w-4 group-hover/link:mr-3 transition-all duration-300\" />\n <span className=\"font-medium\">{link.label}</span>\n </TransitionLink>\n </li>\n ))}\n </ul>\n </div>\n\n {/* Mobile View */}\n <div className=\"md:hidden relative z-10 flex-grow\">\n <details className=\"group/acc\">\n <summary className=\"font-heading font-bold uppercase tracking-widest text-sm text-white list-none cursor-pointer flex justify-between items-center py-2 outline-none select-none\">\n {locale === 'de' ? 'Rechtliches' : 'Legal'}\n <svg className=\"w-4 h-4 text-white/50 transition-transform group-open/acc:rotate-180\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\"><path d=\"M19 9l-7 7-7-7\"/></svg>\n </summary>\n <div className=\"pt-2 pb-4\">\n <ul className=\"space-y-3\">\n {[\n { label: locale === 'de' ? 'Impressum' : 'Imprint', href: `/${locale}/${locale === 'de' ? 'impressum' : 'imprint'}` },\n { label: locale === 'de' ? 'Datenschutz' : 'Privacy Policy', href: `/${locale}/${locale === 'de' ? 'datenschutz' : 'privacy-policy'}` },\n { label: locale === 'de' ? 'AGB' : 'Terms', href: `/${locale}/${locale === 'de' ? 'agb' : 'terms'}` },\n ].map((link) => (\n <li key={link.href}>\n <TransitionLink href={link.href} className=\"flex items-center text-neutral-400 hover:text-white transition-colors py-1\">\n <span className=\"font-medium\">{link.label}</span>\n </TransitionLink>\n </li>\n ))}\n </ul>\n </div>\n </details>\n </div>\n </div>\n\n </div>\n </div>\n\n <div className=\"container mx-auto max-w-7xl relative z-10\">\n <div className=\"flex flex-col md:flex-row justify-between items-center gap-4 md:gap-6 py-4 px-5 md:py-6 md:px-8 rounded-2xl bg-white/[0.02] border border-white/5 backdrop-blur-md text-center md:text-left\">\n <div className=\"flex flex-col md:flex-row items-center gap-4 md:gap-6 h-full\">\n <p className=\"text-neutral-400 text-xs font-medium m-0\">&copy; {new Date().getFullYear()} E-TIB GmbH. {locale === 'de' ? 'Alle Rechte vorbehalten.' : 'All rights reserved.'}</p>\n </div>\n\n <div className=\"flex items-center gap-6\">\n <LanguageSwitcher isSolidMode={false} />\n </div>\n </div>\n\n {/* Quality Badges */}\n <div className=\"mt-4 md:mt-6 flex flex-wrap justify-center gap-x-4 gap-y-3 md:gap-x-8 md:gap-y-4 text-neutral-400 text-[10px] md:text-xs font-medium uppercase tracking-wider px-2\">\n <Tooltip content={locale === 'de' ? 'Ihre Daten werden sicher und verschlüsselt übertragen.' : 'Your data is transmitted securely and encrypted.'}>\n <div className=\"flex items-center gap-2 cursor-help\"><ShieldCheck className=\"w-4 h-4 text-neutral-400\" /><span>SSL Secured</span></div>\n </Tooltip>\n <Tooltip content={locale === 'de' ? 'Wir nutzen 100% Ökostrom für den Betrieb unserer Website.' : 'We use 100% green energy to run our website.'}>\n <div className=\"flex items-center gap-2 cursor-help\"><Leaf className=\"w-4 h-4 text-primary\" /><span>Green Hosting</span></div>\n </Tooltip>\n <Tooltip content={locale === 'de' ? 'Ihre Privatsphäre ist uns wichtig. Wir halten uns streng an den Datenschutz.' : 'Your privacy is important to us. We strictly adhere to data protection laws.'}>\n <div className=\"flex items-center gap-2 cursor-help\"><Lock className=\"w-4 h-4 text-neutral-400\" /><span>DSGVO Compliant</span></div>\n </Tooltip>\n <Tooltip content={locale === 'de' ? 'Blitzschnelle Ladezeiten für ein optimales Nutzererlebnis.' : 'Lightning-fast loading times for an optimal user experience.'}>\n <div className=\"flex items-center gap-2 cursor-help\"><Zap className=\"w-4 h-4 text-neutral-400\" /><span>High Performance</span></div>\n </Tooltip>\n </div>\n\n {/* Mintel Design & Version */}\n <div className=\"mt-6 flex justify-center items-center gap-3 text-neutral-500 text-[10px] md:text-xs opacity-60 hover:opacity-100 transition-opacity\">\n <a href=\"https://mintel.me\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"hover:text-neutral-300 transition-colors flex items-center gap-2\">\n <span>Designed & Engineered by Marc Mintel</span>\n </a>\n {process.env.NEXT_PUBLIC_APP_VERSION && (\n <>\n <span className=\"w-1 h-1 rounded-full bg-white/20\" />\n <div className=\"flex items-center gap-2\">\n <span className=\"font-mono text-[10px]\">v{process.env.NEXT_PUBLIC_APP_VERSION.replace(/^v/, '')}</span>\n {process.env.NEXT_PUBLIC_TARGET && process.env.NEXT_PUBLIC_TARGET !== 'production' && (\n <span className=\"px-1.5 py-0.5 rounded-sm bg-white/10 text-[9px] uppercase tracking-wider\">\n {process.env.NEXT_PUBLIC_TARGET}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n </div>\n </footer>\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 `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` 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 <a href={href} onClick={onClick} {...props}>\n {children}\n </a>\n ),\n}));\n\n// Mock next/image\nvi.mock('next/image', () => ({\n default: ({ src, alt, ...props }: any) => <img src={src} alt={alt} {...props} />,\n}));\n\n// Mock LanguageSwitcher\nvi.mock('./LanguageSwitcher', () => ({\n LanguageSwitcher: () => <div data-testid=\"language-switcher\" />,\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(<Header navLinks={mockNavLinks} />);\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(<Header navLinks={mockNavLinks} />);\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<HTMLDivElement>(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 <header\n className={`fixed top-0 left-0 right-0 z-50 flex justify-center transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] ${\n isStandard \n ? 'bg-[#050B14]/95 backdrop-blur-xl border-b border-white/10' \n : (isSolidMode ? 'pt-4 px-4 sm:px-6' : 'pt-0 px-0')\n }`}\n >\n <div\n className={`relative w-full max-w-7xl mx-auto transition-all duration-700 ease-[cubic-bezier(0.16,1,0.3,1)] flex items-center justify-between ${\n isStandard\n ? 'py-4 px-6 md:px-8'\n : isSolidMode\n ? 'rounded-full bg-white/50 backdrop-blur-xl shadow-[0_8px_32px_rgba(0,0,0,0.08)] border border-white/40 py-2.5 px-8 md:px-10'\n : 'rounded-none bg-transparent py-6 px-4 md:px-8 border border-transparent'\n }`}\n >\n {/* Scroll bound shine effect */}\n <div className=\"absolute inset-0 z-0 pointer-events-none overflow-hidden rounded-full\">\n <div \n ref={shineRef}\n className={`absolute top-0 h-[200%] w-[150%] bg-gradient-to-r from-transparent via-white to-transparent skew-x-[-20deg] mix-blend-overlay transition-opacity duration-300 ${isSolidMode ? 'opacity-100' : 'opacity-0'}`}\n />\n </div>\n\n <TransitionLink href={`/${currentLocale}`} className={`relative flex items-center transition-all duration-500 ease-out z-50 group ${isSolidMode ? 'h-8 md:h-9 w-28 md:w-36' : 'h-10 md:h-12 w-36 md:w-48'}`}>\n <Image\n src={isSolidMode ? \"/assets/logo.png\" : \"/assets/logo-white.png\"}\n alt=\"E-TIB Gruppe\"\n fill\n className=\"object-contain object-left group-hover:scale-105 transition-transform duration-500 origin-left\"\n priority\n fetchPriority=\"high\"\n sizes=\"192px\"\n unoptimized\n />\n {/* Masked sweep overlay on hover */}\n <div \n className=\"absolute inset-0 z-10 pointer-events-none transition-transform duration-500 origin-left group-hover:scale-105\"\n style={{\n WebkitMaskImage: `url(${isSolidMode ? \"/assets/logo.png\" : \"/assets/logo-white.png\"})`,\n WebkitMaskSize: 'contain',\n WebkitMaskRepeat: 'no-repeat',\n WebkitMaskPosition: 'left center',\n maskImage: `url(${isSolidMode ? \"/assets/logo.png\" : \"/assets/logo-white.png\"})`,\n maskSize: 'contain',\n maskRepeat: 'no-repeat',\n maskPosition: 'left center'\n }}\n >\n <div className=\"absolute top-0 -left-[150%] h-[200%] w-[150%] bg-gradient-to-r from-transparent via-white to-transparent skew-x-[-25deg] group-hover:left-[150%] transition-all duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)]\" />\n </div>\n </TransitionLink>\n \n <div className=\"flex items-center gap-2 md:gap-4 ml-auto md:ml-0\">\n <nav className=\"relative z-10 hidden md:flex items-center gap-8 lg:gap-10\">\n {navLinks && navLinks.length > 0 && navLinks.map((link) => (\n <NavItem \n key={link.url} \n link={link} \n currentLocale={currentLocale} \n pathname={pathname} \n isSolidMode={isSolidMode} \n />\n ))}\n </nav>\n \n {/* Right side elements (Language Switcher always visible, Contact button desktop only) */}\n <div className=\"flex items-center gap-2 md:gap-4 lg:gap-6\">\n <div className={`hidden md:block h-5 w-px transition-colors duration-300 ${isSolidMode ? 'bg-neutral-200' : 'bg-white/20'}`} />\n \n {/* Quick Actions (Mobile Only) */}\n <div className=\"md:hidden flex items-center gap-2\">\n <a \n href=\"tel:+492572946760\"\n className={`relative flex items-center justify-center w-9 h-9 rounded-full transition-all duration-300 ${\n isSolidMode \n ? 'bg-primary text-white shadow-md shadow-primary/20 hover:-translate-y-0.5' \n : 'bg-white text-primary shadow-md hover:-translate-y-0.5'\n }`}\n aria-label=\"Anrufen\"\n >\n <svg className=\"w-[16px] h-[16px]\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.5} d=\"M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z\" />\n </svg>\n </a>\n <a \n href=\"mailto:info@e-tib.com\"\n className={`relative flex items-center justify-center w-9 h-9 rounded-full transition-all duration-300 ${\n isSolidMode \n ? 'bg-neutral-100 text-neutral-600 hover:text-primary hover:bg-neutral-200' \n : 'bg-white/10 text-white backdrop-blur-md border border-white/20 hover:bg-white/20'\n }`}\n aria-label=\"E-Mail\"\n >\n <svg className=\"w-[16px] h-[16px]\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.2} d=\"M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z\" />\n </svg>\n </a>\n <a \n href=\"https://maps.google.com/?q=E-TIB+GmbH,+Greven\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className={`relative flex items-center justify-center w-9 h-9 rounded-full transition-all duration-300 ${\n isSolidMode \n ? 'bg-neutral-100 text-neutral-600 hover:text-primary hover:bg-neutral-200' \n : 'bg-white/10 text-white backdrop-blur-md border border-white/20 hover:bg-white/20'\n }`}\n aria-label=\"Route\"\n >\n <svg className=\"w-[16px] h-[16px]\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.2} d=\"M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.2} d=\"M15 11a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n </a>\n </div>\n\n {/* Language Switcher visible on desktop only */}\n <div className=\"z-50 relative hidden md:flex items-center\">\n <LanguageSwitcher isSolidMode={isSolidMode} />\n </div>\n \n {/* Contact Button Desktop Only */}\n <TransitionLink\n href={`/${currentLocale}/${currentLocale === 'de' ? 'kontakt' : 'contact'}`}\n className={`relative hidden md:inline-flex ml-2 px-7 py-2.5 rounded-full font-bold text-xs lg:text-sm uppercase tracking-widest transition-all duration-500 overflow-hidden group/cta isolate select-none ${\n isSolidMode\n ? 'bg-primary text-white shadow-lg shadow-primary/20 hover:shadow-primary/40 hover:-translate-y-0.5 border border-transparent'\n : 'bg-white/10 text-white backdrop-blur-md border border-white/30 hover:bg-white/20 hover:-translate-y-0.5 hover:shadow-[0_0_20px_rgba(255,255,255,0.2)]'\n }`}\n >\n <span className=\"relative z-10 transition-transform duration-300 group-hover/cta:scale-105 inline-block\">{currentLocale === 'de' ? 'Kontakt' : 'Contact'}</span>\n {/* Sleek shine sweep effect */}\n <span className=\"absolute top-0 -left-[150%] h-[200%] w-[150%] bg-gradient-to-r from-transparent via-white/30 to-transparent skew-x-[-20deg] group-hover/cta:left-[100%] transition-all duration-1000 ease-[cubic-bezier(0.16,1,0.3,1)] z-30 pointer-events-none\" />\n </TransitionLink>\n\n </div>\n </div>\n </div>\n </header>\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 <div \n className=\"relative group py-2\"\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n <TransitionLink \n href={mappedUrl} \n className={`relative font-bold text-xs lg:text-sm uppercase tracking-widest transition-colors duration-300 group flex items-center gap-1.5 select-none ${\n isSolidMode \n ? (isActive ? 'text-primary' : 'text-text-primary hover:text-primary')\n : (isActive ? 'text-white' : 'text-white/80 hover:text-white')\n }`}\n >\n {link.label}\n {link.children && (\n <svg className={`w-3.5 h-3.5 transition-transform duration-300 ${isHovered ? 'rotate-180' : ''}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.5} d=\"M19 9l-7 7-7-7\" />\n </svg>\n )}\n \n {/* Active Indicator */}\n {isActive && !link.children && (\n <div \n className={`absolute -bottom-1 left-0 right-0 h-0.5 rounded-full ${isSolidMode ? 'bg-primary' : 'bg-white'}`}\n />\n )}\n \n {/* Subtle hover underline */}\n <span className={`absolute -bottom-1 left-1/2 w-0 h-0.5 -translate-x-1/2 rounded-full transition-all duration-300 ease-out group-hover:w-full ${isSolidMode ? 'bg-primary/30' : 'bg-white/30'} ${isActive ? 'opacity-0' : 'opacity-100'}`} />\n </TransitionLink>\n\n <>\n {link.children && (\n <div\n className={`absolute top-full left-1/2 -translate-x-1/2 pt-6 w-64 z-50 transition-all duration-300 origin-top ${\n isHovered ? 'opacity-100 scale-100 translate-y-0 visible' : 'opacity-0 scale-95 translate-y-2 invisible'\n }`}\n style={{ perspective: 1000 }}\n >\n <div className={`p-2.5 rounded-3xl shadow-[0_30px_60px_-15px_rgba(0,0,0,0.3)] border ${isSolidMode ? 'bg-white/95 border-neutral-200/60' : 'bg-[#050B14]/95 border-white/[0.08] shadow-[0_0_40px_rgba(0,0,0,0.4)]'} backdrop-blur-2xl flex flex-col gap-1 relative overflow-hidden`}>\n {/* Top glowing accent line */}\n <div className=\"absolute top-0 left-0 w-full h-[2px] bg-gradient-to-r from-transparent via-primary to-transparent opacity-70\" />\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 <div key={`group-${idx}`} className={`mt-2 mb-1 px-4 text-[11px] font-bold uppercase tracking-wider ${isSolidMode ? 'text-neutral-400' : 'text-neutral-500'}`}>\n {child.label}\n </div>\n );\n }\n\n return (\n <TransitionLink\n key={child.url}\n href={childUrl}\n onClick={() => 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 <div className=\"relative z-10 flex items-center justify-between\">\n <span className=\"relative\">\n {child.label}\n <span className={`absolute -bottom-0.5 left-0 w-0 h-0.5 rounded-full transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] group-hover/dropitem:w-full ${isSolidMode ? 'bg-primary/40' : 'bg-primary/60'} ${isChildActive ? 'opacity-0' : 'opacity-100'}`} />\n </span>\n <svg className={`w-4 h-4 transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)] -translate-x-3 opacity-0 group-hover/dropitem:translate-x-0 group-hover/dropitem:opacity-100 ${isSolidMode ? 'text-primary' : 'text-white'}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.5} d=\"M9 5l7 7-7 7\" />\n </svg>\n </div>\n </TransitionLink>\n );\n })}\n </div>\n </div>\n )}\n </>\n </div>\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: <Home className=\"w-[22px] h-[22px] md:w-6 md:h-6\" strokeWidth={2.2} />,\n exactMatch: true\n },\n {\n id: 'kompetenzen',\n label: navLinks[0]?.label || (currentLocale === 'de' ? 'Kompetenzen' : 'Competencies'),\n url: competenciesUrl,\n icon: <Layers className=\"w-[22px] h-[22px] md:w-6 md:h-6\" strokeWidth={2.2} />\n },\n {\n id: 'referenzen',\n label: navLinks[2]?.label || (currentLocale === 'de' ? 'Referenzen' : 'References'),\n url: referencesUrl,\n icon: <Star className=\"w-[22px] h-[22px] md:w-6 md:h-6\" strokeWidth={2.2} />\n },\n {\n id: 'kontakt',\n label: currentLocale === 'de' ? 'Kontakt' : 'Contact',\n url: contactUrl,\n icon: <Mail className=\"w-[22px] h-[22px] md:w-6 md:h-6\" strokeWidth={2.2} />\n },\n {\n id: 'menu',\n label: currentLocale === 'de' ? 'Mehr' : 'Menu',\n url: '#',\n icon: <Menu className=\"w-[24px] h-[24px] md:w-6 md:h-6\" strokeWidth={2.5} />,\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 <div className=\"md:hidden fixed bottom-0 left-0 right-0 z-[9999] px-2 pb-[calc(env(safe-area-inset-bottom)_+_12px)] pt-4 pointer-events-none flex justify-center\">\n <nav className=\"flex justify-between items-center bg-white/95 backdrop-blur-2xl border border-white/60 shadow-[0_8px_32px_rgba(0,0,0,0.12)] rounded-3xl p-1.5 pointer-events-auto w-full max-w-[480px]\">\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 <m.div\n layoutId=\"mobile-nav-active-bg\"\n className=\"absolute inset-0 bg-primary/10 rounded-2xl shadow-sm border border-primary/10\"\n transition={{ type: 'spring', stiffness: 400, damping: 25 }}\n />\n )}\n \n <div className=\"relative flex flex-col items-center justify-center z-10 w-full h-full\">\n <m.div \n whileTap={{ scale: 0.8 }}\n animate={{ y: isActive ? -2 : 0 }}\n transition={{ type: 'spring', stiffness: 400, damping: 17 }}\n >\n {item.icon}\n </m.div>\n <m.span \n animate={{ y: isActive ? 0 : 2 }}\n className=\"text-[10px] font-bold mt-1 tracking-wide\"\n >\n {item.label}\n </m.span>\n </div>\n </>\n );\n\n if (item.isFlyoutTrigger) {\n return (\n <button \n key={item.label}\n onClick={() => 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 </button>\n );\n }\n\n return (\n <TransitionLink \n key={item.label}\n href={mappedUrl}\n onClick={() => 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 </TransitionLink>\n );\n })}\n </nav>\n </div>\n\n <AnimatePresence>\n {isFlyoutOpen && (\n <>\n <m.div \n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.3 }}\n className=\"fixed inset-0 bg-black/40 backdrop-blur-sm z-[9997] md:hidden\"\n onClick={() => setIsFlyoutOpen(false)}\n />\n <m.div\n initial={{ y: '100%', opacity: 0 }}\n animate={{ y: 0, opacity: 1 }}\n exit={{ y: '100%', opacity: 0 }}\n transition={{ type: 'spring', stiffness: 350, damping: 30 }}\n drag=\"y\"\n dragConstraints={{ top: 0, bottom: 0 }}\n dragElastic={0.2}\n onDragEnd={(e, info) => {\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 <div className=\"w-16 h-1.5 bg-neutral-200 rounded-full mx-auto mb-6 cursor-grab active:cursor-grabbing\" />\n <h3 className=\"text-xs font-bold uppercase tracking-widest text-neutral-400 mb-4 px-2\">{currentLocale === 'de' ? 'Über uns & Mehr' : 'About us & More'}</h3>\n <div className=\"flex flex-col gap-1\">\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 <TransitionLink\n key={i}\n href={childUrl}\n onClick={() => 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 <svg className={`w-4 h-4 ${isChildActive ? 'text-primary' : 'text-neutral-400'}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2.5} d=\"M9 5l7 7-7 7\" />\n </svg>\n </TransitionLink>\n );\n })}\n </div>\n </m.div>\n </>\n )}\n </AnimatePresence>\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 <AnimatePresence>\n {isLoading && (\n <m.div\n key=\"initial-loader\"\n initial={{ y: 0 }}\n exit={{ y: '-100%' }}\n transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1], delay: 0.2 }}\n className=\"fixed inset-0 z-[9999] bg-[#050B14] flex flex-col items-center justify-center overflow-hidden\"\n >\n {/* Subtle grid background */}\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:32px_32px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)]\" />\n\n {/* Deep Glow */}\n <div className=\"absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/10 via-transparent to-transparent opacity-80 mix-blend-screen\" />\n \n <m.div \n initial={{ opacity: 0, scale: 0.9 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 1.2, ease: [0.16, 1, 0.3, 1] }}\n className=\"relative z-10 flex flex-col items-center w-full px-4\"\n >\n {/* Single Masked Logo Container to prevent double-logo offset */}\n <div \n className=\"relative w-64 h-20 sm:w-80 sm:h-24 mb-12 filter drop-shadow-[0_0_15px_rgba(255,255,255,0.15)]\"\n style={{\n WebkitMaskImage: 'url(/assets/logo-white.png)',\n WebkitMaskSize: 'contain',\n WebkitMaskRepeat: 'no-repeat',\n WebkitMaskPosition: 'center',\n maskImage: 'url(/assets/logo-white.png)',\n maskSize: 'contain',\n maskRepeat: 'no-repeat',\n maskPosition: 'center'\n }}\n >\n {/* Base logo color (slightly dimmed to make the sweep visible) */}\n <div className=\"absolute inset-0 bg-white/70\" />\n \n {/* Sweeping intense white light */}\n <m.div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent skew-x-[-25deg]\"\n initial={{ left: '-150%' }}\n animate={{ left: '250%' }}\n transition={{ duration: 2.5, ease: \"easeInOut\", repeat: Infinity, repeatDelay: 0.5 }}\n />\n </div>\n \n {/* Elegant Progress Bar */}\n <div className=\"flex flex-col items-center w-full max-w-xs\">\n <div className=\"flex justify-between w-full mb-3 text-[10px] sm:text-xs font-mono text-white/40 tracking-[0.2em] uppercase\">\n <span>System</span>\n <span className=\"text-primary/80 animate-pulse\">Online</span>\n </div>\n <m.div \n initial={{ opacity: 0, y: 10 }}\n animate={{ opacity: 1, y: 0 }}\n transition={{ delay: 0.3 }}\n className=\"h-[2px] w-full bg-white/5 overflow-hidden relative rounded-full\"\n >\n <m.div \n className=\"absolute inset-y-0 left-0 bg-gradient-to-r from-primary-dark via-primary to-primary-light\"\n initial={{ width: \"0%\" }}\n animate={{ width: \"100%\" }}\n transition={{ duration: 3.8, ease: [0.16, 1, 0.3, 1] }}\n />\n </m.div>\n </div>\n </m.div>\n </m.div>\n )}\n </AnimatePresence>\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 <AnimatePresence>\n {isTransitioning && (\n <m.div\n key=\"shutter\"\n initial={{ y: '-100%' }}\n animate={{ y: '0%' }}\n exit={{ y: '100%' }}\n transition={{ duration: 0.9, ease: [0.16, 1, 0.3, 1] }}\n className=\"fixed left-0 w-full z-[9998] bg-[#050B14] pointer-events-none flex flex-col items-center justify-center overflow-visible\"\n style={{\n height: '100vh',\n top: '0',\n }}\n >\n {/* Subtle grid background */}\n <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:32px_32px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)]\" />\n\n {/* Deep Glow inside the Shutter */}\n <div className=\"absolute inset-0 bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-primary/10 via-transparent to-transparent opacity-80 mix-blend-screen\" />\n \n <m.div \n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 1.05 }}\n transition={{ duration: 0.4, delay: 0.2 }}\n className=\"relative z-10 flex flex-col items-center w-full px-4\"\n >\n {/* Single Masked Logo Container to prevent double-logo offset */}\n <div \n className=\"relative w-48 h-16 sm:w-64 sm:h-20 filter drop-shadow-[0_0_15px_rgba(255,255,255,0.15)]\"\n style={{\n WebkitMaskImage: 'url(/assets/logo-white.png)',\n WebkitMaskSize: 'contain',\n WebkitMaskRepeat: 'no-repeat',\n WebkitMaskPosition: 'center',\n maskImage: 'url(/assets/logo-white.png)',\n maskSize: 'contain',\n maskRepeat: 'no-repeat',\n maskPosition: 'center'\n }}\n >\n {/* Base logo color (slightly dimmed to make the sweep visible) */}\n <div className=\"absolute inset-0 bg-white/70\" />\n \n {/* Sweeping intense white light */}\n <m.div\n className=\"absolute inset-0 bg-gradient-to-r from-transparent via-white to-transparent skew-x-[-25deg]\"\n initial={{ left: '-150%' }}\n animate={{ left: '250%' }}\n transition={{ duration: 1.5, ease: \"easeInOut\", repeat: Infinity, repeatDelay: 0.2 }}\n />\n </div>\n \n {/* Transition Message or Graphical Visualizer */}\n <AnimatePresence>\n {transitionMessage && (\n transitionMessage.startsWith('LANG_SWITCH:') ? (\n (() => {\n const [, fromLang, toLang] = transitionMessage.split(':');\n return (\n <m.div\n key=\"lang-switch-graphic\"\n initial={{ opacity: 0, scale: 0.8 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 1.2 }}\n className=\"mt-12 mb-4 flex items-center gap-6 sm:gap-10 drop-shadow-[0_0_30px_rgba(255,255,255,0.1)]\"\n >\n {/* Old Language Flag - Fading out */}\n <div className=\"w-20 h-20 sm:w-28 sm:h-28 rounded-[2rem] bg-white/5 border border-white/10 backdrop-blur-md flex items-center justify-center text-5xl sm:text-7xl relative overflow-hidden group grayscale opacity-50\">\n <m.div \n className=\"absolute inset-0 bg-white/10\"\n animate={{ scale: [1, 2], opacity: [0.5, 0] }}\n transition={{ duration: 1.5, repeat: Infinity }}\n />\n <span className=\"relative z-10 leading-none\">{fromLang}</span>\n </div>\n \n {/* Animated Arrow connecting them */}\n <div className=\"flex flex-col items-center gap-3\">\n <m.div \n animate={{ x: [0, 20, 0] }} \n transition={{ duration: 1.2, repeat: Infinity, ease: \"easeInOut\" }}\n className=\"text-primary drop-shadow-[0_0_15px_rgba(var(--color-primary),0.8)]\"\n >\n <svg width=\"40\" height=\"40\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\"></line>\n <polyline points=\"12 5 19 12 12 19\"></polyline>\n </svg>\n </m.div>\n <div className=\"flex gap-2 opacity-50\">\n <span className=\"w-2 h-2 rounded-full bg-primary animate-pulse shadow-[0_0_8px_rgba(var(--color-primary),1)]\" />\n <span className=\"w-2 h-2 rounded-full bg-primary animate-pulse shadow-[0_0_8px_rgba(var(--color-primary),1)]\" style={{ animationDelay: '150ms' }} />\n <span className=\"w-2 h-2 rounded-full bg-primary animate-pulse shadow-[0_0_8px_rgba(var(--color-primary),1)]\" style={{ animationDelay: '300ms' }} />\n </div>\n </div>\n\n {/* New Language Flag - Intense Glow */}\n <div className=\"w-24 h-24 sm:w-32 sm:h-32 rounded-[2.5rem] bg-primary/10 border border-primary/50 shadow-[0_0_80px_rgba(var(--color-primary),0.5)] backdrop-blur-xl flex items-center justify-center text-6xl sm:text-8xl relative overflow-hidden ring-4 ring-primary/30\">\n <span className=\"relative z-10 drop-shadow-[0_0_15px_rgba(255,255,255,0.6)] leading-none\">{toLang}</span>\n <m.div \n className=\"absolute inset-0 w-[200%] h-full bg-gradient-to-r from-transparent via-white/30 to-transparent skew-x-[-20deg]\"\n animate={{ x: ['-100%', '100%'] }}\n transition={{ duration: 1.5, repeat: Infinity, ease: \"linear\" }}\n />\n </div>\n </m.div>\n );\n })()\n ) : (\n <m.div\n key=\"text-message\"\n initial={{ opacity: 0, y: 15, scale: 0.9 }}\n animate={{ opacity: 1, y: 0, scale: 1 }}\n exit={{ opacity: 0, y: -15, scale: 0.9 }}\n transition={{ type: \"spring\", stiffness: 300, damping: 25 }}\n className=\"mt-10 mb-2 px-6 py-2.5 rounded-full border border-primary/40 bg-primary/10 shadow-[0_0_40px_rgba(var(--color-primary),0.3)] backdrop-blur-xl relative overflow-hidden flex items-center justify-center\"\n >\n <m.div \n className=\"absolute inset-0 w-[200%] h-full bg-gradient-to-r from-transparent via-white/20 to-transparent skew-x-[-20deg]\"\n animate={{ x: ['-100%', '100%'] }}\n transition={{ duration: 1.5, repeat: Infinity, ease: \"linear\" }}\n />\n <span className=\"text-white font-heading tracking-[0.2em] uppercase text-xs sm:text-sm font-extrabold flex items-center gap-3 relative z-10 drop-shadow-[0_0_8px_rgba(255,255,255,0.5)]\">\n <m.svg \n animate={{ rotate: 360 }}\n transition={{ duration: 8, repeat: Infinity, ease: \"linear\" }}\n xmlns=\"http://www.w3.org/2000/svg\" className=\"w-5 h-5 text-primary-light\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <line x1=\"2\" y1=\"12\" x2=\"22\" y2=\"12\"></line>\n <path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\"></path>\n </m.svg>\n {transitionMessage}\n </span>\n </m.div>\n )\n )}\n </AnimatePresence>\n </m.div>\n </m.div>\n )}\n </AnimatePresence>\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<HTMLCanvasElement>(null);\n const wrapRef = useRef<HTMLDivElement>(null);\n const animRef = useRef<number>(0);\n const particlesRef = useRef<Particle[]>([]);\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 <div\n ref={wrapRef}\n className=\"w-full h-full relative overflow-visible\"\n onPointerMove={onMove}\n onPointerEnter={onEnter}\n onPointerLeave={onLeave}\n style={{ cursor: 'pointer' }}\n >\n <canvas ref={canvasRef} className=\"w-full h-full block\" style={{ imageRendering: 'auto' }} />\n </div>\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<Message[]>([]);\n const [honeypot, setHoneypot] = useState('');\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [copiedIndex, setCopiedIndex] = useState<number | null>(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<HTMLInputElement>(null);\n const modalRef = useRef<HTMLDivElement>(null);\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const loadingIntervalRef = useRef<ReturnType<typeof setInterval> | 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<HTMLInputElement>) => {\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 <div\n className=\"fixed inset-0 z-[100] flex items-start justify-center pt-6 md:pt-12 px-4\"\n onClick={handleBackdropClick}\n role=\"dialog\"\n aria-modal=\"true\"\n style={{ animation: 'chatBackdropIn 0.4s ease-out forwards' }}\n >\n {/* Animated backdrop */}\n <div\n className=\"absolute inset-0 bg-[#000a18]/90 backdrop-blur-2xl\"\n style={{ animation: 'chatFadeIn 0.3s ease-out' }}\n />\n\n <div\n ref={modalRef}\n className=\"relative w-full max-w-3xl flex flex-col\"\n style={{\n height: 'min(90vh, 900px)',\n animation: 'chatSlideUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards',\n }}\n >\n {/* ── Glassmorphism container ── */}\n <div className=\"flex flex-col h-full rounded-3xl overflow-hidden border border-white/[0.08] bg-gradient-to-b from-white/[0.06] to-white/[0.02] shadow-[0_32px_64px_-12px_rgba(0,0,0,0.6)]\">\n {/* ── Header ── */}\n <div className=\"flex items-center justify-between px-5 py-4 border-b border-white/[0.06]\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 overflow-hidden rounded-full\">\n <AIOrb isThinking={isLoading} hasError={!!error} />\n </div>\n <div>\n <h2 className=\"text-white font-bold text-sm tracking-wide\">Ohm</h2>\n <p className=\"text-[10px] text-white/30 font-medium tracking-wider uppercase\">\n {isLoading ? t('thinking') : error ? t('errorStatus') : t('online')}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {messages.length > 0 && (\n <button\n onClick={handleCopyChat}\n className=\"flex items-center gap-1.5 text-[10px] font-bold text-white/40 hover:text-white/80 transition-all duration-200 hover:bg-white/5 rounded-full px-3 py-1.5 cursor-pointer uppercase tracking-wider\"\n title={t('copyChatTitle')}\n >\n {copiedAll ? (\n <Check className=\"w-3.5 h-3.5 text-accent\" />\n ) : (\n <Copy className=\"w-3.5 h-3.5\" />\n )}\n <span>{copiedAll ? t('copied') : t('copyChat')}</span>\n </button>\n )}\n <button\n onClick={onClose}\n className=\"text-white/30 hover:text-white/80 transition-all duration-200 hover:bg-white/5 rounded-xl p-2 cursor-pointer\"\n aria-label=\"Close\"\n >\n <X className=\"w-5 h-5\" />\n </button>\n </div>\n </div>\n\n {/* ── Chat Area ── */}\n <div className=\"flex-1 overflow-y-auto px-5 py-6 space-y-5 scroll-smooth chat-scrollbar\">\n {/* Empty state */}\n {messages.length === 0 && !isLoading && !error && (\n <div\n className=\"flex flex-col items-center justify-center h-full text-center space-y-5\"\n style={{ animation: 'chatFadeIn 0.6s ease-out 0.3s both' }}\n >\n <div className=\"w-24 h-24 mb-2\">\n <AIOrb isThinking={false} hasError={false} />\n </div>\n <div>\n <p className=\"text-xl md:text-2xl font-bold text-white/80\">\n {t('howCanIHelp')}\n </p>\n <p className=\"text-sm text-white/30 mt-2 max-w-md\">\n {t('helpDescription')}\n </p>\n </div>\n {/* Quick prompts */}\n <div className=\"flex flex-wrap justify-center gap-2 mt-4\">\n {prompts.map(\n (prompt) => (\n <button\n key={prompt}\n onClick={() => 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 </button>\n ),\n )}\n </div>\n </div>\n )}\n\n {/* Messages */}\n {messages.map((msg, index) => (\n <div\n key={index}\n className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}\n style={{\n animation: `chatMessageIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) ${index * 0.05}s both`,\n }}\n >\n <div\n className={`relative group max-w-[85%] rounded-2xl px-5 py-4 ${\n msg.role === 'user'\n ? 'bg-accent text-primary font-semibold rounded-br-lg'\n : 'bg-white/[0.05] border border-white/[0.06] text-white/90 rounded-bl-lg'\n }`}\n >\n {/* Copy Button */}\n <button\n onClick={() => 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 <Check className=\"w-3.5 h-3.5\" />\n ) : (\n <Copy className=\"w-3.5 h-3.5\" />\n )}\n </button>\n\n {msg.role === 'assistant' && (\n <div className=\"flex items-center gap-2 mb-2\">\n <Sparkles className=\"w-3 h-3 text-accent/60\" />\n <span className=\"text-[10px] font-bold tracking-widest uppercase text-accent/50\">\n Ohm\n </span>\n </div>\n )}\n <div\n className={`text-sm md:text-[15px] leading-relaxed ${\n msg.role === 'assistant'\n ? 'prose prose-invert prose-sm prose-p:leading-relaxed prose-a:text-accent prose-strong:text-accent/90 prose-ul:list-disc prose-ol:list-decimal'\n : ''\n }`}\n >\n {msg.role === 'assistant' ? (\n <ReactMarkdown remarkPlugins={[remarkGfm]}>{msg.content}</ReactMarkdown>\n ) : (\n <p className=\"whitespace-pre-wrap\">{msg.content}</p>\n )}\n </div>\n\n {/* Timestamp */}\n {!msg.products?.length && (\n <p\n className={`text-[9px] mt-2 font-medium tracking-wide ${msg.role === 'user' ? 'text-primary/40' : 'text-white/20'}`}\n >\n {new Date(msg.timestamp).toLocaleTimeString('de', {\n hour: '2-digit',\n minute: '2-digit',\n })}\n </p>\n )}\n\n {/* Product cards */}\n {msg.role === 'assistant' && msg.products && msg.products.length > 0 && (\n <div className=\"mt-4 space-y-2 border-t border-white/[0.06] pt-4\">\n <h4 className=\"text-[10px] font-bold tracking-widest uppercase text-white/30 mb-2\">\n {t('recommendedProducts')}\n </h4>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {msg.products.map((product, idx) => (\n <Link\n key={idx}\n href={`/produkte/${product.slug}`}\n onClick={() => {\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 <div className=\"min-w-0\">\n <p className=\"text-[9px] font-bold text-white/25 tracking-wider\">\n {product.sku}\n </p>\n <h5 className=\"text-xs font-bold text-white/70 group-hover:text-accent truncate transition-colors\">\n {product.title}\n </h5>\n </div>\n <ChevronRight className=\"w-3.5 h-3.5 text-white/20 group-hover:text-accent shrink-0 ml-3 group-hover:translate-x-0.5 transition-all\" />\n </Link>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n ))}\n\n {/* Loading indicator */}\n {isLoading && (\n <div\n className=\"flex justify-start\"\n style={{ animation: 'chatMessageIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards' }}\n >\n <div className=\"flex items-center gap-4 bg-white/[0.03] border border-white/[0.06] rounded-2xl rounded-bl-lg px-5 py-4\">\n <div className=\"w-10 h-10 shrink-0\">\n <AIOrb isThinking={true} hasError={false} />\n </div>\n <div>\n <p\n className=\"text-sm text-white/50 font-medium\"\n style={{ animation: 'chatTextSwap 0.4s ease-out' }}\n key={loadingText}\n >\n {loadingText}\n </p>\n <div className=\"flex gap-1 mt-2\">\n {[0, 1, 2].map((i) => (\n <div\n key={i}\n className=\"w-1.5 h-1.5 rounded-full bg-accent/40\"\n style={{\n animation: 'chatDotBounce 1.2s ease-in-out infinite',\n animationDelay: `${i * 0.15}s`,\n }}\n />\n ))}\n </div>\n </div>\n </div>\n </div>\n )}\n\n {/* Error */}\n {error && (\n <div className=\"flex justify-start\" style={{ animation: 'chatShake 0.5s ease-out' }}>\n <div className=\"flex items-center gap-4 bg-red-500/[0.06] border border-red-500/20 rounded-2xl rounded-bl-lg px-5 py-4\">\n <div className=\"w-10 h-10 shrink-0\">\n <AIOrb isThinking={false} hasError={true} />\n </div>\n <div>\n <h3 className=\"text-sm font-bold text-red-300\">{t('errorTitle')}</h3>\n <p className=\"text-xs text-red-300/60 mt-1\">{error}</p>\n <button\n onClick={() => {\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 <RotateCcw className=\"w-3 h-3\" />\n {t('tryAgain')}\n </button>\n </div>\n </div>\n </div>\n )}\n\n <div ref={messagesEndRef} />\n </div>\n\n {/* ── Input Area ── */}\n <div className=\"px-5 pb-5 pt-3 border-t border-white/[0.04]\">\n <div\n className={`relative flex items-center rounded-2xl transition-all duration-300 ${\n query.trim()\n ? 'bg-white/[0.08] border border-accent/30 shadow-[0_0_20px_-4px_rgba(130,237,32,0.1)]'\n : 'bg-white/[0.04] border border-white/[0.06]'\n }`}\n >\n <input\n ref={inputRef}\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={onKeyDown}\n placeholder={t('placeholder')}\n className=\"flex-1 bg-transparent border-none text-white text-sm md:text-base px-5 py-4 focus:outline-none placeholder:text-white/20\"\n disabled={isLoading}\n tabIndex={1}\n autoFocus\n />\n <input\n type=\"text\"\n className=\"hidden\"\n value={honeypot}\n onChange={(e) => setHoneypot(e.target.value)}\n tabIndex={-1}\n autoComplete=\"off\"\n aria-hidden=\"true\"\n />\n <button\n onClick={() => handleSearch()}\n disabled={!query.trim() || isLoading}\n className={`mr-2 w-9 h-9 rounded-xl flex items-center justify-center shrink-0 transition-all duration-300 cursor-pointer ${\n query.trim()\n ? 'bg-accent text-primary shadow-lg shadow-accent/20 hover:shadow-accent/40 hover:scale-105 active:scale-95'\n : 'bg-white/5 text-white/20'\n }`}\n aria-label={t('send')}\n >\n <ArrowUp className=\"w-4 h-4\" strokeWidth={2.5} />\n </button>\n </div>\n <div className=\"flex items-center justify-center gap-3 mt-2.5\">\n <span className=\"text-[9px] uppercase tracking-[0.15em] font-medium text-white/15\">\n {t('footerShortcuts')}\n </span>\n <span className=\"text-[9px] uppercase tracking-[0.15em] font-medium text-white/15\">\n ·\n </span>\n <span className=\"text-[9px] uppercase tracking-[0.15em] font-medium text-accent/40 flex items-center gap-1\">\n {t('footerPrivacy')}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n {/* ── Keyframe animations ── */}\n <style>{`\n @keyframes chatBackdropIn {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes chatFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n @keyframes chatSlideUp {\n from { opacity: 0; transform: translateY(40px) scale(0.96); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n @keyframes chatMessageIn {\n from { opacity: 0; transform: translateY(12px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes chatDotBounce {\n 0%, 80%, 100% { transform: scale(0.6); opacity: 0.3; }\n 40% { transform: scale(1); opacity: 1; }\n }\n @keyframes chatTextSwap {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes chatShake {\n 0%, 100% { transform: translateX(0); }\n 15% { transform: translateX(-6px); }\n 30% { transform: translateX(5px); }\n 45% { transform: translateX(-4px); }\n 60% { transform: translateX(3px); }\n 75% { transform: translateX(-1px); }\n }\n\n /* Custom scrollbar */\n .chat-scrollbar::-webkit-scrollbar {\n width: 4px;\n }\n .chat-scrollbar::-webkit-scrollbar-track {\n background: transparent;\n }\n .chat-scrollbar::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.08);\n border-radius: 4px;\n }\n .chat-scrollbar::-webkit-scrollbar-thumb:hover {\n background: rgba(255, 255, 255, 0.16);\n }\n .chat-scrollbar {\n scrollbar-width: thin;\n scrollbar-color: rgba(255, 255, 255, 0.08) transparent;\n }\n `}</style>\n </div>\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 <m.span ref={ref} className={className}>\n {prefix}<m.span>{rounded}</m.span>{suffix}\n </m.span>\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<PostData | null> {\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<PostData[]> {\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<Partial<PostData>[]> {\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.