RC: (upload) astro initial structure
This commit is contained in:
334
src/components/ParallaxHero.astro
Normal file
334
src/components/ParallaxHero.astro
Normal file
@@ -0,0 +1,334 @@
|
||||
---
|
||||
// ParallaxHero.astro — Osmo-style multi-layer GSAP parallax, Petronas theme
|
||||
---
|
||||
|
||||
<div class="parallax">
|
||||
<section class="parallax__header">
|
||||
<div class="parallax__visuals">
|
||||
|
||||
<div data-parallax-layers class="parallax__layers">
|
||||
|
||||
<!-- LAYER 1 — bg photo (slowest, most travel) -->
|
||||
<img
|
||||
src="/image_zb1c2e65feww76x.png"
|
||||
loading="eager"
|
||||
data-parallax-layer="1"
|
||||
alt=""
|
||||
class="parallax__layer-img"
|
||||
/>
|
||||
|
||||
<!-- LAYER 2 — teal colour grade (follows bg) -->
|
||||
<div data-parallax-layer="2" class="parallax__layer-tint"></div>
|
||||
|
||||
<!-- LAYER 3 — title (mid speed) -->
|
||||
<div data-parallax-layer="3" class="parallax__layer-title">
|
||||
|
||||
<h1 class="parallax__title">
|
||||
<span class="parallax__title-accent">/</span>slashroot
|
||||
</h1>
|
||||
<!-- <div class="parallax__eyebrow font-mono">
|
||||
<span style="color:var(--petronas-teal)">$</span> cat /var/log/adventures.log
|
||||
</div> -->
|
||||
<div class="parallax__cta">
|
||||
<a href="/blog" class="btn btn-primary">Read the logs</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LAYER 4 — figure foreground PNG (barely moves = pops forward) -->
|
||||
<div data-parallax-layer="4" class="parallax__layer-figure" aria-hidden="true">
|
||||
<img
|
||||
src="/image_zb1c25zb1c25zb1c.png"
|
||||
alt=""
|
||||
class="parallax__figure-img"
|
||||
/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Bottom fade -->
|
||||
<div class="parallax__fade"></div>
|
||||
|
||||
<!-- Scroll hint inside header for absolute positioning -->
|
||||
<div class="parallax__scroll-hint" id="scroll-hint" aria-hidden="true">
|
||||
<span class="font-mono">scroll</span>
|
||||
<div class="scroll-line"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js" is:inline></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js" is:inline></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/lenis@1.1.13/dist/lenis.min.js" is:inline></script>
|
||||
|
||||
<script is:inline>
|
||||
if (history.scrollRestoration) {
|
||||
history.scrollRestoration = "manual";
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
document.querySelectorAll('[data-parallax-layers]').forEach((trigger) => {
|
||||
const header = trigger.closest('.parallax__header');
|
||||
const tl = gsap.timeline({
|
||||
scrollTrigger: {
|
||||
trigger: header,
|
||||
start: "top top",
|
||||
end: "bottom top",
|
||||
scrub: 0,
|
||||
invalidateOnRefresh: true
|
||||
}
|
||||
});
|
||||
|
||||
// tl.to('[data-parallax-layer="1"]', { yPercent: 40, ease: "none" }, 0)
|
||||
tl.fromTo('[data-parallax-layer="1"]',
|
||||
{ yPercent: 0 }, // FORCE the start position
|
||||
{ yPercent: 40, ease: "none" }, // The end position
|
||||
0 // Keep your 0 position parameter
|
||||
)
|
||||
.to('[data-parallax-layer="2"]', { yPercent: 40, ease: "none" }, 0)
|
||||
.to('[data-parallax-layer="3"]', { yPercent: 20, ease: "none" }, 0)
|
||||
.to('[data-parallax-layer="4"]', { yPercent: 6, ease: "none" }, 0);
|
||||
});
|
||||
|
||||
// Lenis smooth scroll
|
||||
const lenis = new Lenis();
|
||||
lenis.on('scroll', ScrollTrigger.update);
|
||||
gsap.ticker.add((time) => { lenis.raf(time * 1000); });
|
||||
gsap.ticker.lagSmoothing(0);
|
||||
|
||||
// Fade scroll hint
|
||||
const hint = document.getElementById('scroll-hint');
|
||||
if (hint) {
|
||||
ScrollTrigger.create({
|
||||
start: 60,
|
||||
onEnter: () => gsap.to(hint, { opacity: 0, duration: 0.4 }),
|
||||
onLeaveBack: () => gsap.to(hint, { opacity: 1, duration: 0.4 }),
|
||||
});
|
||||
}
|
||||
|
||||
// Entrance animation
|
||||
const intro = gsap.timeline({ defaults: { ease: "power2.out" } });
|
||||
intro
|
||||
.from('.parallax__eyebrow', { opacity: 0, y: 20, duration: 0.8 }, 0.3)
|
||||
.from('.parallax__title', { opacity: 0, y: 32, duration: 1.0 }, 0.5)
|
||||
.from('.parallax__subtitle', { opacity: 0, y: 16, duration: 0.8 }, 0.72)
|
||||
.from('.parallax__cta', { opacity: 0, y: 12, duration: 0.7 }, 0.92)
|
||||
.from('.parallax__figure-img', { opacity: 0, y: 24, duration: 1.0 }, 0.25);
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
ScrollTrigger.refresh();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.parallax {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Osmo natural height — no 200vh hack */
|
||||
.parallax__header {
|
||||
min-height: 100svh;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Osmo: absolute + 120% height, no sticky */
|
||||
.parallax__visuals {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 110%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.parallax__layers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Osmo magic offset — pulled up so it has room to scroll down */
|
||||
.parallax__layer-img {
|
||||
position: absolute;
|
||||
top: -20.5%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 117.5%;
|
||||
object-fit: cover;
|
||||
object-position: center 30%;
|
||||
display: block;
|
||||
filter: saturate(0.25) brightness(0.45);
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* Layer 2 — teal tint, same geometry as bg */
|
||||
.parallax__layer-tint {
|
||||
position: absolute;
|
||||
top: -17.5%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 117.5%;
|
||||
background:
|
||||
radial-gradient(ellipse 65% 50% at 50% 60%, rgba(0,210,190,0.13) 0%, transparent 65%),
|
||||
linear-gradient(to bottom,
|
||||
rgba(5,10,10,0.65) 0%,
|
||||
transparent 20%,
|
||||
transparent 65%,
|
||||
rgba(5,10,10,0.85) 100%
|
||||
),
|
||||
linear-gradient(to right,
|
||||
rgba(5,10,10,0.5) 0%,
|
||||
transparent 25%,
|
||||
transparent 75%,
|
||||
rgba(5,10,10,0.5) 100%
|
||||
);
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* Layer 3 — title: same top offset as bg, centred vertically */
|
||||
.parallax__layer-title {
|
||||
position: absolute;
|
||||
top: -20.5%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 117.5%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 1.5rem;
|
||||
padding-bottom: 12vh;
|
||||
text-align: center;
|
||||
z-index: 1;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.parallax__eyebrow {
|
||||
font-size: clamp(0.68rem, 1.4vw, 0.88rem);
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.12em;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.parallax__title {
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
font-size: clamp(4rem, 13vw, 11rem);
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 0.92;
|
||||
color: var(--text-primary);
|
||||
text-shadow:
|
||||
0 0 120px rgba(0,210,190,0.2),
|
||||
0 4px 60px rgba(0,0,0,0.95);
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.parallax__title-accent { color: var(--petronas-teal); }
|
||||
|
||||
.parallax__subtitle {
|
||||
font-size: clamp(0.72rem, 1.8vw, 0.95rem);
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 2.5rem;
|
||||
max-width: 54ch;
|
||||
}
|
||||
|
||||
.parallax__cta {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
width: 120%;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.parallax__cta-ghost {
|
||||
font-size: 0.88rem;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.06em;
|
||||
transition: color 150ms;
|
||||
}
|
||||
.parallax__cta-ghost:hover { color: var(--petronas-teal); }
|
||||
|
||||
/* Layer 4 — figure: same Osmo offset as bg so GSAP travel is consistent.
|
||||
SVG is constrained with max-width so it doesn't fill the whole container. */
|
||||
.parallax__layer-figure {
|
||||
position: absolute;
|
||||
top: -9.5%;
|
||||
left: 0;
|
||||
width: 120%;
|
||||
height: 105.5%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 2vh;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.parallax__figure-img {
|
||||
width: clamp(200px, 50vh, 650px);
|
||||
height: 70vh;
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
object-position: bottom center;
|
||||
filter: drop-shadow(0 -8px 24px rgba(0,210,190,0.15));
|
||||
}
|
||||
|
||||
/* ── Bottom fade ─────────────────────────────────────────────── */
|
||||
.parallax__fade {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 180px;
|
||||
background: linear-gradient(to bottom, transparent, var(--bg-void));
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Scroll hint — absolute inside the 100svh header */
|
||||
.parallax__scroll-hint {
|
||||
position: absolute;
|
||||
bottom: 2.5rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
z-index: 30;
|
||||
font-size: 0.62rem;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.scroll-line {
|
||||
width: 1px;
|
||||
height: 48px;
|
||||
background: linear-gradient(to bottom, var(--petronas-teal), transparent);
|
||||
transform-origin: top;
|
||||
animation: scroll-grow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes scroll-grow {
|
||||
0% { transform: scaleY(0); opacity: 1; }
|
||||
65% { transform: scaleY(1); opacity: 1; }
|
||||
100% { transform: scaleY(1); opacity: 0; }
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.parallax__figure-svg { width: 110px; }
|
||||
.parallax__cta { gap: 1rem; }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user