feat(landing): Mobile viewport optimizations

This commit is contained in:
2026-01-10 08:24:57 +01:00
parent 52158a8e48
commit fd53521992
8 changed files with 275 additions and 167 deletions

View File

@@ -18,14 +18,14 @@
}"
>
<template #title>
<NuxtLink to="/" class="flex items-center gap-3 group">
<NuxtLink to="/" class="flex items-center gap-2 sm:gap-3 group">
<div
class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-cyan-500 flex items-center justify-center shadow-lg shadow-primary-500/25 group-hover:shadow-primary-500/40 transition-shadow"
>
<UIcon name="i-lucide-scale" class="w-5 h-5 text-white" />
</div>
<span
class="font-heading text-xl font-bold bg-gradient-to-r from-primary-600 to-cyan-600 bg-clip-text text-transparent"
class="hidden sm:inline font-heading text-xl font-bold bg-gradient-to-r from-primary-600 to-cyan-600 bg-clip-text text-transparent"
>
LegalConsentHub
</span>
@@ -43,7 +43,7 @@
<USelectMenu
v-model="selectedLocale"
:items="localeItems"
class="w-[100px]"
class="w-[90px] sm:w-[100px]"
:ui="{
base: 'text-sm'
}"
@@ -55,7 +55,7 @@
/>
<UButton
to="#newsletter"
class="btn-gradient px-5 py-2.5 rounded-xl font-semibold shadow-lg shadow-primary-500/25 hover:shadow-primary-500/40 transition-shadow"
class="hidden sm:flex btn-gradient px-5 py-2.5 rounded-xl font-semibold shadow-lg shadow-primary-500/25 hover:shadow-primary-500/40 transition-shadow"
>
<span>{{ $t('common.stayInformed') }}</span>
</UButton>

View File

@@ -1,6 +1,23 @@
@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 */

View File

@@ -16,8 +16,7 @@
<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', { highlight: '' })
}}<span class="gradient-text">{{ $t('features.titleHighlight') }}</span>
{{ $t('features.title') }}<span class="gradient-text">{{ $t('features.titleHighlight') }}</span>{{ $t('features.titleSuffix') }}
</h2>
</div>
</template>

View File

@@ -5,8 +5,8 @@
}"
>
<div class="grid lg:grid-cols-2 gap-12 lg:gap-16 items-center">
<!-- Left: Content -->
<div class="order-2 lg:order-1">
<!-- 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"
>
@@ -48,7 +48,7 @@
>
<div class="flex items-center gap-3">
<div
class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-cyan-500 flex items-center justify-center"
class="w-10 h-10 rounded-xl bg-gradient-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>
@@ -72,9 +72,9 @@
</div>
</div>
<!-- Right: 3D Document Preview -->
<div class="order-1 lg:order-2 flex justify-center">
<div class="relative animate-scale-in">
<!-- 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"
@@ -89,7 +89,7 @@
<!-- 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 max-w-sm"
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-gradient-to-r from-primary-600 to-cyan-600 px-6 py-5">
@@ -156,23 +156,16 @@
</div>
</div>
<!-- Floating badge -->
<div class="absolute -top-4 -right-4 z-10 animate-float-fast">
<!-- 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-gradient-to-r from-accent-500 to-violet-500 text-white px-4 py-2 rounded-full shadow-lg text-sm font-semibold flex items-center gap-2"
class="bg-gradient-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>
<!-- Decorative elements -->
<div class="absolute -top-8 -left-8 w-32 h-32 bg-primary-400/20 rounded-full blur-3xl animate-pulse-glow" />
<div
class="absolute -bottom-8 -right-8 w-40 h-40 bg-accent-400/20 rounded-full blur-3xl animate-pulse-glow"
style="animation-delay: 1s"
/>
</div>
</div>
</div>

View File

