196 lines
4.4 KiB
Vue
196 lines
4.4 KiB
Vue
<template>
|
|
<section class="hero">
|
|
<div class="hero-bg" :style="'background-image:url('+image+')'"></div>
|
|
<div class="hero-overlay"></div>
|
|
|
|
<div class="hero-content">
|
|
<h1 class="hero-title animate-fade">
|
|
<GradientText>{{titleLineOne}}</GradientText><br>
|
|
<span>{{titleLineTwo}}</span>
|
|
</h1>
|
|
|
|
<p class="hero-subtitle animate-slide">
|
|
{{subtitleLineOne}}<br/>
|
|
{{subtitleLineTwo}}<br/>
|
|
</p>
|
|
<div class="hero-buttons">
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
<ScrollIndicator />
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Hero Section */
|
|
.hero {
|
|
position: relative;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.hero-bg {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 120%;
|
|
background-size: cover;
|
|
background-position: center;
|
|
will-change: transform;
|
|
}
|
|
|
|
.hero-overlay {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 120%;
|
|
background: var(--gradient-hero);
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.hero-content {
|
|
position: relative;
|
|
z-index: 10;
|
|
text-align: center;
|
|
max-width: 1000px;
|
|
padding: 0 2rem;
|
|
display:flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.hero-title {
|
|
font-size: clamp(3rem, 8vw, 6rem);
|
|
font-weight: bold;
|
|
margin-bottom: 1.5rem;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.hero-subtitle {
|
|
font-size: clamp(1.125rem, 2.5vw, 1.5rem);
|
|
color: var(--muted-foreground);
|
|
margin-bottom: 2rem;
|
|
max-width: 600px;
|
|
margin-left: auto;
|
|
margin-right: auto;
|
|
}
|
|
|
|
.hero-buttons {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
align-items: center;
|
|
}
|
|
|
|
.hero-title,
|
|
.hero-subtitle,
|
|
.hero-buttons{
|
|
opacity: 0;
|
|
transform: translateY(18px);
|
|
transition: opacity 1000ms cubic-bezier(.22,.9,.32,1),
|
|
transform 1000ms cubic-bezier(.22,.9,.32,1);
|
|
will-change: opacity, transform;
|
|
}
|
|
|
|
/* visible state toggled by JS */
|
|
.hero-title.is-visible,
|
|
.hero-subtitle.is-visible,
|
|
.hero-buttons.is-visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
@media (min-width: 640px) {
|
|
.hero-buttons {
|
|
width: 50%;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
}
|
|
@media (max-width: 767px) {
|
|
|
|
.hero-content {
|
|
padding: 0 1rem;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script setup lang="ts">
|
|
|
|
import EdgyButton from "@/components/dredgy/EdgyButton.vue";
|
|
import GradientText from "@/components/dredgy/GradientText.vue";
|
|
import { onMounted } from "vue";
|
|
import ScrollIndicator from "@/components/dredgy/ScrollIndicator.vue";
|
|
|
|
const initParallax = (): void => {
|
|
const heroBackground = document.querySelector('.hero-bg') as HTMLElement | null;
|
|
const heroSection = document.querySelector('.hero') as HTMLElement | null;
|
|
if (!heroBackground || !heroSection) return;
|
|
|
|
window.addEventListener('scroll', () => {
|
|
const scrolled = window.pageYOffset;
|
|
const heroRect = heroSection.getBoundingClientRect();
|
|
const heroHeight = heroSection.offsetHeight;
|
|
|
|
if (heroRect.bottom > 0) {
|
|
const rate = scrolled * -0.15;
|
|
const maxMovement = heroHeight * 0.1;
|
|
const constrainedRate = Math.max(rate, -maxMovement);
|
|
|
|
heroBackground.style.transform = `translateY(${constrainedRate}px)`;
|
|
}
|
|
});
|
|
};
|
|
|
|
// Add loading animations
|
|
|
|
const initLoadingAnimations = (): void => {
|
|
document.body.classList.add('loaded');
|
|
|
|
const heroTitle = document.querySelector('.hero-title') as HTMLElement | null;
|
|
const heroSubtitle = document.querySelector('.hero-subtitle') as HTMLElement | null;
|
|
const heroButtons = document.querySelector('.hero-buttons') as HTMLElement | null;
|
|
|
|
if (heroTitle) {
|
|
setTimeout(() => {
|
|
// force reflow (safe), then add class to trigger transition
|
|
void heroTitle.offsetWidth;
|
|
heroTitle.classList.add('is-visible');
|
|
}, 500);
|
|
}
|
|
|
|
if (heroSubtitle) {
|
|
setTimeout(() => {
|
|
void heroSubtitle.offsetWidth;
|
|
heroSubtitle.classList.add('is-visible');
|
|
}, 750);
|
|
}
|
|
|
|
if (heroButtons) {
|
|
setTimeout(() => {
|
|
void heroButtons.offsetWidth;
|
|
heroButtons.classList.add('is-visible');
|
|
}, 900);
|
|
}
|
|
};
|
|
|
|
defineProps({
|
|
image: String,
|
|
titleLineOne: String,
|
|
titleLineTwo: String,
|
|
subtitleLineOne: String,
|
|
subtitleLineTwo: String,
|
|
})
|
|
|
|
onMounted(() => {
|
|
initParallax()
|
|
initLoadingAnimations()
|
|
})
|
|
</script>
|