feat(landing): Move landing into separate repository

This commit is contained in:
2026-01-17 08:37:57 +01:00
parent 78b340bbc6
commit 643b6d13ef
48 changed files with 0 additions and 19086 deletions

24
landing/.gitignore vendored
View File

@@ -1,24 +0,0 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

View File

@@ -1 +0,0 @@
shamefully-hoist=true

View File

@@ -1 +0,0 @@
22

View File

@@ -1,7 +0,0 @@
# Ignore artifacts:
build
coverage
.nuxt
.output
.api-client
pnpm-lock.yaml

View File

@@ -1,6 +0,0 @@
{
"trailingComma": "none",
"semi": false,
"singleQuote": true,
"printWidth": 120
}

View File

@@ -1,28 +0,0 @@
FROM node:22.16.0-alpine AS builder
WORKDIR /app
RUN npm install -g pnpm@10.13.1
COPY landing/package.json landing/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY landing/ .
RUN pnpm build
FROM node:22.16.0-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.output /app/.output
ENV NODE_ENV=production
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

View File

@@ -1,75 +0,0 @@
# Nuxt Minimal Starter
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

View File

@@ -1,119 +0,0 @@
export default defineAppConfig({
ui: {
// Primary color for the app - Teal
colors: {
primary: 'primary',
neutral: 'slate',
success: 'success',
warning: 'warning',
error: 'red'
},
// Button customizations with custom gradient variants
button: {
defaultVariants: {
size: 'lg'
},
variants: {
variant: {
gradient:
'bg-gradient-to-br from-primary-500 to-cyan-500 text-white font-semibold rounded-xl shadow-lg shadow-primary-500/25 hover:from-cyan-500 hover:to-accent-500 hover:shadow-xl hover:shadow-primary-500/30 transition-all',
gradientOutline:
'bg-white dark:bg-gray-900 border-2 border-transparent bg-clip-padding font-semibold rounded-xl text-primary-600 dark:text-primary-400 ring-2 ring-primary-500 hover:bg-primary-50 dark:hover:bg-primary-950 transition-all'
}
}
},
// Card customizations with enhanced hover states and glass variant
card: {
slots: {
root: 'rounded-2xl overflow-hidden transition-all duration-300'
},
variants: {
variant: {
glass: ''
}
},
compoundVariants: [
{
variant: 'glass',
class: {
root: 'bg-white/70 dark:bg-black/40 backdrop-blur-xl border border-white/30 dark:border-white/10'
}
}
]
},
// Page Hero customizations for landing page
pageHero: {
slots: {
root: 'relative isolate overflow-hidden',
title: 'font-heading text-5xl sm:text-6xl lg:text-7xl text-pretty tracking-tight font-bold text-highlighted',
description: 'text-lg sm:text-xl text-muted max-w-3xl'
}
},
// Page Section customizations
pageSection: {
slots: {
title: 'font-heading text-3xl sm:text-4xl lg:text-5xl text-pretty tracking-tight font-bold text-highlighted',
description: 'text-base sm:text-lg text-muted'
}
},
// Page Feature customizations
pageFeature: {
slots: {
title: 'text-lg font-semibold text-highlighted',
description: 'text-base text-muted'
}
},
// Page Card customizations
pageCard: {
slots: {
root: 'rounded-2xl transition-all duration-300',
title: 'text-lg font-semibold text-highlighted',
description: 'text-base text-muted'
}
},
// Page CTA customizations
pageCTA: {
slots: {
title: 'font-heading text-3xl sm:text-4xl text-pretty tracking-tight font-bold',
description: 'text-base sm:text-lg'
}
},
// Header customizations - transparent with blur
header: {
slots: {
root: 'bg-white/80 dark:bg-gray-950/80 backdrop-blur-xl border-b border-gray-200/50 dark:border-gray-800/50',
title: 'font-heading text-xl font-bold'
}
},
// Footer customizations
footer: {
slots: {
root: 'border-t border-gray-200 dark:border-gray-800'
}
},
// Input customizations for newsletter form
input: {
defaultVariants: {
size: 'lg'
}
},
// Accordion customizations
accordion: {
slots: {
trigger: 'text-base font-medium',
content: 'text-base text-muted'
}
}
}
})

View File

@@ -1,258 +0,0 @@
<template>
<UApp>
<!-- Skip link for accessibility -->
<a href="#main-content" class="skip-link"> {{ $t('common.skipToContent') }} </a>
<NuxtRouteAnnouncer />
<!-- Header with transparent background and blur on scroll -->
<UHeader
:title="'GremiumHub'"
:ui="{
root: [
'fixed top-0 left-0 right-0 z-50 transition-all duration-300',
isScrolled
? 'bg-white/90 dark:bg-gray-950/90 backdrop-blur-xl shadow-sm border-b border-gray-200/50 dark:border-gray-800/50'
: 'bg-transparent border-transparent'
].join(' ')
}"
>
<template #title>
<div class="flex items-center gap-2 sm:gap-3">
<div
class="w-10 h-10 rounded-xl bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center shadow-lg shadow-primary-500/25"
>
<UIcon name="i-lucide-scale" class="w-5 h-5 text-white" />
</div>
<span
class="hidden sm:inline font-heading text-xl font-bold bg-linear-to-r from-primary-600 to-cyan-600 bg-clip-text text-transparent"
>
GremiumHub
</span>
</div>
</template>
<UNavigationMenu
:items="navigationItems"
:ui="{
link: 'text-gray-700 dark:text-gray-200 hover:text-primary-600 dark:hover:text-primary-400 font-medium transition-colors'
}"
/>
<template #right>
<UDropdownMenu
:items="localeMenuItems"
:modal="false"
:ui="{ content: 'min-w-32' }"
:content="{
align: 'end'
}"
>
<UButton
icon="i-lucide-globe"
color="neutral"
variant="ghost"
:aria-label="$t('common.changeLanguage')"
class="text-gray-600 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400"
/>
</UDropdownMenu>
<UColorModeButton
:ui="{
base: 'text-gray-600 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400'
}"
/>
<UButton to="/#newsletter" variant="gradient" class="hidden sm:flex px-5 py-2.5">
{{ $t('common.stayInformed') }}
</UButton>
</template>
<template #body>
<UNavigationMenu
:items="navigationItems"
orientation="vertical"
class="-mx-2.5"
:ui="{
link: 'text-gray-700 dark:text-gray-200 hover:text-primary-600 dark:hover:text-primary-400 font-medium'
}"
/>
<div class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-800">
<UButton to="#newsletter" size="lg" block variant="gradient">
{{ $t('common.stayInformed') }}
</UButton>
</div>
</template>
</UHeader>
<!-- Main Content -->
<UMain id="main-content">
<NuxtPage />
</UMain>
<!-- Footer -->
<footer class="bg-gray-50 dark:bg-gray-900 border-t border-gray-200 dark:border-gray-800">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 lg:py-16">
<div class="grid grid-cols-1 md:grid-cols-4 gap-8 lg:gap-12">
<!-- Brand column -->
<div class="md:col-span-2">
<NuxtLink to="/" class="flex items-center gap-3 mb-4">
<div
class="w-10 h-10 rounded-xl bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center"
>
<UIcon name="i-lucide-scale" class="w-5 h-5 text-white" />
</div>
<span class="font-heading text-xl font-bold text-gray-900 dark:text-white"> GremiumHub </span>
</NuxtLink>
<p class="text-gray-600 dark:text-gray-400 max-w-sm mb-6">
{{ $t('footer.brandDescription') }}
</p>
<div class="flex items-center gap-3">
<UButton
icon="i-lucide-linkedin"
color="neutral"
variant="ghost"
to="https://linkedin.com"
target="_blank"
aria-label="LinkedIn"
class="hover:text-primary-600 dark:hover:text-primary-400"
/>
<UButton
icon="i-lucide-mail"
color="neutral"
variant="ghost"
to="/kontakt"
aria-label="E-Mail"
class="hover:text-primary-600 dark:hover:text-primary-400"
/>
</div>
</div>
<!-- Links column -->
<div>
<h4 class="font-heading font-bold text-gray-900 dark:text-white mb-4">{{ $t('common.navigation') }}</h4>
<ul class="space-y-3">
<li v-for="item in navigationItems" :key="item.label">
<NuxtLink
:to="item.to"
class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
>
{{ item.label }}
</NuxtLink>
</li>
</ul>
</div>
<!-- Legal column -->
<div>
<h4 class="font-heading font-bold text-gray-900 dark:text-white mb-4">{{ $t('common.legal') }}</h4>
<ul class="space-y-3">
<li v-for="item in footerLinks" :key="item.label">
<NuxtLink
:to="item.to"
class="text-gray-600 dark:text-gray-400 hover:text-primary-600 dark:hover:text-primary-400 transition-colors"
>
{{ item.label }}
</NuxtLink>
</li>
</ul>
</div>
</div>
<!-- Bottom bar -->
<div class="mt-12 pt-8 border-t border-gray-200 dark:border-gray-800">
<div class="flex flex-col sm:flex-row justify-between items-center gap-4">
<p class="text-sm text-gray-500 dark:text-gray-400">
© {{ new Date().getFullYear() }} GremiumHub. {{ $t('common.allRightsReserved') }}
</p>
<div class="flex items-center gap-6 text-sm text-gray-500 dark:text-gray-400">
<div class="flex items-center gap-2">
<UIcon name="i-lucide-server" class="w-4 h-4" />
<span>{{ $t('common.hostedInGermany') }}</span>
</div>
<div class="flex items-center gap-2">
<UIcon name="i-lucide-shield-check" class="w-4 h-4" />
<span>{{ $t('common.gdprCompliant') }}</span>
</div>
</div>
</div>
</div>
</div>
</footer>
</UApp>
</template>
<script setup lang="ts">
import type { NavigationMenuItem, DropdownMenuItem } from '@nuxt/ui'
const { t, locale, locales, setLocale } = useI18n({ useScope: 'global' })
// Locale dropdown menu items
const localeMenuItems = computed<DropdownMenuItem[]>(() =>
(locales.value as Array<{ code: string; name: string }>)
.map((l) => ({
label: l.name,
icon: locale.value === l.code ? 'i-lucide-check' : undefined,
active: locale.value === l.code,
onSelect: () => setLocale(l.code as 'en' | 'de')
}))
.reverse()
)
// Track scroll position for header styling
// Use useState to ensure consistent SSR/client hydration
const isScrolled = useState('header-scrolled', () => false)
onMounted(() => {
const handleScroll = () => {
isScrolled.value = window.scrollY > 50
}
window.addEventListener('scroll', handleScroll, { passive: true })
// Defer initial scroll check to avoid hydration mismatch
nextTick(() => {
handleScroll()
})
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
})
})
const navigationItems = computed<NavigationMenuItem[]>(() => [
{
label: t('nav.forWorksCouncils'),
to: '/#betriebsraete',
active: false
},
{
label: t('nav.forCompanies'),
to: '/unternehmen',
active: false
},
{
label: t('nav.features'),
to: '/#features',
active: false
},
{
label: t('nav.team'),
to: '/team',
active: false
},
{
label: t('nav.contact'),
to: '/kontakt',
active: false
}
])
const footerLinks = computed<NavigationMenuItem[]>(() => [
{
label: t('footer.imprint'),
to: '/impressum'
},
{
label: t('footer.privacy'),
to: '/datenschutz'
}
])
</script>

View File