@@ -84,150 +84,38 @@
<!-- Floating product preview cards -->
<div class="mt-16 lg:mt-24 relative animate-scale-in" style="animation-delay: 600ms">
<div class="flex flex-col lg:flex-row items-center justify-center gap-6 lg:gap-8">
<!-- Left card - tilted -->
<div class="card-3d w-full max-w-xs lg:-mr-8 lg:mt-12 order-2 lg:order-1">
<div
class="card-3d-inner glass rounded-2xl p-5 shadow-xl transform lg:-rotate-6 hover:rotate-0 transition-transform duration-500"
>
<div class="flex items-center gap-3 mb-4">
<div
class="w-10 h-10 rounded-xl bg-gradient-to-br from-warning-400 to-warning-500 flex items-center justify-center"
>
<UIcon name="i-lucide-alert-triangle" class="w-5 h-5 text-white" />
</div>
<div>
<p class="font-semibold text-gray-900 dark:text-white 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="space-y-2">
<div 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.privacyImpact') }}</span>
</div>
<div 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 gap-2">
<div class="w-4 h-4 rounded bg-success-100 dark:bg-success-900/50 flex items-center justify-center">
<UIcon name="i-lucide-check" class="w-2.5 h-2.5 text-success-600" />
</div>
<span class="text-xs text-gray-600 dark:text-gray-300">{{ $t('hero.cards.accessDefined') }}</span>
</div>
<!-- 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>
<!-- Center card - main -->
<div class="w-full max-w-md z-10 order-1 lg:order-2">
<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-gradient-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-gradient-to-r from-primary-500 to-cyan-500 rounded-full" />
<!-- 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>
<!-- 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>
<!-- Center card - main (Microsoft 365) -->
<div class="w-full max-w-md z-10">
<LandingHeroMainCard />
</div>
<!-- Action button -->
<button
class="w-full py-3 px-4 rounded-xl bg-gradient-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>
</div>
<!-- Right card - tilted -->
<div class="card-3d w-full max-w-xs lg:-ml-8 lg:mt-12 order-3">
<div
class="card-3d-inner glass rounded-2xl p-5 shadow-xl transform lg:rotate-6 hover:rotate-0 transition-transform duration-500"
>
<div class="flex items-center gap-3 mb-4">
<div
class="w-10 h-10 rounded-xl bg-gradient-to-br from-success-400 to-success-500 flex items-center justify-center"
>
<UIcon name="i-lucide-check-circle" class="w-5 h-5 text-white" />
</div>
<div>
<p class="font-semibold text-gray-900 dark:text-white 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="space-y-2">
<div 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">SAP S/4HANA</span>
<UIcon name="i-lucide-check" class="w-4 h-4 text-success-500" />
</div>
<div 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">Workday HCM</span>
<UIcon name="i-lucide-check" class="w-4 h-4 text-success-500" />
</div>
<div 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>
<!-- 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>
@@ -252,19 +140,29 @@
</div>
</div>
<!-- Scroll indicator -->
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 animate-bounce">
<!-- Scroll indicator - stable wrapper for click target, animated inner element -->
<div class="absolute bottom-8 left-1/2 -translate-x-1/2 z-20">
<a
href="#betriebsraete"
class="flex items-center justify-center w-12 h-12 rounded-full glass text-gray-600 dark:text-gray-300 hover:bg-white/80 dark:hover:bg-gray-800/80 transition-colors"
class="block"
:aria-label="$t('hero.scrollDown')"
@click.prevent="scrollToSection"
>
<div
class="animate-bounce flex items-center justify-center w-12 h-12 rounded-full glass text-gray-600 dark:text-gray-300 hover:bg-white/80 dark:hover:bg-gray-800/80 transition-colors"
>
<UIcon name="i-lucide-chevron-down" class="w-6 h-6" />
</div>
</a>
</div>
</section>
</template>
<script setup lang="ts">
// No additional script needed - using $t() from i18n
const scrollToSection = () => {
const target = document.querySelector('#betriebsraete')
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
}
</script>

View File

@@ -0,0 +1,81 @@
<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-gradient-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-gradient-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-gradient-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>

View File

@@ -0,0 +1,54 @@
<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-gradient-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">
defineProps<{
compact?: boolean
}>()
</script>

View File

@@ -0,0 +1,66 @@
<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-gradient-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">
defineProps<{
compact?: boolean
}>()
</script>