RC: (upload) astro initial structure

This commit is contained in:
Raul Costa
2026-04-01 00:19:49 +01:00
commit 8c11192e7b
29 changed files with 8561 additions and 0 deletions

View 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>