@@ -1,837 +0,0 @@
@import 'tailwindcss';
@import '@nuxt/ui';
/* Prevent horizontal overflow from decorative elements (gradient orbs, etc.) */
html,
body {
overflow-x: hidden;
}
/* Ensure all content respects container boundaries on mobile (accounts for scrollbar) */
@media (max-width: 640px) {
.grid {
max-width: 100%;
}
.grid > * {
min-width: 0;
max-width: 100%;
}
}
/* Theme configuration with new teal/cyan/violet palette */
@theme {
/* Typography - Bricolage Grotesque for headings, DM Sans for body */
--font-sans: 'DM Sans', system-ui, sans-serif;
--font-heading: 'Bricolage Grotesque', system-ui, sans-serif;
/* Primary color - Teal/Cyan gradient base */
--color-primary-50: #f0fdfa;
--color-primary-100: #ccfbf1;
--color-primary-200: #99f6e4;
--color-primary-300: #5eead4;
--color-primary-400: #2dd4bf;
--color-primary-500: #14b8a6;
--color-primary-600: #0d9488;
--color-primary-700: #0f766e;
--color-primary-800: #115e59;
--color-primary-900: #134e4a;
--color-primary-950: #042f2e;
/* Accent color - Violet for highlights */
--color-accent-50: #f5f3ff;
--color-accent-100: #ede9fe;
--color-accent-200: #ddd6fe;
--color-accent-300: #c4b5fd;
--color-accent-400: #a78bfa;
--color-accent-500: #8b5cf6;
--color-accent-600: #7c3aed;
--color-accent-700: #6d28d9;
--color-accent-800: #5b21b6;
--color-accent-900: #4c1d95;
--color-accent-950: #2e1065;
/* Cyan for gradient effects */
--color-cyan-50: #ecfeff;
--color-cyan-100: #cffafe;
--color-cyan-200: #a5f3fc;
--color-cyan-300: #67e8f9;
--color-cyan-400: #22d3ee;
--color-cyan-500: #06b6d4;
--color-cyan-600: #0891b2;
--color-cyan-700: #0e7490;
--color-cyan-800: #155e75;
--color-cyan-900: #164e63;
--color-cyan-950: #083344;
/* Success color - Emerald */
--color-success-50: #ecfdf5;
--color-success-100: #d1fae5;
--color-success-200: #a7f3d0;
--color-success-300: #6ee7b7;
--color-success-400: #34d399;
--color-success-500: #10b981;
--color-success-600: #059669;
--color-success-700: #047857;
--color-success-800: #065f46;
--color-success-900: #064e3b;
--color-success-950: #022c22;
/* Warning color - Amber */
--color-warning-50: #fffbeb;
--color-warning-100: #fef3c7;
--color-warning-200: #fde68a;
--color-warning-300: #fcd34d;
--color-warning-400: #fbbf24;
--color-warning-500: #f59e0b;
--color-warning-600: #d97706;
--color-warning-700: #b45309;
--color-warning-800: #92400e;
--color-warning-900: #78350f;
--color-warning-950: #451a03;
/* Animation durations */
--animate-duration-slow: 1s;
--animate-duration-normal: 0.5s;
--animate-duration-fast: 0.2s;
}
/* Heading styles with Bricolage Grotesque */
.font-heading {
font-family: var(--font-heading);
}
h1,
h2,
h3 {
font-family: var(--font-heading);
}
/* Gradient text effect */
.gradient-text {
background: linear-gradient(
135deg,
var(--color-primary-500) 0%,
var(--color-cyan-500) 50%,
var(--color-accent-500) 100%
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.gradient-text-teal {
background: linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-cyan-500) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* ============================================
ANIMATIONS
============================================ */
/* Fade in up animation */
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Fade in animation */
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* Float animation for orbs */
@keyframes orb-float {
0%,
100% {
transform: translate(0, 0) scale(1);
}
25% {
transform: translate(10px, -20px) scale(1.05);
}
50% {
transform: translate(-5px, -10px) scale(0.95);
}
75% {
transform: translate(-15px, -25px) scale(1.02);
}
}
/* Slower float for large elements - more noticeable movement */
@keyframes float-slow {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-15px);
}
}
/* Fast float for small elements */
@keyframes float-fast {
0%,
100% {
transform: translateY(0) rotate(0deg);
}
50% {
transform: translateY(-10px) rotate(2deg);
}
}
/* Gradient rotation for backgrounds */
@keyframes gradient-rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* Shimmer effect */
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
/* Pulse glow effect */
@keyframes pulse-glow {
0%,
100% {
opacity: 0.5;
transform: scale(1);
}
50% {
opacity: 0.8;
transform: scale(1.05);
}
}
/* Particle drift */
@keyframes particle-drift {
0% {
transform: translate(0, 0) rotate(0deg);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translate(var(--drift-x, 100px), var(--drift-y, -100px)) rotate(360deg);
opacity: 0;
}
}
/* Scale in animation */
@keyframes scale-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* Border gradient animation */
@keyframes border-dance {
0%,
100% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
}
/* Count up number animation */
@keyframes count-pulse {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.02);
}
}
/* Tilt hover animation */
@keyframes tilt-hover {
0%,
100% {
transform: perspective(1000px) rotateX(0deg) rotateY(0deg);
}
25% {
transform: perspective(1000px) rotateX(2deg) rotateY(-2deg);
}
75% {
transform: perspective(1000px) rotateX(-2deg) rotateY(2deg);
}
}
/* ============================================
ANIMATION UTILITY CLASSES
============================================ */
.animate-fade-in-up {
animation: fade-in-up 0.7s ease-out forwards;
}
.animate-fade-in {
animation: fade-in 0.5s ease-out forwards;
}
.animate-orb-float {
animation: orb-float 20s ease-in-out infinite;
}
.animate-float-slow {
animation: float-slow 4s ease-in-out infinite;
}
.animate-float-fast {
animation: float-fast 4s ease-in-out infinite;
}
.animate-gradient-rotate {
animation: gradient-rotate 20s linear infinite;
}
.animate-shimmer {
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
background-size: 200% 100%;
animation: shimmer 2s infinite;
}
.animate-pulse-glow {
animation: pulse-glow 3s ease-in-out infinite;
}
.animate-scale-in {
animation: scale-in 0.6s ease-out forwards;
}
.animate-border-dance {
background-size: 200% 200%;
animation: border-dance 4s ease infinite;
}
.animate-tilt-hover {
animation: tilt-hover 6s ease-in-out infinite;
}
/* Staggered animation delays */
.delay-75 {
animation-delay: 75ms;
}
.delay-100 {
animation-delay: 100ms;
}
.delay-150 {
animation-delay: 150ms;
}
.delay-200 {
animation-delay: 200ms;
}
.delay-300 {
animation-delay: 300ms;
}
.delay-400 {
animation-delay: 400ms;
}
.delay-500 {
animation-delay: 500ms;
}
.delay-600 {
animation-delay: 600ms;
}
.delay-700 {
animation-delay: 700ms;
}
.delay-800 {
animation-delay: 800ms;
}
.delay-1000 {
animation-delay: 1000ms;
}
/* Initial state for animated elements */
.animate-on-scroll {
opacity: 0;
}
.animate-on-scroll.is-visible {
animation: fade-in-up 0.7s ease-out forwards;
}
/* ============================================
REDUCED MOTION SUPPORT
============================================ */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
.animate-orb-float,
.animate-float-slow,
.animate-float-fast,
.animate-gradient-rotate,
.animate-shimmer,
.animate-pulse-glow,
.animate-border-dance,
.animate-tilt-hover {
animation: none;
}
}
/* ============================================
GRADIENT BACKGROUNDS
============================================ */
/* Light hero gradient with orbs */
.hero-gradient-light {
background: linear-gradient(180deg, #ffffff 0%, #f0fdfa 50%, #ecfeff 100%);
}
/* Dark hero gradient */
.dark .hero-gradient-light {
background: linear-gradient(180deg, #0a0a0a 0%, #042f2e 50%, #083344 100%);
}
/* Gradient orb styles */
.gradient-orb {
position: absolute;
border-radius: 50%;
filter: blur(80px);
opacity: 0.6;
}
.gradient-orb-teal {
background: linear-gradient(135deg, var(--color-primary-400), var(--color-cyan-400));
}
.gradient-orb-violet {
background: linear-gradient(135deg, var(--color-accent-400), var(--color-accent-600));
}
.gradient-orb-cyan {
background: linear-gradient(135deg, var(--color-cyan-300), var(--color-primary-400));
}
.dark .gradient-orb {
opacity: 0.3;
}
/* Mesh gradient for CTAs */
.mesh-gradient-cta {
background:
radial-gradient(at 40% 20%, var(--color-primary-500) 0px, transparent 50%),
radial-gradient(at 80% 0%, var(--color-cyan-400) 0px, transparent 50%),
radial-gradient(at 0% 50%, var(--color-accent-500) 0px, transparent 50%),
radial-gradient(at 80% 50%, var(--color-primary-400) 0px, transparent 50%),
radial-gradient(at 0% 100%, var(--color-cyan-500) 0px, transparent 50%),
linear-gradient(135deg, var(--color-primary-600) 0%, var(--color-accent-600) 100%);
}
/* ============================================
GLASS MORPHISM
============================================ */
.glass {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.dark .glass {
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.glass-subtle {
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.dark .glass-subtle {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.05);
}
/* ============================================
CARD EFFECTS
============================================ */
/* Card with gradient border on hover */
.card-gradient-border {
position: relative;
background: white;
border-radius: 1rem;
transition: all 0.3s ease;
}
.card-gradient-border::before {
content: '';
position: absolute;
inset: -2px;
border-radius: inherit;
background: linear-gradient(135deg, var(--color-primary-400), var(--color-cyan-400), var(--color-accent-400));
opacity: 0;
transition: opacity 0.3s ease;
z-index: -1;
}
.card-gradient-border:hover::before {
opacity: 1;
}
.dark .card-gradient-border {
background: #1a1a1a;
}
/* Card hover lift effect - subtle version without clipping issues */
.card-hover-lift {
transition:
transform 0.3s ease,
box-shadow 0.3s ease;
}
.card-hover-lift:hover {
transform: translateY(-4px);
box-shadow:
0 10px 25px -5px rgba(0, 0, 0, 0.1),
0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
/* Spotlight effect for cards */
.card-spotlight {
position: relative;
overflow: hidden;
}
.card-spotlight::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(
600px circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
rgba(20, 184, 166, 0.1),
transparent 40%
);
opacity: 0;
transition: opacity 0.3s ease;
pointer-events: none;
}
.card-spotlight:hover::after {
opacity: 1;
}
/* 3D tilt effect */
.card-3d {
transform-style: preserve-3d;
perspective: 1000px;
}
.card-3d-inner {
transition: transform 0.3s ease;
transform-style: preserve-3d;
}
.card-3d:hover .card-3d-inner {
transform: rotateX(5deg) rotateY(-5deg);
}
/* ============================================
BUTTON STYLES
============================================ */
/* Gradient button */
.btn-gradient {
background: linear-gradient(135deg, var(--color-primary-500) 0%, var(--color-cyan-500) 100%);
color: white;
border: none;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
}
.btn-gradient::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, var(--color-cyan-500) 0%, var(--color-accent-500) 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.btn-gradient:hover::before {
opacity: 1;
}
.btn-gradient span {
position: relative;
z-index: 1;
}
/* Outline gradient button */
.btn-outline-gradient {
position: relative;
background: transparent;
border: 2px solid transparent;
background-image:
linear-gradient(white, white), linear-gradient(135deg, var(--color-primary-500), var(--color-cyan-500));
background-origin: border-box;
background-clip: padding-box, border-box;
transition: all 0.3s ease;
}
.dark .btn-outline-gradient {
background-image:
linear-gradient(#1a1a1a, #1a1a1a), linear-gradient(135deg, var(--color-primary-500), var(--color-cyan-500));
}
.btn-outline-gradient:hover {
background-image:
linear-gradient(var(--color-primary-50), var(--color-primary-50)),
linear-gradient(135deg, var(--color-primary-500), var(--color-cyan-500));
}
.dark .btn-outline-gradient:hover {
background-image:
linear-gradient(var(--color-primary-950), var(--color-primary-950)),
linear-gradient(135deg, var(--color-primary-500), var(--color-cyan-500));
}
/* ============================================
DECORATIVE ELEMENTS
============================================ */
/* Dots pattern */
.dots-pattern {
background-image: radial-gradient(var(--color-primary-200) 1px, transparent 1px);
background-size: 24px 24px;
}
.dark .dots-pattern {
background-image: radial-gradient(var(--color-primary-800) 1px, transparent 1px);
}
/* Grid pattern */
.grid-pattern {
background-image:
linear-gradient(to right, var(--color-primary-100) 1px, transparent 1px),
linear-gradient(to bottom, var(--color-primary-100) 1px, transparent 1px);
background-size: 40px 40px;
}
.dark .grid-pattern {
background-image:
linear-gradient(to right, var(--color-primary-900) 1px, transparent 1px),
linear-gradient(to bottom, var(--color-primary-900) 1px, transparent 1px);
}
/* Section divider with gradient */
.section-divider {
height: 1px;
background: linear-gradient(90deg, transparent, var(--color-primary-300), var(--color-cyan-300), transparent);
}
.dark .section-divider {
background: linear-gradient(90deg, transparent, var(--color-primary-700), var(--color-cyan-700), transparent);
}
/* Gradient line accent */
.gradient-line {
height: 4px;
border-radius: 2px;
background: linear-gradient(90deg, var(--color-primary-500), var(--color-cyan-500), var(--color-accent-500));
}
/* ============================================
ACCESSIBILITY
============================================ */
/* Focus styles */
:focus-visible {
outline: 2px solid var(--color-primary-500);
outline-offset: 2px;
}
/* Skip link */
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: var(--color-primary-600);
color: white;
padding: 8px 16px;
z-index: 100;
transition: top 0.3s;
border-radius: 0 0 8px 0;
}
.skip-link:focus {
top: 0;
}
/* ============================================
PARTICLES (for newsletter section)
============================================ */
.particle {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-primary-400);
opacity: 0;
animation: particle-drift 8s ease-in-out infinite;
}
.particle:nth-child(1) {
--drift-x: 80px;
--drift-y: -120px;
animation-delay: 0s;
left: 10%;
top: 80%;
}
.particle:nth-child(2) {
--drift-x: -60px;
--drift-y: -100px;
animation-delay: 1s;
left: 20%;
top: 70%;
}
.particle:nth-child(3) {
--drift-x: 100px;
--drift-y: -80px;
animation-delay: 2s;
left: 80%;
top: 90%;
}
.particle:nth-child(4) {
--drift-x: -80px;
--drift-y: -140px;
animation-delay: 3s;
left: 70%;
top: 75%;
}
.particle:nth-child(5) {
--drift-x: 60px;
--drift-y: -90px;
animation-delay: 4s;
left: 30%;
top: 85%;
}
.particle:nth-child(6) {
--drift-x: -100px;
--drift-y: -110px;
animation-delay: 5s;
left: 90%;
top: 80%;
}
.particle:nth-child(7) {
--drift-x: 40px;
--drift-y: -130px;
animation-delay: 6s;
left: 50%;
top: 95%;
}
.particle:nth-child(8) {
--drift-x: -40px;
--drift-y: -70px;
animation-delay: 7s;
left: 40%;
top: 70%;
}
/* Particle color variations */
.particle:nth-child(odd) {
background: var(--color-cyan-400);
}
.particle:nth-child(3n) {
background: var(--color-accent-400);
width: 8px;
height: 8px;
}
/* ============================================
BENTO GRID
============================================ */
.bento-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(200px, auto);
gap: 1.5rem;
}
.bento-item-large {
grid-column: span 2;
grid-row: span 2;
}
.bento-item-tall {
grid-row: span 2;
}
.bento-item-wide {
grid-column: span 2;
}
@media (max-width: 1024px) {
.bento-grid {
grid-template-columns: repeat(2, 1fr);
}
.bento-item-large {
grid-column: span 2;
grid-row: span 1;
}
}
@media (max-width: 640px) {
.bento-grid {
grid-template-columns: 1fr;
}
.bento-item-large,
.bento-item-wide {
grid-column: span 1;
}
.bento-item-tall {
grid-row: span 1;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

View File

@@ -1,105 +0,0 @@
<template>
<UPageSection
:ui="{
root: 'bg-gray-50 dark:bg-gray-900/50'
}"
>
<template #title>
<div class="flex flex-col items-center text-center mb-4">
<span
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-success-100 dark:bg-success-900/30 text-success-700 dark:text-success-300 text-sm font-medium mb-4"
>
<UIcon name="i-lucide-shield" class="w-4 h-4" />
{{ $t('additionalFeatures.badge') }}
</span>
<h2
class="font-heading text-3xl sm:text-4xl lg:text-5xl text-pretty tracking-tight font-bold text-gray-900 dark:text-white"
>
{{ $t('additionalFeatures.title', { highlight: '' })
}}<span class="gradient-text">{{ $t('additionalFeatures.titleHighlight') }}</span>
</h2>
</div>
</template>
<template #description>
<p class="text-center text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
{{ $t('additionalFeatures.description') }}
</p>
</template>
<!-- Icon grid with hover animations -->
<div class="mt-12 grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
<div
v-for="(feature, index) in features"
:key="index"
class="group animate-fade-in-up"
:style="{ animationDelay: `${index * 100}ms` }"
>
<div
class="h-full p-6 rounded-2xl bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 hover:border-transparent transition-all relative overflow-hidden"
>
<!-- Gradient border on hover -->
<div
class="absolute inset-0 rounded-2xl bg-linear-to-br from-primary-500 via-cyan-500 to-accent-500 opacity-0 group-hover:opacity-100 transition-opacity -z-10"
/>
<div class="absolute inset-[2px] rounded-[14px] bg-white dark:bg-gray-900 -z-10" />
<div class="flex items-start gap-4">
<!-- Icon with gradient background -->
<div class="relative shrink-0">
<div
class="w-14 h-14 rounded-2xl bg-linear-to-br from-primary-100 to-cyan-100 dark:from-primary-900/50 dark:to-cyan-900/50 flex items-center justify-center group-hover:scale-110 transition-transform"
>
<UIcon :name="feature.icon" class="w-7 h-7 text-primary-600 dark:text-primary-400" />
</div>
<!-- Animated ring on hover -->
<div
class="absolute inset-0 rounded-2xl ring-2 ring-primary-500/0 group-hover:ring-primary-500/50 transition-all scale-100 group-hover:scale-125 opacity-0 group-hover:opacity-100"
/>
</div>
<div class="flex-1">
<h3
class="font-heading text-lg font-bold text-gray-900 dark:text-white mb-2 group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors"
>
{{ feature.title }}
</h3>
<p class="text-sm text-gray-600 dark:text-gray-300 leading-relaxed">
{{ feature.description }}
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Bottom CTA -->
<div class="mt-12 text-center">
<p class="text-gray-500 dark:text-gray-400 mb-4">{{ $t('additionalFeatures.ctaQuestion') }}</p>
<UButton to="/kontakt" variant="gradientOutline" leading-icon="i-lucide-message-circle">
{{ $t('additionalFeatures.ctaButton') }}
</UButton>
</div>
</UPageSection>
</template>
<script setup lang="ts">
const { t } = useI18n({ useScope: 'global' })
const featureIcons = [
'i-lucide-puzzle',
'i-lucide-server',
'i-lucide-lock',
'i-lucide-sparkles',
'i-lucide-settings',
'i-lucide-headphones'
]
const features = computed(() =>
featureIcons.map((icon, index) => ({
icon,
title: t(`additionalFeatures.items[${index}].title`),
description: t(`additionalFeatures.items[${index}].description`)
}))
)
</script>

View File

@@ -1,101 +0,0 @@
<template>
<div class="absolute inset-0 overflow-hidden pointer-events-none" :class="variant">
<!-- Animated gradient orbs -->
<div
v-for="(orb, index) in orbs"
:key="index"
class="absolute rounded-full blur-3xl animate-float"
:class="orb.color"
:style="{
width: orb.size,
height: orb.size,
top: orb.top,
left: orb.left,
right: orb.right,
bottom: orb.bottom,
animationDelay: `${index * 2}s`,
animationDuration: `${6 + index * 2}s`
}"
/>
<!-- Grid pattern -->
<div v-if="showGrid" class="absolute inset-0 dots-pattern opacity-20" />
<!-- Animated lines (for tech feel) -->
<svg v-if="showLines" class="absolute inset-0 w-full h-full opacity-10" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" class="[stop-color:var(--color-primary-500)]" stop-opacity="0" />
<stop offset="50%" class="[stop-color:var(--color-primary-500)]" stop-opacity="1" />
<stop offset="100%" class="[stop-color:var(--color-primary-500)]" stop-opacity="0" />
</linearGradient>
</defs>
<!-- Horizontal animated lines -->
<g v-for="i in 3" :key="`h-${i}`">
<line
x1="0"
:y1="`${20 + i * 25}%`"
x2="100%"
:y2="`${20 + i * 25}%`"
stroke="url(#lineGradient)"
stroke-width="1"
class="animate-pulse-soft"
:style="{ animationDelay: `${i * 0.5}s` }"
/>
</g>
</svg>
<!-- Floating particles -->
<div v-if="showParticles" class="absolute inset-0">
<div
v-for="i in 12"
:key="`particle-${i}`"
class="absolute w-1 h-1 rounded-full bg-primary-400/30 animate-float"
:style="{
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 5}s`,
animationDuration: `${4 + Math.random() * 4}s`
}"
/>
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
variant?: 'hero' | 'section' | 'cta'
showGrid?: boolean
showLines?: boolean
showParticles?: boolean
}
const props = withDefaults(defineProps<Props>(), {
variant: 'section',
showGrid: true,
showLines: false,
showParticles: false
})
const orbs = computed(() => {
switch (props.variant) {
case 'hero':
return [
{ size: '400px', top: '-100px', left: '-100px', color: 'bg-primary-500/20' },
{ size: '300px', top: '200px', right: '-50px', color: 'bg-success-500/15' },
{ size: '250px', bottom: '-50px', left: '30%', color: 'bg-primary-400/15' }
]
case 'cta':
return [
{ size: '300px', top: '-50px', right: '-100px', color: 'bg-white/10' },
{ size: '200px', bottom: '-50px', left: '-50px', color: 'bg-white/5' }
]
default:
return [
{ size: '200px', top: '10%', left: '-50px', color: 'bg-primary-500/10' },
{ size: '150px', bottom: '20%', right: '-30px', color: 'bg-success-500/10' }
]
}
})
</script>

View File

@@ -1,177 +0,0 @@
<template>
<UPageSection
id="unternehmen"
:ui="{
root: 'scroll-mt-20 bg-gray-50 dark:bg-gray-900/50'
}"
>
<template #title>
<div class="flex flex-col items-center text-center mb-4">
<span
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-accent-100 dark:bg-accent-900/30 text-accent-700 dark:text-accent-300 text-sm font-medium mb-4"
>
<UIcon name="i-lucide-building-2" class="w-4 h-4" />
{{ $t('company.badge') }}
</span>
<h2
class="font-heading text-3xl sm:text-4xl lg:text-5xl text-pretty tracking-tight font-bold text-gray-900 dark:text-white"
>
{{ $t('company.title', { highlight: '' })
}}<span class="gradient-text">{{ $t('company.titleHighlight') }}</span>
</h2>
</div>
</template>
<template #description>
<p class="text-center text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
{{ $t('company.description') }}
</p>
</template>
<!-- Comparison view -->
<div class="mt-12 grid lg:grid-cols-2 gap-8 lg:gap-12">
<!-- Pain points column -->
<div class="space-y-6">
<div class="flex items-center gap-3 mb-6">
<div
class="w-12 h-12 rounded-2xl bg-linear-to-br from-warning-500 to-orange-500 flex items-center justify-center shadow-lg shadow-warning-500/25"
>
<UIcon name="i-lucide-x" class="w-6 h-6 text-white" />
</div>
<div>
<h3 class="font-heading text-xl font-bold text-gray-900 dark:text-white">
{{ $t('company.awayFrom.title') }}
</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ $t('company.awayFrom.subtitle') }}</p>
</div>
</div>
<div class="space-y-3">
<div
v-for="(point, index) in painPoints"
:key="index"
class="group flex gap-4 p-4 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:border-warning-300 dark:hover:border-warning-600 transition-all animate-fade-in-up"
:style="{ animationDelay: `${index * 75}ms` }"
>
<div
class="mt-0.5 w-8 h-8 rounded-lg bg-warning-100 dark:bg-warning-900/30 flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform"
>
<UIcon :name="point.icon" class="w-4 h-4 text-warning-600 dark:text-warning-400" />
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">{{ point.text }}</p>
</div>
</div>
</div>
<!-- Benefits column -->
<div class="space-y-6">
<div class="flex items-center gap-3 mb-6">
<div
class="w-12 h-12 rounded-2xl bg-linear-to-br from-success-500 to-emerald-500 flex items-center justify-center shadow-lg shadow-success-500/25"
>
<UIcon name="i-lucide-check" class="w-6 h-6 text-white" />
</div>
<div>
<h3 class="font-heading text-xl font-bold text-gray-900 dark:text-white">
{{ $t('company.towards.title') }}
</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ $t('company.towards.subtitle') }}</p>
</div>
</div>
<div class="space-y-3">
<div
v-for="(benefit, index) in benefits"
:key="index"
class="group flex gap-4 p-4 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:border-success-300 dark:hover:border-success-600 transition-all animate-fade-in-up"
:style="{ animationDelay: `${index * 75 + 100}ms` }"
>
<div
class="mt-0.5 w-8 h-8 rounded-lg bg-success-100 dark:bg-success-900/30 flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform"
>
<UIcon :name="benefit.icon" class="w-4 h-4 text-success-600 dark:text-success-400" />
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">{{ benefit.text }}</p>
</div>
</div>
</div>
</div>
<!-- Highlights section -->
<div class="mt-12">
<div class="section-divider mb-8" />
<h3 class="font-heading text-2xl sm:text-3xl text-gray-900 dark:text-white text-center mb-10">
{{ $t('company.highlightsTitle', { highlight: '' })
}}<span class="gradient-text">{{ $t('company.highlightsTitleHighlight') }}</span>
</h3>
<div class="grid sm:grid-cols-3 gap-6">
<div
v-for="(highlight, index) in highlights"
:key="index"
class="group animate-fade-in-up"
:style="{ animationDelay: `${index * 150}ms` }"
>
<div
class="h-full p-6 rounded-2xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:shadow-xl hover:shadow-accent-500/10 hover:border-accent-200 dark:hover:border-accent-700 transition-all"
>
<div
class="w-12 h-12 rounded-2xl bg-linear-to-br from-accent-500 to-violet-500 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform shadow-lg shadow-accent-500/25"
>
<UIcon :name="highlight.icon" class="w-6 h-6 text-white" />
</div>
<h4 class="font-heading text-lg font-bold text-gray-900 dark:text-white mb-2">{{ highlight.title }}</h4>
<p class="text-sm text-gray-600 dark:text-gray-300">{{ highlight.description }}</p>
</div>
</div>
</div>
</div>
</UPageSection>
</template>
<script setup lang="ts">
const { t } = useI18n({ useScope: 'global' })
const painPointIcons = [
'i-lucide-refresh-cw',
'i-lucide-ban',
'i-lucide-files',
'i-lucide-puzzle',
'i-lucide-help-circle',
'i-lucide-hourglass',
'i-lucide-wallet'
]
const benefitIcons = [
'i-lucide-calendar-check',
'i-lucide-users',
'i-lucide-gauge',
'i-lucide-thumbs-up',
'i-lucide-file-check-2',
'i-lucide-sparkles'
]
const highlightIcons = ['i-lucide-repeat', 'i-lucide-rocket', 'i-lucide-refresh-cw']
const painPoints = computed(() =>
painPointIcons.map((icon, index) => ({
icon,
text: t(`company.painPoints[${index}]`)
}))
)
const benefits = computed(() =>
benefitIcons.map((icon, index) => ({
icon,
text: t(`company.benefits[${index}]`)
}))
)
const highlights = computed(() =>
highlightIcons.map((icon, index) => ({
icon,
title: t(`company.highlights[${index}].title`),
description: t(`company.highlights[${index}].description`)
}))
)
</script>

View File

@@ -1,205 +0,0 @@
<template>
<UPageSection
id="betriebsraete"
:ui="{
root: 'scroll-mt-20'
}"
>
<template #title>
<div class="flex flex-col items-center text-center mb-4">
<span
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-sm font-medium mb-4"
>
<UIcon name="i-lucide-users" class="w-4 h-4" />
{{ $t('worksCouncil.badge') }}
</span>
<h2
class="font-heading text-3xl sm:text-4xl lg:text-5xl text-pretty tracking-tight font-bold text-gray-900 dark:text-white"
>
{{ $t('worksCouncil.title', { highlight: '' })
}}<span class="gradient-text">{{ $t('worksCouncil.titleHighlight') }}</span>
</h2>
</div>
</template>
<template #description>
<p class="text-center text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
{{ $t('worksCouncil.description') }}
</p>
</template>
<!-- Comparison view -->
<div class="mt-12 grid lg:grid-cols-2 gap-8 lg:gap-12">
<!-- Pain points column -->
<div class="space-y-6">
<div class="flex items-center gap-3 mb-6">
<div
class="w-12 h-12 rounded-2xl bg-linear-to-br from-warning-500 to-orange-500 flex items-center justify-center shadow-lg shadow-warning-500/25"
>
<UIcon name="i-lucide-x" class="w-6 h-6 text-white" />
</div>
<div>
<h3 class="font-heading text-xl font-bold text-gray-900 dark:text-white">
{{ $t('worksCouncil.tabs.awayFrom') }}
</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ $t('worksCouncil.awayFromSubtitle') }}</p>
</div>
</div>
<div class="space-y-3">
<div
v-for="(point, index) in painPoints"
:key="index"
class="group flex gap-4 p-4 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:border-warning-300 dark:hover:border-warning-600 transition-all animate-fade-in-up"
:style="{ animationDelay: `${index * 75}ms` }"
>
<div
class="mt-0.5 w-8 h-8 rounded-lg bg-warning-100 dark:bg-warning-900/30 flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform"
>
<UIcon :name="point.icon" class="w-4 h-4 text-warning-600 dark:text-warning-400" />
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">{{ point.text }}</p>
</div>
</div>
</div>
<!-- Benefits column -->
<div class="space-y-6">
<div class="flex items-center gap-3 mb-6">
<div
class="w-12 h-12 rounded-2xl bg-linear-to-br from-success-500 to-emerald-500 flex items-center justify-center shadow-lg shadow-success-500/25"
>
<UIcon name="i-lucide-check" class="w-6 h-6 text-white" />
</div>
<div>
<h3 class="font-heading text-xl font-bold text-gray-900 dark:text-white">
{{ $t('worksCouncil.tabs.towards') }}
</h3>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ $t('worksCouncil.towardsSubtitle') }}</p>
</div>
</div>
<div class="space-y-3">
<div
v-for="(benefit, index) in benefits"
:key="index"
class="group flex gap-4 p-4 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:border-success-300 dark:hover:border-success-600 transition-all animate-fade-in-up"
:style="{ animationDelay: `${index * 75 + 100}ms` }"
>
<div
class="mt-0.5 w-8 h-8 rounded-lg bg-success-100 dark:bg-success-900/30 flex items-center justify-center shrink-0 group-hover:scale-110 transition-transform"
>
<UIcon :name="benefit.icon" class="w-4 h-4 text-success-600 dark:text-success-400" />
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">{{ benefit.text }}</p>
</div>
</div>
</div>
</div>
<!-- Extra Benefit Kicker - Highlighted summary -->
<div
class="mt-4 animate-fade-in-up"
:style="{ animationDelay: '700ms' }"
role="complementary"
:aria-label="$t('worksCouncil.extraBenefitKicker')"
>
<div
class="relative overflow-hidden rounded-2xl bg-gradient-to-r from-primary-500 via-cyan-500 to-accent-500 p-[2px]"
>
<div
class="rounded-[14px] bg-white dark:bg-gray-900 px-6 py-5 sm:px-8 sm:py-6"
>
<div class="flex items-center gap-4 sm:gap-5">
<div
class="shrink-0 w-12 h-12 sm:w-14 sm:h-14 rounded-2xl bg-gradient-to-br from-primary-500 via-cyan-500 to-accent-500 flex items-center justify-center shadow-lg shadow-primary-500/25"
>
<UIcon name="i-lucide-sparkles" class="w-6 h-6 sm:w-7 sm:h-7 text-white" />
</div>
<p class="text-base sm:text-lg font-semibold text-gray-900 dark:text-white leading-relaxed">
{{ $t('worksCouncil.extraBenefitKicker') }}
</p>
</div>
</div>
</div>
</div>
<!-- Highlights section -->
<div class="mt-12">
<div class="section-divider mb-8" />
<h3 class="font-heading text-2xl sm:text-3xl text-gray-900 dark:text-white text-center mb-10">
{{ $t('worksCouncil.highlightsTitle', { highlight: '' })
}}<span class="gradient-text">{{ $t('worksCouncil.highlightsTitleHighlight') }}</span>
</h3>
<div class="grid sm:grid-cols-3 gap-6">
<div
v-for="(highlight, index) in highlights"
:key="index"
class="group animate-fade-in-up"
:style="{ animationDelay: `${index * 150}ms` }"
>
<div
class="h-full p-6 rounded-2xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:shadow-xl hover:shadow-primary-500/10 hover:border-primary-200 dark:hover:border-primary-700 transition-all"
>
<div
class="w-12 h-12 rounded-2xl bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center mb-4 group-hover:scale-110 transition-transform shadow-lg shadow-primary-500/25"
>
<UIcon :name="highlight.icon" class="w-6 h-6 text-white" />
</div>
<h4 class="font-heading text-lg font-bold text-gray-900 dark:text-white mb-2">{{ highlight.title }}</h4>
<p class="text-sm text-gray-600 dark:text-gray-300">{{ highlight.description }}</p>
</div>
</div>
</div>
</div>
</UPageSection>
</template>
<script setup lang="ts">
const { t } = useI18n({ useScope: 'global' })
const painPointIcons = [
'i-lucide-search',
'i-lucide-git-branch',
'i-lucide-timer',
'i-lucide-brain',
'i-lucide-eye-off',
'i-lucide-file-question',
'i-lucide-calendar-x'
]
const benefitIcons = [
'i-lucide-layout-list',
'i-lucide-route',
'i-lucide-clock',
'i-lucide-git-compare',
'i-lucide-scan-eye',
'i-lucide-shield-check',
'i-lucide-file-check'
]
const highlightIcons = ['i-lucide-zap', 'i-lucide-handshake', 'i-lucide-shield']
const painPoints = computed(() =>
painPointIcons.map((icon, index) => ({
icon,
text: t(`worksCouncil.painPoints[${index}]`)
}))
)
const benefits = computed(() =>
benefitIcons.map((icon, index) => ({
icon,
text: t(`worksCouncil.benefits[${index}]`)
}))
)
const highlights = computed(() =>
highlightIcons.map((icon, index) => ({
icon,
title: t(`worksCouncil.highlights[${index}].title`),
description: t(`worksCouncil.highlights[${index}].description`)
}))
)
</script>

View File

@@ -1,178 +0,0 @@
<template>
<section id="kontakt" class="relative py-16 lg:py-20 overflow-hidden scroll-mt-20">
<!-- Mesh gradient background -->
<div class="absolute inset-0 mesh-gradient-cta" />
<!-- Animated gradient overlay -->
<div class="absolute inset-0 bg-linear-to-br from-primary-600/90 via-cyan-600/90 to-accent-600/90" />
<!-- Grid pattern -->
<div class="absolute inset-0 grid-pattern opacity-10" />
<!-- Floating elements -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="absolute top-10 left-10 w-20 h-20 rounded-full bg-white/10 animate-float-slow" />
<div
class="absolute bottom-20 right-20 w-32 h-32 rounded-full bg-white/5 animate-float-slow"
style="animation-delay: 2s"
/>
<div
class="absolute top-1/2 left-1/4 w-16 h-16 rounded-full bg-white/10 animate-float-fast"
style="animation-delay: 1s"
/>
</div>
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
<!-- Left: Content -->
<div class="text-center lg:text-left">
<span
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-white/20 backdrop-blur-sm text-white text-sm font-medium mb-6"
>
<UIcon name="i-lucide-users" class="w-4 h-4" />
{{ $t('expertAccess.badge') }}
</span>
<h2 class="font-heading text-3xl sm:text-4xl lg:text-5xl font-bold text-white mb-6 animate-fade-in-up">
{{ $t('expertAccess.title') }}
</h2>
<p class="text-lg text-white/90 mb-8 animate-fade-in-up" style="animation-delay: 100ms">
{{ $t('expertAccess.description') }}
</p>
<!-- CTA buttons -->
<div
class="flex flex-wrap justify-center lg:justify-start gap-4 animate-fade-in-up"
style="animation-delay: 200ms"
>
<UButton
to="/kontakt"
size="xl"
class="bg-white text-primary-700 hover:bg-white/90 px-8 py-4 text-lg font-semibold rounded-xl shadow-lg"
>
{{ $t('expertAccess.cta.contact') }}
<UIcon name="i-lucide-arrow-right" class="w-5 h-5 ml-2" />
</UButton>
<UButton
to="#features"
size="xl"
class="bg-white/10 text-white border-2 border-white/30 hover:bg-white/20 px-8 py-4 text-lg font-semibold rounded-xl backdrop-blur-sm"
>
{{ $t('expertAccess.cta.learnMore') }}
</UButton>
</div>
</div>
<!-- Right: Expert cards with connection lines -->
<div class="flex justify-center animate-scale-in" style="animation-delay: 300ms">
<div class="relative">
<!-- Connection lines SVG -->
<svg class="absolute inset-0 w-full h-full pointer-events-none" viewBox="0 0 400 300">
<defs>
<linearGradient id="lineGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color: rgba(255, 255, 255, 0.3)" />
<stop offset="50%" style="stop-color: rgba(255, 255, 255, 0.6)" />
<stop offset="100%" style="stop-color: rgba(255, 255, 255, 0.3)" />
</linearGradient>
</defs>
<!-- Animated connection lines -->
<path
d="M200,150 L100,80"
stroke="url(#lineGradient)"
stroke-width="2"
fill="none"
stroke-dasharray="5,5"
class="animate-pulse"
/>
<path
d="M200,150 L300,80"
stroke="url(#lineGradient)"
stroke-width="2"
fill="none"
stroke-dasharray="5,5"
class="animate-pulse"
style="animation-delay: 0.5s"
/>
<path
d="M200,150 L200,250"
stroke="url(#lineGradient)"
stroke-width="2"
fill="none"
stroke-dasharray="5,5"
class="animate-pulse"
style="animation-delay: 1s"
/>
</svg>
<!-- Center hub -->
<div class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-10">
<div
class="w-20 h-20 rounded-full bg-white/20 backdrop-blur-sm border-2 border-white/40 flex items-center justify-center animate-pulse-glow"
>
<UIcon name="i-lucide-link" class="w-8 h-8 text-white" />
</div>
</div>
<!-- Expert cards -->
<div class="grid grid-cols-2 gap-6 w-80 sm:w-96">
<!-- Legal expert card -->
<div class="col-span-1 animate-float-slow">
<div
class="bg-white/15 backdrop-blur-md rounded-2xl p-5 border border-white/20 hover:bg-white/20 transition-all"
>
<div class="w-12 h-12 rounded-xl bg-white/20 flex items-center justify-center mb-4">
<UIcon name="i-lucide-scale" class="w-6 h-6 text-white" />
</div>
<h4 class="font-heading font-bold text-white mb-1">{{ $t('expertAccess.experts.labor.title') }}</h4>
<p class="text-sm text-white/70">{{ $t('expertAccess.experts.labor.description') }}</p>
</div>
</div>
<!-- Technical expert card -->
<div class="col-span-1 animate-float-slow" style="animation-delay: 1s">
<div
class="bg-white/15 backdrop-blur-md rounded-2xl p-5 border border-white/20 hover:bg-white/20 transition-all"
>
<div class="w-12 h-12 rounded-xl bg-white/20 flex items-center justify-center mb-4">
<UIcon name="i-lucide-cpu" class="w-6 h-6 text-white" />
</div>
<h4 class="font-heading font-bold text-white mb-1">
{{ $t('expertAccess.experts.technical.title') }}
</h4>
<p class="text-sm text-white/70">{{ $t('expertAccess.experts.technical.description') }}</p>
</div>
</div>
<!-- Spacer for center positioning -->
<div class="col-span-2 h-24" />
<!-- Process integration card -->
<div class="col-span-2 animate-float-slow" style="animation-delay: 2s">
<div
class="bg-white/15 backdrop-blur-md rounded-2xl p-5 border border-white/20 hover:bg-white/20 transition-all"
>
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-white/20 flex items-center justify-center shrink-0">
<UIcon name="i-lucide-file-check" class="w-6 h-6 text-white" />
</div>
<div>
<h4 class="font-heading font-bold text-white mb-1">
{{ $t('expertAccess.experts.process.title') }}
</h4>
<p class="text-sm text-white/70">{{ $t('expertAccess.experts.process.description') }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
</script>

View File

@@ -1,69 +0,0 @@
<template>
<UPageSection
id="features"
:ui="{
root: 'scroll-mt-20'
}"
>
<template #title>
<div class="flex flex-col items-center text-center mb-4">
<span
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-cyan-100 dark:bg-cyan-900/30 text-cyan-700 dark:text-cyan-300 text-sm font-medium mb-4"
>
<UIcon name="i-lucide-sparkles" class="w-4 h-4" />
{{ $t('features.badge') }}
</span>
<h2
class="font-heading text-3xl sm:text-4xl lg:text-5xl text-pretty tracking-tight font-bold text-gray-900 dark:text-white"
>
{{ $t('features.title') }}<span class="gradient-text">{{ $t('features.titleHighlight') }}</span>{{ $t('features.titleSuffix') }}
</h2>
</div>
</template>
<template #description>
<p class="text-center text-lg text-gray-600 dark:text-gray-300 max-w-3xl mx-auto">
{{ $t('features.description') }}
</p>
</template>
<div class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-12">
<UPageCard
v-for="(feature, index) in features"
:key="index"
:icon="feature.icon"
:title="feature.title"
:description="feature.description"
variant="subtle"
spotlight
spotlight-color="primary"
class="animate-fade-in-up"
:style="{ animationDelay: `${index * 100}ms` }"
/>
</div>
</UPageSection>
</template>
<script setup lang="ts">
const { t } = useI18n({ useScope: 'global' })
const featureIcons = [
'i-lucide-route',
'i-lucide-shield-alert',
'i-lucide-gauge',
'i-lucide-history',
'i-lucide-message-square',
'i-lucide-file-text',
'i-lucide-pen-tool',
'i-lucide-bell',
'i-lucide-shield-check'
]
const features = computed(() =>
featureIcons.map((icon, index) => ({
icon,
title: t(`features.items[${index}].title`),
description: t(`features.items[${index}].description`)
}))
)
</script>

View File

@@ -1,188 +0,0 @@
<template>
<UPageSection
:ui="{
root: 'scroll-mt-20'
}"
>
<div class="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
<!-- Left: Content (first on mobile, first on desktop) -->
<div>
<span
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-accent-100 dark:bg-accent-900/30 text-accent-700 dark:text-accent-300 text-sm font-medium mb-6"
>
<UIcon name="i-lucide-file-text" class="w-4 h-4" />
{{ $t('frameworkAgreement.badge') }}
</span>
<h2
class="font-heading text-3xl sm:text-4xl lg:text-5xl text-pretty tracking-tight font-bold text-gray-900 dark:text-white mb-6"
>
{{ $t('frameworkAgreement.title', { highlight: '' })
}}<span class="gradient-text">{{ $t('frameworkAgreement.titleHighlight') }}</span>
</h2>
<p class="text-lg text-gray-600 dark:text-gray-300 mb-8">
{{ $t('frameworkAgreement.description') }}
</p>
<!-- Animated checklist -->
<ul class="space-y-4 mb-8">
<li
v-for="(point, index) in bulletPoints"
:key="index"
class="flex gap-4 animate-fade-in-up"
:style="{ animationDelay: `${index * 100}ms` }"
>
<div
class="mt-1 w-6 h-6 rounded-full bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center shrink-0"
>
<UIcon name="i-lucide-check" class="w-3.5 h-3.5 text-white" />
</div>
<p class="text-gray-700 dark:text-gray-300">{{ point }}</p>
</li>
</ul>
<!-- Price info -->
<div
class="p-4 rounded-xl bg-linear-to-r from-primary-50 to-cyan-50 dark:from-primary-950/50 dark:to-cyan-950/50 border border-primary-200 dark:border-primary-800"
>
<div class="flex items-center gap-3">
<div
class="w-10 h-10 rounded-xl bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center shrink-0"
>
<UIcon name="i-lucide-tag" class="w-5 h-5 text-white" />
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ $t('common.price') }}</p>
<p class="font-heading text-lg font-bold text-gray-900 dark:text-white">{{ $t('common.onRequest') }}</p>
</div>
</div>
</div>
<!-- CTA -->
<div class="mt-8">
<UButton to="/kontakt" size="xl" variant="gradient" trailing-icon="i-lucide-arrow-right" class="px-8 py-4 text-lg">
{{ $t('common.learnMore') }}
</UButton>
</div>
</div>
<!-- Right: 3D Document Preview (second on mobile, second on desktop) -->
<div class="flex justify-center px-4 sm:px-0 py-8 overflow-visible">
<div class="relative animate-scale-in overflow-visible">
<!-- 3D Document with tilt effect -->
<div
class="relative transform-gpu transition-transform duration-500 hover:rotate-0"
style="transform: perspective(1000px) rotateY(-8deg) rotateX(5deg)"
@mouseenter="isHovered = true"
@mouseleave="isHovered = false"
>
<!-- Shadow layer -->
<div
class="absolute inset-0 bg-gray-900/20 dark:bg-black/40 blur-2xl rounded-3xl translate-x-4 translate-y-4"
/>
<!-- Document card -->
<div
class="relative bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-2xl overflow-hidden w-full max-w-[320px] sm:max-w-sm"
>
<!-- Document header -->
<div class="bg-linear-to-r from-primary-600 to-cyan-600 px-6 py-5">
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-white/20 backdrop-blur-sm flex items-center justify-center">
<UIcon name="i-lucide-file-text" class="w-6 h-6 text-white" />
</div>
<div>
<h4 class="font-heading font-bold text-white text-lg">
{{ $t('frameworkAgreement.document.title') }}
</h4>
<p class="text-sm text-white/80">{{ $t('frameworkAgreement.document.subtitle') }}</p>
</div>
</div>
</div>
<!-- Document content preview -->
<div class="p-6">
<!-- Table of contents -->
<div class="space-y-4">
<div class="text-xs uppercase tracking-wider text-gray-500 dark:text-gray-400 font-semibold">
{{ $t('frameworkAgreement.document.tableOfContents') }}
</div>
<!-- Section items with staggered animation -->
<div
v-for="(section, index) in documentSections"
:key="index"
class="group flex items-center gap-3 p-3 rounded-xl bg-gray-50 dark:bg-gray-800 hover:bg-primary-50 dark:hover:bg-primary-900/20 transition-colors cursor-pointer"
:class="{ 'animate-fade-in-up': isHovered }"
:style="{ animationDelay: `${index * 100}ms` }"
>
<div
class="w-8 h-8 rounded-lg bg-linear-to-br from-primary-100 to-cyan-100 dark:from-primary-900/50 dark:to-cyan-900/50 flex items-center justify-center text-sm font-bold text-primary-600 dark:text-primary-400 group-hover:scale-110 transition-transform"
>
{{ index + 1 }}
</div>
<div class="flex-1 min-w-0">
<div class="text-sm font-medium text-gray-900 dark:text-white truncate">{{ section.title }}</div>
<div class="text-xs text-gray-500 dark:text-gray-400 truncate">{{ section.description }}</div>
</div>
<UIcon
:name="section.icon"
class="w-4 h-4 text-gray-400 group-hover:text-primary-500 transition-colors"
/>
</div>
</div>
<!-- Footer info -->
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<UIcon name="i-lucide-layers" class="w-4 h-4 text-gray-400" />
<span class="text-xs text-gray-500 dark:text-gray-400">{{
$t('frameworkAgreement.document.regulationBlocks')
}}</span>
</div>
<UBadge color="success" variant="soft" size="sm">
<UIcon name="i-lucide-check-circle" class="w-3 h-3 mr-1" />
{{ $t('frameworkAgreement.document.fieldTested') }}
</UBadge>
</div>
</div>
</div>
</div>
<!-- Floating badge - hidden on very small screens to prevent overflow -->
<div class="absolute -top-4 right-0 sm:-right-4 z-10 animate-float-fast">
<div
class="bg-linear-to-r from-accent-500 to-violet-500 text-white px-3 sm:px-4 py-2 rounded-full shadow-lg text-xs sm:text-sm font-semibold flex items-center gap-2"
>
<UIcon name="i-lucide-download" class="w-4 h-4" />
{{ $t('common.onRequest') }}
</div>
</div>
</div>
</div>
</div>
</div>
</UPageSection>
</template>
<script setup lang="ts">
const { t } = useI18n({ useScope: 'global' })
const isHovered = ref(false)
const bulletPointCount = 3
const bulletPoints = computed(() =>
Array.from({ length: bulletPointCount }, (_, index) => t(`frameworkAgreement.bulletPoints[${index}]`))
)
const sectionIcons = ['i-lucide-target', 'i-lucide-git-branch', 'i-lucide-shield', 'i-lucide-refresh-cw']
const documentSections = computed(() =>
sectionIcons.map((icon, index) => ({
icon,
title: t(`frameworkAgreement.document.sections[${index}].title`),
description: t(`frameworkAgreement.document.sections[${index}].description`)
}))
)
</script>

View File

@@ -1,138 +0,0 @@
<template>
<section class="relative min-h-screen flex items-center justify-center overflow-hidden hero-gradient-light">
<!-- Animated gradient orbs -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<!-- Large teal orb -->
<div
class="gradient-orb gradient-orb-teal w-[600px] h-[600px] -top-40 -left-40 animate-orb-float"
style="animation-delay: 0s"
/>
<!-- Violet orb -->
<div
class="gradient-orb gradient-orb-violet w-[500px] h-[500px] top-1/4 -right-32 animate-orb-float"
style="animation-delay: 3s"
/>
<!-- Cyan orb -->
<div
class="gradient-orb gradient-orb-cyan w-[400px] h-[400px] bottom-0 left-1/4 animate-orb-float"
style="animation-delay: 6s"
/>
<!-- Small accent orb -->
<div
class="gradient-orb gradient-orb-violet w-[200px] h-[200px] top-1/2 left-10 animate-orb-float opacity-40"
style="animation-delay: 9s"
/>
<!-- Grid pattern overlay -->
<div class="absolute inset-0 grid-pattern opacity-30" />
</div>
<!-- Content -->
<div class="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 lg:py-32">
<div class="text-center max-w-4xl mx-auto">
<!-- Badge -->
<div class="animate-fade-in-up mb-8">
<span
class="inline-flex items-center gap-2 px-4 py-2 rounded-full glass text-sm font-medium text-gray-700 dark:text-gray-200"
>
<span class="relative flex h-2 w-2">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary-400 opacity-75" />
<span class="relative inline-flex rounded-full h-2 w-2 bg-primary-500" />
</span>
{{ $t('hero.badge') }}
</span>
</div>
<!-- Title with gradient text -->
<h1 class="font-heading text-4xl sm:text-5xl md:text-6xl lg:text-7xl tracking-tight mb-6">
<span class="animate-fade-in-up block text-gray-900 dark:text-white" style="animation-delay: 100ms">
{{ $t('hero.title1') }}
</span>
<span class="animate-fade-in-up block gradient-text" style="animation-delay: 200ms">
{{ $t('hero.title2') }}
</span>
</h1>
<!-- Description -->
<p
class="animate-fade-in-up text-lg sm:text-xl text-gray-600 dark:text-gray-300 max-w-2xl mx-auto mb-10"
style="animation-delay: 300ms"
>
{{ $t('hero.description') }}
</p>
<!-- CTA Buttons -->
<div class="flex flex-wrap justify-center gap-4 animate-fade-in-up" style="animation-delay: 400ms">
<UButton size="xl" to="/kontakt" variant="gradient" trailing-icon="i-lucide-arrow-right" class="px-8 py-4 text-lg">
{{ $t('hero.cta.requestDemo') }}
</UButton>
<UButton size="xl" to="#features" variant="gradientOutline" class="px-8 py-4 text-lg">
{{ $t('hero.cta.discoverFeatures') }}
</UButton>
</div>
</div>
<!-- Floating product preview cards -->
<div class="mt-16 lg:mt-24 relative animate-scale-in" style="animation-delay: 600ms">
<!-- Mobile layout: Main card first, then status cards side-by-side -->
<div class="flex flex-col items-center gap-6 lg:hidden px-2">
<!-- Main Card (Microsoft 365) -->
<div class="w-full max-w-sm">
<LandingHeroMainCard />
</div>
<!-- Status cards wrapper - side-by-side on mobile with overflow protection -->
<div class="flex flex-row gap-3 w-full max-w-sm overflow-hidden">
<LandingHeroStatusCardRisk compact />
<LandingHeroStatusCardCompleted compact />
</div>
</div>
<!-- Desktop layout: 3 cards in a row with tilted side cards -->
<div class="hidden lg:flex flex-row items-center justify-center gap-8">
<!-- Left card - tilted (Risk Assessment) -->
<div class="card-3d w-full max-w-xs -mr-8 mt-12">
<div class="card-3d-inner transform -rotate-6 hover:rotate-0 transition-transform duration-500">
<LandingHeroStatusCardRisk />
</div>
</div>
<!-- Center card - main (Microsoft 365) -->
<div class="w-full max-w-md z-10">
<LandingHeroMainCard />
</div>
<!-- Right card - tilted (Completed) -->
<div class="card-3d w-full max-w-xs -ml-8 mt-12">
<div class="card-3d-inner transform rotate-6 hover:rotate-0 transition-transform duration-500">
<LandingHeroStatusCardCompleted />
</div>
</div>
</div>
<!-- Floating notification - positioned above all cards with higher z-index -->
<div
class="absolute top-0 right-8 lg:right-16 xl:right-24 z-20 animate-float-slow hidden lg:block"
style="animation-delay: 1s"
>
<div class="glass rounded-xl px-4 py-3 shadow-lg flex items-center gap-3">
<div
class="w-8 h-8 rounded-full bg-linear-to-br from-success-400 to-success-500 flex items-center justify-center"
>
<UIcon name="i-lucide-sparkles" class="w-4 h-4 text-white" />
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">{{ $t('hero.cards.bvCreated') }}</p>
<p class="text-xs text-gray-500 dark:text-gray-400">{{ $t('hero.cards.readyForSignature') }}</p>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
</script>

View File

@@ -1,188 +0,0 @@
<template>
<section id="newsletter" class="relative py-16 lg:py-20 overflow-hidden scroll-mt-20">
<!-- Background with gradient and particles -->
<div
class="absolute inset-0 bg-linear-to-br from-primary-50 via-cyan-50 to-accent-50 dark:from-gray-950 dark:via-primary-950/30 dark:to-accent-950/30"
/>
<!-- Animated particles -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="particle" />
<div class="particle" />
<div class="particle" />
<div class="particle" />
<div class="particle" />
<div class="particle" />
<div class="particle" />
<div class="particle" />
</div>
<!-- Gradient orbs -->
<div class="absolute top-0 left-1/4 w-96 h-96 bg-primary-400/20 rounded-full blur-3xl animate-orb-float" />
<div
class="absolute bottom-0 right-1/4 w-80 h-80 bg-accent-400/20 rounded-full blur-3xl animate-orb-float"
style="animation-delay: 3s"
/>
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Glass card -->
<div class="max-w-2xl mx-auto">
<UCard variant="glass" :ui="{ root: 'rounded-3xl shadow-2xl', body: 'p-8 sm:p-12' }">
<!-- Icon -->
<div class="flex justify-center mb-6">
<div
class="w-16 h-16 rounded-2xl bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center animate-float-slow shadow-lg shadow-primary-500/25"
>
<UIcon name="i-lucide-mail" class="w-8 h-8 text-white" />
</div>
</div>
<!-- Title -->
<h2
class="font-heading text-3xl sm:text-4xl font-bold text-center text-gray-900 dark:text-white mb-4 animate-fade-in-up"
>
{{ $t('newsletter.title', { highlight: '' })
}}<span class="gradient-text">{{ $t('newsletter.titleHighlight') }}</span>
</h2>
<!-- Description -->
<p
class="text-center text-lg text-gray-600 dark:text-gray-300 mb-8 animate-fade-in-up"
style="animation-delay: 100ms"
>
{{ $t('newsletter.description') }}
</p>
<!-- Form -->
<UForm
:state="formState"
:schema="schema"
class="animate-fade-in-up"
style="animation-delay: 200ms"
@submit="onSubmit"
>
<div class="flex flex-col sm:flex-row gap-3">
<UFormField name="email" class="flex-1">
<UInput
v-model="formState.email"
type="email"
:placeholder="$t('newsletter.placeholder')"
size="xl"
:disabled="isLoading || isSuccess"
:ui="{
root: 'w-full',
base: 'rounded-xl bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-primary-500 focus:border-transparent'
}"
>
<template #leading>
<UIcon name="i-lucide-mail" class="w-5 h-5 text-gray-400" />
</template>
</UInput>
</UFormField>
<UButton
type="submit"
size="xl"
variant="gradient"
:loading="isLoading"
:disabled="isSuccess"
class="px-8 whitespace-nowrap"
>
<template v-if="isSuccess">
<UIcon name="i-lucide-check" class="w-5 h-5 mr-2" />
{{ $t('newsletter.submitted') }}
</template>
<template v-else>
{{ $t('newsletter.submit') }}
</template>
</UButton>
</div>
</UForm>
<!-- Success message with animation -->
<Transition
enter-active-class="transition-all duration-500 ease-out"
enter-from-class="opacity-0 scale-95 translate-y-4"
enter-to-class="opacity-100 scale-100 translate-y-0"
leave-active-class="transition-all duration-300 ease-in"
leave-from-class="opacity-100 scale-100 translate-y-0"
leave-to-class="opacity-0 scale-95 translate-y-4"
>
<div
v-if="isSuccess"
class="mt-6 p-4 rounded-xl bg-success-100 dark:bg-success-900/30 border border-success-200 dark:border-success-800"
>
<div class="flex items-center justify-center gap-3 text-success-700 dark:text-success-300">
<div class="w-8 h-8 shrink-0 rounded-full bg-success-500 flex items-center justify-center">
<UIcon name="i-lucide-check" class="w-5 h-5 text-white" />
</div>
<span class="font-medium">{{ $t('newsletter.success') }}</span>
</div>
</div>
</Transition>
<!-- Privacy note -->
<p
class="mt-6 text-sm text-center text-gray-500 dark:text-gray-400 animate-fade-in-up"
style="animation-delay: 300ms"
>
<i18n-t keypath="newsletter.privacyNote" tag="span" scope="global">
<template #link>
<NuxtLink to="/datenschutz" class="text-primary-600 dark:text-primary-400 hover:underline font-medium">
{{ $t('newsletter.privacyLink') }}
</NuxtLink>
</template>
</i18n-t>
</p>
<!-- Trust indicators -->
<div
class="mt-8 flex flex-wrap justify-center gap-6 text-sm animate-fade-in-up"
style="animation-delay: 400ms"
>
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-shield-check" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('newsletter.trust.gdpr') }}</span>
</div>
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-lock" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('newsletter.trust.encrypted') }}</span>
</div>
<div class="flex items-center gap-2 text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-x-circle" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('newsletter.trust.noSpam') }}</span>
</div>
</div>
</UCard>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
const { t } = useI18n({ useScope: 'global' })
const { isLoading, isSuccess, submitEmail } = useNewsletterSignup()
const schema = computed(() =>
z.object({
email: z.string().min(1, t('newsletter.validation.required')).email(t('newsletter.validation.invalid'))
})
)
const formState = reactive<{ email: string }>({
email: ''
})
async function onSubmit(event: FormSubmitEvent<{ email: string }>) {
await submitEmail(event.data.email)
}
</script>

View File

@@ -1,124 +0,0 @@
<template>
<section
class="py-10 lg:py-12 bg-linear-to-r from-primary-50 via-cyan-50 to-accent-50 dark:from-primary-950/30 dark:via-cyan-950/30 dark:to-accent-950/30"
>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Stats grid -->
<div class="grid grid-cols-2 lg:grid-cols-4 gap-8 lg:gap-12">
<div
v-for="(stat, index) in stats"
:key="index"
class="text-center animate-fade-in-up"
:style="{ animationDelay: `${index * 100}ms` }"
>
<div class="relative inline-block mb-3">
<!-- Animated number -->
<span ref="counterRefs" class="font-heading text-4xl sm:text-5xl lg:text-6xl font-bold gradient-text">
{{ animatedValues[index] }}{{ stat.suffix }}
</span>
<!-- Decorative glow -->
<div class="absolute -inset-4 bg-primary-400/20 blur-2xl rounded-full -z-10 animate-pulse-glow" />
</div>
<p class="text-sm sm:text-base text-gray-600 dark:text-gray-300 font-medium">
{{ stat.label }}
</p>
</div>
</div>
<!-- Trust badges -->
<div class="mt-10 pt-8 border-t border-gray-200 dark:border-gray-800">
<p class="text-center text-sm text-gray-500 dark:text-gray-400 mb-8">{{ $t('stats.trustAndSecurity') }}</p>
<div class="flex flex-wrap justify-center items-center gap-8 lg:gap-12">
<div
v-for="(badge, index) in trustBadges"
:key="index"
class="flex items-center gap-3 px-4 py-2 rounded-full bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 shadow-sm hover:shadow-md transition-shadow animate-fade-in-up"
:style="{ animationDelay: `${(index + 4) * 100}ms` }"
>
<div
class="w-8 h-8 rounded-full bg-linear-to-br from-primary-100 to-cyan-100 dark:from-primary-900/50 dark:to-cyan-900/50 flex items-center justify-center"
>
<UIcon :name="badge.icon" class="w-4 h-4 text-primary-600 dark:text-primary-400" />
</div>
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">{{ badge.label }}</span>
</div>
</div>
</div>
</div>
</section>
</template>
<script setup lang="ts">
const { t } = useI18n({ useScope: 'global' })
const stats = computed(() => [
{ value: 70, suffix: '%', label: t('stats.timeSaved') },
{ value: 100, suffix: '%', label: t('stats.auditProof') },
{ value: 24, suffix: '/7', label: t('stats.availability') },
{ value: 0, suffix: '', label: t('stats.mediaBreaks') }
])
const trustBadges = computed(() => [
{ icon: 'i-lucide-shield-check', label: t('stats.badges.gdpr') },
{ icon: 'i-lucide-server', label: t('stats.badges.hosting') },
{ icon: 'i-lucide-lock', label: t('stats.badges.encrypted') },
{ icon: 'i-lucide-key', label: t('stats.badges.sso') }
])
// Animated counter values
const animatedValues = ref(stats.value.map(() => 0))
const hasAnimated = ref(false)
// Counter animation function
function animateCounter(index: number, target: number, duration: number = 2000) {
const start = 0
const startTime = performance.now()
function update(currentTime: number) {
const elapsed = currentTime - startTime
const progress = Math.min(elapsed / duration, 1)
// Easing function (ease-out cubic)
const easeOut = 1 - Math.pow(1 - progress, 3)
animatedValues.value[index] = Math.round(start + (target - start) * easeOut)
if (progress < 1) {
requestAnimationFrame(update)
}
}
requestAnimationFrame(update)
}
// Start animation when section is visible
onMounted(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !hasAnimated.value) {
hasAnimated.value = true
stats.value.forEach((stat, index) => {
setTimeout(() => {
animateCounter(index, stat.value)
}, index * 200)
})
}
})
},
{ threshold: 0.3 }
)
const section = document.querySelector('section')
if (section) {
observer.observe(section)
}
onUnmounted(() => {
observer.disconnect()
})
})
</script>

View File

@@ -1,176 +0,0 @@
<template>
<section class="relative min-h-screen py-20 lg:py-28 overflow-hidden hero-gradient-light">
<!-- Animated gradient orbs (same as homepage HeroSection) -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<!-- Large teal orb -->
<div
class="gradient-orb gradient-orb-teal w-[600px] h-[600px] -top-40 -left-40 animate-orb-float"
style="animation-delay: 0s"
/>
<!-- Violet orb -->
<div
class="gradient-orb gradient-orb-violet w-[500px] h-[500px] top-1/4 -right-32 animate-orb-float"
style="animation-delay: 3s"
/>
<!-- Cyan orb -->
<div
class="gradient-orb gradient-orb-cyan w-[400px] h-[400px] bottom-0 left-1/4 animate-orb-float"
style="animation-delay: 6s"
/>
<!-- Small accent orb -->
<div
class="gradient-orb gradient-orb-violet w-[200px] h-[200px] top-1/2 left-10 animate-orb-float opacity-40"
style="animation-delay: 9s"
/>
<!-- Grid pattern overlay (same as homepage) -->
<div class="absolute inset-0 grid-pattern opacity-30" />
</div>
<div class="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Section header -->
<div class="text-center mb-16 lg:mb-20">
<span
class="inline-flex items-center gap-2 px-4 py-1.5 rounded-full bg-primary-100 dark:bg-primary-900/50 text-primary-700 dark:text-primary-300 text-sm font-medium mb-6 animate-fade-in-up"
>
<UIcon name="i-lucide-users" class="w-4 h-4" />
{{ $t('team.badge') }}
</span>
<h1
class="font-heading text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white mb-6 animate-fade-in-up delay-100"
>
{{ $t('team.title') }}
<span class="gradient-text-teal">{{ $t('team.titleHighlight') }}</span>
</h1>
<p class="text-lg sm:text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto animate-fade-in-up delay-200">
{{ $t('team.description') }}
</p>
</div>
<!-- Team members grid -->
<div class="grid md:grid-cols-2 gap-8 lg:gap-12 max-w-5xl mx-auto">
<!-- Raphael Lugowski -->
<UCard
as="article"
variant="glass"
class="group card-hover-lift animate-fade-in-up delay-300"
:ui="{ root: 'rounded-3xl', body: 'p-8 lg:p-10' }"
>
<div class="flex flex-col items-center text-center">
<!-- Profile image with gradient border -->
<div class="relative mb-6">
<div
class="absolute -inset-1 rounded-full bg-linear-to-br from-primary-500 via-cyan-500 to-accent-500 opacity-75 group-hover:opacity-100 blur-sm transition-opacity"
aria-hidden="true"
/>
<div
class="relative w-40 h-40 lg:w-48 lg:h-48 rounded-full overflow-hidden border-4 border-white dark:border-gray-800"
>
<img
src="~/assets/img/Raphael%20Lugowski.jpg"
:alt="$t('team.members.raphael.imageAlt')"
class="w-full h-full object-cover"
loading="lazy"
/>
</div>
</div>
<!-- Name and role -->
<h2 class="font-heading text-2xl lg:text-3xl font-bold text-gray-900 dark:text-white mb-2">
{{ $t('team.members.raphael.name') }}
</h2>
<p class="text-primary-600 dark:text-primary-400 font-medium mb-4">
{{ $t('team.members.raphael.role') }}
</p>
<!-- LinkedIn link -->
<a
href="https://www.linkedin.com/in/raphael-lugowski/"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-gray-500 hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400 transition-colors mb-6"
:aria-label="$t('team.members.raphael.linkedinLabel')"
>
<UIcon name="i-lucide-linkedin" class="w-5 h-5" />
<span class="text-sm">LinkedIn</span>
</a>
<!-- Description -->
<p class="text-gray-600 dark:text-gray-300 leading-relaxed">
{{ $t('team.members.raphael.description') }}
</p>
</div>
</UCard>
<!-- Denis Lugowski -->
<UCard
as="article"
variant="glass"
class="group card-hover-lift animate-fade-in-up delay-400"
:ui="{ root: 'rounded-3xl', body: 'p-8 lg:p-10' }"
>
<div class="flex flex-col items-center text-center">
<!-- Profile image with gradient border -->
<div class="relative mb-6">
<div
class="absolute -inset-1 rounded-full bg-linear-to-br from-primary-500 via-cyan-500 to-accent-500 opacity-75 group-hover:opacity-100 blur-sm transition-opacity"
aria-hidden="true"
/>
<div
class="relative w-40 h-40 lg:w-48 lg:h-48 rounded-full overflow-hidden border-4 border-white dark:border-gray-800"
>
<img
src="~/assets/img/Denis Lugowski.jpg"
:alt="$t('team.members.denis.imageAlt')"
class="w-full h-full object-cover"
loading="lazy"
/>
</div>
</div>
<!-- Name and role -->
<h2 class="font-heading text-2xl lg:text-3xl font-bold text-gray-900 dark:text-white mb-2">
{{ $t('team.members.denis.name') }}
</h2>
<p class="text-primary-600 dark:text-primary-400 font-medium mb-4">
{{ $t('team.members.denis.role') }}
</p>
<!-- LinkedIn link -->
<a
href="https://www.linkedin.com/in/denis-lugowski-39914a17a/"
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-2 text-gray-500 hover:text-primary-600 dark:text-gray-400 dark:hover:text-primary-400 transition-colors mb-6"
:aria-label="$t('team.members.denis.linkedinLabel')"
>
<UIcon name="i-lucide-linkedin" class="w-5 h-5" />
<span class="text-sm">LinkedIn</span>
</a>
<!-- Description -->
<p class="text-gray-600 dark:text-gray-300 leading-relaxed">
{{ $t('team.members.denis.description') }}
</p>
</div>
</UCard>
</div>
<!-- CTA section -->
<div class="mt-16 lg:mt-20 text-center animate-fade-in-up delay-500">
<p class="text-gray-600 dark:text-gray-300 mb-6">
{{ $t('team.cta.text') }}
</p>
<UButton to="/kontakt" size="xl" variant="gradient" trailing-icon="i-lucide-mail" class="px-8 py-4">
{{ $t('team.cta.button') }}
</UButton>
</div>
</div>
</section>
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
</script>

View File

@@ -1,84 +0,0 @@
<template>
<div class="glass rounded-2xl overflow-hidden shadow-2xl border border-white/50 dark:border-white/10">
<!-- Mock header -->
<div
class="flex items-center justify-between px-5 py-4 bg-white/50 dark:bg-gray-900/50 border-b border-gray-200/50 dark:border-gray-700/50"
>
<div class="flex items-center gap-3">
<div
class="w-8 h-8 rounded-lg bg-linear-to-br from-primary-500 to-cyan-500 flex items-center justify-center"
>
<UIcon name="i-lucide-file-text" class="w-4 h-4 text-white" />
</div>
<div>
<p class="font-semibold text-gray-900 dark:text-white text-sm">
Microsoft 365
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ $t('hero.cards.operatingAgreement') }}
</p>
</div>
</div>
<UBadge color="primary" variant="soft" size="sm">
{{ $t('hero.cards.inProgress') }}
</UBadge>
</div>
<!-- Mock content -->
<div class="p-5 space-y-4">
<!-- Progress section -->
<div>
<div class="flex justify-between text-sm mb-2">
<span class="text-gray-600 dark:text-gray-300 font-medium">
{{ $t('hero.cards.progress') }}
</span>
<span class="text-primary-600 dark:text-primary-400 font-semibold">67%</span>
</div>
<div class="h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
<div class="h-full w-[67%] bg-linear-to-r from-primary-500 to-cyan-500 rounded-full" />
</div>
</div>
<!-- Status items -->
<div class="grid grid-cols-2 gap-3">
<div
class="flex items-center gap-2 p-3 rounded-xl bg-success-50 dark:bg-success-900/20 border border-success-200 dark:border-success-800"
>
<UIcon name="i-lucide-check-circle" class="w-5 h-5 text-success-500" />
<div>
<p class="text-xs font-medium text-success-700 dark:text-success-300">
{{ $t('hero.cards.sectionsCompleted') }}
</p>
<p class="text-xs text-success-600 dark:text-success-400">
{{ $t('hero.cards.completed') }}
</p>
</div>
</div>
<div
class="flex items-center gap-2 p-3 rounded-xl bg-primary-50 dark:bg-primary-900/20 border border-primary-200 dark:border-primary-800"
>
<UIcon name="i-lucide-message-square" class="w-5 h-5 text-primary-500" />
<div>
<p class="text-xs font-medium text-primary-700 dark:text-primary-300">
{{ $t('hero.cards.commentsNew') }}
</p>
<p class="text-xs text-primary-600 dark:text-primary-400">
{{ $t('hero.cards.new') }}
</p>
</div>
</div>
</div>
<!-- Action button -->
<button
class="w-full py-3 px-4 rounded-xl bg-linear-to-r from-primary-500 to-cyan-500 text-white font-semibold text-sm hover:from-primary-600 hover:to-cyan-600 transition-all shadow-lg shadow-primary-500/25"
>
{{ $t('hero.cards.continueEditing') }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
</script>

View File

@@ -1,56 +0,0 @@
<template>
<div
class="bg-white dark:bg-gray-900 rounded-2xl shadow-lg border border-gray-100 dark:border-gray-800"
:class="compact ? 'p-4 flex-1 min-w-0' : 'p-5'"
>
<div :class="compact ? 'flex items-center gap-2 mb-3' : 'flex items-center gap-3 mb-4'">
<div
class="bg-linear-to-br from-success-400 to-success-500 flex items-center justify-center shrink-0"
:class="compact ? 'w-8 h-8 rounded-lg' : 'w-10 h-10 rounded-xl'"
>
<UIcon name="i-lucide-check-circle" :class="compact ? 'w-4 h-4' : 'w-5 h-5'" class="text-white" />
</div>
<div class="min-w-0">
<p
class="font-semibold text-gray-900 dark:text-white"
:class="compact ? 'text-xs truncate' : 'text-sm'"
>
{{ $t('hero.cards.completedTitle') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ $t('hero.cards.lastWeek') }}
</p>
</div>
</div>
<div :class="compact ? 'space-y-1.5' : 'space-y-2'">
<div
class="flex items-center justify-between bg-success-50 dark:bg-success-900/30"
:class="compact ? 'p-1.5 rounded' : 'p-2 rounded-lg'"
>
<span class="text-xs text-gray-700 dark:text-gray-200 truncate">SAP S/4HANA</span>
<UIcon name="i-lucide-check" :class="compact ? 'w-3 h-3' : 'w-4 h-4'" class="text-success-500 shrink-0" />
</div>
<div
class="flex items-center justify-between bg-success-50 dark:bg-success-900/30"
:class="compact ? 'p-1.5 rounded' : 'p-2 rounded-lg'"
>
<span class="text-xs text-gray-700 dark:text-gray-200 truncate">Workday HCM</span>
<UIcon name="i-lucide-check" :class="compact ? 'w-3 h-3' : 'w-4 h-4'" class="text-success-500 shrink-0" />
</div>
<!-- Desktop-only: Salesforce item -->
<div v-if="!compact" class="flex items-center justify-between p-2 rounded-lg bg-success-50 dark:bg-success-900/30">
<span class="text-xs text-gray-700 dark:text-gray-200">Salesforce CRM</span>
<UIcon name="i-lucide-check" class="w-4 h-4 text-success-500" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
defineProps<{
compact?: boolean
}>()
</script>

View File

@@ -1,68 +0,0 @@
<template>
<div
class="bg-white dark:bg-gray-900 rounded-2xl shadow-lg border border-gray-100 dark:border-gray-800"
:class="compact ? 'p-4 flex-1 min-w-0' : 'p-5'"
>
<div :class="compact ? 'flex items-center gap-2 mb-3' : 'flex items-center gap-3 mb-4'">
<div
class="bg-linear-to-br from-warning-400 to-warning-500 flex items-center justify-center shrink-0"
:class="compact ? 'w-8 h-8 rounded-lg' : 'w-10 h-10 rounded-xl'"
>
<UIcon name="i-lucide-alert-triangle" :class="compact ? 'w-4 h-4' : 'w-5 h-5'" class="text-white" />
</div>
<div class="min-w-0">
<p
class="font-semibold text-gray-900 dark:text-white"
:class="compact ? 'text-xs truncate' : 'text-sm'"
>
{{ $t('hero.cards.riskAssessment') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ $t('hero.cards.pointsOpen') }}
</p>
</div>
</div>
<div :class="compact ? 'space-y-1.5' : 'space-y-2'">
<div class="flex items-center" :class="compact ? 'gap-1.5' : 'gap-2'">
<div
class="rounded bg-warning-100 dark:bg-warning-900/50 flex items-center justify-center shrink-0"
:class="compact ? 'w-3 h-3' : 'w-4 h-4'"
>
<UIcon name="i-lucide-clock" :class="compact ? 'w-2 h-2' : 'w-2.5 h-2.5'" class="text-warning-600" />
</div>
<span class="text-xs text-gray-600 dark:text-gray-300" :class="{ 'truncate': compact }">
{{ $t('hero.cards.privacyImpact') }}
</span>
</div>
<!-- Desktop-only: Performance check item -->
<div v-if="!compact" class="flex items-center gap-2">
<div class="w-4 h-4 rounded bg-warning-100 dark:bg-warning-900/50 flex items-center justify-center">
<UIcon name="i-lucide-clock" class="w-2.5 h-2.5 text-warning-600" />
</div>
<span class="text-xs text-gray-600 dark:text-gray-300">
{{ $t('hero.cards.performanceCheck') }}
</span>
</div>
<div class="flex items-center" :class="compact ? 'gap-1.5' : 'gap-2'">
<div
class="rounded bg-success-100 dark:bg-success-900/50 flex items-center justify-center shrink-0"
:class="compact ? 'w-3 h-3' : 'w-4 h-4'"
>
<UIcon name="i-lucide-check" :class="compact ? 'w-2 h-2' : 'w-2.5 h-2.5'" class="text-success-600" />
</div>
<span class="text-xs text-gray-600 dark:text-gray-300" :class="{ 'truncate': compact }">
{{ $t('hero.cards.accessDefined') }}
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
useI18n({ useScope: 'global' })
defineProps<{
compact?: boolean
}>()
</script>

View File

@@ -1,46 +0,0 @@
export interface ContactFormData {
name: string
email: string
message: string
}
export function useContactForm() {
const isLoading = ref(false)
const isSuccess = ref(false)
const error = ref<string | null>(null)
const submitForm = async (data: ContactFormData) => {
const { t } = useI18n({ useScope: 'global' })
isLoading.value = true
error.value = null
try {
await $fetch('/api/contact/send', {
method: 'POST',
body: data
})
isSuccess.value = true
} catch (e: unknown) {
const fetchError = e as { statusMessage?: string }
error.value = fetchError.statusMessage || t('errors.generic')
throw e
} finally {
isLoading.value = false
}
}
const reset = () => {
isLoading.value = false
isSuccess.value = false
error.value = null
}
return {
isLoading,
isSuccess,
error,
submitForm,
reset
}
}

View File

@@ -1,40 +0,0 @@
export function useNewsletterSignup() {
const isLoading = ref(false)
const isSuccess = ref(false)
const error = ref<string | null>(null)
const submitEmail = async (email: string) => {
const { t } = useI18n({ useScope: 'global' })
isLoading.value = true
error.value = null
try {
await $fetch('/api/newsletter/subscribe', {
method: 'POST',
body: { email }
})
isSuccess.value = true
} catch (e: unknown) {
const fetchError = e as { statusMessage?: string }
error.value = fetchError.statusMessage || t('errors.generic')
throw e
} finally {
isLoading.value = false
}
}
const reset = () => {
isLoading.value = false
isSuccess.value = false
error.value = null
}
return {
isLoading,
isSuccess,
error,
submitEmail,
reset
}
}

View File

@@ -1,378 +0,0 @@
<template>
<div class="min-h-screen pt-32 pb-16">
<!-- Hero Section -->
<div class="relative overflow-hidden">
<div
class="absolute inset-0 bg-linear-to-br from-primary-50/50 via-transparent to-cyan-50/50 dark:from-primary-950/30 dark:to-cyan-950/30"
/>
<div class="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 class="font-heading text-4xl sm:text-5xl font-bold text-gray-900 dark:text-white mb-4">
Datenschutzerklärung
</h1>
<p class="text-lg text-gray-600 dark:text-gray-400">Datenschutzerklärung (DSGVO) für gremiumhub.de</p>
<p class="text-sm text-gray-500 dark:text-gray-500 mt-2">Stand: 10.01.2026</p>
</div>
</div>
<!-- Content -->
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="prose prose-gray dark:prose-invert max-w-none">
<!-- 1. Verantwortlicher -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">1. Verantwortlicher</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO) ist:
</p>
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-6 border border-gray-200 dark:border-gray-700">
<p class="text-gray-700 dark:text-gray-300 mb-0">
<strong class="text-gray-900 dark:text-white">Raphael Lugowski und Denis Lugowski GremiumHub GbR</strong
><br />
Brooksheide 4a, 22549 Hamburg, Deutschland<br />
E-Mail: kontakt@gremiumhub.de<br />
Telefon: +49 176 47028443
</p>
</div>
</section>
<!-- 2. Allgemeine Hinweise -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
2. Allgemeine Hinweise zur Datenverarbeitung
</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Wir verarbeiten personenbezogene Daten nur, soweit dies zur Bereitstellung einer funktionsfähigen Website,
unserer Inhalte sowie zur Bearbeitung von Anfragen bzw. zur Durchführung des Newsletter-Versands
erforderlich ist.
</p>
<p class="text-gray-700 dark:text-gray-300 mb-2">
<strong class="text-gray-900 dark:text-white">Rechtsgrundlagen</strong> (je nach Verarbeitung):
</p>
<ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-2">
<li>Art. 6 Abs. 1 lit. <strong>b</strong> DSGVO (Vertrag / vorvertragliche Maßnahmen)</li>
<li>
Art. 6 Abs. 1 lit. <strong>f</strong> DSGVO (berechtigtes Interesse, z. B. technische Bereitstellung,
IT-Sicherheit)
</li>
<li>Art. 6 Abs. 1 lit. <strong>a</strong> DSGVO (Einwilligung, z. B. Newsletter)</li>
</ul>
</section>
<!-- 3. Hosting -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
3. Hosting (netcup) &amp; Server-Logfiles
</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Unsere Website wird bei <strong class="text-gray-900 dark:text-white">netcup</strong> gehostet. Der
Hosting-Anbieter verarbeitet personenbezogene Daten in unserem Auftrag als
<strong class="text-gray-900 dark:text-white">Auftragsverarbeiter</strong>. Wir haben mit netcup einen
Vertrag zur Auftragsverarbeitung (Art. 28 DSGVO) abgeschlossen.
</p>
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-6 border border-gray-200 dark:border-gray-700 mb-6">
<p class="text-gray-700 dark:text-gray-300 mb-0">
<strong class="text-gray-900 dark:text-white">Hosting-Anbieter:</strong><br />
netcup GmbH, Emmy-Noether-Straße 10, 76131 Karlsruhe, Deutschland
</p>
</div>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">3.1 Server-Logfiles</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Beim Aufruf unserer Website werden durch den Hosting-Anbieter in unserem Auftrag sogenannte
<strong class="text-gray-900 dark:text-white">Server-Logfiles</strong> verarbeitet. Diese Daten fallen
technisch bedingt an und können z. B. enthalten:
</p>
<ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1 mb-4">
<li>IP-Adresse</li>
<li>Datum und Uhrzeit des Zugriffs</li>
<li>aufgerufene Seite/Datei</li>
<li>Referrer-URL</li>
<li>User-Agent (Browser/Betriebssystem)</li>
</ul>
<p class="text-gray-700 dark:text-gray-300 mb-2">
<strong class="text-gray-900 dark:text-white">Zweck:</strong> Auslieferung der Website, Stabilität,
Sicherheit (z. B. Missbrauchs-/Angriffserkennung).
</p>
<p class="text-gray-700 dark:text-gray-300 mb-2">
<strong class="text-gray-900 dark:text-white">Rechtsgrundlage:</strong> Art. 6 Abs. 1 lit. f DSGVO
(berechtigtes Interesse an sicherem, stabilem Betrieb).
</p>
<p class="text-gray-700 dark:text-gray-300">
<strong class="text-gray-900 dark:text-white">Speicherdauer:</strong> Server-Logfiles werden in der Regel
nur so lange gespeichert, wie dies zur Sicherstellung des Betriebs und der Sicherheit erforderlich ist (bei
netcup typischerweise bis maximal 14 Tage, abhängig von Produkt/Setup).
</p>
</section>
<!-- 4. Kontaktaufnahme -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
4. Kontaktaufnahme (E-Mail, Kontaktformular)
</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Wenn Sie uns per E-Mail oder über ein Kontaktformular kontaktieren, verarbeiten wir die von Ihnen
übermittelten Daten (z. B. Name, E-Mail-Adresse, Nachrichteninhalt) zur Bearbeitung Ihrer Anfrage.
</p>
<p class="text-gray-700 dark:text-gray-300 mb-2">
<strong class="text-gray-900 dark:text-white">Zweck:</strong> Bearbeitung und Beantwortung Ihrer Anfrage.
</p>
<p class="text-gray-700 dark:text-gray-300 mb-2">
<strong class="text-gray-900 dark:text-white">Rechtsgrundlage:</strong>
</p>
<ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-1 mb-4">
<li>
Art. 6 Abs. 1 lit. b DSGVO (wenn Ihre Anfrage auf einen Vertrag / vorvertragliche Maßnahmen abzielt),
sonst
</li>
<li>Art. 6 Abs. 1 lit. f DSGVO (berechtigtes Interesse an Kommunikation).</li>
</ul>
<p class="text-gray-700 dark:text-gray-300">
<strong class="text-gray-900 dark:text-white">Speicherdauer:</strong> so lange erforderlich zur Bearbeitung;
anschließend Löschung nach Löschkonzept bzw. gesetzlichen Aufbewahrungspflichten.
</p>
</section>
<!-- 5. Newsletter -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
5. Newsletter (Versand über Brevo) inkl. Statistik
</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Auf unserer Website können Sie sich zu unserem Newsletter anmelden, um Updates zur Entwicklung der Web-App
zu erhalten.
</p>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Für die technische Umsetzung des Newsletters (Versand und Verwaltung) greifen wir auf die Lösungen des
Anbieters Sendinblue GmbH (<strong class="text-gray-900 dark:text-white">Brevo</strong>) zurück. Brevo
stellt die Einhaltung der EU-DSGVO bei der Verarbeitung von personenbezogenen Daten sicher. Näheres,
insbesondere zum Verarbeitungszweck, entnehmen Sie bitte den folgenden Ziffern sowie auch der
<a
href="https://www.brevo.com/de/legal/privacypolicy/"
target="_blank"
rel="noopener noreferrer"
class="text-primary-600 dark:text-primary-400 hover:underline"
>Datenschutzerklärung der Sendinblue GmbH</a
>.
</p>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">
5.1 Dienstleister (Auftragsverarbeiter)
</h3>
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-6 border border-gray-200 dark:border-gray-700 mb-6">
<p class="text-gray-700 dark:text-gray-300 mb-0">
<strong class="text-gray-900 dark:text-white">Brevo / Sendinblue GmbH</strong> (Marke Brevo")<br />
Köpenicker Straße 126, 10179 Berlin, Deutschland
</p>
</div>
<p class="text-gray-700 dark:text-gray-300 mb-6">
Brevo verarbeitet die Daten in unserem Auftrag als Auftragsverarbeiter. Wir haben mit Brevo eine
Vereinbarung zur Auftragsverarbeitung nach Art. 28 DSGVO abgeschlossen (Data Processing Agreement, DPA).
</p>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">
5.2 Welche Daten werden verarbeitet?
</h3>
<ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-3 mb-6">
<li>
<strong class="text-gray-900 dark:text-white">Pflichtangabe:</strong> E-Mail-Adresse<br />
<span class="text-gray-600 dark:text-gray-400 text-sm ml-5"
>Zweck: Die Angabe der E-Mail-Adresse ist erforderlich, damit die Ausgaben des Newsletters den
jeweiligen Empfängern auch zugehen.</span
>
</li>
<li>
<strong class="text-gray-900 dark:text-white">Freiwillige Angabe:</strong> Name (optional)<br />
<span class="text-gray-600 dark:text-gray-400 text-sm ml-5"
>Zweck: Die Angabe des Vornamens und Nachnamens ist rein freiwillig, damit wir den Abonnenten im Sinne
einer persönlicheren Kommunikation namentlich ansprechen können.</span
>
</li>
<li>
<strong class="text-gray-900 dark:text-white">Nachweisdaten</strong> im Rahmen des Double-Opt-In (z. B.
Zeitpunkt der Anmeldung und Bestätigung; IP-Adresse), um die Einwilligung zu dokumentieren.
</li>
</ul>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">5.3 Double-Opt-In</h3>
<p class="text-gray-700 dark:text-gray-300 mb-6">
Nach der Anmeldung erhalten Sie eine E-Mail, in der Sie die Anmeldung bestätigen. Erst danach werden Sie in
den Verteiler aufgenommen.
</p>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">
5.4 Statistik/Tracking im Newsletter (Öffnungen &amp; Klicks)
</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Wir werten Newsletter-Kampagnen statistisch aus (z. B. Öffnungs- und Klickraten), um Inhalte zu optimieren.
Diesbezüglich gilt Folgendes:
</p>
<p class="text-gray-700 dark:text-gray-300 mb-6">
Die Zahl der E-Mail-Öffnungen sowie der Klicks wird IP-bezogen durch unseren Anbieter Sendinblue GmbH
(Brevo) nachverfolgt, damit wir beurteilen können, ob die versandten Newsletter von den eingetragenen
Personen geöffnet bzw. weitergehende Inhalte angeklickt werden. Sollten wir feststellen, dass unsere
Newsletter von Personen mehrfach nicht (mehr) geöffnet werden, ist es uns bzw. unserem Anbieter Sendinblue
GmbH (Brevo) nur über die IP möglich, dies zu erkennen, sodass wir die entsprechenden Personen gemäß dem
Grundsatz der Speicherbegrenzung aus dem Newsletter entfernen und ihre Daten löschen können. Weiterhin ist
die Erfassung der IP notwendig, um feststellen zu können, ob die in dem Newsletter veröffentlichten Inhalte
für unsere Abonnenten inhaltlich auch wirklich interessant sind und von ihnen eingesehen werden. Anhand der
Öffnungsrate oder auch Klickrate, sofern weitere Inhalte in den Newslettern platziert werden, können wir die
für unsere Abonnenten interessanten Themen bestimmen und unser Angebot entsprechend fortlaufend anpassen und
verbessern.
</p>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">
5.5 Rechtsgrundlage, Widerruf
</h3>
<p class="text-gray-700 dark:text-gray-300 mb-2">
<strong class="text-gray-900 dark:text-white">Rechtsgrundlage:</strong> Art. 6 Abs. 1 lit. a, 7 DSGVO
(Einwilligung).
</p>
<p class="text-gray-700 dark:text-gray-300 mb-6">
<strong class="text-gray-900 dark:text-white">Widerruf:</strong> Sie können Ihre Einwilligung jederzeit mit
Wirkung für die Zukunft widerrufen, z. B. über den Abmeldelink in jedem Newsletter.
</p>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">
5.6 Speicherort / Datenverarbeitung in der EU
</h3>
<p class="text-gray-700 dark:text-gray-300 mb-6">
Brevo gibt an, dass die Hosting-Server und Datenbanken innerhalb der Europäischen Union betrieben werden.
</p>
<h3 class="font-heading text-xl font-semibold text-gray-900 dark:text-white mb-3">
5.7 Unterauftragsverarbeiter &amp; mögliche Drittlandübermittlungen
</h3>
<p class="text-gray-700 dark:text-gray-300">
Brevo setzt Unterauftragsverarbeiter ein. Je nach eingesetzten Funktionen/Unterauftragsverarbeitern können
Datenverarbeitungen auch außerhalb des EWR stattfinden. In solchen Fällen werden geeignete Garantien
eingesetzt (z. B. EU-Standardvertragsklauseln und ggf. ergänzende Maßnahmen; für bestimmte US-Anbieter ggf.
zusätzlich EU-US Data Privacy Framework, soweit anwendbar).
</p>
</section>
<!-- 6. Cookies -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">6. Cookies</h2>
<p class="text-gray-700 dark:text-gray-300">
Wir verwenden <strong class="text-gray-900 dark:text-white">nur technisch notwendige Cookies</strong>, die
für Betrieb und Sicherheit der Website erforderlich sind. Analyse- oder Marketing-Cookies setzen wir nicht
ein.
</p>
</section>
<!-- 7. Download der Web-App -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">7. Download der Web-App</h2>
<p class="text-gray-700 dark:text-gray-300">
Die Web-App wird nicht direkt über die Website betrieben, sondern gesondert heruntergeladen und installiert.
Beim Herunterladen können wie beim normalen Seitenaufruf technische Zugriffsdaten (Server-Logfiles)
anfallen (siehe Abschnitt „Hosting &amp; Server-Logfiles").
</p>
</section>
<!-- 8. Empfänger -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
8. Empfänger / Weitergabe von Daten
</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Wir geben personenbezogene Daten nur weiter, wenn dies erforderlich ist, z. B. an:
</p>
<ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-2">
<li><strong class="text-gray-900 dark:text-white">netcup</strong> (Hosting als Auftragsverarbeiter)</li>
<li>
<strong class="text-gray-900 dark:text-white">Brevo</strong> (Newsletter-Versand als Auftragsverarbeiter)
</li>
</ul>
</section>
<!-- 9. Ihre Rechte -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">9. Ihre Rechte</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">Sie haben das Recht auf:</p>
<ul class="list-disc list-inside text-gray-700 dark:text-gray-300 space-y-2">
<li>Auskunft (Art. 15 DSGVO)</li>
<li>Berichtigung (Art. 16 DSGVO)</li>
<li>Löschung (Art. 17 DSGVO)</li>
<li>Einschränkung der Verarbeitung (Art. 18 DSGVO)</li>
<li>Datenübertragbarkeit (Art. 20 DSGVO)</li>
<li>Widerspruch gegen bestimmte Verarbeitungen (Art. 21 DSGVO)</li>
<li>Widerruf erteilter Einwilligungen (Art. 7 Abs. 3 DSGVO)</li>
</ul>
</section>
<!-- 10. Beschwerderecht -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
10. Beschwerderecht bei der Aufsichtsbehörde
</h2>
<p class="text-gray-700 dark:text-gray-300">
Sie haben das Recht, sich bei einer Datenschutz-Aufsichtsbehörde zu beschweren (z. B. in dem Bundesland
Ihres Aufenthalts oder unseres Unternehmenssitzes).
</p>
</section>
<!-- 11. SSL/TLS -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
11. SSL-/TLS-Verschlüsselung
</h2>
<div class="bg-green-50 dark:bg-green-900/20 rounded-xl p-4 border border-green-200 dark:border-green-800">
<div class="flex items-start gap-3">
<UIcon name="i-lucide-shield-check" class="w-5 h-5 text-green-600 dark:text-green-400 mt-0.5 shrink-0" />
<p class="text-green-800 dark:text-green-200 text-sm mb-0">
Diese Website nutzt aus Sicherheitsgründen eine SSL-/TLS-Verschlüsselung.
</p>
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// SEO Meta
useSeoMeta({
title: 'Datenschutzerklärung GremiumHub',
description:
'Datenschutzerklärung (DSGVO) für gremiumhub.de Informationen zur Verarbeitung Ihrer personenbezogenen Daten.',
ogTitle: 'Datenschutzerklärung GremiumHub',
ogDescription:
'Datenschutzerklärung (DSGVO) für gremiumhub.de Informationen zur Verarbeitung Ihrer personenbezogenen Daten.',
ogImage: '/og-image.png',
ogType: 'website',
twitterCard: 'summary_large_image',
robots: 'noindex, nofollow'
})
// Structured data for SEO
useHead({
htmlAttrs: {
lang: 'de'
},
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebPage',
name: 'Datenschutzerklärung GremiumHub',
description: 'Datenschutzerklärung (DSGVO) für gremiumhub.de',
publisher: {
'@type': 'Organization',
name: 'GremiumHub GbR',
address: {
'@type': 'PostalAddress',
streetAddress: 'Brooksheide 4a',
addressLocality: 'Hamburg',
postalCode: '22549',
addressCountry: 'DE'
},
email: 'kontakt@gremiumhub.de',
telephone: '+4917647028443'
}
})
}
]
})
</script>

View File

@@ -1,202 +0,0 @@
<template>
<div class="min-h-screen pt-32 pb-16">
<!-- Hero Section -->
<div class="relative overflow-hidden">
<div class="absolute inset-0 bg-linear-to-br from-primary-50/50 via-transparent to-cyan-50/50 dark:from-primary-950/30 dark:to-cyan-950/30" />
<div class="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<h1 class="font-heading text-4xl sm:text-5xl font-bold text-gray-900 dark:text-white mb-4">
Impressum
</h1>
<p class="text-lg text-gray-600 dark:text-gray-400">
Rechtliche Angaben gemäß § 5 DDG
</p>
</div>
</div>
<!-- Content -->
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="prose prose-gray dark:prose-invert max-w-none">
<!-- Provider Section -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Anbieter / Diensteanbieter (§ 5 DDG)
</h2>
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-6 border border-gray-200 dark:border-gray-700">
<p class="text-gray-700 dark:text-gray-300 mb-0">
Raphael Lugowski und Denis Lugowski<br />
<strong class="text-gray-900 dark:text-white">GremiumHub GbR</strong><br />
Brooksheide 4a<br />
22549 Hamburg<br />
Deutschland
</p>
</div>
</section>
<!-- Authorized Representative -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Vertretungsberechtigt
</h2>
<p class="text-gray-700 dark:text-gray-300">
Raphael Lugowski
</p>
</section>
<!-- Contact -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Kontakt
</h2>
<div class="space-y-3">
<div class="flex items-center gap-3">
<UIcon name="i-lucide-mail" class="w-5 h-5 text-primary-500" />
<a
href="mailto:kontakt@gremiumhub.de"
class="text-primary-600 dark:text-primary-400 hover:underline"
>
kontakt@gremiumhub.de
</a>
</div>
<div class="flex items-center gap-3">
<UIcon name="i-lucide-phone" class="w-5 h-5 text-primary-500" />
<a
href="tel:+4917647028443"
class="text-primary-600 dark:text-primary-400 hover:underline"
>
+49 176 47028443
</a>
</div>
</div>
</section>
<!-- Tax Information -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Umsatzsteuer-ID / Steuernummer
</h2>
<p class="text-gray-700 dark:text-gray-300">
USt-IdNr.: noch nicht vorhanden<br />
Steuernummer: noch nicht vorhanden (Meldung ans Finanzamt erfolgt)
</p>
</section>
<!-- Registry Entry -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Registereintrag
</h2>
<p class="text-gray-700 dark:text-gray-300">
Kein Handelsregistereintrag vorhanden.
</p>
</section>
<!-- Responsible for Content -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Verantwortlich für den Inhalt (§ 18 Abs. 2 MStV)
</h2>
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-6 border border-gray-200 dark:border-gray-700">
<p class="text-gray-700 dark:text-gray-300 mb-0">
Raphael Lugowski<br />
Brooksheide 4a<br />
22549 Hamburg
</p>
</div>
</section>
<!-- Liability for Content -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Haftung für Inhalte
</h2>
<p class="text-gray-700 dark:text-gray-300">
Als Diensteanbieter sind wir für eigene Inhalte auf diesen Seiten nach den allgemeinen Gesetzen verantwortlich. Wir übernehmen jedoch keine Gewähr für Aktualität, Richtigkeit und Vollständigkeit der bereitgestellten Inhalte.
</p>
</section>
<!-- Liability for Links -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Haftung für Links
</h2>
<p class="text-gray-700 dark:text-gray-300">
Unsere Website enthält ggf. Links zu externen Websites Dritter, auf deren Inhalte wir keinen Einfluss haben. Für diese fremden Inhalte übernehmen wir keine Gewähr; verantwortlich ist stets der jeweilige Anbieter oder Betreiber der Seiten.
</p>
</section>
<!-- Copyright -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Urheberrecht
</h2>
<p class="text-gray-700 dark:text-gray-300">
Die durch die Seitenbetreiber erstellten Inhalte und Werke auf diesen Seiten unterliegen dem deutschen Urheberrecht. Beiträge Dritter sind als solche gekennzeichnet.
</p>
</section>
<!-- Consumer Dispute Resolution -->
<section class="mb-10">
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-4">
Verbraucherstreitbeilegung
</h2>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Wir sind nicht verpflichtet und nicht bereit, an Streitbeilegungsverfahren vor einer Verbraucherschlichtungsstelle teilzunehmen.
</p>
<div class="bg-amber-50 dark:bg-amber-900/20 rounded-xl p-4 border border-amber-200 dark:border-amber-800">
<div class="flex items-start gap-3">
<UIcon name="i-lucide-info" class="w-5 h-5 text-amber-600 dark:text-amber-400 mt-0.5 shrink-0" />
<p class="text-amber-800 dark:text-amber-200 text-sm mb-0">
Hinweis EU-OS/ODR-Plattform: Die EU-Online-Streitbeilegungsplattform wurde zum 20.07.2025 eingestellt (Verordnung (EU) 2024/3228). Ein Link darauf ist daher nicht mehr erforderlich.
</p>
</div>
</div>
</section>
</div>
</div>
</div>
</template>
<script setup lang="ts">
// SEO Meta
useSeoMeta({
title: 'Impressum GremiumHub',
description: 'Impressum und rechtliche Informationen der GremiumHub GbR.',
ogTitle: 'Impressum GremiumHub',
ogDescription: 'Impressum und rechtliche Informationen der GremiumHub GbR.',
ogImage: '/og-image.png',
ogType: 'website',
twitterCard: 'summary_large_image',
robots: 'noindex, nofollow'
})
// Structured data for SEO
useHead({
htmlAttrs: {
lang: 'de'
},
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'WebPage',
name: 'Impressum GremiumHub',
description: 'Impressum und rechtliche Informationen der GremiumHub GbR.',
publisher: {
'@type': 'Organization',
name: 'GremiumHub GbR',
address: {
'@type': 'PostalAddress',
streetAddress: 'Brooksheide 4a',
addressLocality: 'Hamburg',
postalCode: '22549',
addressCountry: 'DE'
},
email: 'kontakt@gremiumhub.de',
telephone: '+4917647028443'
}
})
}
]
})
</script>

View File

@@ -1,75 +0,0 @@
<template>
<div class="landing-page">
<!-- Hero Section (above the fold - load eagerly) -->
<LandingHeroSection />
<!-- Below-the-fold sections - lazy loaded for better initial performance -->
<LazyLandingStatsSection />
<LazyLandingBenefitsWorksCouncil />
<LazyLandingFeaturesGrid />
<LazyLandingAdditionalFeatures />
<LazyLandingFrameworkAgreement />
<LazyLandingNewsletterSignup />
<LazyLandingExpertAccess />
</div>
</template>
<script setup lang="ts">
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({
title: () => t('meta.title'),
description: () => t('meta.description'),
ogTitle: () => t('meta.title'),
ogDescription: () => t('meta.ogDescription'),
ogImage: '/og-image.png',
ogType: 'website',
twitterCard: 'summary_large_image'
})
// Structured data for SEO
useHead({
htmlAttrs: {
lang: () => locale.value
},
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
name: 'GremiumHub',
applicationCategory: 'BusinessApplication',
operatingSystem: 'Web',
description: t('meta.description'),
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'EUR',
description: t('common.onRequest')
},
featureList: [
t('features.items[0].title'),
t('features.items[1].title'),
t('features.items[3].title'),
t('features.items[5].title'),
t('features.items[8].title')
]
})
}
]
})
</script>
<style scoped>
/* Smooth scrolling for anchor links */
.landing-page {
scroll-behavior: smooth;
}
/* Add padding to account for fixed header */
.landing-page > :deep(section:first-child) {
padding-top: 0;
}
</style>

View File

@@ -1,291 +0,0 @@
<template>
<div class="min-h-screen pt-24 pb-16">
<!-- Background with gradient -->
<div class="fixed inset-0 -z-10">
<div
class="absolute inset-0 bg-linear-to-br from-primary-50 via-cyan-50 to-accent-50 dark:from-gray-950 dark:via-primary-950/30 dark:to-accent-950/30"
/>
<!-- Gradient orbs -->
<div class="absolute top-20 left-1/4 w-96 h-96 bg-primary-400/20 rounded-full blur-3xl animate-orb-float" />
<div
class="absolute bottom-20 right-1/4 w-80 h-80 bg-accent-400/20 rounded-full blur-3xl animate-orb-float"
style="animation-delay: 3s"
/>
</div>
<div class="relative max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Header section -->
<div class="text-center mb-12">
<!-- Badge -->
<span
class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 text-sm font-medium mb-6"
>
<UIcon name="i-lucide-message-circle" class="w-4 h-4" />
{{ $t('contact.badge') }}
</span>
<!-- Title -->
<h1
class="font-heading text-4xl sm:text-5xl lg:text-6xl font-bold text-gray-900 dark:text-white mb-6 animate-fade-in-up"
>
{{ $t('contact.title', { highlight: '' })
}}<span class="gradient-text">{{ $t('contact.titleHighlight') }}</span>
</h1>
<!-- Description -->
<p
class="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto animate-fade-in-up"
style="animation-delay: 100ms"
>
{{ $t('contact.description') }}
</p>
</div>
<!-- Form section -->
<div class="animate-fade-in-up" style="animation-delay: 150ms">
<UCard variant="glass" :ui="{ root: 'rounded-3xl shadow-2xl', body: 'p-8 sm:p-10' }">
<!-- Success state -->
<Transition
enter-active-class="transition-all duration-500 ease-out"
enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="transition-all duration-300 ease-in"
leave-from-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<div v-if="isSuccess" class="text-center py-8">
<div
class="w-20 h-20 mx-auto mb-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center"
>
<UIcon name="i-lucide-check-circle" class="w-10 h-10 text-success-600 dark:text-success-400" />
</div>
<h2 class="font-heading text-2xl font-bold text-gray-900 dark:text-white mb-3">
{{ $t('contact.success.title') }}
</h2>
<p class="text-gray-600 dark:text-gray-300 mb-8">
{{ $t('contact.success.message') }}
</p>
<div class="flex flex-col sm:flex-row gap-3 justify-center">
<UButton to="/" variant="gradientOutline" leading-icon="i-lucide-home">
{{ $t('contact.success.backToHome') }}
</UButton>
<UButton variant="gradient" leading-icon="i-lucide-mail-plus" @click="resetForm">
{{ $t('contact.success.sendAnother') }}
</UButton>
</div>
</div>
</Transition>
<!-- Form -->
<UForm v-if="!isSuccess" :state="formState" :schema="schema" class="space-y-6" @submit="onSubmit">
<!-- Name field -->
<UFormField :label="$t('contact.form.name')" name="name" required class="w-full">
<UInput
v-model="formState.name"
size="xl"
:disabled="isLoading"
autocomplete="name"
class="w-full"
:ui="{
base: 'w-full rounded-xl bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-primary-500 focus:border-transparent'
}"
>
<template #leading>
<UIcon name="i-lucide-user" class="w-5 h-5 text-gray-400" />
</template>
</UInput>
</UFormField>
<!-- Email field -->
<UFormField :label="$t('contact.form.email')" name="email" required class="w-full">
<UInput
v-model="formState.email"
type="email"
size="xl"
:disabled="isLoading"
autocomplete="email"
class="w-full"
:ui="{
base: 'w-full rounded-xl bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-primary-500 focus:border-transparent'
}"
>
<template #leading>
<UIcon name="i-lucide-mail" class="w-5 h-5 text-gray-400" />
</template>
</UInput>
</UFormField>
<!-- Message field -->
<UFormField :label="$t('contact.form.message')" name="message" required class="w-full">
<UTextarea
v-model="formState.message"
:rows="6"
:disabled="isLoading"
class="w-full"
size="xl"
:ui="{
base: 'w-full rounded-xl bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-primary-500 focus:border-transparent resize-none'
}"
/>
<template #hint>
<span class="text-xs text-gray-400"> {{ formState.message.length }} / 5000 </span>
</template>
</UFormField>
<!-- Error message -->
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0 -translate-y-2"
enter-to-class="opacity-100 translate-y-0"
>
<div
v-if="error"
class="p-4 rounded-xl bg-error-100 dark:bg-error-900/30 border border-error-200 dark:border-error-800"
role="alert"
>
<div class="flex items-center gap-3 text-error-700 dark:text-error-300">
<UIcon name="i-lucide-alert-circle" class="w-5 h-5 shrink-0" />
<span>{{ error }}</span>
</div>
</div>
</Transition>
<!-- Submit button -->
<UButton type="submit" size="xl" block variant="gradient" :loading="isLoading" class="rounded-xl">
<template v-if="isLoading">
{{ $t('contact.form.sending') }}
</template>
<template v-else>
<UIcon name="i-lucide-send" class="w-5 h-5 mr-2" />
{{ $t('contact.form.submit') }}
</template>
</UButton>
</UForm>
</UCard>
<!-- Trust indicators -->
<div class="mt-8 flex flex-wrap justify-center gap-6 animate-fade-in-up" style="animation-delay: 300ms">
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-shield-check" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('contact.trust.gdpr') }}</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-lock" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('contact.trust.encrypted') }}</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-clock" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('contact.trust.responseTime') }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { z } from 'zod'
import type { FormSubmitEvent } from '@nuxt/ui'
import type { ContactFormData } from '~/composables/useContactForm'
const { t, locale } = useI18n({ useScope: 'global' })
const { isLoading, isSuccess, error, submitForm, reset } = useContactForm()
// SEO Meta
useSeoMeta({
title: () => t('contact.meta.title'),
description: () => t('contact.meta.description'),
ogTitle: () => t('contact.meta.title'),
ogDescription: () => t('contact.meta.description'),
ogImage: '/og-image.png',
ogType: 'website',
twitterCard: 'summary_large_image'
})
// Structured data
useHead({
htmlAttrs: {
lang: () => locale.value
},
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'ContactPage',
name: t('contact.meta.title'),
description: t('contact.meta.description')
})
}
]
})
// Form validation schema
const schema = computed(() =>
z.object({
name: z.string().min(1, t('contact.validation.nameRequired')).max(100, t('contact.validation.nameMax')),
email: z.string().min(1, t('contact.validation.emailRequired')).email(t('contact.validation.emailInvalid')),
message: z.string().min(10, t('contact.validation.messageMin')).max(5000, t('contact.validation.messageMax'))
})
)
// Form state
const formState = reactive<ContactFormData>({
name: '',
email: '',
message: ''
})
// Submit handler
async function onSubmit(event: FormSubmitEvent<ContactFormData>) {
await submitForm(event.data)
}
// Reset form
function resetForm() {
formState.name = ''
formState.email = ''
formState.message = ''
reset()
}
</script>
<style scoped>
/* Orb float animation */
@keyframes orb-float {
0%,
100% {
transform: translateY(0) scale(1);
}
50% {
transform: translateY(-20px) scale(1.05);
}
}
.animate-orb-float {
animation: orb-float 8s ease-in-out infinite;
}
/* Fade in up animation */
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out forwards;
opacity: 0;
}
</style>

View File

@@ -1,168 +0,0 @@
<template>
<div class="min-h-screen pt-24 pb-16 flex items-center">
<!-- Background with gradient -->
<div class="fixed inset-0 -z-10">
<div
class="absolute inset-0 bg-linear-to-br from-primary-50 via-cyan-50 to-accent-50 dark:from-gray-950 dark:via-primary-950/30 dark:to-accent-950/30"
/>
<!-- Gradient orbs -->
<div class="absolute top-20 left-1/4 w-96 h-96 bg-primary-400/20 rounded-full blur-3xl animate-orb-float" />
<div
class="absolute bottom-20 right-1/4 w-80 h-80 bg-accent-400/20 rounded-full blur-3xl animate-orb-float"
style="animation-delay: 3s"
/>
</div>
<div class="relative max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<!-- Success card -->
<UCard
variant="glass"
:ui="{ root: 'rounded-3xl shadow-2xl animate-fade-in-up', body: 'p-10 sm:p-14' }"
>
<!-- Checkmark icon with animation -->
<div class="relative mb-8">
<div
class="w-24 h-24 mx-auto rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center animate-scale-in"
>
<UIcon name="i-lucide-check-circle" class="w-14 h-14 text-success-600 dark:text-success-400" />
</div>
<!-- Decorative rings -->
<div
class="absolute inset-0 w-24 h-24 mx-auto rounded-full border-2 border-success-400/30 animate-ping-slow"
/>
</div>
<!-- Title -->
<h1
class="font-heading text-3xl sm:text-4xl font-bold text-gray-900 dark:text-white mb-4 animate-fade-in-up"
style="animation-delay: 200ms"
>
{{ $t('newsletterConfirmed.title') }}
</h1>
<!-- Description -->
<p
class="text-lg text-gray-600 dark:text-gray-300 mb-8 animate-fade-in-up"
style="animation-delay: 300ms"
>
{{ $t('newsletterConfirmed.description') }}
</p>
<!-- CTA Button -->
<div class="animate-fade-in-up" style="animation-delay: 400ms">
<UButton to="/" size="xl" variant="gradient" leading-icon="i-lucide-home" class="px-8 py-4">
{{ $t('newsletterConfirmed.backToHome') }}
</UButton>
</div>
</UCard>
<!-- Trust indicators -->
<div class="mt-8 flex flex-wrap justify-center gap-6 animate-fade-in-up" style="animation-delay: 500ms">
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-shield-check" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('newsletter.trust.gdpr') }}</span>
</div>
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-300">
<div class="w-6 h-6 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center">
<UIcon name="i-lucide-bell-off" class="w-3.5 h-3.5 text-success-600 dark:text-success-400" />
</div>
<span>{{ $t('newsletter.trust.noSpam') }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({
title: () => t('newsletterConfirmed.meta.title'),
description: () => t('newsletterConfirmed.meta.description'),
ogTitle: () => t('newsletterConfirmed.meta.title'),
ogDescription: () => t('newsletterConfirmed.meta.description'),
ogImage: '/og-image.png',
ogType: 'website',
twitterCard: 'summary_large_image',
robots: 'noindex, nofollow'
})
// Set language
useHead({
htmlAttrs: {
lang: () => locale.value
}
})
</script>
<style scoped>
/* Orb float animation */
@keyframes orb-float {
0%,
100% {
transform: translateY(0) scale(1);
}
50% {
transform: translateY(-20px) scale(1.05);
}
}
.animate-orb-float {
animation: orb-float 8s ease-in-out infinite;
}
/* Fade in up animation */
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fade-in-up 0.6s ease-out forwards;
opacity: 0;
}
/* Scale in animation for checkmark */
@keyframes scale-in {
0% {
transform: scale(0);
opacity: 0;
}
50% {
transform: scale(1.1);
}
100% {
transform: scale(1);
opacity: 1;
}
}
.animate-scale-in {
animation: scale-in 0.5s ease-out forwards;
}
/* Slow ping animation for decorative ring */
@keyframes ping-slow {
0% {
transform: scale(1);
opacity: 0.5;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
.animate-ping-slow {
animation: ping-slow 2s ease-out infinite;
}
</style>

View File

@@ -1,52 +0,0 @@
<template>
<LandingTeamSection />
</template>
<script setup lang="ts">
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({
title: () => t('team.meta.title'),
description: () => t('team.meta.description'),
ogTitle: () => t('team.meta.title'),
ogDescription: () => t('team.meta.description'),
ogImage: '/og-image.png',
ogType: 'website',
twitterCard: 'summary_large_image'
})
// Structured data for SEO
useHead({
htmlAttrs: {
lang: () => locale.value
},
script: [
{
type: 'application/ld+json',
innerHTML: JSON.stringify({
'@context': 'https://schema.org',
'@type': 'AboutPage',
name: t('team.meta.title'),
description: t('team.meta.description'),
mainEntity: {
'@type': 'Organization',
name: 'GremiumHub',
member: [
{
'@type': 'Person',
name: 'Raphael Lugowski',
jobTitle: t('team.members.raphael.role')
},
{
'@type': 'Person',
name: 'Denis Lugowski',
jobTitle: t('team.members.denis.role')
}
]
}
})
}
]
})
</script>

View File

@@ -1,32 +0,0 @@
<template>
<div class="benefits-company-page">
<LandingBenefitsCompany />
</div>
</template>
<script setup lang="ts">
const { t, locale } = useI18n({ useScope: 'global' })
// SEO Meta
useSeoMeta({
title: () => `${t('company.title', { highlight: t('company.titleHighlight') })} - GremiumHub`,
description: () => t('company.description'),
ogTitle: () => `${t('company.title', { highlight: t('company.titleHighlight') })} - GremiumHub`,
ogDescription: () => t('company.description'),
ogImage: '/og-image.png',
ogType: 'website',
twitterCard: 'summary_large_image'
})
useHead({
htmlAttrs: {
lang: () => locale.value
}
})
</script>
<style scoped>
.benefits-company-page {
padding-top: 6rem;
}
</style>

View File

@@ -1,15 +0,0 @@
// @ts-check
import withNuxt from './.nuxt/eslint.config.mjs'
export default withNuxt({
rules: {
'vue/html-self-closing': [
'error',
{
html: {
void: 'any'
}
}
]
}
})

View File

@@ -1,413 +0,0 @@
{
"meta": {
"title": "Digitale Mitbestimmung bei IT- und KI-Systemen - per Web-App, strukturierter Workflow bis zur Vereinbarung",
"description": "Ein durchgängiger risiko-differenzierender Workflow für Betriebsrat und Arbeitgeber: Informationen liefern, Unterlagen bündeln, Rückfragen klären, Abstimmungen steuern und Vereinbarungen finalisieren zentral dokumentiert.",
"ogDescription": "Strukturierte IT-Mitbestimmung mit klaren Prozessen und revisionssicherer Dokumentation."
},
"common": {
"skipToContent": "Zum Hauptinhalt springen",
"changeLanguage": "Sprache ändern",
"stayInformed": "Informiert bleiben",
"learnMore": "Mehr erfahren",
"contact": "Kontakt aufnehmen",
"features": "Features",
"navigation": "Navigation",
"legal": "Rechtliches",
"allRightsReserved": "Alle Rechte vorbehalten",
"hostedInGermany": "Hosted in Germany",
"gdprCompliant": "DSGVO-konform",
"price": "Pauschalpreis für Web-App-Abonnenten",
"onRequest": "Jetzt Angebot einholen"
},
"nav": {
"forWorksCouncils": "Für Betriebsräte",
"forCompanies": "Für Unternehmen",
"features": "Features",
"team": "Team",
"contact": "Kontakt"
},
"footer": {
"brandDescription": "Digitale Mitbestimmung für IT- und KI-Systeme. Strukturierte Prozesse, risikobasierter Ansatz, revisionssichere Dokumentation.",
"imprint": "Impressum",
"privacy": "Datenschutz"
},
"hero": {
"badge": "Bald verfügbar",
"title1": "Mitbestimmung bei IT- und KI-Systemen per Web-App",
"title2": "Strukturierter Workflow bis zur Vereinbarung",
"description": "Ein durchgängiger digitaler risiko-differenzierter Workflow für Betriebsrat und Arbeitgeber in unserer neuen Web-App: Informationen liefern, Unterlagen bündeln, Rückfragen klären, Abstimmungen steuern und Vereinbarungen finalisieren zentral dokumentiert",
"cta": {
"requestDemo": "Demo anfragen",
"discoverFeatures": "Features entdecken"
},
"cards": {
"riskAssessment": "Risikoprüfung",
"pointsOpen": "2 Punkte offen",
"privacyImpact": "KI-Einsatz",
"performanceCheck": "Berichte und Auswertungen",
"accessDefined": "Zugriffsrechte definiert",
"operatingAgreement": "Betriebsvereinbarung",
"inProgress": "In Bearbeitung",
"progress": "Fortschritt",
"sectionsCompleted": "5 Abschnitte",
"completed": "abgeschlossen",
"commentsNew": "4 Kommentare",
"new": "neu",
"continueEditing": "Weiter bearbeiten",
"completedTitle": "Abgeschlossen",
"lastWeek": "Letztes Quartal",
"bvCreated": "Betriebsvereinbarung erstellt!",
"readyForSignature": "Unterschriftsreif"
},
"scrollDown": "Nach unten scrollen"
},
"stats": {
"timeSaved": "Zeitersparnis",
"auditProof": "Revisionssicher dokumentiert",
"availability": "Verfügbarkeit",
"mediaBreaks": "Medienbrüche",
"trustAndSecurity": "Vertrauen & Sicherheit",
"badges": {
"gdpr": "DSGVO-konform",
"hosting": "Hosting in Deutschland",
"encrypted": "Ende-zu-Ende verschlüsselt",
"sso": "SSO-fähig"
}
},
"worksCouncil": {
"badge": "Für Betriebsräte",
"title": "Vorteile für {highlight}",
"titleHighlight": "Betriebsräte",
"description": "IT-Mitbestimmung ist intransparent, umfangreich, langwierig, kompliziert und insgesamt schwerfällig? Unsere Web-App bietet die Lösung für alle diese Probleme.",
"tabs": {
"awayFrom": "Typische Probleme der IT-Mitbestimmung",
"towards": "Unsere Web-App bietet die Lösung"
},
"awayFromSubtitle": "Typische Herausforderungen aus Betriebsratssicht",
"towardsSubtitle": "Mit GremiumHub",
"painPoints": [
"Informationen zu IT-/KI-Systemen mühsam zusammensuchen (E-Mails, PDFs, PowerPoint, Excel-Chaos)",
"Medienbrüche, Versionschaos, wiederkehrende Rückfragen und Endlosschleifen",
"Ad-hoc-Systemeinführungen unter Zeitdruck (\"Hauruck\" statt geführter Prozess)",
"Überforderung durch hohe Zahl an IT- und KI-Systemen und dessen Komplexität",
"Blackbox-Problem bei Funktionen, Auswertungen, Schnittstellen und Änderungen",
"Verhandlungen ins Blaue hinein - ohne belastbare Datenbasis",
"Heute erst geregelt und abgeschlossen, morgen schon wieder veraltet"
],
"benefits": [
"Strukturierte, vollständige Informationsgrundlage (einheitlich je System/Verarbeitung)",
"Klarer Mitbestimmungsprozess mit bewährten Schritten, Zuständigkeiten und Risikoklassifizierung",
"Erhebliche Zeitersparnis gegenüber konventionellem Verfahren bei IT-/KI-Systemen",
"Vergleichbarkeit über viele Systeme hinweg durch Standardisierung und einheitliche Templates",
"Transparenz über Systemfähigkeiten inkl. möglicher Leistungs-/Verhaltenskontrolle",
"Schutzmaßnahmen konsequent dokumentiert und pro jeweilige Auswertung/Verarbeitung nachvollziehbar",
"Betriebs-/Dienstvereinbarungen auf belastbarer Grundlage schneller, konsistenter, leicht aktualisierbar"
],
"extraBenefitKicker": "Spürbare Entlastung: mehr Zeit für andere Mitbestimmungsthemen und kontinuierlichen Arbeitnehmerschutz",
"highlightsTitle": "Hierbei unterstützt unsere {highlight}",
"highlightsTitleHighlight": "Web-App",
"highlights": [
{
"title": "Schneller prüfen",
"description": "Relevante Funktionen und vorhandene Risiken sind auf einen Blick sichtbar."
},
{
"title": "Besser verhandeln",
"description": "Klare, konsistente Unterlagen statt Interpretationsspielräumen."
},
{
"title": "Nachhaltig absichern",
"description": "Änderungen und neue Auswertungen lassen sich strukturiert nachziehen."
}
]
},
"company": {
"badge": "Für Unternehmen",
"title": "Vorteile für {highlight}",
"titleHighlight": "Unternehmen",
"description": "Schneller und zuverlässiger zur abgestimmten Einführung von IT- und KI-Systemen mit planbaren Timelines.",
"awayFrom": {
"title": "Probleme bei Einführung von IT-/KI-Systemen",
"subtitle": "Typische Herausforderungen aus Arbeitgebersicht"
},
"towards": {
"title": "Unsere Web-App bietet die Lösung",
"subtitle": "Mit GremiumHub"
},
"painPoints": [
"Unkalkulierbare Schleifen im Mitbestimmungsverfahren (fehlende Infos, Nachforderungen)",
"Verzögerungen, Projektstopps und Last-Minute-Änderungen kurz vor Go-live",
"Hoher Koordinationsaufwand (E-Mail/Excel/Word) und Versionschaos",
"Komplexität und Reibung durch uneinheitliche Prozesse und unterschiedliche Erwartungshaltungen",
"Unklare Prozesse bei Updates, Funktionserweiterungen, neuen Modulen etc.",
"Verlust wertvoller Zeit und begrenzter Ressourcen",
"Vermeidbare erhebliche Kosten (Einigungsstelle, externe Beratung, Eskalationen)"
],
"benefits": [
"Planbare, schnellere Verfahren durch Standardisierung und klare Prozessschritte",
"Bessere Zusammenarbeit mit Betriebsrat durch mehr Transparenz und nachvollziehbare Dokumentation",
"Risikobasierter Ansatz: Schnellverfahren für Standard-/Low-Risk-Systeme, Fokus auf High-Risk",
"Höhere Einigungswahrscheinlichkeit, weil die Verhandlungsgrundlage strukturiert ist",
"Qualitativ konsistente, robuste Betriebs-/Dienstvereinbarungen (auch bei Änderungen zügig updatefähig)",
"Entlastung von Fachbereichen/IT/HR mehr Zeit für Wertschöpfung"
],
"highlightsTitle": "Das sind die Auswirkungen in der {highlight}",
"highlightsTitleHighlight": "Praxis",
"highlights": [
{
"title": "Weniger Iterationen",
"description": "Unterlagen sind vollständig und vergleichbar Nachforderungen sinken."
},
{
"title": "Schneller zur Freigabe",
"description": "Klare Schritte statt \"Hauruck\" und kurzfristiger Umplanungen."
},
{
"title": "Change-ready",
"description": "Updates, neue Module oder neue Auswertungen werden strukturiert nachgezogen, ohne jedes Mal neu zu starten."
}
]
},
"features": {
"badge": "Features",
"title": "Features, die Mitbestimmung äußerst ",
"titleHighlight": "effizient",
"titleSuffix": " machen",
"description": "Eine strukturierte risikobasierte Eingabelogik, klare Prozesse und revisionssichere Dokumentation damit IT-/KI-Systeme schneller bewertet, abgestimmt und sauber vereinbart werden können.",
"items": [
{
"title": "Geführter Mitbestimmungsprozess",
"description": "Vorgegebene, erweiterbare Eingabeparameter strukturieren den gesamten Ablauf von der Systembeschreibung bis zur Vereinbarung."
},
{
"title": "Risikobasierter Assistent",
"description": "Ein regelbasierter, risikoorientierter Assistent passt die Eingabemaske an Komplexität, Umfang und Potenziale zur Leistungs-/Verhaltensauswertung an."
},
{
"title": "Ampelsystem & Direktfeedback",
"description": "Kritikalität wird transparent bewertet und direkt beim Ausfüllen zurückgespielt für schnelle Orientierung und weniger Schleifen."
},
{
"title": "Versionierung & Audit-Trail",
"description": "Änderungen werden versioniert und lückenlos nachvollziehbar dokumentiert inklusive Historie und Vergleich."
},
{
"title": "Zusammenarbeit per Kommentaren",
"description": "Inline-Kommentare ermöglichen Abstimmung direkt an einzelnen Inhalten ohne Medienbrüche."
},
{
"title": "Automatische BV-/DV-Generierung",
"description": "Nach Abschluss der Eingaben erzeugt das System strukturierte, übersichtliche und signaturbereite Betriebs- oder Dienstvereinbarungen."
},
{
"title": "Signaturfähig mit qeS (eIDAS)",
"description": "Rechtsverbindliche Unterzeichnung per qualifizierter elektronischer Signatur nach eIDAS."
},
{
"title": "Benachrichtigungen & News-Center",
"description": "Aktuell bleiben über Status-Updates, Änderungen und Aufgaben per Nachrichten-Center und E-Mail."
},
{
"title": "SSO & Governance",
"description": "SSO-Integration via SAML oder OIDC sowie ein umfangreich konfigurierbares Rollen- und Berechtigungskonzept."
}
]
},
"additionalFeatures": {
"badge": "Vertrauen & Sicherheit",
"title": "Das spricht noch für unsere {highlight}",
"titleHighlight": "Web-App",
"description": "Alles aus einer Hand, alles in einer Web-App ohne Drittanbieter, DSGVO-konform mit Serverstandort in Deutschland.",
"items": [
{
"title": "Keine Zusatztools erforderlich",
"description": "Keine externen Zusatzlösungen für Prozess, Abstimmung und Dokumentenerstellung alles läuft in der Web-App zusammen."
},
{
"title": "Hosting & Backups in Deutschland",
"description": "Betrieb und Datensicherung erfolgen in Deutschland transparente Datenflüsse ohne Auslandsverarbeitung."
},
{
"title": "Verschlüsselte Übertragung",
"description": "Alle Verbindungen sind per TLS/HTTPS verschlüsselt, damit Inhalte und Zugangsdaten beim Transport geschützt sind."
},
{
"title": "Einfach zu bedienen",
"description": "Nutzerfreundliche, selbsterklärende Oberfläche mit geführten Eingaben spart Zeit und reduziert Schulungsaufwand."
},
{
"title": "Hohe Anpassbarkeit",
"description": "Templates, Felder und Prozessschritte lassen sich auf Organisation und Abläufe konkret anpassen (auf Anfrage)."
},
{
"title": "Persönlicher Support",
"description": "Bei Fragen steht Ihnen unser Support-Team zur Verfügung schnell, kompetent und auf Deutsch."
}
],
"ctaQuestion": "Haben Sie Fragen zu unseren Sicherheitsstandards?",
"ctaButton": "Kontakt aufnehmen"
},
"frameworkAgreement": {
"badge": "Regelungs-Framework zur Web-App",
"title": "Rahmen­betriebs­vereinbarung {highlight}",
"titleHighlight": "IT- und KI-Systeme",
"description": "Für den Einstieg entwirft unsere Kooperations-Kanzlei auf Nachfrage eine Rahmen­betriebs-/­Rahmen­dienst­vereinbarung für IT- und KI-Systeme. Sie ist auf die Struktur des Tools sowie Ihr Unternehmen zugeschnitten und enthält praxiserprobte Regelungsbausteine.",
"bulletPoints": [
"Fundierte Vorlage für eine Rahmen-­BV/­DV zu IT- und KI-Systemen",
"Auf die Logik der Web-App und Dokumentationsstruktur zugeschnitten",
"Enthält praxiserprobte Regelungsbausteine als Startpunkt für Web-App-basierte Vereinbarungen"
],
"document": {
"title": "Rahmen-BV IT und KI-Systeme",
"subtitle": "Grundlegende Regelung zur Web-App",
"tableOfContents": "Inhaltsverzeichnis",
"sections": [
{
"title": "Geltungsbereich",
"description": "Sachlicher Geltungsbereich IT- und KI-Systeme"
},
{
"title": "Mitbestimmungsverfahren",
"description": "Regelungssystematik und Zuständigkeiten"
},
{
"title": "Leistungs- und Verhaltenskontrolle",
"description": "Schutzmaßnahmen und Zulässigkeit"
},
{
"title": "Änderungsmanagement",
"description": "Updates und Erweiterungen"
}
],
"regulationBlocks": "14 Regelungsbausteine",
"fieldTested": "Praxiserprobt"
}
},
"newsletter": {
"title": "Bleiben Sie {highlight}",
"titleHighlight": "informiert",
"description": "Erhalten Sie Updates zur Entwicklung der Web-App für die IT-Mitbestimmung und seien Sie unter den Ersten, die vom Release oder neuen Funktionen erfahren.",
"placeholder": "Ihre E-Mail-Adresse",
"submit": "Anmelden",
"submitted": "E-Mail gesendet!",
"success": "Bitte bestätigen Sie Ihr Abonnement über den Link in der E-Mail, die wir Ihnen soeben gesendet haben.",
"privacyNote": "Hiermit willige ich darin ein, dass die GremiumHub GbR mich regelmäßig (max. 2 mal monatlich) über die Entwicklung der Web-App informiert. Ja, ich habe die {link} zur Kenntnis genommen. Mit der elektronischen Verarbeitung meiner Daten in dem in der Datenschutzerklärung angegebenenen Umfang bin ich einverstanden. Diese Einwilligung ist jederzeit für die Zukunft widerruflich.",
"privacyLink": "Datenschutzerklärung",
"validation": {
"required": "Bitte geben Sie eine E-Mail-Adresse ein",
"invalid": "Bitte geben Sie eine gültige E-Mail-Adresse ein"
},
"trust": {
"gdpr": "DSGVO-konform",
"encrypted": "Verschlüsselt",
"noSpam": "Kein Spam"
}
},
"newsletterConfirmed": {
"meta": {
"title": "Newsletter bestätigt GremiumHub",
"description": "Vielen Dank für die Bestätigung Ihres Newsletter-Abonnements."
},
"title": "Abonnement bestätigt!",
"description": "Vielen Dank! Sie sind jetzt für unseren Newsletter angemeldet und erhalten Updates zur Entwicklung von GremiumHub.",
"backToHome": "Zur Startseite"
},
"expertAccess": {
"badge": "Expertennetzwerk",
"title": "Externer Sachverstand Kooperation mit Spezialkanzlei für Betriebsräte",
"description": "Wenn Betriebsparteien bei einzelnen Fragen nicht weiterkommen, kann optional externer Sachverstand direkt im System angefragt werden. Wir haben insofern eine Kooperation mit Betriebsrat Kanzlei und der Wagner IT Systems GmbH, die zu attraktiven Konditionen beraten. Dabei bleiben Anfragen, Rückfragen und Ergebnisse nachvollziehbar dokumentiert.",
"cta": {
"contact": "Kontakt aufnehmen",
"learnMore": "Mehr erfahren"
},
"experts": {
"labor": {
"title": "Arbeitsrecht",
"description": "Fachanwälte für Arbeitsrecht"
},
"technical": {
"title": "Technik",
"description": "IT-Sachverständige"
},
"process": {
"title": "Direkt im Verfahren",
"description": "Dokumentiert & nachvollziehbar"
}
}
},
"errors": {
"generic": "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später erneut."
},
"contact": {
"meta": {
"title": "Kontakt GremiumHub",
"description": "Nehmen Sie Kontakt mit uns auf. Wir freuen uns auf Ihre Nachricht und antworten zeitnah."
},
"badge": "Kontakt",
"title": "Sprechen Sie mit {highlight}",
"titleHighlight": "uns",
"description": "Haben Sie Fragen zu GremiumHub oder möchten Sie mehr über unsere Lösung erfahren? Schreiben Sie uns wir melden uns zeitnah bei Ihnen.",
"form": {
"name": "Ihr Name",
"email": "E-Mail-Adresse",
"message": "Ihre Nachricht",
"submit": "Nachricht senden",
"sending": "Wird gesendet..."
},
"validation": {
"nameRequired": "Bitte geben Sie Ihren Namen ein",
"nameMax": "Der Name darf maximal 100 Zeichen lang sein",
"emailRequired": "Bitte geben Sie eine E-Mail-Adresse ein",
"emailInvalid": "Bitte geben Sie eine gültige E-Mail-Adresse ein",
"messageRequired": "Bitte geben Sie eine Nachricht ein",
"messageMin": "Die Nachricht muss mindestens 10 Zeichen lang sein",
"messageMax": "Die Nachricht darf maximal 5000 Zeichen lang sein"
},
"success": {
"title": "Nachricht gesendet!",
"message": "Vielen Dank für Ihre Nachricht. Wir werden uns schnellstmöglich bei Ihnen melden.",
"backToHome": "Zurück zur Startseite",
"sendAnother": "Weitere Nachricht senden"
},
"trust": {
"gdpr": "DSGVO-konform",
"encrypted": "Verschlüsselt",
"responseTime": "Antwort innerhalb von 24h"
},
"alternativeContact": {
"title": "Oder direkt erreichen",
"email": "kontakt{'@'}gremiumhub.de",
"phone": "+49 176 47028443"
}
},
"team": {
"meta": {
"title": "Unser Team GremiumHub",
"description": "Lernen Sie die Köpfe hinter GremiumHub kennen: Experten für Arbeitsrecht und Softwareentwicklung, die digitale Mitbestimmung vorantreiben."
},
"badge": "Unser Team",
"title": "Die Köpfe hinter ",
"titleHighlight": "GremiumHub",
"description": "Wir verbinden juristische Expertise im Betriebsverfassungsrecht mit technischem Know-how in der Softwareentwicklung für eine moderne, strukturierte IT-Mitbestimmung.",
"members": {
"raphael": {
"name": "Raphael Lugowski",
"role": "Fachanwalt für Arbeitsrecht",
"description": "Raphael ist Gründer der Betriebsrat Kanzlei in Hamburg und spezialisiert auf das Betriebsverfassungsrecht. Mit seiner langjährigen Erfahrung in der Beratung von Betriebsräten unterstützt er Gremien bei IT-Mitbestimmung, KI-Systemen und Betriebsänderungen. Er entwickelt praxiserprobte Betriebsvereinbarungen und begleitet Betriebsräte in Einigungsstellen sowie vor dem Arbeitsgericht.",
"imageAlt": "Profilbild von Raphael Lugowski",
"linkedinLabel": "LinkedIn-Profil von Raphael Lugowski öffnen"
},
"denis": {
"name": "Denis Lugowski",
"role": "Senior Full-Stack Developer",
"description": "Denis ist Informatiker (M.Sc.) und Full-Stack-Entwickler mit langjähriger Erfahrung in Frontend- und Backend-Entwicklung. Er arbeitet im Enterprise-Umfeld, wo höchste Anforderungen an Sicherheit, Datenschutz, Skalierbarkeit und Zuverlässigkeit gestellt werden.",
"imageAlt": "Profilbild von Denis Lugowski",
"linkedinLabel": "LinkedIn-Profil von Denis Lugowski öffnen"
}
},
"cta": {
"text": "Haben Sie Fragen oder möchten Sie mehr über GremiumHub erfahren?",
"button": "Kontakt aufnehmen"
}
}
}

View File

@@ -1,413 +0,0 @@
{
"meta": {
"title": "Digital Co-determination for IT and AI Systems - Web App with Structured Workflow to Agreement",
"description": "A comprehensive risk-differentiated workflow for works councils and employers: Provide information, bundle documents, clarify questions, manage approvals and finalize agreements centrally documented.",
"ogDescription": "Structured IT co-determination with clear processes and audit-proof documentation."
},
"common": {
"skipToContent": "Skip to main content",
"changeLanguage": "Change language",
"stayInformed": "Stay informed",
"learnMore": "Learn more",
"contact": "Contact us",
"features": "Features",
"navigation": "Navigation",
"legal": "Legal",
"allRightsReserved": "All rights reserved",
"hostedInGermany": "Hosted in Germany",
"gdprCompliant": "GDPR compliant",
"price": "Flat rate for web app subscribers",
"onRequest": "Get a quote now"
},
"nav": {
"forWorksCouncils": "For Works Councils",
"forCompanies": "For Companies",
"features": "Features",
"team": "Team",
"contact": "Contact"
},
"footer": {
"brandDescription": "Digital co-determination for IT and AI systems. Structured processes, risk-based approach, audit-proof documentation.",
"imprint": "Imprint",
"privacy": "Privacy Policy"
},
"hero": {
"badge": "Coming soon",
"title1": "Co-determination for IT and AI Systems via Web App",
"title2": "Structured Workflow to Agreement",
"description": "A comprehensive digital risk-differentiated workflow for works councils and employers in our new web app: Provide information, bundle documents, clarify questions, manage approvals and finalize agreements centrally documented",
"cta": {
"requestDemo": "Request demo",
"discoverFeatures": "Discover features"
},
"cards": {
"riskAssessment": "Risk Assessment",
"pointsOpen": "2 points open",
"privacyImpact": "AI Usage",
"performanceCheck": "Reports and Evaluations",
"accessDefined": "Access rights defined",
"operatingAgreement": "Operating Agreement",
"inProgress": "In Progress",
"progress": "Progress",
"sectionsCompleted": "5 sections",
"completed": "completed",
"commentsNew": "4 comments",
"new": "new",
"continueEditing": "Continue editing",
"completedTitle": "Completed",
"lastWeek": "Last quarter",
"bvCreated": "Operating agreement created!",
"readyForSignature": "Ready for signature"
},
"scrollDown": "Scroll down"
},
"stats": {
"timeSaved": "Time savings",
"auditProof": "Audit-proof documented",
"availability": "Availability",
"mediaBreaks": "Media breaks",
"trustAndSecurity": "Trust & Security",
"badges": {
"gdpr": "GDPR compliant",
"hosting": "Hosting in Germany",
"encrypted": "End-to-end encrypted",
"sso": "SSO capable"
}
},
"worksCouncil": {
"badge": "For Works Councils",
"title": "Benefits for {highlight}",
"titleHighlight": "Works Councils",
"description": "IT co-determination is intransparent, extensive, lengthy, complicated and overall cumbersome? Our web app offers the solution for all these problems.",
"tabs": {
"awayFrom": "Typical Problems of IT Co-determination",
"towards": "Our Web App Offers the Solution"
},
"awayFromSubtitle": "Typical challenges from works council perspective",
"towardsSubtitle": "With GremiumHub",
"painPoints": [
"Laboriously gathering information on IT/AI systems (emails, PDFs, PowerPoint, Excel chaos)",
"Media breaks, version chaos and recurring inquiries",
"Ad-hoc system introductions under time pressure (chaos instead of process)",
"Overwhelm due to the volume and complexity of systems",
"Black box for functions, evaluations, interfaces and changes",
"Negotiations without reliable data basis",
"Regulated today, outdated tomorrow"
],
"benefits": [
"Structured, complete information basis (uniform per system/processing)",
"Clear co-determination process with proven steps, responsibilities and risk classification",
"Significant time savings compared to conventional procedures for IT/AI systems",
"Comparability across many systems through standardization and uniform templates",
"Transparency about system capabilities including potential performance/behavior monitoring",
"Protective measures consistently documented and traceable per evaluation/processing",
"Operating/service agreements on a solid basis faster, more consistent, easily updatable"
],
"extraBenefitKicker": "Noticeable relief: more time for other co-determination topics and continuous employee protection",
"highlightsTitle": "This is how our {highlight} supports you",
"highlightsTitleHighlight": "Web App",
"highlights": [
{
"title": "Review faster",
"description": "Relevant functions and risks are visible at a glance."
},
{
"title": "Negotiate better",
"description": "Clear, consistent documentation instead of room for interpretation."
},
{
"title": "Secure sustainably",
"description": "Changes and new evaluations can be tracked in a structured way."
}
]
},
"company": {
"badge": "For Companies",
"title": "Benefits for {highlight}",
"titleHighlight": "Companies",
"description": "Faster to agreed IT and AI system implementation with predictable timelines.",
"awayFrom": {
"title": "Problems with IT/AI System Implementation",
"subtitle": "Typical challenges from employer perspective"
},
"towards": {
"title": "Our Web App Offers the Solution",
"subtitle": "With GremiumHub"
},
"painPoints": [
"Unpredictable loops in the co-determination process (missing info, follow-up requests)",
"Delays, project stops and last-minute changes shortly before go-live",
"High coordination effort (email/Excel/Word) and version chaos",
"Complexity and friction due to inconsistent processes and different expectations",
"Unclear processes for updates, feature extensions, new modules etc.",
"Loss of valuable time and limited resources",
"Avoidable significant costs (arbitration board, external consulting, escalations)"
],
"benefits": [
"Plannable, faster procedures through standardization and clear process steps",
"Better collaboration with works councils through transparency and traceable documentation",
"Risk-based approach: fast-track for standard/low-risk systems, focus on high-risk",
"Higher probability of agreement because the negotiation basis is structured",
"Qualitatively consistent, robust operating agreements (also updatable when changes occur)",
"Relief for departments/IT/HR more time for value creation"
],
"highlightsTitle": "How it works in {highlight}",
"highlightsTitleHighlight": "practice",
"highlights": [
{
"title": "Fewer iterations",
"description": "Documents are complete and comparable follow-up requests decrease."
},
{
"title": "Faster to approval",
"description": "Clear steps instead of chaos and short-term rescheduling."
},
{
"title": "Change-ready",
"description": "Updates, new modules or new evaluations are structurally tracked without starting from scratch each time."
}
]
},
"features": {
"badge": "Features",
"title": "Features that make co-determination ",
"titleHighlight": "efficient",
"titleSuffix": "",
"description": "A structured input logic, clear processes and audit-proof documentation so IT/AI systems can be evaluated, coordinated and properly agreed upon faster.",
"items": [
{
"title": "Guided Co-determination Process",
"description": "Predefined, extensible input parameters structure the entire workflow from system description to agreement."
},
{
"title": "Risk-based Assistant",
"description": "A rule-based, risk-oriented assistant adapts the input form to complexity, scope and potential for performance/behavior evaluation."
},
{
"title": "Traffic Light System & Direct Feedback",
"description": "Criticality is transparently assessed and fed back directly during completion for quick orientation and fewer loops."
},
{
"title": "Versioning & Audit Trail",
"description": "Changes are versioned and documented traceably without gaps including history and comparison."
},
{
"title": "Collaboration via Comments",
"description": "Inline comments enable coordination directly on individual content without media breaks."
},
{
"title": "Automatic Agreement Generation",
"description": "After completing the inputs, the system generates structured, clear and signature-ready operating agreements."
},
{
"title": "Signature-ready with QES (eIDAS)",
"description": "Optionally legally binding signing via qualified electronic signature according to eIDAS."
},
{
"title": "Notifications & News Center",
"description": "Stay current on status updates, changes and tasks via news center and email."
},
{
"title": "SSO & Governance",
"description": "SSO integration via SAML or OIDC as well as an extensively configurable role and permission concept."
}
]
},
"additionalFeatures": {
"badge": "Trust & Security",
"title": "Additional {highlight}",
"titleHighlight": "Benefits",
"description": "Everything from one source without third-party providers, with server location in Germany.",
"items": [
{
"title": "No Additional Tools Required",
"description": "No external additional solutions for process, coordination and document creation everything runs in one system."
},
{
"title": "Hosting & Backups in Germany",
"description": "Operations and data backup take place in Germany transparent data flows without unexpected foreign processing."
},
{
"title": "Encrypted Transmission",
"description": "All connections are encrypted via TLS/HTTPS, so content and credentials are protected during transport."
},
{
"title": "Easy to Use",
"description": "User-friendly, self-explanatory interface with guided inputs reduces training effort."
},
{
"title": "Highly Customizable",
"description": "Templates, fields and process steps can be specifically adapted to organization and workflows (on request)."
},
{
"title": "Personal Support",
"description": "Our support team is available for questions fast, competent and in German."
}
],
"ctaQuestion": "Do you have questions about our security standards?",
"ctaButton": "Contact us"
},
"frameworkAgreement": {
"badge": "Regulatory Framework for the Web App",
"title": "Framework Operating Agreement {highlight}",
"titleHighlight": "IT and AI Systems",
"description": "To get started, our partner law firm drafts on request a framework operating/service agreement for IT and AI systems. It is tailored to the structure of the tool as well as your company and contains field-tested regulation building blocks.",
"bulletPoints": [
"Well-founded template for a framework operating/service agreement for IT and AI systems",
"Tailored to the web app logic and documentation structure",
"Contains field-tested regulation building blocks as a starting point for web app-based agreements"
],
"document": {
"title": "Framework Agreement IT and AI Systems",
"subtitle": "Basic Regulation for the Web App",
"tableOfContents": "Table of Contents",
"sections": [
{
"title": "Scope",
"description": "Material scope for IT and AI systems"
},
{
"title": "Co-determination Process",
"description": "Regulatory system and responsibilities"
},
{
"title": "Performance and Behavior Monitoring",
"description": "Protective measures and permissibility"
},
{
"title": "Change Management",
"description": "Updates and extensions"
}
],
"regulationBlocks": "14 regulation blocks",
"fieldTested": "Field-tested"
}
},
"newsletter": {
"title": "Stay {highlight}",
"titleHighlight": "informed",
"description": "Receive updates on the development of GremiumHub and be among the first to learn about new features.",
"placeholder": "Your email address",
"submit": "Subscribe",
"submitted": "Email sent!",
"success": "Please confirm your subscription via the link in the email we just sent you.",
"privacyNote": "I hereby consent to GremiumHub GbR informing me regularly (max. 2 times per month) about the development of the web app. Yes, I have taken note of the {link}. I agree to the electronic processing of my data to the extent specified in the privacy policy. This consent can be revoked at any time for the future.",
"privacyLink": "Privacy Policy",
"validation": {
"required": "Please enter an email address",
"invalid": "Please enter a valid email address"
},
"trust": {
"gdpr": "GDPR compliant",
"encrypted": "Encrypted",
"noSpam": "No spam"
}
},
"newsletterConfirmed": {
"meta": {
"title": "Newsletter Confirmed GremiumHub",
"description": "Thank you for confirming your newsletter subscription."
},
"title": "Subscription Confirmed!",
"description": "Thank you! You are now subscribed to our newsletter and will receive updates on the development of GremiumHub.",
"backToHome": "Back to Home"
},
"expertAccess": {
"badge": "Expert Network",
"title": "External Expertise Cooperation with Specialist Law Firm for Works Councils",
"description": "When the parties cannot resolve certain questions, external expertise can optionally be requested directly in the system. We have a cooperation with Betriebsrat Kanzlei and Wagner IT Systems GmbH, who provide consulting at attractive rates. Requests, follow-up questions and results remain traceably documented.",
"cta": {
"contact": "Contact us",
"learnMore": "Learn more"
},
"experts": {
"labor": {
"title": "Labor Law",
"description": "Specialized labor lawyers"
},
"technical": {
"title": "Technology",
"description": "IT experts"
},
"process": {
"title": "Directly in Process",
"description": "Documented & traceable"
}
}
},
"errors": {
"generic": "An error occurred. Please try again later."
},
"contact": {
"meta": {
"title": "Contact GremiumHub",
"description": "Get in touch with us. We look forward to hearing from you and will respond promptly."
},
"badge": "Contact",
"title": "Get in touch with {highlight}",
"titleHighlight": "us",
"description": "Have questions about GremiumHub or want to learn more about our solution? Write to us we'll get back to you promptly.",
"form": {
"name": "Your name",
"email": "Email address",
"message": "Your message",
"submit": "Send message",
"sending": "Sending..."
},
"validation": {
"nameRequired": "Please enter your name",
"nameMax": "Name must be at most 100 characters",
"emailRequired": "Please enter an email address",
"emailInvalid": "Please enter a valid email address",
"messageRequired": "Please enter a message",
"messageMin": "Message must be at least 10 characters",
"messageMax": "Message must be at most 5000 characters"
},
"success": {
"title": "Message sent!",
"message": "Thank you for your message. We will get back to you as soon as possible.",
"backToHome": "Back to home",
"sendAnother": "Send another message"
},
"trust": {
"gdpr": "GDPR compliant",
"encrypted": "Encrypted",
"responseTime": "Response within 24h"
},
"alternativeContact": {
"title": "Or reach us directly",
"email": "kontakt{'@'}gremiumhub.de",
"phone": "+49 176 47028443"
}
},
"team": {
"meta": {
"title": "Our Team GremiumHub",
"description": "Meet the people behind GremiumHub: Experts in labor law and software development driving digital co-determination forward."
},
"badge": "Our Team",
"title": "The People Behind ",
"titleHighlight": "GremiumHub",
"description": "We combine legal expertise in works council law with technical know-how in software development for modern, structured IT co-determination.",
"members": {
"raphael": {
"name": "Raphael Lugowski",
"role": "Labor Law Attorney",
"description": "Raphael is the founder of Betriebsrat Kanzlei in Hamburg, specializing in works council law. With years of experience advising works councils, he supports committees on IT co-determination, AI systems, and corporate restructuring. He develops field-tested operating agreements and represents works councils in arbitration boards and labor courts.",
"imageAlt": "Profile picture of Raphael Lugowski",
"linkedinLabel": "Open Raphael Lugowski's LinkedIn profile"
},
"denis": {
"name": "Denis Lugowski",
"role": "Senior Full-Stack Developer",
"description": "Denis is a computer scientist (M.Sc.) and full-stack developer with many years of experience in frontend and backend development. He works in an enterprise environment where the highest standards for security, data protection, scalability and reliability are required.",
"imageAlt": "Profile picture of Denis Lugowski",
"linkedinLabel": "Open Denis Lugowski's LinkedIn profile"
}
},
"cta": {
"text": "Have questions or want to learn more about GremiumHub?",
"button": "Contact us"
}
}
}

View File

@@ -1,76 +0,0 @@
export default defineNuxtConfig({
compatibilityDate: '2025-07-15',
modules: ['@nuxt/ui', '@nuxt/eslint', '@nuxt/fonts', '@nuxtjs/i18n'],
css: ['~/assets/css/main.css'],
devtools: { enabled: true },
ssr: true,
// Prerender static pages at build time for instant loading
routeRules: {
'/': { prerender: true },
'/team': { prerender: true },
'/unternehmen': { prerender: true },
'/impressum': { prerender: true },
'/datenschutz': { prerender: true },
'/newsletter-bestaetigt': { prerender: true },
'/kontakt': { prerender: true }
},
// Icon configuration - bundle icons for SSR to prevent hydration mismatches
icon: {
serverBundle: 'remote'
},
// Runtime configuration for server-side API keys
runtimeConfig: {
brevoApiKey: 'NOT_SET',
brevoSenderEmail: 'NOT_SET',
brevoSenderName: 'NOT_SET',
brevoContactEmail: 'NOT_SET',
brevoNewsletterListId: 'NOT_SET',
brevoDoiTemplateId: 'NOT_SET',
public: {
siteUrl: 'NOT_SET'
}
},
// Font configuration - Bricolage Grotesque for headings, DM Sans for body
fonts: {
families: [
{
name: 'Bricolage Grotesque',
provider: 'google',
weights: [400, 600, 700]
},
{
name: 'DM Sans',
provider: 'google',
weights: [400, 500, 600]
}
]
},
// i18n configuration
i18n: {
defaultLocale: 'de',
strategy: 'no_prefix',
locales: [
{ code: 'en', name: 'English', file: 'en.json' },
{ code: 'de', name: 'Deutsch', file: 'de.json' }
]
},
// App configuration
app: {
head: {
title: 'GremiumHub - Digitale Mitbestimmung für IT- und KI-Systeme',
meta: [
{
name: 'description',
content:
'Struktur statt Hauruck: Alle relevanten Informationen an einem Ort, klare Prozessschritte und nachvollziehbare Dokumentation für IT-Mitbestimmung.'
}
]
}
}
})

View File

@@ -1,37 +0,0 @@
{
"name": "legalconsenthub-landing",
"type": "module",
"private": true,
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"format": "prettier . --write",
"type-check": "nuxi typecheck",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"check": "pnpm run lint && pnpm run type-check && pnpm run format && pnpm run test"
},
"dependencies": {
"@nuxt/eslint": "1.12.1",
"@nuxt/fonts": "0.12.1",
"@nuxt/ui": "4.3.0",
"@nuxtjs/i18n": "10.2.1",
"eslint": "9.39.2",
"nuxt": "4.2.2",
"vue": "3.5.26",
"vue-router": "4.6.4",
"zod": "4.3.4"
},
"devDependencies": {
"prettier": "^3.7.4",
"typescript": "5.9.3"
},
"volta": {
"node": "22.16.0",
"pnpm": "10.11.0"
},
"packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad"
}

View File

@@ -1,15 +0,0 @@
import type { NuxtError } from 'nuxt/app'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('vue:error', (error: unknown, _instance, info) => {
const statusCode = (error as NuxtError)?.statusCode
if (statusCode && statusCode >= 500) {
console.error(`[${statusCode}] Vue Error:`, error, info)
}
// Print out all errors that are not HTTP errors
if (!statusCode) {
console.error('[unknown] Vue Error:', error, info)
}
})
})

13323
landing/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,2 +0,0 @@
User-Agent: *
Disallow:

View File

@@ -1,135 +0,0 @@
import { z } from 'zod'
const contactSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
message: z.string().min(10).max(5000)
})
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
// Validate request body
const body = await readBody(event)
const result = contactSchema.safeParse(body)
if (!result.success) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid form data',
data: result.error.flatten()
})
}
const { name, email, message } = result.data
// Check if API key is configured
if (!config.brevoApiKey || config.brevoApiKey === 'NOT_SET') {
console.error('BREVO_API_KEY is not configured')
throw createError({
statusCode: 500,
statusMessage: 'Email service is not configured'
})
}
try {
// Build HTML email content
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: linear-gradient(135deg, #0ea5e9, #06b6d4); color: white; padding: 24px; border-radius: 12px 12px 0 0; }
.content { background: #f8fafc; padding: 24px; border: 1px solid #e2e8f0; border-top: none; border-radius: 0 0 12px 12px; }
.field { margin-bottom: 16px; }
.label { font-weight: 600; color: #64748b; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
.value { margin-top: 4px; padding: 12px; background: white; border-radius: 8px; border: 1px solid #e2e8f0; }
.message-value { white-space: pre-wrap; }
.footer { margin-top: 24px; padding-top: 16px; border-top: 1px solid #e2e8f0; font-size: 12px; color: #94a3b8; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1 style="margin: 0; font-size: 24px;">Neue Kontaktanfrage</h1>
<p style="margin: 8px 0 0; opacity: 0.9;">über GremiumHub Website</p>
</div>
<div class="content">
<div class="field">
<div class="label">Name</div>
<div class="value">${escapeHtml(name)}</div>
</div>
<div class="field">
<div class="label">E-Mail</div>
<div class="value"><a href="mailto:${escapeHtml(email)}">${escapeHtml(email)}</a></div>
</div>
<div class="field">
<div class="label">Nachricht</div>
<div class="value message-value">${escapeHtml(message)}</div>
</div>
<div class="footer">
Diese Nachricht wurde über das Kontaktformular auf gremiumhub.de gesendet.
</div>
</div>
</div>
</body>
</html>
`.trim()
// Send email via Brevo SMTP API
const response = await $fetch<{ messageId?: string }>('https://api.brevo.com/v3/smtp/email', {
method: 'POST',
headers: {
'api-key': config.brevoApiKey,
'Content-Type': 'application/json'
},
body: {
sender: {
name: config.brevoSenderName,
email: config.brevoSenderEmail
},
to: [
{
email: config.brevoContactEmail,
name: 'GremiumHub Team'
}
],
replyTo: {
email,
name
},
subject: `Kontaktanfrage von ${name}`,
htmlContent
}
})
return {
success: true,
messageId: response.messageId
}
} catch (error: unknown) {
const fetchError = error as { statusCode?: number; data?: { code?: string; message?: string } }
console.error('Brevo SMTP API error:', fetchError.data || error)
throw createError({
statusCode: fetchError.statusCode || 500,
statusMessage: fetchError.data?.message || 'Failed to send message'
})
}
})
// Helper function to escape HTML special characters
function escapeHtml(text: string): string {
const htmlEntities: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;'
}
return text.replace(/[&<>"']/g, (char) => htmlEntities[char] ?? char)
}

View File

@@ -1,126 +0,0 @@
import { z } from 'zod'
const subscribeSchema = z.object({
email: z.string().email()
})
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig()
// Validate request body
const body = await readBody(event)
const result = subscribeSchema.safeParse(body)
if (!result.success) {
throw createError({
statusCode: 400,
statusMessage: 'Invalid email address'
})
}
const { email } = result.data
// Check if API key is configured
if (!config.brevoApiKey) {
console.error('BREVO_API_KEY is not configured')
throw createError({
statusCode: 500,
statusMessage: 'Newsletter service is not configured'
})
}
// Check if DOI template ID is configured
if (!config.brevoDoiTemplateId) {
console.error('BREVO_DOI_TEMPLATE_ID is not configured')
throw createError({
statusCode: 500,
statusMessage: 'Newsletter service is not configured'
})
}
console.log('DOI_TEMPLATE_ID', config.brevoDoiTemplateId)
// Check if public site URL is configured (for DOI redirect)
if (!config.public.siteUrl) {
console.error('NUXT_PUBLIC_SITE_URL is not configured')
throw createError({
statusCode: 500,
statusMessage: 'Newsletter service is not configured'
})
}
// Check if newsletter list ID is configured
if (!config.brevoNewsletterListId) {
console.error('BREVO_NEWSLETTER_LIST_ID is not configured')
throw createError({
statusCode: 500,
statusMessage: 'Newsletter service is not configured'
})
}
const templateId = parseInt(config.brevoDoiTemplateId, 10)
if (isNaN(templateId)) {
console.error('BREVO_DOI_TEMPLATE_ID is not a valid number')
throw createError({
statusCode: 500,
statusMessage: 'Newsletter service is not configured'
})
}
const listId = parseInt(config.brevoNewsletterListId, 10)
if (isNaN(listId)) {
console.error('BREVO_NEWSLETTER_LIST_ID is not a valid number')
throw createError({
statusCode: 500,
statusMessage: 'Newsletter service is not configured'
})
}
// Build the redirection URL to the confirmation page
const redirectionUrl = `${config.public.siteUrl}/newsletter-bestaetigt`
try {
// Build request body for Brevo Double Opt-In API
const brevoBody = {
email,
templateId,
redirectionUrl,
includeListIds: [listId]
}
console.log('Brevo DOI request body:', brevoBody)
await $fetch('https://api.brevo.com/v3/contacts/doubleOptinConfirmation', {
method: 'POST',
headers: {
accept: 'application/json',
'api-key': config.brevoApiKey,
'content-type': 'application/json'
},
body: brevoBody
})
return {
success: true
}
} catch (error: unknown) {
// Handle Brevo API errors
const fetchError = error as { statusCode?: number; data?: { code?: string; message?: string } }
// Contact already exists (duplicate_parameter error) - treat as success
// User will receive another confirmation email
if (fetchError.data?.code === 'duplicate_parameter') {
return {
success: true,
message: 'Confirmation email sent'
}
}
console.error('Brevo API error:', fetchError.data || error)
throw createError({
statusCode: fetchError.statusCode || 500,
statusMessage: fetchError.data?.message || 'Failed to subscribe to newsletter'
})
}
})

View File

@@ -1,16 +0,0 @@
import type { H3Error } from 'h3'
// Due to a bug in nitro, the stack trace is missing on the server side (https://github.com/nuxt/nuxt/issues/30102)
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('error', (error, { event }) => {
const statusCode = (error as H3Error)?.statusCode
if (statusCode && statusCode >= 500) {
console.error(`[${statusCode}] ${event?.path}`, error)
}
// Print out all errors that are not HTTP errors
if (!statusCode) {
console.error(`[Error] ${event?.path}`, error)
}
})
})

View File

@@ -1,18 +0,0 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"files": [],
"references": [
{
"path": "./.nuxt/tsconfig.app.json"
},
{
"path": "./.nuxt/tsconfig.server.json"
},
{
"path": "./.nuxt/tsconfig.shared.json"
},
{
"path": "./.nuxt/tsconfig.node.json"
}
]
}