Initial Commit.
54
.gitignore
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Production
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# TypeScript
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
.vite/
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
.cache
|
||||||
|
.eslintcache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# System Files
|
||||||
|
Thumbs.db
|
||||||
28
eslint.config.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import reactHooks from 'eslint-plugin-react-hooks';
|
||||||
|
import reactRefresh from 'eslint-plugin-react-refresh';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
{ ignores: ['dist'] },
|
||||||
|
{
|
||||||
|
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||||
|
files: ['**/*.{ts,tsx}'],
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 2020,
|
||||||
|
globals: globals.browser,
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'react-hooks': reactHooks,
|
||||||
|
'react-refresh': reactRefresh,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...reactHooks.configs.recommended.rules,
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
18
index.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/logos/technologie-team-favicon.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="description" content="Das Technologie Team ist eine mittelständische Unternehmensgruppe, die sich auf den IT- und Technologiesektor spezialisiert hat." />
|
||||||
|
<meta name="keywords" content="IT, Technologie, Unternehmensgruppe, Deutschland, Oberhausen" />
|
||||||
|
<meta property="og:title" content="Technologie Team - IT-/Technologie-Unternehmensgruppe" />
|
||||||
|
<meta property="og:description" content="Das Technologie Team ist eine mittelständische Unternehmensgruppe, die sich auf den IT- und Technologiesektor spezialisiert hat." />
|
||||||
|
<meta property="og:image" content="/logos/technologie-team.png" />
|
||||||
|
<title>Technologie Team - IT-/Technologie-Unternehmensgruppe</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4501
package-lock.json
generated
Normal file
33
package.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "technologie-team-landing",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lucide-react": "^0.263.1",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^6.29.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.18",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||||
|
"@typescript-eslint/parser": "^6.21.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"eslint": "^8.57.1",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.2",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.19",
|
||||||
|
"postcss": "^8.5.2",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"vite": "^5.4.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
BIN
public/images/2gdigital.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
public/images/bocitsec.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/images/gis.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
public/images/hero.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
4
public/images/linkedin.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" width="60px" height="60px">
|
||||||
|
<path fill="#FFFFFF" d="M24,4H6C4.895,4,4,4.895,4,6v18c0,1.105,0.895,2,2,2h18c1.105,0,2-0.895,2-2V6C26,4.895,25.105,4,24,4z M10.954,22h-2.95 v-9.492h2.95V22z M9.449,11.151c-0.951,0-1.72-0.771-1.72-1.72c0-0.949,0.77-1.719,1.72-1.719c0.948,0,1.719,0.771,1.719,1.719 C11.168,10.38,10.397,11.151,9.449,11.151z M22.004,22h-2.948v-4.616c0-1.101-0.02-2.517-1.533-2.517 c-1.535,0-1.771,1.199-1.771,2.437V22h-2.948v-9.492h2.83v1.297h0.04c0.394-0.746,1.356-1.533,2.791-1.533 c2.987,0,3.539,1.966,3.539,4.522V22z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 625 B |
BIN
public/images/linqit.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
public/images/netrix.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
public/images/peter.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
BIN
public/images/rittec.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
public/images/ritterdigital.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
public/images/tech01.png
Normal file
|
After Width: | Height: | Size: 228 KiB |
BIN
public/images/tech02.png
Normal file
|
After Width: | Height: | Size: 269 KiB |
BIN
public/images/tech03.png
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
public/images/tech04.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
public/images/zentrale.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
7
public/logos/technologie-team-favicon.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect width="32" height="32" rx="4" fill="#C25B3F"/>
|
||||||
|
<path d="M8 8H24V12H8V8Z" fill="white"/>
|
||||||
|
<path d="M8 14H16V26H8V14Z" fill="white"/>
|
||||||
|
<path d="M18 14H24V18H18V14Z" fill="white"/>
|
||||||
|
<path d="M18 20H24V26H18V20Z" fill="white"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 339 B |
BIN
public/logos/tteamdunkel.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
public/logos/tteamhell.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
67
src/App.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
import { useEffect, useCallback } from 'react';
|
||||||
|
import Navbar from './components/Navbar';
|
||||||
|
import Hero from './components/Hero';
|
||||||
|
import Mission from './components/Mission';
|
||||||
|
import Technologies from './components/Technologies';
|
||||||
|
import GroupValue from './components/GroupValue';
|
||||||
|
import GroupCompanies from './components/GroupCompanies';
|
||||||
|
import ExtendedGroup from './components/ExtendedGroup';
|
||||||
|
import Footer from './components/Footer';
|
||||||
|
import ScrollProgress from './components/ScrollProgress';
|
||||||
|
import ScrollToTop from './components/ScrollToTop';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const initializeObserver = useCallback(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
entry.target.classList.add('animate-fade-in');
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{
|
||||||
|
threshold: 0.1,
|
||||||
|
rootMargin: '0px',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const sections = document.querySelectorAll('section');
|
||||||
|
sections.forEach((section) => {
|
||||||
|
observer.observe(section);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
sections.forEach((section) => observer.unobserve(section));
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanup = initializeObserver();
|
||||||
|
return cleanup;
|
||||||
|
}, [initializeObserver]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<div className="min-h-screen bg-white flex flex-col">
|
||||||
|
<ScrollProgress />
|
||||||
|
<Navbar />
|
||||||
|
<main className="flex-grow">
|
||||||
|
<Hero />
|
||||||
|
<Mission />
|
||||||
|
<Technologies />
|
||||||
|
<GroupValue />
|
||||||
|
<GroupCompanies />
|
||||||
|
<ExtendedGroup />
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
<ScrollToTop />
|
||||||
|
</div>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
48
src/components/ErrorBoundary.tsx
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { Component, ErrorInfo, ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
hasError: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends Component<Props, State> {
|
||||||
|
public state: State = {
|
||||||
|
hasError: false
|
||||||
|
};
|
||||||
|
|
||||||
|
public static getDerivedStateFromError(): State {
|
||||||
|
return { hasError: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||||
|
console.error('Uncaught error:', error, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
|
<div className="text-center p-8">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">
|
||||||
|
Etwas ist schiefgelaufen
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-600 mb-4">
|
||||||
|
Bitte laden Sie die Seite neu oder versuchen Sie es später erneut.
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => window.location.reload()}
|
||||||
|
className="bg-[#C25B3F] hover:bg-[#A34832] text-white px-6 py-2 rounded-md transition-colors"
|
||||||
|
>
|
||||||
|
Seite neu laden
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/components/ExtendedGroup.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export default function ExtendedGroup() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const section = document.getElementById('extended-group');
|
||||||
|
if (section) {
|
||||||
|
observer.observe(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (section) {
|
||||||
|
observer.unobserve(section);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const stats = [
|
||||||
|
{ text: 'seit 1984 am Markt', highlight: '1984' },
|
||||||
|
{ text: 'ca. 140 Unternehmen', highlight: '140' },
|
||||||
|
{ text: '6500 Mitarbeiter', highlight: '6500' }
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="extended-group" className="py-12 sm:py-16 lg:py-24 bg-white overflow-hidden">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className={`transition-all duration-700 ${
|
||||||
|
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'
|
||||||
|
}`}>
|
||||||
|
<h2 className="font-outfit text-3xl sm:text-4xl lg:text-5xl font-bold text-[#C25B3F]
|
||||||
|
mb-4 sm:mb-6 section-enter section-enter-active">
|
||||||
|
Die erweiterte Unternehmensgruppe
|
||||||
|
</h2>
|
||||||
|
<p className="font-outfit text-base sm:text-lg text-gray-600 mb-8 sm:mb-12 max-w-4xl
|
||||||
|
leading-relaxed section-enter section-enter-active delay-100">
|
||||||
|
Das Technologie Team und deren Beteiligungen werden selbstständig und unabhängig geführt.
|
||||||
|
Gleichzeitig können die Beteiligungen aber auf die Leistungen und Netzwerke der anderen
|
||||||
|
Gesellschafterunternehmen zurückgreifen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="relative rounded-2xl overflow-hidden shadow-lg group"
|
||||||
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
|
onMouseLeave={() => setIsHovered(false)}>
|
||||||
|
{/* Image Container */}
|
||||||
|
<div className="relative">
|
||||||
|
<img
|
||||||
|
src="/images/zentrale.png"
|
||||||
|
alt="Zentrale Oberhausen"
|
||||||
|
className="w-full aspect-[2/1] object-cover transition-transform duration-700
|
||||||
|
group-hover:scale-105 will-change-transform"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Dark Overlay */}
|
||||||
|
<div className={`absolute inset-0 bg-black/20 transition-opacity duration-500
|
||||||
|
${isHovered ? 'opacity-0' : 'opacity-100'}`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Info Section */}
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90
|
||||||
|
via-black/60 to-transparent p-4 sm:p-6 lg:p-8 transform transition-all
|
||||||
|
duration-500 translate-y-0 group-hover:translate-y-0">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 text-white">
|
||||||
|
{stats.map((stat, index) => (
|
||||||
|
<div key={index} className="flex flex-col items-center sm:items-start
|
||||||
|
transform transition-all duration-500
|
||||||
|
group-hover:translate-y-0 opacity-90
|
||||||
|
group-hover:opacity-100">
|
||||||
|
<p className="font-outfit text-base sm:text-lg text-center sm:text-left">
|
||||||
|
<span className="font-semibold text-orange-400">{stat.highlight}</span>
|
||||||
|
{' ' + stat.text.replace(stat.highlight, '')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Location Badge */}
|
||||||
|
<div className="absolute top-4 left-4 bg-white/90 backdrop-blur-sm px-4 py-2
|
||||||
|
rounded-full shadow-lg transform transition-all duration-300
|
||||||
|
group-hover:scale-105">
|
||||||
|
<p className="font-outfit text-sm font-medium text-gray-800">
|
||||||
|
Zentrale: Oberhausen
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
151
src/components/Footer.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
type FooterSection = {
|
||||||
|
title: string;
|
||||||
|
content: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
{ href: '#technologies', text: 'Unterstütze Technologien' },
|
||||||
|
{ href: '#group', text: 'Gruppenbeitritt' },
|
||||||
|
{ href: '#impressum', text: 'Impressum' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
const [hoveredLink, setHoveredLink] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const footer = document.querySelector('footer');
|
||||||
|
if (footer) {
|
||||||
|
observer.observe(footer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (footer) {
|
||||||
|
observer.unobserve(footer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const sections: FooterSection[] = [
|
||||||
|
{
|
||||||
|
title: 'Links',
|
||||||
|
content: (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{links.map((link) => (
|
||||||
|
<a
|
||||||
|
key={link.href}
|
||||||
|
href={link.href}
|
||||||
|
className="block transform transition-all duration-300 hover:text-white
|
||||||
|
hover:translate-x-1 focus:outline-none focus:text-white
|
||||||
|
group relative"
|
||||||
|
onMouseEnter={() => setHoveredLink(link.href)}
|
||||||
|
onMouseLeave={() => setHoveredLink(null)}
|
||||||
|
>
|
||||||
|
<span className="relative z-10 inline-block">{link.text}</span>
|
||||||
|
<span className={`absolute left-0 bottom-0 w-0 h-0.5 bg-[#C25B3F]
|
||||||
|
transition-all duration-300 group-hover:w-full
|
||||||
|
${hoveredLink === link.href ? 'w-full' : 'w-0'}`} />
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Ort',
|
||||||
|
content: (
|
||||||
|
<address className="not-italic space-y-2">
|
||||||
|
<p className="transition-colors duration-300 hover:text-white">
|
||||||
|
Essener Straße 2-24
|
||||||
|
</p>
|
||||||
|
<p className="transition-colors duration-300 hover:text-white">
|
||||||
|
46047 Oberhausen
|
||||||
|
</p>
|
||||||
|
</address>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Mailkontakt',
|
||||||
|
content: (
|
||||||
|
<div>
|
||||||
|
<a href="mailto:kontakt@technologie.team"
|
||||||
|
className="block mb-4 transition-all duration-300 hover:text-white
|
||||||
|
hover:translate-x-1 relative group">
|
||||||
|
<span>kontakt@technologie.team</span>
|
||||||
|
<span className="absolute left-0 bottom-0 w-0 h-0.5 bg-[#C25B3F]
|
||||||
|
transition-all duration-300 group-hover:w-full" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://linkedin.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-block transform transition-all duration-300
|
||||||
|
hover:scale-110 focus:outline-none focus:scale-110"
|
||||||
|
aria-label="Besuchen Sie uns auf LinkedIn"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="/images/linkedin.svg"
|
||||||
|
alt="LinkedIn"
|
||||||
|
className="h-8 w-8 sm:h-10 sm:w-10"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<footer className="bg-gray-900 text-gray-400 py-12 sm:py-16">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className={`flex flex-col space-y-12 sm:space-y-16
|
||||||
|
transition-all duration-700 transform
|
||||||
|
${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
||||||
|
{/* Logo */}
|
||||||
|
<Link to="/"
|
||||||
|
className="block transition-transform duration-300 hover:scale-105
|
||||||
|
focus:outline-none focus:scale-105">
|
||||||
|
<img
|
||||||
|
src="/logos/tteamhell.png"
|
||||||
|
alt="Technologie Team Logo"
|
||||||
|
className="h-12 sm:h-15"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Navigation Section */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8 sm:gap-12">
|
||||||
|
{sections.map((section, index) => (
|
||||||
|
<div key={section.title}
|
||||||
|
className={`transition-all duration-500 delay-${index * 100}
|
||||||
|
${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'}`}>
|
||||||
|
<h3 className="font-outfit text-white font-semibold mb-4 text-base sm:text-lg
|
||||||
|
transform transition-all duration-300 hover:text-[#C25B3F]">
|
||||||
|
{section.title}
|
||||||
|
</h3>
|
||||||
|
{section.content}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="border-t border-gray-800 mt-12 sm:mt-16 pt-6 sm:pt-8
|
||||||
|
text-center text-sm text-gray-500">
|
||||||
|
<p className="transition-colors duration-300 hover:text-gray-400">
|
||||||
|
Copyright © {new Date().getFullYear()} TechnologieTeam
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
127
src/components/GroupCompanies.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const companies = [
|
||||||
|
{
|
||||||
|
name: 'b.o.c. IT-SECURITY',
|
||||||
|
logo: '/images/bocitsec.png',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'GIS',
|
||||||
|
logo: '/images/gis.png',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RITTER TECHNOLOGIE',
|
||||||
|
logo: '/images/rittec.png',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'NETRIX',
|
||||||
|
logo: '/images/netrix.png',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RITTER digital',
|
||||||
|
logo: '/images/ritterdigital.png',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'LINQ-IT',
|
||||||
|
logo: '/images/linqit.png',
|
||||||
|
link: '#'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '2G.digital',
|
||||||
|
logo: '/images/2gdigital.png',
|
||||||
|
link: '#'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function GroupCompanies() {
|
||||||
|
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const section = document.getElementById('companies');
|
||||||
|
if (section) {
|
||||||
|
observer.observe(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (section) {
|
||||||
|
observer.unobserve(section);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="companies" className="py-12 sm:py-16 lg:py-24 bg-white overflow-hidden">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<h2 className={`font-outfit text-3xl sm:text-4xl lg:text-5xl font-bold text-[#C25B3F]
|
||||||
|
mb-8 sm:mb-12 lg:mb-16 transition-all duration-700
|
||||||
|
${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-8'}`}>
|
||||||
|
Unsere IT-/Technologie Gruppenunternehmen
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Angepasstes Grid für 2 Spalten auf Mobile */}
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-3 sm:gap-6 lg:gap-8">
|
||||||
|
{companies.map((company, index) => (
|
||||||
|
<a
|
||||||
|
key={index}
|
||||||
|
href={company.link}
|
||||||
|
className={`
|
||||||
|
relative bg-white rounded-xl p-4 sm:p-6 lg:p-8
|
||||||
|
transition-all duration-500 ease-out
|
||||||
|
hover:shadow-xl hover:-translate-y-1
|
||||||
|
flex items-center justify-center
|
||||||
|
aspect-[4/3] group
|
||||||
|
border border-gray-100
|
||||||
|
${hoveredIndex === index ? 'shadow-lg scale-[1.02]' : 'shadow-sm'}
|
||||||
|
${isVisible ? 'animate-fade-in' : 'opacity-0'}
|
||||||
|
animate-delay-${(index + 1) * 100}
|
||||||
|
`}
|
||||||
|
onMouseEnter={() => setHoveredIndex(index)}
|
||||||
|
onMouseLeave={() => setHoveredIndex(null)}
|
||||||
|
aria-label={`Besuchen Sie ${company.name}`}
|
||||||
|
>
|
||||||
|
{/* Logo */}
|
||||||
|
<img
|
||||||
|
src={company.logo}
|
||||||
|
alt={company.name}
|
||||||
|
className="w-full max-w-[85%] max-h-[65%] object-contain
|
||||||
|
transition-transform duration-500
|
||||||
|
group-hover:scale-110 will-change-transform"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Company Name Overlay - nur auf größeren Bildschirmen */}
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 p-2 sm:p-3 bg-gradient-to-t
|
||||||
|
from-gray-900/10 to-transparent opacity-0
|
||||||
|
group-hover:opacity-100 transition-opacity duration-300
|
||||||
|
hidden sm:block">
|
||||||
|
<p className="text-center text-gray-600 text-sm font-medium">
|
||||||
|
{company.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Hover Effect Border */}
|
||||||
|
<div className="absolute inset-0 rounded-xl border-2 border-[#C25B3F] opacity-0
|
||||||
|
scale-105 group-hover:opacity-100 group-hover:scale-100
|
||||||
|
transition-all duration-300" />
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
136
src/components/GroupValue.tsx
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Users2, Eye, BarChart3, Settings2, DollarSign, Search, Link2, TrendingUp, GraduationCap, Monitor } from 'lucide-react';
|
||||||
|
|
||||||
|
const valueItems = [
|
||||||
|
{
|
||||||
|
icon: Users2,
|
||||||
|
title: 'Management Team',
|
||||||
|
description: 'Ein erfahrenes, kompetentes Managementteam sorgt für strategische Unterstützung.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Eye,
|
||||||
|
title: 'Fokus auf Transparenz',
|
||||||
|
description: 'Transparenz in allen Prozessen, was Vertrauen und offene Kommunikation fördert.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: BarChart3,
|
||||||
|
title: 'Schlankes Reporting',
|
||||||
|
description: 'Ein effektives Berichtssystem ermöglicht es sich auf seine Aufgaben zu konzentrieren und agil zu reagieren.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Settings2,
|
||||||
|
title: 'Operative Hebel',
|
||||||
|
description: 'Gemeinsame Stärken nutzen und durchgängige Experten-Lösungen schaffen.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: DollarSign,
|
||||||
|
title: 'Finanzielle Hebel',
|
||||||
|
description: 'Finanzielle Stärken bündeln und zum Wohle der Gesamtlösung einsetzen.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Search,
|
||||||
|
title: 'Recruiting',
|
||||||
|
description: 'Unterstützung durch Personal-Experten'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Link2,
|
||||||
|
title: 'Gruppenzugehörigkeit',
|
||||||
|
description: 'Mehr aus den einzelnen Möglichkeiten rausholen und gemeinsam bessere Lösungen und Entwicklungen schaffen.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: TrendingUp,
|
||||||
|
title: 'Personal Perspektive',
|
||||||
|
description: 'Individuelle Fördermöglichkeiten in einem großen Ökosystem, mit agilen Entscheidungswegen.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: GraduationCap,
|
||||||
|
title: 'Ausbildung',
|
||||||
|
description: 'Kontinuierliche Schulung und Weiterentwicklung der Mitarbeiterkompetenzen.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: Monitor,
|
||||||
|
title: 'Digitalisierung',
|
||||||
|
description: 'Nutzung neuester technischer Möglichkeiten zur Verbesserung von Geschäftsprozessen.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function GroupValue() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const section = document.getElementById('group-value');
|
||||||
|
if (section) {
|
||||||
|
observer.observe(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (section) {
|
||||||
|
observer.unobserve(section);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="group-value" className="bg-[#C25B3F] py-12 sm:py-16 lg:py-24 overflow-hidden">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className={`mb-8 sm:mb-12 lg:mb-16 max-w-3xl mx-auto text-center
|
||||||
|
${isVisible ? 'animate-fade-in' : 'opacity-0'}`}>
|
||||||
|
<h2 className="font-outfit text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-4 sm:mb-8
|
||||||
|
section-enter section-enter-active">
|
||||||
|
Unser Gruppenmehrwert
|
||||||
|
</h2>
|
||||||
|
<p className="font-outfit text-base sm:text-lg text-white/90 leading-relaxed
|
||||||
|
section-enter section-enter-active delay-100">
|
||||||
|
IT-/Technologieunternehmen zeichnen sich durch ihre Kompetenz und die exzellente Umsetzung von
|
||||||
|
Kundenanforderungen aus. Unser Ziel ist es, diese Qualitäten zu erhalten, die Unternehmenskultur
|
||||||
|
zu wahren und Raum für weiteres Wachstum und Entwicklung zu schaffen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4 sm:gap-6 lg:gap-8">
|
||||||
|
{valueItems.map((item, index) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={`relative bg-white/10 backdrop-blur-sm rounded-2xl p-6 sm:p-8
|
||||||
|
transition-all duration-500 group hover:bg-white/20
|
||||||
|
hover:scale-102 hover:shadow-xl will-change-transform
|
||||||
|
${isVisible ? 'animate-fade-in' : 'opacity-0'}
|
||||||
|
animate-delay-${(index + 1) * 100}`}
|
||||||
|
>
|
||||||
|
<div className="mb-4 sm:mb-6 transform transition-transform duration-300
|
||||||
|
group-hover:scale-110 group-hover:rotate-3">
|
||||||
|
<Icon className="w-8 h-8 sm:w-10 sm:h-10 text-white"
|
||||||
|
strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<h3 className="font-outfit text-base sm:text-lg font-semibold text-white mb-2 sm:mb-3
|
||||||
|
transform transition-transform duration-300
|
||||||
|
group-hover:translate-x-1">
|
||||||
|
{item.title}
|
||||||
|
</h3>
|
||||||
|
<p className="font-outfit text-sm text-white/80 leading-relaxed
|
||||||
|
transition-opacity duration-300 group-hover:text-white">
|
||||||
|
{item.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Hover Effect Overlay */}
|
||||||
|
<div className="absolute inset-0 rounded-2xl bg-gradient-to-tr from-white/0 to-white/10
|
||||||
|
opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
87
src/components/Hero.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export default function Hero() {
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsLoaded(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="relative overflow-hidden bg-gray-900">
|
||||||
|
{/* Hero section with background */}
|
||||||
|
<div className="relative min-h-screen flex items-center">
|
||||||
|
{/* Background image with overlay */}
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 z-0"
|
||||||
|
style={{
|
||||||
|
backgroundImage: 'url("/images/hero.png")',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-b from-black/70 via-black/50 to-black/60" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content */}
|
||||||
|
<div className="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className={`${isLoaded ? 'animate-fade-in' : ''}`}>
|
||||||
|
<h1 className="font-outfit text-3xl xs:text-4xl sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl
|
||||||
|
font-bold text-white mb-4 sm:mb-6 md:mb-8 tracking-normal leading-tight
|
||||||
|
section-enter section-enter-active">
|
||||||
|
<span className="block">
|
||||||
|
IHRE IT-/TECHNOLOGIE-
|
||||||
|
</span>
|
||||||
|
<span className="block">
|
||||||
|
UNTERNEHMENSGRUPPE
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p className="font-outfit text-lg xs:text-xl sm:text-2xl md:text-3xl text-gray-200
|
||||||
|
mb-6 sm:mb-8 md:mb-12 tracking-normal leading-relaxed
|
||||||
|
section-enter section-enter-active delay-200">
|
||||||
|
Gemeinsam Stärker | Für Unsere Kunden | Durchgängig In Ihren Prozessen
|
||||||
|
</p>
|
||||||
|
<div className="section-enter section-enter-active delay-300">
|
||||||
|
<Link
|
||||||
|
to="/mehr"
|
||||||
|
className="group relative font-outfit inline-flex items-center justify-center
|
||||||
|
overflow-hidden rounded bg-[#C25B3F] px-6 sm:px-8 py-3 sm:py-4
|
||||||
|
text-base sm:text-lg md:text-xl font-medium uppercase tracking-wide
|
||||||
|
text-white w-full sm:w-auto shadow-lg
|
||||||
|
transition-all duration-300 ease-out
|
||||||
|
hover:bg-[#A34832]"
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-2">
|
||||||
|
Mehr erfahren
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={2}
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Subtle background effects */}
|
||||||
|
<div className="absolute inset-0 pointer-events-none">
|
||||||
|
<div className="absolute top-1/4 left-0 w-96 h-96 bg-orange-500/10 rounded-full
|
||||||
|
blur-3xl mix-blend-overlay opacity-75" />
|
||||||
|
<div className="absolute bottom-1/4 right-0 w-96 h-96 bg-orange-500/10 rounded-full
|
||||||
|
blur-3xl mix-blend-overlay opacity-75" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
25
src/components/ImageLoader.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
interface ImageLoaderProps {
|
||||||
|
src: string;
|
||||||
|
alt: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ImageLoader({ src, alt, className = '' }: ImageLoaderProps) {
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
{isLoading && (
|
||||||
|
<div className="absolute inset-0 bg-gray-100 animate-pulse" />
|
||||||
|
)}
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={alt}
|
||||||
|
className={`${className} ${isLoading ? 'opacity-0' : 'opacity-100 transition-opacity duration-300'}`}
|
||||||
|
onLoad={() => setIsLoading(false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
93
src/components/Mission.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import peterImage from '../../public/images/peter.png';
|
||||||
|
|
||||||
|
export default function Mission() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setIsVisible(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ threshold: 0.1 }
|
||||||
|
);
|
||||||
|
|
||||||
|
const section = document.getElementById('mission');
|
||||||
|
if (section) {
|
||||||
|
observer.observe(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (section) {
|
||||||
|
observer.unobserve(section);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="mission" className="py-16 sm:py-20 lg:py-24 bg-white">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className={`grid grid-cols-1 lg:grid-cols-12 gap-8 lg:gap-16 items-start
|
||||||
|
${isVisible ? 'animate-fade-in' : 'opacity-0'}`}>
|
||||||
|
{/* CEO Image Card */}
|
||||||
|
<div className="lg:col-span-4 lg:sticky lg:top-24">
|
||||||
|
<div className="group bg-gray-100 rounded-2xl overflow-hidden shadow-lg
|
||||||
|
transition-all duration-300 hover:shadow-2xl transform hover:-translate-y-1">
|
||||||
|
<div className="overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={peterImage}
|
||||||
|
alt="Peter Heim"
|
||||||
|
className="w-full aspect-[90/100] object-cover object-top
|
||||||
|
transition-transform duration-500 group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 sm:p-8 bg-gray-800 transform transition-all duration-300
|
||||||
|
group-hover:bg-gray-900">
|
||||||
|
<h3 className="font-outfit text-xl sm:text-2xl font-semibold text-white mb-1
|
||||||
|
transform transition-all duration-300 group-hover:translate-x-1">
|
||||||
|
Peter Heim
|
||||||
|
</h3>
|
||||||
|
<p className="font-outfit text-base sm:text-lg text-gray-200
|
||||||
|
transform transition-all duration-300 group-hover:translate-x-1">
|
||||||
|
CEO
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mission Text Content */}
|
||||||
|
<div className="lg:col-span-8">
|
||||||
|
<h2 className="font-outfit text-3xl sm:text-4xl lg:text-5xl font-bold text-[#C25B3F]
|
||||||
|
mb-8 sm:mb-12 section-enter section-enter-active">
|
||||||
|
Unser Leitbild
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div className="space-y-6 sm:space-y-8">
|
||||||
|
<p className="font-outfit text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed
|
||||||
|
transition-all duration-300 hover:text-gray-900 section-enter section-enter-active">
|
||||||
|
Das Technologie Team ist eine mittelständische Unternehmensgruppe, die sich auf den
|
||||||
|
IT- und Technologiesektor spezialisiert hat. Wir konzentrieren uns auf Beteiligungen in
|
||||||
|
Nachfolgesituationen und die langfristige Weiterentwicklung erfolgreicher
|
||||||
|
Unternehmen. Unser Ziel ist es, eine führende Rolle in der Technologiebranche zu
|
||||||
|
übernehmen, indem wir vertrauensvolle Transaktionen und nachhaltige Strategien für
|
||||||
|
Unternehmensfortführungen anbieten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="font-outfit text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed
|
||||||
|
transition-all duration-300 hover:text-gray-900 section-enter section-enter-active
|
||||||
|
delay-100">
|
||||||
|
Wir legen Wert auf langfristige Geschäftsbeziehungen und agieren in agilen
|
||||||
|
Arbeitsstrukturen. Unser Fokus liegt auf der nahtlosen Fortführung des Tagesgeschäfts
|
||||||
|
sowie auf der Unterstützung bei der Erreichung zukünftiger Ziele. So bieten wir
|
||||||
|
umfassende IT- und Technologieleistungen „aus einer Hand" und nutzen die
|
||||||
|
wachsenden Möglichkeiten der IT und Technologien optimal für Ihr Wachstum.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/components/Navbar.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
export default function Navbar() {
|
||||||
|
return (
|
||||||
|
<nav className="fixed top-0 left-0 right-0 bg-white shadow-md z-50 overflow-x-hidden">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div className="flex justify-start items-center h-20 sm:h-28">
|
||||||
|
{/* Left-aligned Logo section */}
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Link to="/" className="flex items-center">
|
||||||
|
<img
|
||||||
|
src="/logos/tteamdunkel.png"
|
||||||
|
alt="Technologie Team Logo"
|
||||||
|
className="h-16 sm:h-24 w-auto"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/components/ScrollProgress.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useScrollProgress } from '../hooks/useScrollProgress';
|
||||||
|
|
||||||
|
export default function ScrollProgress() {
|
||||||
|
const progress = useScrollProgress();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed top-0 left-0 w-full h-1 z-50">
|
||||||
|
<div
|
||||||
|
className="h-full bg-[#C25B3F] transition-all duration-100"
|
||||||
|
style={{ width: `${progress * 100}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
src/components/ScrollToTop.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { ArrowUp } from 'lucide-react';
|
||||||
|
|
||||||
|
export default function ScrollToTop() {
|
||||||
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const toggleVisibility = () => {
|
||||||
|
if (window.pageYOffset > 300) {
|
||||||
|
setIsVisible(true);
|
||||||
|
} else {
|
||||||
|
setIsVisible(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', toggleVisibility);
|
||||||
|
return () => window.removeEventListener('scroll', toggleVisibility);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const scrollToTop = () => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={`fixed bottom-8 right-8 bg-[#C25B3F] hover:bg-[#A34832] text-white p-3 rounded-full shadow-lg transition-all duration-300 z-50 ${
|
||||||
|
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10 pointer-events-none'
|
||||||
|
}`}
|
||||||
|
onClick={scrollToTop}
|
||||||
|
aria-label="Zum Seitenanfang scrollen"
|
||||||
|
>
|
||||||
|
<ArrowUp className="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
196
src/components/Technologies.tsx
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
import { useState, useCallback, useEffect } from 'react';
|
||||||
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
|
|
||||||
|
interface Technology {
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const technologies: Technology[] = [
|
||||||
|
{
|
||||||
|
title: 'ERP, eCommerce, Warenwirtschaft, digitale Geschäftsprozesse',
|
||||||
|
image: '/images/tech01.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'IT-Infrastruktur, as a Service Modelle, Cloudsysteme',
|
||||||
|
image: '/images/tech02.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'IT-Sicherheitstechnik, Netzwerktechnik',
|
||||||
|
image: '/images/tech03.png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Beratung, Projektunterstützung',
|
||||||
|
image: '/images/tech04.png',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Technologies() {
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [touchStart, setTouchStart] = useState<number | null>(null);
|
||||||
|
const [isMobile, setIsMobile] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkMobile = () => {
|
||||||
|
setIsMobile(window.innerWidth < 768);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkMobile();
|
||||||
|
window.addEventListener('resize', checkMobile);
|
||||||
|
return () => window.removeEventListener('resize', checkMobile);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handlePrevious = useCallback(() => {
|
||||||
|
setCurrentIndex((prev) => (prev === 0 ? technologies.length - 1 : prev - 1));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleNext = useCallback(() => {
|
||||||
|
setCurrentIndex((prev) => (prev === technologies.length - 1 ? 0 : prev + 1));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
|
setTouchStart(e.touches[0].clientX);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchEnd = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||||
|
if (!touchStart) return;
|
||||||
|
const touchEnd = e.changedTouches[0].clientX;
|
||||||
|
const diff = touchStart - touchEnd;
|
||||||
|
|
||||||
|
if (Math.abs(diff) > 50) {
|
||||||
|
if (diff > 0) handleNext();
|
||||||
|
else handlePrevious();
|
||||||
|
}
|
||||||
|
setTouchStart(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="bg-gray-50 overflow-hidden">
|
||||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 sm:py-12">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between mb-6">
|
||||||
|
<h2 className="font-outfit text-2xl sm:text-3xl font-bold text-[#C25B3F]">
|
||||||
|
Unterstützte Technologien
|
||||||
|
</h2>
|
||||||
|
{!isMobile && (
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={handlePrevious}
|
||||||
|
className="p-2 rounded-full bg-white shadow-lg
|
||||||
|
transition-all duration-300 hover:bg-gray-50 active:scale-95
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-orange-500"
|
||||||
|
aria-label="Vorherige"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-5 h-5 text-gray-600" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleNext}
|
||||||
|
className="p-2 rounded-full bg-[#C25B3F] shadow-lg
|
||||||
|
transition-all duration-300 hover:bg-[#A34832] active:scale-95
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-orange-500"
|
||||||
|
aria-label="Nächste"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-5 h-5 text-white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Desktop View */}
|
||||||
|
{!isMobile && (
|
||||||
|
<div className="relative">
|
||||||
|
<div className="grid grid-cols-4 gap-6">
|
||||||
|
{technologies.map((tech, idx) => {
|
||||||
|
const isVisible = Math.abs(idx - currentIndex) <= 3;
|
||||||
|
const order = (idx - currentIndex + technologies.length) % technologies.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className={`transition-all duration-500 transform
|
||||||
|
${isVisible ? 'opacity-100 scale-100' : 'opacity-0 scale-95'}
|
||||||
|
order-${order}`}
|
||||||
|
>
|
||||||
|
<div className="relative aspect-[4/3] rounded-xl overflow-hidden shadow-lg group">
|
||||||
|
<img
|
||||||
|
src={tech.image}
|
||||||
|
alt={tech.title}
|
||||||
|
className="w-full h-full object-cover transition-transform duration-700
|
||||||
|
group-hover:scale-105"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/90
|
||||||
|
via-black/40 to-transparent">
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 p-4">
|
||||||
|
<h3 className="font-outfit text-white text-lg font-medium leading-snug">
|
||||||
|
{tech.title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Mobile View */}
|
||||||
|
{isMobile && (
|
||||||
|
<div
|
||||||
|
className="relative"
|
||||||
|
onTouchStart={onTouchStart}
|
||||||
|
onTouchEnd={onTouchEnd}
|
||||||
|
>
|
||||||
|
<div className="absolute z-10 inset-y-0 left-0 right-0 flex items-center justify-between pointer-events-none">
|
||||||
|
<button
|
||||||
|
onClick={handlePrevious}
|
||||||
|
className="pointer-events-auto p-2 rounded-full bg-white/90 shadow-lg
|
||||||
|
transition-all duration-300 hover:bg-white active:scale-95
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-orange-500"
|
||||||
|
aria-label="Vorherige"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-5 h-5 text-gray-600" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleNext}
|
||||||
|
className="pointer-events-auto p-2 rounded-full bg-[#C25B3F] shadow-lg
|
||||||
|
transition-all duration-300 hover:bg-[#A34832] active:scale-95
|
||||||
|
focus:outline-none focus:ring-2 focus:ring-orange-500"
|
||||||
|
aria-label="Nächste"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-5 h-5 text-white" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative aspect-[4/3] rounded-xl overflow-hidden shadow-lg">
|
||||||
|
<img
|
||||||
|
src={technologies[currentIndex].image}
|
||||||
|
alt={technologies[currentIndex].title}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/90 via-black/40 to-transparent">
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 p-4">
|
||||||
|
<h3 className="font-outfit text-white text-lg font-medium leading-snug">
|
||||||
|
{technologies[currentIndex].title}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center gap-2 mt-4">
|
||||||
|
{technologies.map((_, idx) => (
|
||||||
|
<button
|
||||||
|
key={idx}
|
||||||
|
onClick={() => setCurrentIndex(idx)}
|
||||||
|
className={`h-1 rounded-full transition-all duration-300
|
||||||
|
${currentIndex === idx ? 'w-8 bg-[#C25B3F]' : 'w-4 bg-gray-300'}`}
|
||||||
|
aria-label={`Gehe zu Slide ${idx + 1}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
src/hooks/useScrollProgress.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
export function useScrollProgress() {
|
||||||
|
const [progress, setProgress] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const updateScroll = () => {
|
||||||
|
const currentProgress = window.scrollY;
|
||||||
|
const scrollHeight = document.documentElement.scrollHeight - window.innerHeight;
|
||||||
|
|
||||||
|
if (scrollHeight) {
|
||||||
|
setProgress(Number((currentProgress / scrollHeight).toFixed(2)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('scroll', updateScroll);
|
||||||
|
|
||||||
|
return () => window.removeEventListener('scroll', updateScroll);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return progress;
|
||||||
|
}
|
||||||
86
src/index.css
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
@apply text-gray-900 antialiased;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.section-title {
|
||||||
|
@apply text-3xl md:text-4xl font-bold text-gray-900 mb-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-container {
|
||||||
|
@apply max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 md:py-24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in {
|
||||||
|
animation: fadeIn 0.6s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Section transitions */
|
||||||
|
.section-enter {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-enter-active {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: opacity 0.5s, transform 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.section-container {
|
||||||
|
@apply py-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
@apply text-4xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
@apply text-3xl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading states */
|
||||||
|
.image-loading {
|
||||||
|
@apply relative overflow-hidden bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-loading::after {
|
||||||
|
content: '';
|
||||||
|
@apply absolute inset-0 bg-gradient-to-r from-transparent via-white/20 to-transparent;
|
||||||
|
animation: shimmer 1.5s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shimmer {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/main.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { StrictMode } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import App from './App';
|
||||||
|
import ErrorBoundary from './components/ErrorBoundary';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
|
const rootElement = document.getElementById('root');
|
||||||
|
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error('Root element not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = createRoot(rootElement);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<StrictMode>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<App />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</StrictMode>
|
||||||
|
);
|
||||||
1
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
8
tailwind.config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
24
tsconfig.app.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
7
tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
]
|
||||||
|
}
|
||||||
22
tsconfig.node.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
30
vite.config.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': '/src',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 5174,
|
||||||
|
host: true,
|
||||||
|
fs: {
|
||||||
|
strict: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
sourcemap: true,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'vendor': ['react', 'react-dom', 'react-router-dom'],
|
||||||
|
'icons': ['lucide-react']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||