feat: React-Landingpage (Vite + TypeScript + Tailwind) hinzugefügt
Originalgetreue Migration der HTML-Landingpage in eine React-SPA. Registrierungsformular mit Webhook-Integration und n8n-Response-Anzeige. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
BotKonzept Frontend — a static SaaS landing page and customer dashboard for an AI chatbot service. Users can upload PDFs, create chatbots trained on their documents, and embed them on their websites.
|
||||
|
||||
**Stack:** Vanilla HTML5 / CSS3 / JavaScript (ES6+). No framework, no bundler, no package manager.
|
||||
|
||||
## Local Development
|
||||
|
||||
```bash
|
||||
python3 -m http.server 8000
|
||||
# → http://localhost:8000
|
||||
```
|
||||
|
||||
No build step required. Edit files and refresh the browser.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
index.html # Public landing page: hero, features, pricing, registration form, FAQ
|
||||
dashboard.html # Authenticated customer dashboard
|
||||
css/
|
||||
style.css # Landing page styles (~30KB) — CSS variables, components, animations
|
||||
dashboard.css # Dashboard styles (~21KB) — sidebar layout, chat UI, file upload
|
||||
js/
|
||||
main.js # Landing page logic — registration form, FAQ accordion, scroll animations
|
||||
dashboard.js # Dashboard logic — file upload, chatbot chat, embed code, settings
|
||||
logo/ # SVG logo asset
|
||||
```
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
### API / Webhook Integration
|
||||
|
||||
Endpoints are defined as `CONFIG` objects at the top of each JS file:
|
||||
|
||||
- **Registration webhook:** `https://n8n.userman.de/webhook/botkonzept-registration` (`main.js`)
|
||||
- **API base:** `https://api.botkonzept.de` (`dashboard.js`)
|
||||
|
||||
The dashboard has a full **demo mode** that works without a backend — it uses `localStorage` for session state and simulates uploads/responses with dummy data.
|
||||
|
||||
### dashboard.js Feature Areas
|
||||
|
||||
- **Auth:** Session check via `localStorage`; falls back to demo mode
|
||||
- **File Upload:** Drag-and-drop + click, PDF-only, max 10 MB; simulates upload progress
|
||||
- **Chat:** User/bot message UI, typing indicator, webhook call with demo fallback
|
||||
- **Embed Code:** Clipboard API copy with `document.execCommand` fallback
|
||||
- **Trial Banner:** Dynamic discount logic — 30% on day 3, 15% on day 5, 48-hour countdown
|
||||
- **Settings:** Bot name, welcome message, and color saved to `localStorage`
|
||||
|
||||
### main.js Feature Areas
|
||||
|
||||
- Registration form: validates email + URL, POSTs to n8n webhook with German error messages
|
||||
- FAQ accordion (one item open at a time)
|
||||
- Scroll animations via `IntersectionObserver`
|
||||
- Typing animation in chat preview section
|
||||
|
||||
### CSS Design Tokens
|
||||
|
||||
Defined as CSS variables in `style.css`:
|
||||
- Primary: `#6366f1` (indigo), Secondary: `#0ea5e9` (sky blue), Accent: amber, Success: emerald, Error: red
|
||||
- Font: Inter (Google Fonts CDN)
|
||||
- Responsive breakpoints: `>1024px` desktop, `768–1024px` tablet, `<768px` mobile, `<480px` small mobile
|
||||
|
||||
### Security
|
||||
|
||||
- Chat messages are HTML-escaped before insertion to prevent XSS
|
||||
- Form inputs validated client-side before submission
|
||||
|
||||
## React-Migration (landing-react/)
|
||||
|
||||
Vollständige React-SPA unter `landing-react/` — originalgetreue Portierung der `index.html`-Landingpage.
|
||||
|
||||
**Stack:** Vite · React 18 · TypeScript · Tailwind CSS · Lucide React
|
||||
|
||||
```bash
|
||||
cd landing-react
|
||||
npm install
|
||||
npm run dev # Entwicklungsserver → http://localhost:5173
|
||||
npm run build # Produktions-Build → dist/
|
||||
npm run preview # Build lokal vorschauen
|
||||
```
|
||||
|
||||
**Komponenten:** `Navbar`, `Hero`, `TrustedBy`, `Features`, `Steps`, `Pricing`, `RegistrationSection`, `FAQ`, `CTA`, `Footer`
|
||||
|
||||
**Registrierungsformular:** POST an `https://n8n.zq0.de/webhook-test/test`. Status-States: `idle → loading → success | error`. Erfolgsantwort wird als JSON-Karte oder Bild dargestellt.
|
||||
|
||||
**Globale Stile:** Gradient-Utilities (`.gradient-text`, `.bg-gradient-primary`, `.btn-gradient`), Typing-Dots-Animation und FAQ-Accordion-Transition in `src/index.css`. Tailwind-Farberweiterungen (`primary`, `primary-dark`, `primary-light`, `secondary`, `accent`, `success`, `error`) in `tailwind.config.js`.
|
||||
|
||||
## Localization
|
||||
|
||||
All user-facing strings are in **German (de-DE)**. Number/date formatting uses `de-DE` locale.
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
dist/
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="BotKonzept - Ihr KI-Chatbot für die Website. Einfach einrichten, PDF hochladen, fertig!" />
|
||||
<meta name="keywords" content="KI Chatbot, Website Chatbot, RAG, PDF Chatbot, Kundenservice Automation" />
|
||||
<title>BotKonzept - KI-Chatbot für Ihre Website</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
# Diesen Block in eure bestehende Nginx-Server-Konfiguration einfügen.
|
||||
# Er proxyt /api/register an n8n weiter – kein CORS-Problem, da der
|
||||
# Request serverseitig von eurer eigenen Domain abgeht.
|
||||
|
||||
location /api/register {
|
||||
proxy_pass https://n8n.zq0.de/webhook-test/test;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host n8n.zq0.de;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Timeout auf 5 Minuten setzen – n8n kann lange brauchen
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 10s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
Generated
+2776
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "botkonzept-landing",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.460.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"postcss": "^8.4.49",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="359px" height="60px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 2150 359"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#FEFEFE;fill-rule:nonzero}
|
||||
.fil4 {fill:#88CED7;fill-rule:nonzero}
|
||||
.fil3 {fill:#4FC0EF;fill-rule:nonzero}
|
||||
.fil2 {fill:#3A7ABD;fill-rule:nonzero}
|
||||
.fil0 {fill:#234182;fill-rule:nonzero}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Bot_x0020_TV">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<g id="_1548485603296">
|
||||
<g>
|
||||
<path class="fil0" d="M204 168l0 22c0,8 5,14 12,14 3,0 6,-2 9,-4 2,-3 4,-6 4,-10l0 -22c0,-8 -5,-14 -12,-14 -3,0 -6,2 -9,4 -2,3 -4,6 -4,10z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="fil0" d="M142 168l0 22c0,8 5,14 12,14 3,0 6,-2 9,-4 2,-3 4,-6 4,-10l0 -22c0,-8 -5,-14 -12,-14 -3,0 -6,2 -9,4 -2,3 -4,6 -4,10z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="_1548485603200">
|
||||
<g>
|
||||
<path class="fil1" d="M209 192l2 0c1,0 1,0 1,-1l0 -24c0,-1 0,-1 -1,-1l-2 0c-1,0 -1,0 -1,1l0 24c0,1 0,1 1,1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="fil1" d="M147 192l2 0c1,0 1,0 1,-1l0 -24c0,-1 0,-1 -1,-1l-2 0c-1,0 -1,0 -1,1l0 24c0,1 0,1 1,1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="fil0" d="M119 24l0 32 -38 0 0 -32c0,-10 8,-19 19,-19 10,0 19,8 19,19zm221 259l-221 0 0 56c0,10 -8,19 -18,20 -11,1 -20,-8 -20,-19l0 -219 0 -8 47 0c-5,0 -8,3 -9,8 0,1 0,1 0,2l0 113c0,5 4,9 9,9l211 0c10,0 19,8 20,18 1,11 -8,20 -19,20z"/>
|
||||
<g id="_1548485605696">
|
||||
<path class="fil2" d="M251 302l38 0 0 38c0,10 -8,19 -19,19 -10,0 -19,-8 -19,-19l0 -38z"/>
|
||||
</g>
|
||||
<g id="_1548485607712">
|
||||
<path class="fil3" d="M359 94c0,10 -8,19 -19,19l-32 0 0 -38 32 0c10,0 19,8 19,19z"/>
|
||||
</g>
|
||||
<g id="_1548485606320">
|
||||
<path class="fil4" d="M19 245l43 0 0 38 -43 0c-10,0 -19,-8 -19,-19 0,-10 8,-19 19,-19z"/>
|
||||
</g>
|
||||
<path class="fil2" d="M251 237l0 -2 0 -112c0,-5 -4,-9 -9,-9l-222 0c-10,0 -19,-8 -20,-18 -1,-11 8,-20 19,-20l232 0 0 -55c0,-11 9,-20 20,-19 10,1 18,9 18,20l0 217 0 8 -47 0c4,0 8,-3 9,-8 0,0 0,-1 0,-1z"/>
|
||||
<path class="fil0" d="M391 213l0 -212 90 0c40,0 60,16 60,49 0,24 -13,41 -39,52 28,5 42,21 42,48 0,42 -22,63 -66,63l-87 0zm86 -22c25,0 38,-13 38,-38 0,-25 -17,-37 -51,-37l-12 0 0 -18c40,-5 61,-20 61,-44 0,-20 -11,-30 -34,-30l-60 0 0 167 58 0z"/>
|
||||
<path id="_1" class="fil0" d="M609 107c0,57 26,86 77,86 51,0 76,-29 76,-86 0,-56 -25,-85 -76,-85 -52,0 -77,28 -77,85zm-29 1c0,-72 35,-108 106,-108 70,0 105,36 105,108 0,71 -35,107 -105,107 -71,0 -106,-36 -106,-107z"/>
|
||||
<polygon id="_2" class="fil0" points="977,1 977,24 910,24 910,213 883,213 883,24 816,24 816,1 "/>
|
||||
<path id="_3" class="fil0" d="M1037 1l0 212 -27 0 0 -212 27 0zm149 0l-95 100 97 112 -38 0 -86 -102 0 -17 88 -93 35 0z"/>
|
||||
<path id="_4" class="fil0" d="M1216 107c0,57 26,86 77,86 51,0 76,-29 76,-86 0,-56 -25,-85 -76,-85 -52,0 -77,28 -77,85zm-29 1c0,-72 35,-108 106,-108 70,0 105,36 105,108 0,71 -35,107 -105,107 -71,0 -106,-36 -106,-107z"/>
|
||||
<polygon id="_5" class="fil0" points="1594,1 1594,24 1468,191 1592,191 1592,213 1435,213 1435,191 1565,24 1438,24 1438,1 "/>
|
||||
<polygon id="_6" class="fil0" points="1777,1 1777,24 1666,24 1666,95 1771,95 1771,117 1666,117 1666,191 1779,191 1779,213 1638,213 1638,1 "/>
|
||||
<path id="_7" class="fil0" d="M1820 213l0 -212 86 0c43,0 65,19 65,57 0,41 -30,66 -89,75l-6 -23c43,-6 65,-23 65,-51 0,-24 -13,-35 -38,-35l-56 0 0 189 -27 0z"/>
|
||||
<polygon id="_8" class="fil0" points="2150,1 2150,24 2083,24 2083,213 2056,213 2056,24 1989,24 1989,1 "/>
|
||||
<polygon class="fil2" points="391,359 391,254 411,254 446,333 481,254 500,254 500,359 482,359 482,286 454,359 437,359 409,286 409,359 "/>
|
||||
<polygon id="_1_8" class="fil2" points="593,254 593,271 541,271 541,297 591,297 591,315 541,315 541,342 594,342 594,359 522,359 522,254 "/>
|
||||
<path id="_2_9" class="fil2" d="M653 342c22,0 32,-13 32,-38 0,-22 -11,-32 -32,-32l-21 0 0 71 21 0zm-41 17l0 -104 41 0c34,0 52,16 52,49 0,37 -17,55 -52,55l-41 0z"/>
|
||||
<polygon id="_3_10" class="fil2" points="740,254 740,359 721,359 721,254 "/>
|
||||
<polygon id="_4_11" class="fil2" points="773,359 753,359 797,254 818,254 863,359 842,359 830,330 796,330 802,313 823,313 807,274 "/>
|
||||
<polygon id="_5_12" class="fil2" points="876,359 876,254 896,254 931,333 966,254 985,254 985,359 967,359 967,286 939,359 922,359 893,286 893,359 "/>
|
||||
<polygon id="_6_13" class="fil2" points="1078,254 1078,271 1026,271 1026,297 1076,297 1076,315 1026,315 1026,342 1079,342 1079,359 1007,359 1007,254 "/>
|
||||
<polygon id="_7_14" class="fil2" points="1165,254 1165,271 1135,271 1135,359 1116,359 1116,271 1086,271 1086,254 "/>
|
||||
<polygon id="_8_15" class="fil2" points="1251,254 1251,271 1196,342 1251,342 1251,359 1174,359 1174,342 1232,271 1176,271 1176,254 "/>
|
||||
<polygon id="_9" class="fil2" points="1332,359 1332,254 1352,254 1407,330 1407,254 1425,254 1425,359 1406,359 1351,281 1351,359 "/>
|
||||
<polygon id="_10" class="fil2" points="1518,254 1518,271 1466,271 1466,297 1516,297 1516,315 1466,315 1466,342 1519,342 1519,359 1447,359 1447,254 "/>
|
||||
<polygon id="_11" class="fil2" points="1605,254 1605,271 1576,271 1576,359 1556,359 1556,271 1526,271 1526,254 "/>
|
||||
<polygon id="_12" class="fil2" points="1608,254 1628,254 1639,329 1666,254 1684,254 1710,326 1721,254 1740,254 1721,359 1703,359 1674,277 1643,359 1625,359 "/>
|
||||
<path id="_13" class="fil2" d="M1770 306c0,24 11,36 32,36 21,0 31,-12 31,-36 0,-24 -10,-36 -31,-36 -22,0 -32,12 -32,36zm-20 0c0,-35 17,-53 52,-53 34,0 51,18 51,53 0,35 -17,53 -51,53 -33,0 -51,-18 -52,-53z"/>
|
||||
<path id="_14" class="fil2" d="M1872 358l0 -104 47 0c21,0 32,9 32,28 0,13 -8,23 -25,31l33 45 -24 0 -32 -45 0 -9c18,-3 28,-10 28,-21 0,-8 -4,-12 -13,-12l-26 0 0 87 -20 0z"/>
|
||||
<path id="_15" class="fil2" d="M1988 254l0 104 -19 0 0 -104 19 0zm73 0l-44 49 49 56 -27 0 -40 -49 0 -13 39 -43 24 0z"/>
|
||||
<path id="_16" class="fil2" d="M2071 354l0 -17c10,4 22,6 35,6 16,0 24,-5 24,-16 0,-8 -5,-12 -15,-12l-16 0c-21,0 -32,-10 -32,-29 0,-21 15,-32 46,-32 12,0 23,2 33,5l0 17c-10,-4 -21,-6 -33,-6 -17,0 -26,5 -26,15 0,8 4,12 13,12l16 0c23,0 34,10 34,29 0,22 -14,33 -43,33 -13,0 -25,-2 -35,-5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
@@ -0,0 +1,29 @@
|
||||
import Navbar from './components/Navbar'
|
||||
import Hero from './components/Hero'
|
||||
import TrustedBy from './components/TrustedBy'
|
||||
import Features from './components/Features'
|
||||
import Steps from './components/Steps'
|
||||
import Pricing from './components/Pricing'
|
||||
import RegistrationSection from './components/RegistrationSection'
|
||||
import FAQ from './components/FAQ'
|
||||
import CTA from './components/CTA'
|
||||
import Footer from './components/Footer'
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<>
|
||||
<Navbar />
|
||||
<main>
|
||||
<Hero />
|
||||
<TrustedBy />
|
||||
<Features />
|
||||
<Steps />
|
||||
<Pricing />
|
||||
<RegistrationSection />
|
||||
<FAQ />
|
||||
<CTA />
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Rocket } from 'lucide-react'
|
||||
|
||||
export default function CTA() {
|
||||
const scrollToRegister = () => {
|
||||
const el = document.getElementById('register')
|
||||
if (el) {
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - 80
|
||||
window.scrollTo({ top, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="py-[80px]"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
<div className="max-w-container mx-auto px-6 text-center text-white">
|
||||
<h2 className="text-[clamp(2rem,4vw,3rem)] font-bold text-white mb-4">
|
||||
Bereit, Ihren Kundenservice zu revolutionieren?
|
||||
</h2>
|
||||
<p className="text-xl opacity-90 mb-8">
|
||||
Starten Sie noch heute Ihre kostenlose 7-Tage-Trial
|
||||
</p>
|
||||
<button
|
||||
onClick={scrollToRegister}
|
||||
className="inline-flex items-center gap-2 px-8 py-4 bg-white text-primary font-semibold text-lg rounded-lg hover:bg-gray-100 hover:text-primary-dark transition-all duration-300"
|
||||
>
|
||||
<Rocket size={20} />
|
||||
Jetzt kostenlos starten
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useState } from 'react'
|
||||
import { ChevronDown } from 'lucide-react'
|
||||
|
||||
interface FAQItem {
|
||||
question: string
|
||||
answer: string
|
||||
}
|
||||
|
||||
const faqs: FAQItem[] = [
|
||||
{
|
||||
question: 'Wie funktioniert der KI-Chatbot?',
|
||||
answer:
|
||||
'Unser Chatbot nutzt modernste KI-Technologie (RAG - Retrieval Augmented Generation). Sie laden Ihre Dokumente hoch, und der Bot durchsucht diese, um präzise Antworten auf Kundenfragen zu geben. Die KI versteht den Kontext und formuliert natürliche Antworten.',
|
||||
},
|
||||
{
|
||||
question: 'Welche Dateiformate werden unterstützt?',
|
||||
answer:
|
||||
'Aktuell unterstützen wir PDF-Dateien. Weitere Formate wie Word, Excel und Textdateien sind in Planung. Sie können beliebig viele PDFs hochladen – FAQs, Produktkataloge, Anleitungen, etc.',
|
||||
},
|
||||
{
|
||||
question: 'Ist der Service DSGVO-konform?',
|
||||
answer:
|
||||
'Ja, 100%! Alle Daten werden ausschließlich auf Servern in Deutschland gehostet. Wir verarbeiten keine Daten außerhalb der EU. Sie erhalten einen Auftragsverarbeitungsvertrag (AVV) auf Anfrage.',
|
||||
},
|
||||
{
|
||||
question: 'Kann ich den Chatbot an mein Design anpassen?',
|
||||
answer:
|
||||
'Ja! Im Starter- und Business-Plan können Sie Farben, Logo und Begrüßungstext anpassen. Der Chatbot fügt sich nahtlos in Ihr Website-Design ein.',
|
||||
},
|
||||
{
|
||||
question: 'Was passiert nach der Trial-Phase?',
|
||||
answer:
|
||||
'Nach 7 Tagen endet Ihre Trial automatisch. Sie können jederzeit auf einen bezahlten Plan upgraden. Wenn Sie innerhalb der ersten 3 Tage upgraden, erhalten Sie 30% Rabatt!',
|
||||
},
|
||||
{
|
||||
question: 'Wie integriere ich den Chatbot in meine Website?',
|
||||
answer:
|
||||
'Ganz einfach! Sie erhalten einen Code-Snippet, den Sie vor dem </body>-Tag Ihrer Website einfügen. Das funktioniert mit WordPress, Shopify, Wix, und jeder anderen Website.',
|
||||
},
|
||||
]
|
||||
|
||||
export default function FAQ() {
|
||||
const [openIndex, setOpenIndex] = useState<number | null>(null)
|
||||
|
||||
const toggle = (idx: number) => {
|
||||
setOpenIndex((prev) => (prev === idx ? null : idx))
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="faq" className="py-[100px] bg-white">
|
||||
<div className="max-w-container mx-auto px-6">
|
||||
{/* Section Header */}
|
||||
<div className="text-center max-w-[700px] mx-auto mb-[60px]">
|
||||
<span
|
||||
className="inline-block px-4 py-1.5 text-white text-xs font-semibold uppercase tracking-wider rounded-full mb-4"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
FAQ
|
||||
</span>
|
||||
<h2 className="text-[clamp(2rem,4vw,3rem)] font-bold text-gray-900 mb-4">
|
||||
Häufig gestellte Fragen
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600">Alles was Sie wissen müssen</p>
|
||||
</div>
|
||||
|
||||
{/* Accordion */}
|
||||
<div className="max-w-[800px] mx-auto">
|
||||
{faqs.map((item, idx) => (
|
||||
<div key={idx} className="border-b border-gray-200">
|
||||
<button
|
||||
onClick={() => toggle(idx)}
|
||||
className="w-full flex items-center justify-between py-6 bg-transparent border-none cursor-pointer text-left text-lg font-semibold text-gray-900 hover:text-primary transition-colors duration-150"
|
||||
>
|
||||
<span>{item.question}</span>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className={`text-gray-400 flex-shrink-0 transition-transform duration-300 ${
|
||||
openIndex === idx ? 'rotate-180' : ''
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div className={`faq-answer ${openIndex === idx ? 'open' : ''}`}>
|
||||
<p className="pb-6 text-gray-600 leading-[1.7]">{item.answer}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { FileText, Brain, Code2, Shield, Palette, BarChart2 } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface Feature {
|
||||
icon: ReactNode
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
const features: Feature[] = [
|
||||
{
|
||||
icon: <FileText size={28} />,
|
||||
title: 'PDF-Upload',
|
||||
description:
|
||||
'Laden Sie einfach Ihre Dokumente hoch. Der Chatbot lernt automatisch aus Ihren Inhalten.',
|
||||
},
|
||||
{
|
||||
icon: <Brain size={28} />,
|
||||
title: 'KI-gestützte Antworten',
|
||||
description:
|
||||
'Modernste KI-Technologie für natürliche und präzise Antworten auf Kundenfragen.',
|
||||
},
|
||||
{
|
||||
icon: <Code2 size={28} />,
|
||||
title: 'Einfache Integration',
|
||||
description:
|
||||
'Ein Zeile Code – mehr brauchen Sie nicht. Funktioniert mit jeder Website.',
|
||||
},
|
||||
{
|
||||
icon: <Shield size={28} />,
|
||||
title: 'DSGVO-konform',
|
||||
description:
|
||||
'Alle Daten werden in Deutschland gehostet. 100% DSGVO-konform.',
|
||||
},
|
||||
{
|
||||
icon: <Palette size={28} />,
|
||||
title: 'Anpassbares Design',
|
||||
description:
|
||||
'Passen Sie Farben und Stil an Ihre Marke an. Ihr Chatbot, Ihr Look.',
|
||||
},
|
||||
{
|
||||
icon: <BarChart2 size={28} />,
|
||||
title: 'Analytics Dashboard',
|
||||
description:
|
||||
'Verstehen Sie, was Ihre Kunden fragen. Detaillierte Statistiken und Insights.',
|
||||
},
|
||||
]
|
||||
|
||||
export default function Features() {
|
||||
return (
|
||||
<section id="features" className="py-[100px] bg-white">
|
||||
<div className="max-w-container mx-auto px-6">
|
||||
{/* Section Header */}
|
||||
<div className="text-center max-w-[700px] mx-auto mb-[60px]">
|
||||
<span
|
||||
className="inline-block px-4 py-1.5 text-white text-xs font-semibold uppercase tracking-wider rounded-full mb-4"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
Features
|
||||
</span>
|
||||
<h2 className="text-[clamp(2rem,4vw,3rem)] font-bold text-gray-900 mb-4">
|
||||
Alles was Sie für einen erfolgreichen Chatbot brauchen
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600">
|
||||
Leistungsstarke Funktionen, die Ihren Kundenservice revolutionieren
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{features.map((f) => (
|
||||
<div
|
||||
key={f.title}
|
||||
className="p-8 bg-white border border-gray-200 rounded-2xl transition-all duration-300 hover:-translate-y-1 hover:shadow-lg hover:border-primary-light"
|
||||
>
|
||||
<div
|
||||
className="w-14 h-14 rounded-xl flex items-center justify-center text-white mb-5"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
{f.icon}
|
||||
</div>
|
||||
<h3 className="text-xl font-bold text-gray-900 mb-3">{f.title}</h3>
|
||||
<p className="text-[0.9375rem] text-gray-600">{f.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { Heart, Linkedin, Twitter, Github } from 'lucide-react'
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="pt-[80px] pb-[40px] bg-gray-900 text-gray-400">
|
||||
<div className="max-w-container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-[2fr_1fr_1fr_1fr] gap-12 mb-12">
|
||||
{/* Brand */}
|
||||
<div>
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center gap-3 text-2xl font-bold text-white mb-4 no-underline"
|
||||
onClick={(e) => { e.preventDefault(); window.scrollTo({ top: 0, behavior: 'smooth' }) }}
|
||||
>
|
||||
<img
|
||||
src="/logo.svg"
|
||||
alt="BotKonzept Logo"
|
||||
className="h-10 w-auto"
|
||||
style={{ filter: 'brightness(0) invert(1)' }}
|
||||
/>
|
||||
<span>BotKonzept</span>
|
||||
</a>
|
||||
<p className="mb-6 leading-relaxed">
|
||||
Ihr intelligenter KI-Chatbot für besseren Kundenservice.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
{[
|
||||
{ icon: <Linkedin size={18} />, label: 'LinkedIn' },
|
||||
{ icon: <Twitter size={18} />, label: 'Twitter' },
|
||||
{ icon: <Github size={18} />, label: 'GitHub' },
|
||||
].map((s) => (
|
||||
<a
|
||||
key={s.label}
|
||||
href="#"
|
||||
aria-label={s.label}
|
||||
className="w-10 h-10 bg-gray-800 rounded-lg flex items-center justify-center text-gray-400 hover:bg-primary hover:text-white transition-all duration-300"
|
||||
>
|
||||
{s.icon}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Produkt */}
|
||||
<FooterLinks
|
||||
title="Produkt"
|
||||
links={[
|
||||
{ label: 'Features', href: '#features' },
|
||||
{ label: 'Preise', href: '#pricing' },
|
||||
{ label: 'FAQ', href: '#faq' },
|
||||
{ label: 'Dokumentation', href: '#' },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Unternehmen */}
|
||||
<FooterLinks
|
||||
title="Unternehmen"
|
||||
links={[
|
||||
{ label: 'Über uns', href: '#' },
|
||||
{ label: 'Blog', href: '#' },
|
||||
{ label: 'Karriere', href: '#' },
|
||||
{ label: 'Kontakt', href: '#' },
|
||||
]}
|
||||
/>
|
||||
|
||||
{/* Rechtliches */}
|
||||
<FooterLinks
|
||||
title="Rechtliches"
|
||||
links={[
|
||||
{ label: 'Impressum', href: '#' },
|
||||
{ label: 'Datenschutz', href: '#' },
|
||||
{ label: 'AGB', href: '#' },
|
||||
{ label: 'Cookie-Einstellungen', href: '#' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bottom Bar */}
|
||||
<div className="flex flex-col sm:flex-row justify-between items-center gap-3 pt-8 border-t border-gray-800 text-sm">
|
||||
<p>© 2025 BotKonzept. Alle Rechte vorbehalten.</p>
|
||||
<p className="flex items-center gap-1">
|
||||
Made with <Heart size={14} className="text-red-500" /> in Germany
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
function FooterLinks({
|
||||
title,
|
||||
links,
|
||||
}: {
|
||||
title: string
|
||||
links: { label: string; href: string }[]
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-white font-semibold text-base mb-5">{title}</h4>
|
||||
<ul className="space-y-3">
|
||||
{links.map((l) => (
|
||||
<li key={l.label}>
|
||||
<a
|
||||
href={l.href}
|
||||
className="text-gray-400 hover:text-white transition-colors duration-150"
|
||||
>
|
||||
{l.label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Rocket, Play, Info, Bot, Circle, Send } from 'lucide-react'
|
||||
|
||||
export default function Hero() {
|
||||
const scrollTo = (id: string) => {
|
||||
const el = document.getElementById(id)
|
||||
if (el) {
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - 80
|
||||
window.scrollTo({ top, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section
|
||||
className="relative pt-[160px] pb-[100px] overflow-hidden"
|
||||
style={{ background: 'linear-gradient(180deg, #f9fafb 0%, #ffffff 100%)' }}
|
||||
>
|
||||
{/* Background Orbs */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div
|
||||
className="orb"
|
||||
style={{ width: 600, height: 600, background: '#818cf8', top: -200, right: -100 }}
|
||||
/>
|
||||
<div
|
||||
className="orb"
|
||||
style={{ width: 400, height: 400, background: '#0ea5e9', bottom: -100, left: -100 }}
|
||||
/>
|
||||
<div
|
||||
className="orb"
|
||||
style={{
|
||||
width: 300,
|
||||
height: 300,
|
||||
background: '#f59e0b',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="max-w-container mx-auto px-6 relative z-10 grid grid-cols-1 lg:grid-cols-2 gap-[60px] items-center">
|
||||
{/* Content */}
|
||||
<div>
|
||||
{/* Badge */}
|
||||
<div className="inline-flex items-center gap-2 px-4 py-2 bg-primary-light text-white rounded-full text-sm font-semibold mb-6">
|
||||
<Rocket size={12} />
|
||||
<span>7 Tage kostenlos testen</span>
|
||||
</div>
|
||||
|
||||
<h1 className="text-[clamp(2.5rem,5vw,4rem)] font-bold text-gray-900 leading-tight mb-6">
|
||||
Ihr intelligenter{' '}
|
||||
<span className="gradient-text">KI-Chatbot</span>{' '}
|
||||
für die Website
|
||||
</h1>
|
||||
|
||||
<p className="text-xl text-gray-600 mb-8 max-w-[540px]">
|
||||
Laden Sie einfach Ihre PDFs hoch und Ihr Chatbot beantwortet Kundenfragen
|
||||
automatisch. Keine Programmierung erforderlich – in 5 Minuten einsatzbereit.
|
||||
</p>
|
||||
|
||||
{/* CTA Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 mb-12">
|
||||
<button
|
||||
onClick={() => scrollTo('register')}
|
||||
className="btn-gradient inline-flex items-center justify-center gap-2 px-8 py-4 text-lg rounded-lg"
|
||||
>
|
||||
<Play size={18} />
|
||||
Jetzt kostenlos starten
|
||||
</button>
|
||||
<button
|
||||
onClick={() => scrollTo('how-it-works')}
|
||||
className="inline-flex items-center justify-center gap-2 px-8 py-4 text-lg font-semibold rounded-lg border-2 border-primary text-primary hover:bg-primary hover:text-white transition-all duration-300"
|
||||
>
|
||||
<Info size={18} />
|
||||
Mehr erfahren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="flex gap-12 flex-wrap">
|
||||
{[
|
||||
{ number: '5 Min', label: 'Setup-Zeit' },
|
||||
{ number: '100%', label: 'DSGVO-konform' },
|
||||
{ number: '24/7', label: 'Verfügbar' },
|
||||
].map((stat) => (
|
||||
<div key={stat.label} className="text-center">
|
||||
<span className="block text-[2rem] font-extrabold text-gray-900 leading-none">
|
||||
{stat.number}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">{stat.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Preview Widget */}
|
||||
<div className="flex justify-center lg:justify-end">
|
||||
<div className="bg-white rounded-3xl shadow-xl overflow-hidden w-full max-w-[400px]">
|
||||
{/* Chat Header */}
|
||||
<div
|
||||
className="flex items-center gap-3 px-5 py-4 text-white"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
<div className="w-11 h-11 bg-white/20 rounded-full flex items-center justify-center text-xl">
|
||||
<Bot size={22} />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-semibold text-sm">BotKonzept Assistent</span>
|
||||
<span className="text-xs opacity-90 flex items-center gap-1">
|
||||
<Circle size={7} className="fill-emerald-400 text-emerald-400" />
|
||||
Online
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Messages */}
|
||||
<div className="px-5 py-5 flex flex-col gap-3 min-h-[280px]">
|
||||
<div className="max-w-[85%] bg-gray-100 px-4 py-3 rounded-2xl rounded-bl-sm text-[0.9375rem] self-start">
|
||||
Hallo! 👋 Wie kann ich Ihnen heute helfen?
|
||||
</div>
|
||||
<div className="max-w-[85%] bg-primary text-white px-4 py-3 rounded-2xl rounded-br-sm text-[0.9375rem] self-end">
|
||||
Was sind Ihre Öffnungszeiten?
|
||||
</div>
|
||||
<div className="max-w-[85%] bg-gray-100 px-4 py-3 rounded-2xl rounded-bl-sm text-[0.9375rem] self-start">
|
||||
Unsere Öffnungszeiten sind Montag bis Freitag von 9:00 bis 18:00 Uhr. Am
|
||||
Wochenende sind wir geschlossen.
|
||||
</div>
|
||||
<div className="max-w-[85%] bg-primary text-white px-4 py-3 rounded-2xl rounded-br-sm text-[0.9375rem] self-end">
|
||||
Wie kann ich eine Bestellung aufgeben?
|
||||
</div>
|
||||
{/* Typing Indicator */}
|
||||
<div className="max-w-[85%] bg-gray-100 px-5 py-4 rounded-2xl rounded-bl-sm self-start flex gap-1 items-center">
|
||||
<span className="typing-dot" />
|
||||
<span className="typing-dot" />
|
||||
<span className="typing-dot" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Input */}
|
||||
<div className="flex items-center gap-3 px-5 py-4 border-t border-gray-200">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Nachricht eingeben..."
|
||||
className="flex-1 px-4 py-3 border border-gray-300 rounded-full text-[0.9375rem] outline-none focus:border-primary transition-colors"
|
||||
readOnly
|
||||
/>
|
||||
<button
|
||||
className="w-11 h-11 rounded-full text-white flex items-center justify-center flex-shrink-0 hover:scale-105 transition-transform"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
<Send size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Menu, X } from 'lucide-react'
|
||||
|
||||
export default function Navbar() {
|
||||
const [mobileOpen, setMobileOpen] = useState(false)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const onScroll = () => setScrolled(window.scrollY > 20)
|
||||
window.addEventListener('scroll', onScroll)
|
||||
return () => window.removeEventListener('scroll', onScroll)
|
||||
}, [])
|
||||
|
||||
const scrollTo = (id: string) => {
|
||||
setMobileOpen(false)
|
||||
const el = document.getElementById(id)
|
||||
if (el) {
|
||||
const offset = 80
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - offset
|
||||
window.scrollTo({ top, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`fixed top-0 left-0 right-0 z-50 bg-white/95 backdrop-blur-md border-b border-gray-200 transition-shadow duration-300 ${
|
||||
scrolled ? 'shadow-md' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="max-w-container mx-auto px-6 flex items-center justify-between h-[72px]">
|
||||
{/* Logo */}
|
||||
<a
|
||||
href="#"
|
||||
className="flex items-center gap-3 text-2xl font-bold text-gray-900 no-underline"
|
||||
onClick={(e) => { e.preventDefault(); window.scrollTo({ top: 0, behavior: 'smooth' }) }}
|
||||
>
|
||||
<img src="/logo.svg" alt="BotKonzept Logo" className="h-10 w-auto" />
|
||||
<span>BotKonzept</span>
|
||||
</a>
|
||||
|
||||
{/* Desktop Links */}
|
||||
<div className="hidden md:flex items-center gap-8">
|
||||
{[
|
||||
{ label: 'Features', id: 'features' },
|
||||
{ label: "So funktioniert's", id: 'how-it-works' },
|
||||
{ label: 'Preise', id: 'pricing' },
|
||||
{ label: 'FAQ', id: 'faq' },
|
||||
].map((link) => (
|
||||
<button
|
||||
key={link.id}
|
||||
onClick={() => scrollTo(link.id)}
|
||||
className="text-gray-600 font-medium hover:text-primary transition-colors duration-150 bg-transparent border-none cursor-pointer text-base"
|
||||
>
|
||||
{link.label}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
onClick={() => scrollTo('register')}
|
||||
className="btn-gradient px-4 py-2 text-sm rounded-lg"
|
||||
>
|
||||
Kostenlos testen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Toggle */}
|
||||
<button
|
||||
className="md:hidden text-gray-700 bg-transparent border-none cursor-pointer"
|
||||
onClick={() => setMobileOpen((v) => !v)}
|
||||
aria-label="Menü öffnen"
|
||||
>
|
||||
{mobileOpen ? <X size={24} /> : <Menu size={24} />}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu */}
|
||||
{mobileOpen && (
|
||||
<div className="md:hidden bg-white border-b border-gray-200 shadow-lg px-6 py-5 flex flex-col gap-4">
|
||||
{[
|
||||
{ label: 'Features', id: 'features' },
|
||||
{ label: "So funktioniert's", id: 'how-it-works' },
|
||||
{ label: 'Preise', id: 'pricing' },
|
||||
{ label: 'FAQ', id: 'faq' },
|
||||
].map((link) => (
|
||||
<button
|
||||
key={link.id}
|
||||
onClick={() => scrollTo(link.id)}
|
||||
className="text-gray-600 font-medium hover:text-primary transition-colors text-left bg-transparent border-none cursor-pointer text-base"
|
||||
>
|
||||
{link.label}
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
onClick={() => scrollTo('register')}
|
||||
className="btn-gradient px-4 py-2 text-sm rounded-lg text-center"
|
||||
>
|
||||
Kostenlos testen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import { Check, X, Clock, Gift } from 'lucide-react'
|
||||
|
||||
interface PricingPlan {
|
||||
name: string
|
||||
price: string
|
||||
period: string
|
||||
description: string
|
||||
features: { text: string; included: boolean }[]
|
||||
cta: string
|
||||
featured?: boolean
|
||||
note?: string
|
||||
}
|
||||
|
||||
const plans: PricingPlan[] = [
|
||||
{
|
||||
name: 'Trial',
|
||||
price: '0',
|
||||
period: '/7 Tage',
|
||||
description: 'Perfekt zum Testen',
|
||||
features: [
|
||||
{ text: '100 Dokumente', included: true },
|
||||
{ text: '1.000 Nachrichten', included: true },
|
||||
{ text: '1 Chatbot', included: true },
|
||||
{ text: 'Standard Support', included: true },
|
||||
{ text: 'Custom Branding', included: false },
|
||||
{ text: 'Analytics', included: false },
|
||||
],
|
||||
cta: 'Kostenlos starten',
|
||||
},
|
||||
{
|
||||
name: 'Starter',
|
||||
price: '49',
|
||||
period: '/Monat',
|
||||
description: 'Für kleine Unternehmen',
|
||||
features: [
|
||||
{ text: 'Unbegrenzte Dokumente', included: true },
|
||||
{ text: '10.000 Nachrichten/Monat', included: true },
|
||||
{ text: '1 Chatbot', included: true },
|
||||
{ text: 'Prioritäts-Support', included: true },
|
||||
{ text: 'Custom Branding', included: true },
|
||||
{ text: 'Analytics Dashboard', included: true },
|
||||
],
|
||||
cta: 'Jetzt starten',
|
||||
featured: true,
|
||||
note: '30% Rabatt bei Upgrade innerhalb von 3 Tagen',
|
||||
},
|
||||
{
|
||||
name: 'Business',
|
||||
price: '149',
|
||||
period: '/Monat',
|
||||
description: 'Für wachsende Teams',
|
||||
features: [
|
||||
{ text: 'Unbegrenzte Dokumente', included: true },
|
||||
{ text: '50.000 Nachrichten/Monat', included: true },
|
||||
{ text: '5 Chatbots', included: true },
|
||||
{ text: 'Dedizierter Support', included: true },
|
||||
{ text: 'API-Zugriff', included: true },
|
||||
{ text: 'SLA-Garantie', included: true },
|
||||
],
|
||||
cta: 'Kontakt aufnehmen',
|
||||
},
|
||||
]
|
||||
|
||||
const timeline = [
|
||||
{ days: 'Tag 1-3', discount: '30% Rabatt', price: '€34,30/Monat', active: true },
|
||||
{ days: 'Tag 4-5', discount: '15% Rabatt', price: '€41,65/Monat', active: false },
|
||||
{ days: 'Tag 6-7', discount: 'Normalpreis', price: '€49/Monat', active: false },
|
||||
]
|
||||
|
||||
export default function Pricing() {
|
||||
const scrollToRegister = () => {
|
||||
const el = document.getElementById('register')
|
||||
if (el) {
|
||||
const top = el.getBoundingClientRect().top + window.scrollY - 80
|
||||
window.scrollTo({ top, behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="pricing" className="py-[100px] bg-white">
|
||||
<div className="max-w-container mx-auto px-6">
|
||||
{/* Section Header */}
|
||||
<div className="text-center max-w-[700px] mx-auto mb-[60px]">
|
||||
<span
|
||||
className="inline-block px-4 py-1.5 text-white text-xs font-semibold uppercase tracking-wider rounded-full mb-4"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
Preise
|
||||
</span>
|
||||
<h2 className="text-[clamp(2rem,4vw,3rem)] font-bold text-gray-900 mb-4">
|
||||
Transparente Preise, keine versteckten Kosten
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600">
|
||||
Starten Sie kostenlos und upgraden Sie, wenn Sie bereit sind
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Pricing Cards */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-[60px] max-w-[400px] lg:max-w-none mx-auto">
|
||||
{plans.map((plan) => (
|
||||
<div
|
||||
key={plan.name}
|
||||
className={`relative p-10 bg-white rounded-3xl border-2 transition-all duration-300 ${
|
||||
plan.featured
|
||||
? 'border-primary shadow-xl lg:scale-105'
|
||||
: 'border-gray-200 hover:border-primary-light'
|
||||
}`}
|
||||
>
|
||||
{plan.featured && (
|
||||
<div
|
||||
className="absolute -top-3 left-1/2 -translate-x-1/2 px-5 py-1.5 text-white text-xs font-semibold uppercase rounded-full"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
Beliebt
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="text-center mb-8">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">{plan.name}</h3>
|
||||
<div className="flex items-baseline justify-center gap-1 mb-2">
|
||||
<span className="text-2xl font-semibold text-gray-600">€</span>
|
||||
<span className="text-[3.5rem] font-extrabold text-gray-900 leading-none">
|
||||
{plan.price}
|
||||
</span>
|
||||
<span className="text-base text-gray-500">{plan.period}</span>
|
||||
</div>
|
||||
<p className="text-[0.9375rem] text-gray-600">{plan.description}</p>
|
||||
</div>
|
||||
|
||||
<ul className="mb-8 space-y-0">
|
||||
{plan.features.map((f) => (
|
||||
<li
|
||||
key={f.text}
|
||||
className={`flex items-center gap-3 py-3 border-b border-gray-100 last:border-0 text-[0.9375rem] ${
|
||||
f.included ? 'text-gray-700' : 'text-gray-400'
|
||||
}`}
|
||||
>
|
||||
{f.included ? (
|
||||
<Check size={14} className="text-emerald-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X size={14} className="text-gray-400 flex-shrink-0" />
|
||||
)}
|
||||
{f.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<button
|
||||
onClick={scrollToRegister}
|
||||
className={`w-full py-3 px-6 rounded-lg font-semibold transition-all duration-300 ${
|
||||
plan.featured
|
||||
? 'btn-gradient text-white'
|
||||
: 'border-2 border-primary text-primary hover:bg-primary hover:text-white'
|
||||
}`}
|
||||
>
|
||||
{plan.cta}
|
||||
</button>
|
||||
|
||||
{plan.note && (
|
||||
<p className="text-center mt-4 text-sm text-gray-600 flex items-center justify-center gap-1 flex-wrap">
|
||||
<Gift size={14} className="text-amber-500 flex-shrink-0" />
|
||||
<span className="bg-amber-500 text-white px-2 py-0.5 rounded text-xs font-semibold">
|
||||
30% Rabatt
|
||||
</span>
|
||||
bei Upgrade innerhalb von 3 Tagen
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Discount Timeline */}
|
||||
<div className="max-w-[800px] mx-auto bg-gray-50 rounded-3xl p-10">
|
||||
<h3 className="text-center text-xl font-bold text-gray-900 mb-8 flex items-center justify-center gap-3">
|
||||
<Clock size={22} className="text-primary" />
|
||||
Frühbucher-Rabatte
|
||||
</h3>
|
||||
<div className="relative flex flex-col sm:flex-row justify-between gap-6 sm:gap-0">
|
||||
{/* Connector line (desktop only) */}
|
||||
<div className="hidden sm:block absolute top-3 left-[50px] right-[50px] h-1 bg-gray-300" />
|
||||
|
||||
{timeline.map((item) => (
|
||||
<div
|
||||
key={item.days}
|
||||
className="flex flex-col items-center text-center relative z-10"
|
||||
>
|
||||
<div
|
||||
className={`w-6 h-6 rounded-full border-4 border-white mb-4 ${
|
||||
item.active ? 'bg-emerald-500' : 'bg-gray-300'
|
||||
}`}
|
||||
/>
|
||||
<span className="font-semibold text-gray-700 text-sm">{item.days}</span>
|
||||
<span className="text-primary font-semibold text-sm">{item.discount}</span>
|
||||
<span className="text-xl font-bold text-gray-900">{item.price}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
import { useState } from 'react'
|
||||
import { CheckCircle, AlertCircle, Check } from 'lucide-react'
|
||||
|
||||
type FormStatus = 'idle' | 'loading' | 'success' | 'error'
|
||||
|
||||
interface FormData {
|
||||
firstName: string
|
||||
lastName: string
|
||||
email: string
|
||||
company: string
|
||||
website: string
|
||||
privacy: boolean
|
||||
newsletter: boolean
|
||||
}
|
||||
|
||||
const WEBHOOK_URL = 'https://n8n.zq0.de/webhook-test/test'
|
||||
|
||||
export default function RegistrationSection() {
|
||||
const [status, setStatus] = useState<FormStatus>('idle')
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
const [responseData, setResponseData] = useState<unknown>(null)
|
||||
const [form, setForm] = useState<FormData>({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
company: '',
|
||||
website: '',
|
||||
privacy: false,
|
||||
newsletter: false,
|
||||
})
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value, type, checked } = e.target
|
||||
setForm((prev) => ({ ...prev, [name]: type === 'checkbox' ? checked : value }))
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setStatus('loading')
|
||||
setErrorMessage('')
|
||||
|
||||
try {
|
||||
const res = await fetch(WEBHOOK_URL, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
firstName: form.firstName,
|
||||
lastName: form.lastName,
|
||||
email: form.email,
|
||||
company: form.company || undefined,
|
||||
website: form.website || undefined,
|
||||
newsletter: form.newsletter,
|
||||
timestamp: new Date().toISOString(),
|
||||
source: 'landing-page',
|
||||
userAgent: navigator.userAgent,
|
||||
language: navigator.language,
|
||||
}),
|
||||
})
|
||||
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
|
||||
const contentType = res.headers.get('content-type') ?? ''
|
||||
let data: unknown = null
|
||||
if (contentType.includes('application/json')) {
|
||||
data = await res.json()
|
||||
} else {
|
||||
const text = await res.text()
|
||||
data = text || null
|
||||
}
|
||||
|
||||
setResponseData(data)
|
||||
setStatus('success')
|
||||
} catch (err) {
|
||||
console.error('[Webhook-Fehler]', err)
|
||||
setErrorMessage(err instanceof Error ? err.message : 'Unbekannter Fehler.')
|
||||
setStatus('error')
|
||||
}
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
setStatus('idle')
|
||||
setResponseData(null)
|
||||
setErrorMessage('')
|
||||
setForm({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
company: '',
|
||||
website: '',
|
||||
privacy: false,
|
||||
newsletter: false,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="register" className="py-[100px] bg-gray-50">
|
||||
<div className="max-w-container mx-auto px-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-[60px] items-center">
|
||||
{/* Info */}
|
||||
<div className="lg:text-left">
|
||||
<h2 className="text-[clamp(2rem,4vw,3rem)] font-bold text-gray-900 mb-4">
|
||||
Starten Sie Ihre{' '}
|
||||
<span className="gradient-text">7-Tage Trial</span>
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600 mb-8">
|
||||
Keine Kreditkarte erforderlich. Voller Funktionsumfang. Jederzeit kündbar.
|
||||
</p>
|
||||
<ul className="space-y-0">
|
||||
{[
|
||||
'Sofortiger Zugang',
|
||||
'Keine Zahlungsdaten nötig',
|
||||
'Persönlicher Support',
|
||||
'Alle Features inklusive',
|
||||
].map((benefit) => (
|
||||
<li key={benefit} className="flex items-center gap-3 py-3 text-gray-700">
|
||||
<Check size={20} className="text-emerald-500 flex-shrink-0" />
|
||||
{benefit}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Form Card */}
|
||||
<div className="bg-white rounded-3xl shadow-lg p-10">
|
||||
{status === 'idle' || status === 'loading' ? (
|
||||
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
||||
{/* Name Row */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="firstName" className="text-sm font-medium text-gray-700">
|
||||
Vorname *
|
||||
</label>
|
||||
<input
|
||||
id="firstName"
|
||||
name="firstName"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Max"
|
||||
value={form.firstName}
|
||||
onChange={handleChange}
|
||||
className="px-4 py-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="lastName" className="text-sm font-medium text-gray-700">
|
||||
Nachname *
|
||||
</label>
|
||||
<input
|
||||
id="lastName"
|
||||
name="lastName"
|
||||
type="text"
|
||||
required
|
||||
placeholder="Mustermann"
|
||||
value={form.lastName}
|
||||
onChange={handleChange}
|
||||
className="px-4 py-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="email" className="text-sm font-medium text-gray-700">
|
||||
E-Mail-Adresse *
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
placeholder="max@beispiel.de"
|
||||
value={form.email}
|
||||
onChange={handleChange}
|
||||
className="px-4 py-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="company" className="text-sm font-medium text-gray-700">
|
||||
Unternehmen (optional)
|
||||
</label>
|
||||
<input
|
||||
id="company"
|
||||
name="company"
|
||||
type="text"
|
||||
placeholder="Muster GmbH"
|
||||
value={form.company}
|
||||
onChange={handleChange}
|
||||
className="px-4 py-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="website" className="text-sm font-medium text-gray-700">
|
||||
Website (optional)
|
||||
</label>
|
||||
<input
|
||||
id="website"
|
||||
name="website"
|
||||
type="url"
|
||||
placeholder="https://www.beispiel.de"
|
||||
value={form.website}
|
||||
onChange={handleChange}
|
||||
className="px-4 py-3 border border-gray-300 rounded-lg text-base focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Privacy Checkbox */}
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
id="privacy"
|
||||
name="privacy"
|
||||
type="checkbox"
|
||||
required
|
||||
checked={form.privacy}
|
||||
onChange={handleChange}
|
||||
className="w-4.5 h-4.5 mt-0.5 accent-primary flex-shrink-0"
|
||||
style={{ width: 18, height: 18, accentColor: '#6366f1' }}
|
||||
/>
|
||||
<label htmlFor="privacy" className="text-sm text-gray-600 leading-snug">
|
||||
Ich akzeptiere die{' '}
|
||||
<a href="#" className="text-primary hover:text-primary-dark">
|
||||
Datenschutzerklärung
|
||||
</a>{' '}
|
||||
und{' '}
|
||||
<a href="#" className="text-primary hover:text-primary-dark">
|
||||
AGB
|
||||
</a>{' '}
|
||||
*
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* Newsletter Checkbox */}
|
||||
<div className="flex items-start gap-3">
|
||||
<input
|
||||
id="newsletter"
|
||||
name="newsletter"
|
||||
type="checkbox"
|
||||
checked={form.newsletter}
|
||||
onChange={handleChange}
|
||||
style={{ width: 18, height: 18, accentColor: '#6366f1', flexShrink: 0 }}
|
||||
/>
|
||||
<label htmlFor="newsletter" className="text-sm text-gray-600 leading-snug">
|
||||
Ich möchte Updates und Tipps per E-Mail erhalten (optional)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={status === 'loading'}
|
||||
className="btn-gradient w-full py-4 text-lg rounded-lg flex items-center justify-center gap-3 disabled:opacity-75 disabled:cursor-not-allowed mt-2"
|
||||
>
|
||||
{status === 'loading' ? (
|
||||
<>
|
||||
<span className="spinner" />
|
||||
<span>Wird erstellt…</span>
|
||||
</>
|
||||
) : (
|
||||
'Kostenlos registrieren'
|
||||
)}
|
||||
</button>
|
||||
|
||||
{status === 'loading' && (
|
||||
<p className="text-sm text-gray-500 text-center -mt-2">
|
||||
Ihre Instanz wird erstellt – dies kann bis zu 2 Minuten dauern.
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
) : status === 'success' ? (
|
||||
<SuccessCard data={responseData} />
|
||||
) : (
|
||||
<ErrorCard message={errorMessage} onReset={reset} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
/* ---- Success Card ---- */
|
||||
function SuccessCard({ data }: { data: unknown }) {
|
||||
const message =
|
||||
typeof data === 'string'
|
||||
? data
|
||||
: typeof data === 'object' && data !== null && 'message' in data
|
||||
? String((data as { message: unknown }).message)
|
||||
: null
|
||||
|
||||
return (
|
||||
<div className="text-center py-4">
|
||||
<CheckCircle size={64} className="text-emerald-500 mx-auto mb-5" />
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">Willkommen bei BotKonzept!</h3>
|
||||
{message ? (
|
||||
<p className="text-gray-700 text-base leading-relaxed">{message}</p>
|
||||
) : (
|
||||
<p className="text-gray-600">
|
||||
Ihre Registrierung wurde erfolgreich abgeschickt. Sie erhalten in Kürze eine E-Mail.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/* ---- Error Card ---- */
|
||||
function ErrorCard({ message, onReset }: { message: string; onReset: () => void }) {
|
||||
return (
|
||||
<div className="text-center py-4">
|
||||
<AlertCircle size={64} className="text-red-500 mx-auto mb-5" />
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-3">Ups, etwas ist schiefgelaufen</h3>
|
||||
<div className="bg-red-50 border border-red-200 rounded-xl px-5 py-4 mb-8 text-left">
|
||||
<p className="text-red-800 text-sm font-mono break-words">
|
||||
{message || 'Bitte versuchen Sie es später erneut.'}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onReset}
|
||||
className="border-2 border-primary text-primary px-6 py-2 rounded-lg font-semibold hover:bg-primary hover:text-white transition-all duration-300"
|
||||
>
|
||||
Erneut versuchen
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { UserPlus, Upload, Code2 } from 'lucide-react'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface Step {
|
||||
number: number
|
||||
title: string
|
||||
description: string
|
||||
icon: ReactNode
|
||||
}
|
||||
|
||||
const steps: Step[] = [
|
||||
{
|
||||
number: 1,
|
||||
title: 'Registrieren',
|
||||
description:
|
||||
'Erstellen Sie Ihr kostenloses Konto in weniger als einer Minute. Keine Kreditkarte erforderlich.',
|
||||
icon: <UserPlus size={40} />,
|
||||
},
|
||||
{
|
||||
number: 2,
|
||||
title: 'PDFs hochladen',
|
||||
description:
|
||||
'Laden Sie Ihre Dokumente hoch – FAQs, Produktinfos, Anleitungen. Der Bot lernt automatisch.',
|
||||
icon: <Upload size={40} />,
|
||||
},
|
||||
{
|
||||
number: 3,
|
||||
title: 'Code einbinden',
|
||||
description:
|
||||
'Kopieren Sie den Code-Snippet und fügen Sie ihn in Ihre Website ein. Fertig!',
|
||||
icon: <Code2 size={40} />,
|
||||
},
|
||||
]
|
||||
|
||||
export default function Steps() {
|
||||
return (
|
||||
<section id="how-it-works" className="py-[100px] bg-gray-50">
|
||||
<div className="max-w-container mx-auto px-6">
|
||||
{/* Section Header */}
|
||||
<div className="text-center max-w-[700px] mx-auto mb-[60px]">
|
||||
<span
|
||||
className="inline-block px-4 py-1.5 text-white text-xs font-semibold uppercase tracking-wider rounded-full mb-4"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
So funktioniert's
|
||||
</span>
|
||||
<h2 className="text-[clamp(2rem,4vw,3rem)] font-bold text-gray-900 mb-4">
|
||||
In 3 einfachen Schritten zum eigenen KI-Chatbot
|
||||
</h2>
|
||||
<p className="text-lg text-gray-600">Keine technischen Kenntnisse erforderlich</p>
|
||||
</div>
|
||||
|
||||
{/* Steps */}
|
||||
<div className="flex flex-col md:flex-row items-start justify-center mb-[60px]">
|
||||
{steps.map((step, idx) => (
|
||||
<>
|
||||
<div
|
||||
key={step.number}
|
||||
className="flex flex-col items-center text-center max-w-[280px] px-5"
|
||||
>
|
||||
<div
|
||||
className="w-12 h-12 rounded-full flex items-center justify-center text-white text-xl font-bold mb-5 flex-shrink-0"
|
||||
style={{ background: 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)' }}
|
||||
>
|
||||
{step.number}
|
||||
</div>
|
||||
<h3 className="text-[1.375rem] font-bold text-gray-900 mb-2">{step.title}</h3>
|
||||
<p className="text-[0.9375rem] text-gray-600">{step.description}</p>
|
||||
<div className="mt-5 text-primary-light">{step.icon}</div>
|
||||
</div>
|
||||
|
||||
{/* Connector */}
|
||||
{idx < steps.length - 1 && (
|
||||
<div
|
||||
key={`connector-${idx}`}
|
||||
className="hidden md:block w-[100px] h-0.5 bg-gray-300 mt-6 flex-shrink-0"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Code Preview */}
|
||||
<div className="max-w-[600px] mx-auto bg-gray-900 rounded-2xl overflow-hidden shadow-xl">
|
||||
<div className="flex items-center gap-2 px-4 py-3 bg-gray-800">
|
||||
<span className="w-3 h-3 rounded-full bg-[#ff5f56]" />
|
||||
<span className="w-3 h-3 rounded-full bg-[#ffbd2e]" />
|
||||
<span className="w-3 h-3 rounded-full bg-[#27ca40]" />
|
||||
<span className="ml-auto text-gray-400 text-xs">Ihr Embed-Code</span>
|
||||
</div>
|
||||
<pre className="px-5 py-5 overflow-x-auto m-0">
|
||||
<code className="text-emerald-400 font-mono text-sm">
|
||||
{`<script src="https://botkonzept.de/embed/IHRE-ID.js"></script>`}
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Building2 } from 'lucide-react'
|
||||
|
||||
const companies = ['Unternehmen A', 'Unternehmen B', 'Unternehmen C', 'Unternehmen D']
|
||||
|
||||
export default function TrustedBy() {
|
||||
return (
|
||||
<section className="py-[60px] bg-white border-b border-gray-200">
|
||||
<div className="max-w-container mx-auto px-6">
|
||||
<p className="text-center text-gray-500 text-sm uppercase tracking-widest mb-6">
|
||||
Vertraut von innovativen Unternehmen
|
||||
</p>
|
||||
<div className="flex justify-center items-center gap-12 flex-wrap">
|
||||
{companies.map((name) => (
|
||||
<div key={name} className="text-gray-400 text-sm flex items-center gap-2">
|
||||
<Building2 size={16} />
|
||||
{name}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@apply text-gray-700 bg-white;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.bg-gradient-primary {
|
||||
background: linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%);
|
||||
}
|
||||
|
||||
.btn-gradient {
|
||||
background: linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%);
|
||||
@apply text-white font-semibold rounded-lg transition-all duration-300;
|
||||
}
|
||||
|
||||
.btn-gradient:hover {
|
||||
@apply -translate-y-0.5 shadow-lg;
|
||||
}
|
||||
}
|
||||
|
||||
/* Typing-Dots-Animation */
|
||||
@keyframes typing-bounce {
|
||||
0%, 60%, 100% { transform: translateY(0); }
|
||||
30% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
.typing-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #9ca3af;
|
||||
border-radius: 50%;
|
||||
animation: typing-bounce 1.4s infinite ease-in-out;
|
||||
}
|
||||
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
||||
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
||||
|
||||
/* Gradient Orbs */
|
||||
.orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
opacity: 0.4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* FAQ-Accordion-Transition */
|
||||
.faq-answer {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.35s ease;
|
||||
}
|
||||
.faq-answer.open {
|
||||
max-height: 600px;
|
||||
}
|
||||
|
||||
/* Spinner */
|
||||
@keyframes spin-anim {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid rgba(255,255,255,0.3);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin-anim 0.8s linear infinite;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Scroll-Reveal */
|
||||
.scroll-animate {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
transition: opacity 0.6s ease, transform 0.6s ease;
|
||||
}
|
||||
.scroll-animate.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
DEFAULT: '#6366f1',
|
||||
dark: '#4f46e5',
|
||||
light: '#818cf8',
|
||||
},
|
||||
secondary: '#0ea5e9',
|
||||
accent: '#f59e0b',
|
||||
success: '#10b981',
|
||||
error: '#ef4444',
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'sans-serif'],
|
||||
},
|
||||
backgroundImage: {
|
||||
'gradient-primary': 'linear-gradient(135deg, #6366f1 0%, #0ea5e9 100%)',
|
||||
'gradient-hero': 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
},
|
||||
maxWidth: {
|
||||
container: '1200px',
|
||||
},
|
||||
keyframes: {
|
||||
typing: {
|
||||
'0%, 60%, 100%': { transform: 'translateY(0)' },
|
||||
'30%': { transform: 'translateY(-8px)' },
|
||||
},
|
||||
fadeInUp: {
|
||||
from: { opacity: '0', transform: 'translateY(30px)' },
|
||||
to: { opacity: '1', transform: 'translateY(0)' },
|
||||
},
|
||||
spin: {
|
||||
to: { transform: 'rotate(360deg)' },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
typing: 'typing 1.4s infinite ease-in-out',
|
||||
'typing-2': 'typing 1.4s 0.2s infinite ease-in-out',
|
||||
'typing-3': 'typing 1.4s 0.4s infinite ease-in-out',
|
||||
'fade-in-up': 'fadeInUp 0.6s ease forwards',
|
||||
'spin-slow': 'spin 0.8s linear infinite',
|
||||
},
|
||||
boxShadow: {
|
||||
xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Reference in New Issue
Block a user