Implementierung - SEO - 01.08.2025
This commit is contained in:
@@ -1,87 +1,26 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface FAQItem {
|
||||
question: string;
|
||||
answer: string;
|
||||
category?: string;
|
||||
}
|
||||
import { trackFAQInteraction } from '../services/gtm';
|
||||
|
||||
export function FAQSection() {
|
||||
const { t, i18n } = useTranslation();
|
||||
const [openItems, setOpenItems] = useState<number[]>([]);
|
||||
const { t } = useTranslation();
|
||||
const [openItem, setOpenItem] = useState<number | null>(null);
|
||||
|
||||
const faqs: FAQItem[] = i18n.language === 'de' ? [
|
||||
{
|
||||
question: "Wie kann ich Python für Prozessautomatisierung einsetzen?",
|
||||
answer: "Python eignet sich hervorragend für Prozessautomatisierung durch Libraries wie Selenium für Web-Automation, Pandas für Datenverarbeitung und Schedule für zeitgesteuerte Aufgaben. Ich entwickle maßgeschneiderte Automatisierungslösungen für Ihre Geschäftsprozesse.",
|
||||
category: "python"
|
||||
},
|
||||
{
|
||||
question: "Was macht OLLAMA ideal für lokale KI-Entwicklung?",
|
||||
answer: "OLLAMA ermöglicht das Ausführen großer Sprachmodelle lokal, was Datenschutz gewährleistet und API-Kosten reduziert. Ich spezialisiere mich auf die Integration von OLLAMA in Anwendungen für individuelle KI-Lösungen ohne Cloud-Abhängigkeiten.",
|
||||
category: "ai"
|
||||
},
|
||||
{
|
||||
question: "Welche Vorteile bieten Electron Desktop Apps?",
|
||||
answer: "Electron ermöglicht die Entwicklung plattformübergreifender Desktop-Anwendungen mit Web-Technologien. Sie erhalten eine native App für Windows, Mac und Linux aus einer einzigen Codebasis. Ideal für Tools mit komplexer UI und Offline-Funktionalität.",
|
||||
category: "electron"
|
||||
},
|
||||
{
|
||||
question: "Wie lange dauert die Entwicklung einer Web-Anwendung?",
|
||||
answer: "Die Entwicklungsdauer hängt vom Umfang ab. Ein MVP kann in 4-8 Wochen entwickelt werden, während komplexe Enterprise-Anwendungen 3-6 Monate benötigen. Ich arbeite agil und liefere in 2-Wochen-Sprints nutzbare Zwischenergebnisse.",
|
||||
category: "general"
|
||||
},
|
||||
{
|
||||
question: "Können Sie bestehende Python-Skripte in Web-Apps umwandeln?",
|
||||
answer: "Ja, ich kann Ihre Python-Skripte in moderne Web-Anwendungen mit FastAPI oder Django Backend und React Frontend transformieren. Dies ermöglicht Multi-User-Zugriff, bessere UI/UX und zentrale Datenverwaltung.",
|
||||
category: "python"
|
||||
},
|
||||
{
|
||||
question: "Wie integrieren Sie KI in bestehende Geschäftsprozesse?",
|
||||
answer: "Ich analysiere Ihre Workflows und identifiziere Automatisierungspotenziale. Mit OLLAMA oder anderen KI-Modellen entwickle ich Lösungen für Textanalyse, Dokumentenverarbeitung, Kundenservice-Automation oder Datenextraktion - alles datenschutzkonform on-premise.",
|
||||
category: "ai"
|
||||
}
|
||||
] : [
|
||||
{
|
||||
question: "How can I use Python for process automation?",
|
||||
answer: "Python excels at process automation through libraries like Selenium for web automation, Pandas for data processing, and Schedule for timed tasks. I develop custom automation solutions tailored to your business processes.",
|
||||
category: "python"
|
||||
},
|
||||
{
|
||||
question: "What makes OLLAMA ideal for local AI development?",
|
||||
answer: "OLLAMA enables running large language models locally, ensuring data privacy and reducing API costs. I specialize in integrating OLLAMA into applications for custom AI solutions without cloud dependencies.",
|
||||
category: "ai"
|
||||
},
|
||||
{
|
||||
question: "What are the advantages of Electron desktop apps?",
|
||||
answer: "Electron enables cross-platform desktop app development using web technologies. You get a native app for Windows, Mac, and Linux from a single codebase. Ideal for tools requiring complex UI and offline functionality.",
|
||||
category: "electron"
|
||||
},
|
||||
{
|
||||
question: "How long does web application development take?",
|
||||
answer: "Development time depends on scope. An MVP can be developed in 4-8 weeks, while complex enterprise applications require 3-6 months. I work agile and deliver usable results in 2-week sprints.",
|
||||
category: "general"
|
||||
},
|
||||
{
|
||||
question: "Can you convert existing Python scripts into web apps?",
|
||||
answer: "Yes, I can transform your Python scripts into modern web applications with FastAPI or Django backend and React frontend. This enables multi-user access, better UI/UX, and centralized data management.",
|
||||
category: "python"
|
||||
},
|
||||
{
|
||||
question: "How do you integrate AI into existing business processes?",
|
||||
answer: "I analyze your workflows and identify automation potential. Using OLLAMA or other AI models, I develop solutions for text analysis, document processing, customer service automation, or data extraction - all privacy-compliant on-premise.",
|
||||
category: "ai"
|
||||
}
|
||||
];
|
||||
const faqs = t('faq.questions', { returnObjects: true }) as Array<{
|
||||
question: string;
|
||||
answer: string;
|
||||
category?: string;
|
||||
}>;
|
||||
|
||||
const toggleItem = (index: number) => {
|
||||
setOpenItems(prev =>
|
||||
prev.includes(index)
|
||||
? prev.filter(i => i !== index)
|
||||
: [...prev, index]
|
||||
);
|
||||
const isOpening = openItem !== index;
|
||||
const question = faqs[index]?.question || '';
|
||||
|
||||
// Track FAQ interaction
|
||||
trackFAQInteraction(question, isOpening ? 'open' : 'close');
|
||||
|
||||
setOpenItem(prev => prev === index ? null : index);
|
||||
};
|
||||
|
||||
const faqSchema = {
|
||||
@@ -105,7 +44,7 @@ export function FAQSection() {
|
||||
/>
|
||||
|
||||
<h2 className="text-3xl font-bold text-white mb-8 text-center">
|
||||
{t('faq.title', 'Frequently Asked Questions')}
|
||||
{t('faq.title')}
|
||||
</h2>
|
||||
|
||||
<div className="space-y-4">
|
||||
@@ -117,13 +56,13 @@ export function FAQSection() {
|
||||
<button
|
||||
onClick={() => toggleItem(index)}
|
||||
className="w-full px-6 py-4 text-left flex items-center justify-between hover:bg-zinc-800/70 transition-colors"
|
||||
aria-expanded={openItems.includes(index)}
|
||||
aria-expanded={openItem === index}
|
||||
aria-controls={`faq-answer-${index}`}
|
||||
>
|
||||
<h3 className="text-lg font-medium text-white pr-4">
|
||||
{faq.question}
|
||||
</h3>
|
||||
{openItems.includes(index) ? (
|
||||
{openItem === index ? (
|
||||
<ChevronUp className="h-5 w-5 text-zinc-400 flex-shrink-0" />
|
||||
) : (
|
||||
<ChevronDown className="h-5 w-5 text-zinc-400 flex-shrink-0" />
|
||||
@@ -133,7 +72,7 @@ export function FAQSection() {
|
||||
<div
|
||||
id={`faq-answer-${index}`}
|
||||
className={`overflow-hidden transition-all duration-300 ${
|
||||
openItems.includes(index) ? 'max-h-96' : 'max-h-0'
|
||||
openItem === index ? 'max-h-96' : 'max-h-0'
|
||||
}`}
|
||||
>
|
||||
<div className="px-6 py-4 text-zinc-300 border-t border-zinc-700">
|
||||
@@ -146,12 +85,12 @@ export function FAQSection() {
|
||||
|
||||
<div className="mt-8 text-center">
|
||||
<p className="text-zinc-400">
|
||||
{t('faq.moreQuestions', 'Have more questions?')}{' '}
|
||||
{t('faq.moreQuestions')}{' '}
|
||||
<a
|
||||
href="/contact"
|
||||
className="text-white hover:text-zinc-300 underline transition-colors"
|
||||
>
|
||||
{t('faq.contactUs', 'Contact us')}
|
||||
{t('faq.contactUs')}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,7 @@ export function FloatingPaths({ position }: { position: number }) {
|
||||
}))
|
||||
|
||||
return (
|
||||
<div className="absolute inset-0 pointer-events-none floating-paths" style={{ isolation: 'isolate', zIndex: 0 }}>
|
||||
<div className="absolute inset-0 pointer-events-none floating-paths">
|
||||
<svg className="w-full h-full text-accent" viewBox="0 0 696 316" fill="none" style={{ transform: 'translateZ(0)' }}>
|
||||
<title>Background Paths</title>
|
||||
{paths.map((path) => (
|
||||
|
||||
@@ -8,7 +8,7 @@ const Footer = () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
|
||||
return (
|
||||
<footer className="relative bg-zinc-900 pt-8 pb-4 px-4">
|
||||
<footer className="relative bg-zinc-800/50 backdrop-blur-sm border-t border-zinc-700/30 pt-8 pb-4 px-4">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="flex flex-col space-y-8">
|
||||
{/* Contact Section */}
|
||||
@@ -93,7 +93,7 @@ const Footer = () => {
|
||||
</div>
|
||||
|
||||
{/* Legal Section */}
|
||||
<div className="border-t border-zinc-800 pt-4">
|
||||
<div className="border-t border-zinc-700/30 pt-4">
|
||||
<div className="flex flex-col space-y-4">
|
||||
<p className="text-zinc-400 text-xs text-center">
|
||||
© {currentYear} Damjan Savić. {t('footer.legal.copyright')}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FloatingPaths } from './FloatingPaths';
|
||||
|
||||
const GlobalBackground = () => {
|
||||
const [scrollPosition, setScrollPosition] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
setScrollPosition(window.scrollY * 0.001);
|
||||
};
|
||||
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => window.removeEventListener('scroll', handleScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Background layer - behind everything */}
|
||||
<div
|
||||
className="fixed inset-0 bg-zinc-900"
|
||||
style={{
|
||||
zIndex: -2,
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Gradient overlay */}
|
||||
<div
|
||||
className="fixed inset-0"
|
||||
style={{
|
||||
zIndex: -1,
|
||||
background: 'linear-gradient(135deg, #1e1e1e 0%, #2a2a2a 50%, #1e1e1e 100%)',
|
||||
opacity: 0.9
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* FloatingPaths layer */}
|
||||
<div
|
||||
className="fixed inset-0"
|
||||
style={{
|
||||
zIndex: -1,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<FloatingPaths position={scrollPosition} />
|
||||
</div>
|
||||
|
||||
{/* Test: Visible animated lines */}
|
||||
<div
|
||||
className="fixed inset-0 pointer-events-none"
|
||||
style={{ zIndex: -1 }}
|
||||
>
|
||||
<svg
|
||||
className="w-full h-full"
|
||||
preserveAspectRatio="none"
|
||||
style={{ opacity: 0.3 }}
|
||||
>
|
||||
<line
|
||||
x1="0%"
|
||||
y1="20%"
|
||||
x2="100%"
|
||||
y2="20%"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
opacity="0.2"
|
||||
>
|
||||
<animate
|
||||
attributeName="y1"
|
||||
values="20%;80%;20%"
|
||||
dur="10s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="y2"
|
||||
values="20%;80%;20%"
|
||||
dur="10s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
|
||||
<line
|
||||
x1="0%"
|
||||
y1="50%"
|
||||
x2="100%"
|
||||
y2="50%"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
opacity="0.15"
|
||||
>
|
||||
<animate
|
||||
attributeName="x1"
|
||||
values="0%;100%;0%"
|
||||
dur="15s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
|
||||
<line
|
||||
x1="20%"
|
||||
y1="0%"
|
||||
x2="20%"
|
||||
y2="100%"
|
||||
stroke="white"
|
||||
strokeWidth="1"
|
||||
opacity="0.1"
|
||||
>
|
||||
<animate
|
||||
attributeName="x1"
|
||||
values="20%;80%;20%"
|
||||
dur="12s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
<animate
|
||||
attributeName="x2"
|
||||
values="20%;80%;20%"
|
||||
dur="12s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</line>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Grid overlay */}
|
||||
<div
|
||||
className="fixed inset-0 pointer-events-none"
|
||||
style={{
|
||||
zIndex: -1,
|
||||
backgroundImage: `
|
||||
linear-gradient(rgba(255,255,255,.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,.05) 1px, transparent 1px)
|
||||
`,
|
||||
backgroundSize: '50px 50px',
|
||||
opacity: 0.5
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalBackground;
|
||||
@@ -20,6 +20,7 @@ import { Link, useLocation } from "react-router-dom";
|
||||
import Footer from "./Footer";
|
||||
import { useScrollContext } from "./ScrollContext";
|
||||
import { PerformanceMonitor } from "./PerformanceMonitor";
|
||||
import GlobalBackground from "./GlobalBackground";
|
||||
|
||||
interface LayoutProps {
|
||||
children: ReactNode;
|
||||
@@ -122,21 +123,25 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
// Render with opacity 0 on initial mount to prevent flicker
|
||||
if (!isMounted) {
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-900">
|
||||
<div className="min-h-screen bg-transparent">
|
||||
<GlobalBackground />
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-900 flex flex-col relative">
|
||||
<div className="min-h-screen flex flex-col relative">
|
||||
{/* Global Background */}
|
||||
<GlobalBackground />
|
||||
|
||||
{/* Navigation – oberste Ebene */}
|
||||
<nav
|
||||
className={`
|
||||
fixed w-full z-40 transition-all duration-300
|
||||
bg-zinc-800/50 backdrop-blur-sm border-b border-zinc-700/30
|
||||
${scrolled
|
||||
? "bg-zinc-900/80 backdrop-blur-md shadow-lg shadow-zinc-900/50"
|
||||
? "bg-zinc-800/70 backdrop-blur-md shadow-lg shadow-zinc-900/30"
|
||||
: ""
|
||||
}
|
||||
`}
|
||||
@@ -148,7 +153,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center space-x-2 group shrink-0">
|
||||
<motion.img
|
||||
src="/logo.png"
|
||||
src="/logo.svg"
|
||||
alt=""
|
||||
className="h-8 w-auto"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
@@ -206,7 +211,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
animate={{ x: 0 }}
|
||||
exit={{ x: "100%" }}
|
||||
transition={{ type: "spring", stiffness: 300, damping: 30 }}
|
||||
className="fixed right-0 top-0 h-full w-80 bg-zinc-900 shadow-xl md:hidden z-50 border-l border-zinc-800"
|
||||
className="fixed right-0 top-0 h-full w-80 bg-zinc-900/95 backdrop-blur-md shadow-xl md:hidden z-50 border-l border-zinc-800"
|
||||
>
|
||||
{/* Sidebar Header mit Close-Button */}
|
||||
<div className="flex items-center justify-between px-4 h-16 border-b border-zinc-800">
|
||||
@@ -258,7 +263,7 @@ const Layout = ({ children }: LayoutProps) => {
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Hauptinhalt */}
|
||||
<main className="flex-1 pt-2 relative z-30">{children}</main>
|
||||
<main className="flex-1 pt-2 relative z-10 bg-transparent">{children}</main>
|
||||
|
||||
{/* Footer */}
|
||||
<Footer />
|
||||
|
||||
@@ -28,7 +28,7 @@ const Logo = () => {
|
||||
>
|
||||
{!imageError ? (
|
||||
<img
|
||||
src="/logo.png"
|
||||
src="/logo.svg"
|
||||
alt="Logo"
|
||||
className="w-full h-full object-contain filter brightness-200"
|
||||
onError={() => setImageError(true)}
|
||||
@@ -123,7 +123,7 @@ const PageTransition = ({ children }: PageTransitionProps) => {
|
||||
{isTransitioning && (
|
||||
<motion.div
|
||||
key="transition-overlay"
|
||||
className="fixed inset-0 bg-zinc-900 flex items-center justify-center page-transition-active"
|
||||
className="fixed inset-0 bg-zinc-900/95 backdrop-blur-sm flex items-center justify-center page-transition-active"
|
||||
style={{ zIndex: 9999 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
|
||||
Reference in New Issue
Block a user