<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<div class="container">
<nav>
<p id="logo">Imagecho</p>
<button>Download</button>
</nav>
<section class="intro">
</section>
<section class="steps">
<div class="step-counter">
<div class="counter-title">
<h1>steps</h1>
</div>
<div class="count">
<div class="count-container">
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
<h1>4</h1>
<h1>5</h1>
</div>
</div>
</div>
<div class="cards">
<div class="card">
<div class="card-img">
<img src="./img1.jpg" alt="">
</div>
<div class="card-content">
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.</p>
</div>
</div>
<div class="card">
<div class="card-img">
<img src="./img2.jpg" alt="">
</div>
<div class="card-content">
<p>It can be used through clothing without removing the prosthesis and evenly</p>
</div>
</div>
<div class="card">
<div class="card-img">
<img src="./img3.jpg" alt="">
</div>
<div class="card-content">
<p>distributes air within the socket to adapt to different</p>
</div>
</div>
<div class="card">
<div class="card-img">
<img src="./img4.jpg" alt="">
</div>
<div class="card-content">
<p>residual limb morphologies.</p>
</div>
</div>
<div class="card">
<div class="card-img">
<img src="./img5.jpg" alt="">
</div>
<div class="card-content">
<p>The Overlay works with most liners and is compatible with distal fixation</p>
</div>
</div>
<div class="card empty"></div>
<div class="card empty"></div>
</div>
</section>
<section class="outro">
<p> Our Overlay is designed to be used with <span>most liners and is compatible</span> with distal fixation</p>
</section>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script src="https://unpkg.com/lenis@1.1.18/dist/lenis.min.js"></script>
<script src="./script.js"></script>
</body>
</html>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html,
body {
width: 100vw;
height: 900vh;
background-color: #000;
color: #fff;
font-family: "Poppins", sans-serif;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
nav {
position: absolute;
top: 0;
left: 0;
width: 100vw;
padding: 1.5em;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 2;
}
p#logo {
text-transform: uppercase;
font-family: "Poppins", sans-serif;
font-weight: 900;
}
button {
border: none;
outline: none;
font-weight: 500;
background-color: #fff;
color: #000;
padding: 0.75em 1em;
border-radius: 0.25em;
cursor: pointer;
}
section {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
.intro {
background: url("./img2.jpg") no-repeat 50% 50%;
background-size: cover;
}
.outro {
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(180deg, #000, #364549);
background-size: 200% 200%;
}
.outro p {
width: 75%;
color: #fff;
text-align: center;
font-size: 52px;
font-weight: 400;
line-height: 1.125;
}
.outro p span {
color: #75e1ff;
}
.cards {
position: absolute;
top: 25%;
left: 50%;
transform: translate(-50%, -50%);
width: 150vw;
height: 600px;
will-change: transform;
}
.card {
position: absolute;
width: 500px;
height: 550px;
top: 50%;
left: 50%;
transform-origin: center center;
margin-left: -250px;
display: flex;
flex-direction: column;
gap: 1em;
will-change: transform;
}
.card-img {
flex: 1;
border-radius: 0.5em;
overflow: hidden;
}
.card-content {
width: 100%;
height: 60px;
}
.card-content p {
text-align: left;
color: #fff;
font-size: 16px;
font-weight: 500;
line-height: 1.25;
}
.step-counter {
position: absolute;
display: flex;
flex-direction: column;
margin: 2em;
}
.counter-title,
.count {
position: absolute;
width: 1200px;
height: 150px;
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
overflow: hidden;
}
.count {
top: -10px;
}
.count-container {
position: relative;
transform: translateY(150px);
will-change: transform;
}
.step-counter h1 {
width: 100%;
position: relative;
color: #fff;
text-transform: uppercase;
font-size: 150px;
font-weight: 900;
line-height: 1;
letter-spacing: -0.04em;
will-change: transform;
}
.empty {
opacity: 0;
}
@media (max-width: 900px) {
.counter-title {
height: 30px;
}
.step-counter h1 {
font-size: 30px;
}
.count {
top: 0px;
left: -10px;
}
.cards {
top: 27.5%;
}
.card {
width: 375px;
height: 500px;
}
}
document.addEventListener("DOMContentLoaded", () => {
gsap.registerPlugin(ScrollTrigger)
const lenis = new Lenis()
lenis.on("scroll", ScrollTrigger.update)
gsap.ticker.add((time)=>{
lenis.raf(time * 1000)
})
gsap.ticker.lagSmoothing(0)
const stickySection = document.querySelector(".steps")
const stickyHeight = window.innerHeight * 7
const cards = document.querySelectorAll(".card")
const countContainer = document.querySelector(".count-container")
const totalCards = cards.length
ScrollTrigger.create({
trigger: stickySection,
start: "top top",
end: `+=${stickyHeight}px`,
pin: true,
pinSpacing: true,
onUpdate: (self) => {
positionCards(self.progress)
}
})
const getRadius = () =>{
return window.innerWidth < 900 ? window.innerWidth * 7.5 : window.innerWidth * 2.5
}
const arcAngle = Math.PI * 0.4
const startAngle = Math.PI / 2 - arcAngle / 2
function positionCards(progress = 0){
const radius = getRadius()
const totalTravel = 1 + totalCards / 7.5
const adjustedProgress = (progress * totalTravel - 1) * 0.75
cards.forEach((card, index) => {
const normalizedProgress =(totalCards - index - 1) / totalCards
const cardProgress = normalizedProgress + adjustedProgress
const angle = startAngle + cardProgress * arcAngle
const x = radius * Math.cos(angle)
const y = radius * Math.sin(angle)
const rotation = (angle - Math.PI / 2) * (180 / Math.PI)
gsap.set(card,{
x: x,
y: -y + radius,
rotation: -rotation,
transformOrigin:"center center",
})
})
}
positionCards(0)
let currentCardIndex = 0
const options = {
root: null,
rootMargin: "0px",
threshold: 0.5,
}
const observer = new IntersectionObserver((entries)=>{
entries.forEach((entry)=>{
if(entry.isIntersecting){
lastScrollY = window.scrollY
let cardIndex = Array.from(cards).indexOf(entry.target)
currentCardIndex = cardIndex
const targetY = 150 -currentCardIndex * 150
gsap.to(countContainer,{
scrollTo: {y:targetY,autoKill:false},
duration:0.5,
ease:"power2.inOut",
overwrite:true,
})
}
})
},options)
cards.forEach((card) => {
observer.observe(card)
})
window.addEventListener("resize",()=>{
positionCards(0)
})
})