167 lines
4.2 KiB
Vue
167 lines
4.2 KiB
Vue
<template>
|
|
<section
|
|
id="home"
|
|
class="hero-shell relative overflow-hidden bg-gradient-to-br from-gray-50 to-gray-100"
|
|
>
|
|
<!-- Decorative Elements -->
|
|
<div class="absolute inset-0 z-0">
|
|
<div class="absolute top-0 right-0 w-96 h-96 bg-primary-200/30 rounded-full blur-3xl" />
|
|
<div class="absolute bottom-0 left-0 w-96 h-96 bg-gray-300/30 rounded-full blur-3xl" />
|
|
<picture class="absolute inset-0 opacity-15">
|
|
<source
|
|
v-if="heroImage.mobileWebp && heroImage.desktopWebp"
|
|
type="image/webp"
|
|
:srcset="`${heroImage.mobileWebp} 960w, ${heroImage.desktopWebp} 1600w`"
|
|
sizes="(max-width: 1024px) 960px, 1600px"
|
|
>
|
|
<img
|
|
:src="heroImage.fallback"
|
|
alt=""
|
|
aria-hidden="true"
|
|
class="w-full h-full object-cover object-[center_36%]"
|
|
width="1600"
|
|
height="900"
|
|
loading="eager"
|
|
fetchpriority="high"
|
|
decoding="async"
|
|
>
|
|
</picture>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="relative z-20 max-w-7xl mx-auto">
|
|
<div class="text-center">
|
|
<h1 class="text-5xl sm:text-6xl lg:text-7xl font-display font-bold text-gray-900 mb-6 leading-tight animate-fade-in">
|
|
Willkommen beim<br>
|
|
<span class="text-primary-600">Harheimer TC</span>
|
|
</h1>
|
|
|
|
<p class="text-xl sm:text-2xl text-gray-700 mb-8 max-w-3xl mx-auto animate-fade-in-delay-1">
|
|
Tradition trifft Moderne - Ihr Tischtennisverein in Frankfurt-Harheim seit {{ yearsSinceFounding }} Jahren
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
import { useFetch, useHead, useState } from '#imports'
|
|
|
|
function buildInlineFallback() {
|
|
const svg = `
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
|
|
<defs>
|
|
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
<stop offset="0%" stop-color="#eef2f7" />
|
|
<stop offset="100%" stop-color="#d8e0ea" />
|
|
</linearGradient>
|
|
</defs>
|
|
<rect width="1600" height="900" fill="url(#g)" />
|
|
</svg>`
|
|
|
|
return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`
|
|
}
|
|
|
|
const DEFAULT_HERO_IMAGE = {
|
|
key: 'fallback',
|
|
mobileWebp: '',
|
|
desktopWebp: '',
|
|
fallback: buildInlineFallback()
|
|
}
|
|
|
|
const { data: heroImagesResponse } = await useFetch('/api/hero-images')
|
|
|
|
const heroVariants = computed(() => {
|
|
const variants = heroImagesResponse.value?.variants
|
|
return Array.isArray(variants) && variants.length ? variants : [DEFAULT_HERO_IMAGE]
|
|
})
|
|
|
|
function pickRandomHeroImage(variants) {
|
|
const list = Array.isArray(variants) && variants.length ? variants : [DEFAULT_HERO_IMAGE]
|
|
const index = Math.floor(Math.random() * list.length)
|
|
return list[index]
|
|
}
|
|
|
|
const heroImageState = useState('home-hero-image', () => pickRandomHeroImage(heroVariants.value))
|
|
if (!heroVariants.value.some((variant) => variant.key === heroImageState.value?.key)) {
|
|
heroImageState.value = pickRandomHeroImage(heroVariants.value)
|
|
}
|
|
|
|
const heroImage = computed(() => heroImageState.value)
|
|
|
|
const preloadLinks = computed(() => {
|
|
const links = []
|
|
if (heroImage.value.mobileWebp) {
|
|
links.push({
|
|
rel: 'preload',
|
|
as: 'image',
|
|
href: heroImage.value.mobileWebp,
|
|
type: 'image/webp',
|
|
media: '(max-width: 1024px)'
|
|
})
|
|
}
|
|
|
|
if (heroImage.value.desktopWebp) {
|
|
links.push({
|
|
rel: 'preload',
|
|
as: 'image',
|
|
href: heroImage.value.desktopWebp,
|
|
type: 'image/webp',
|
|
media: '(min-width: 1025px)'
|
|
})
|
|
}
|
|
|
|
return links
|
|
})
|
|
|
|
useHead(() => ({
|
|
link: preloadLinks.value
|
|
}))
|
|
|
|
const foundingYear = 1954
|
|
const yearsSinceFounding = new Date().getFullYear() - foundingYear
|
|
</script>
|
|
|
|
<style scoped>
|
|
.hero-shell {
|
|
min-height: 430px;
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.hero-shell {
|
|
min-height: 540px;
|
|
}
|
|
}
|
|
|
|
@media (min-aspect-ratio: 21/9) {
|
|
.hero-shell {
|
|
min-height: 640px;
|
|
}
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(30px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.animate-fade-in {
|
|
animation: fadeIn 0.8s ease-out;
|
|
}
|
|
|
|
.animate-fade-in-delay-1 {
|
|
animation: fadeIn 0.8s ease-out 0.2s both;
|
|
}
|
|
|
|
.animate-fade-in-delay-2 {
|
|
animation: fadeIn 0.8s ease-out 0.4s both;
|
|
}
|
|
</style>
|
|
|