animation
This commit is contained in:
parent
5abc04c382
commit
98c7939fad
@ -124,9 +124,17 @@
|
|||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -15,15 +15,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body
|
||||||
className="font-sans antialiased scrollbar-hide"
|
className="font-sans antialiased scrollbar-hide"
|
||||||
style={{ fontFamily: "DIN Alternate, sans-serif", scrollbarWidth: "none", msOverflowStyle: "none" }}
|
style={{ fontFamily: "DIN Alternate, sans-serif" }}
|
||||||
>
|
>
|
||||||
<style>{`
|
|
||||||
html::-webkit-scrollbar,
|
<Navbar />
|
||||||
body::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`}</style>
|
|
||||||
<Navbar />
|
|
||||||
|
|
||||||
{/* LadderClimb is now global */}
|
{/* LadderClimb is now global */}
|
||||||
<LadderClimb />
|
<LadderClimb />
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import Testimonials from "@/components/testimonials"
|
|||||||
import OurOfferings from "@/components/our-offerings"
|
import OurOfferings from "@/components/our-offerings"
|
||||||
import TrustedBy from "@/components/trusted-by"
|
import TrustedBy from "@/components/trusted-by"
|
||||||
import Footer from "@/components/footer"
|
import Footer from "@/components/footer"
|
||||||
import LadderClimb from "@/components/ladderclimber"
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
9
components/dotlottie.d.ts
vendored
Normal file
9
components/dotlottie.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
declare module 'react' {
|
||||||
|
namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'dotlottie-wc': any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,62 +1,118 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
import { useEffect, useRef } from "react"
|
||||||
|
// import "@lottiefiles/dotlottie-wc" // Removed static import to fix SSR error
|
||||||
|
|
||||||
import React, { useEffect, useRef, useState } from "react"
|
const STOP_DELAY = 700
|
||||||
|
const LADDER_OFFSET_BOTTOM = 80
|
||||||
|
const LADDER_OFFSET_TOP = 20 // Buffer to keep character from going off-screen at the top
|
||||||
|
|
||||||
export default function LadderClimb() {
|
export default function LadderClimb() {
|
||||||
const charRef = useRef<HTMLDivElement>(null)
|
const charRef = useRef<HTMLDivElement>(null)
|
||||||
const ladderRef = useRef<HTMLDivElement>(null)
|
const ladderRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const [animation, setAnimation] = useState<"idle" | "climbing">("idle")
|
const idleRef = useRef<HTMLDivElement>(null)
|
||||||
const [visible, setVisible] = useState(true)
|
const climbRef = useRef<HTMLDivElement>(null)
|
||||||
|
const jumpRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const ladderHeight = useRef(0)
|
const ladderMaxTravel = useRef(0)
|
||||||
const scrollTimeout = useRef<any>(null)
|
const activeState = useRef<"idle" | "climbing" | "jumping">("idle")
|
||||||
|
const scrollTimeout = useRef<NodeJS.Timeout | null>(null)
|
||||||
|
const isFalling = useRef(false)
|
||||||
|
|
||||||
|
const switchAnimation = (newState: "idle" | "climbing" | "jumping") => {
|
||||||
|
if (activeState.current === newState) return
|
||||||
|
activeState.current = newState
|
||||||
|
|
||||||
|
if (!idleRef.current || !climbRef.current || !jumpRef.current) return
|
||||||
|
|
||||||
|
idleRef.current.style.display = "none"
|
||||||
|
climbRef.current.style.display = "none"
|
||||||
|
jumpRef.current.style.display = "none"
|
||||||
|
|
||||||
|
if (newState === "idle") idleRef.current.style.display = "block"
|
||||||
|
else if (newState === "climbing") climbRef.current.style.display = "block"
|
||||||
|
else if (newState === "jumping") jumpRef.current.style.display = "block"
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerFall = () => {
|
||||||
|
if (!charRef.current || isFalling.current) return
|
||||||
|
|
||||||
|
const style = window.getComputedStyle(charRef.current)
|
||||||
|
const matrix = new DOMMatrix(style.transform)
|
||||||
|
const currentVisualY = Math.abs(matrix.m42)
|
||||||
|
|
||||||
|
if (currentVisualY < 5) {
|
||||||
|
switchAnimation("idle")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isFalling.current = true
|
||||||
|
switchAnimation("jumping")
|
||||||
|
|
||||||
|
charRef.current.classList.add("falling")
|
||||||
|
charRef.current.style.transform = `translateY(0px)`
|
||||||
|
|
||||||
|
const handleLanding = (e: TransitionEvent) => {
|
||||||
|
if (e.propertyName !== "transform") return
|
||||||
|
isFalling.current = false
|
||||||
|
charRef.current?.classList.remove("falling")
|
||||||
|
// Force idle animation upon landing
|
||||||
|
switchAnimation("idle")
|
||||||
|
charRef.current?.removeEventListener("transitionend", handleLanding)
|
||||||
|
}
|
||||||
|
charRef.current.addEventListener("transitionend", handleLanding)
|
||||||
|
}
|
||||||
|
|
||||||
const updateDimensions = () => {
|
const updateDimensions = () => {
|
||||||
if (ladderRef.current) {
|
if (ladderRef.current) {
|
||||||
ladderHeight.current = ladderRef.current.clientHeight
|
// Logic: The character can only travel the height of the ladder minus the offsets
|
||||||
}
|
ladderMaxTravel.current = ladderRef.current.clientHeight - LADDER_OFFSET_BOTTOM - LADDER_OFFSET_TOP
|
||||||
if (charRef.current) {
|
|
||||||
charRef.current.style.transform = `translateY(0px)`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
import("@lottiefiles/dotlottie-wc")
|
||||||
updateDimensions()
|
updateDimensions()
|
||||||
setAnimation("idle")
|
|
||||||
|
|
||||||
window.addEventListener("resize", updateDimensions)
|
window.addEventListener("resize", updateDimensions)
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = () => {
|
||||||
if (!charRef.current || !ladderRef.current) return
|
if (!charRef.current) return
|
||||||
|
|
||||||
// show ladder & climbing animation
|
|
||||||
setVisible(true)
|
|
||||||
setAnimation("climbing")
|
|
||||||
|
|
||||||
const scrollTop = window.scrollY
|
const scrollTop = window.scrollY
|
||||||
const docHeight = document.body.scrollHeight
|
const docHeight = document.body.scrollHeight - window.innerHeight
|
||||||
const viewportHeight = window.innerHeight
|
let progress = docHeight > 0 ? scrollTop / docHeight : 0
|
||||||
|
|
||||||
let progress = scrollTop / (docHeight - viewportHeight)
|
// Clamp progress between 0 and 1
|
||||||
progress = Math.max(0, Math.min(1, progress))
|
progress = Math.max(0, Math.min(1, progress))
|
||||||
|
|
||||||
const topOffset = 174
|
// Calculate Target Y based on the ladder's actual height constraint
|
||||||
const bottomOffset = 30
|
const targetY = progress * ladderMaxTravel.current
|
||||||
const maxClimb = ladderHeight.current - topOffset - bottomOffset
|
|
||||||
|
|
||||||
const y = progress * maxClimb
|
// Interrupt fall if user starts scrolling
|
||||||
charRef.current.style.transform = `translateY(-${y}px)`
|
if (isFalling.current) {
|
||||||
|
isFalling.current = false
|
||||||
|
charRef.current.classList.remove("falling")
|
||||||
|
}
|
||||||
|
|
||||||
// hide smoothly after scroll stops
|
// Apply transformation (constrained to the top of the ladder)
|
||||||
|
charRef.current.style.transform = `translateY(-${targetY}px)`
|
||||||
|
|
||||||
|
// Set climbing animation
|
||||||
|
if (activeState.current !== "climbing") switchAnimation("climbing")
|
||||||
|
|
||||||
|
// Stop detection
|
||||||
if (scrollTimeout.current) clearTimeout(scrollTimeout.current)
|
if (scrollTimeout.current) clearTimeout(scrollTimeout.current)
|
||||||
scrollTimeout.current = setTimeout(() => {
|
scrollTimeout.current = setTimeout(() => {
|
||||||
setAnimation("idle")
|
if (targetY > 2) {
|
||||||
setVisible(false)
|
triggerFall()
|
||||||
}, 500)
|
} else {
|
||||||
|
switchAnimation("idle")
|
||||||
|
}
|
||||||
|
}, STOP_DELAY)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("scroll", handleScroll)
|
window.addEventListener("scroll", handleScroll, { passive: true })
|
||||||
|
handleScroll()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("scroll", handleScroll)
|
window.removeEventListener("scroll", handleScroll)
|
||||||
@ -67,77 +123,65 @@ export default function LadderClimb() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* Ladder container */}
|
|
||||||
<div
|
<div
|
||||||
ref={ladderRef}
|
ref={ladderRef}
|
||||||
style={{
|
style={{
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
right: 0,
|
right: 10,
|
||||||
top: 80,
|
top: 80, // Top margin of the ladder rail
|
||||||
height: "100vh",
|
height: "calc(100vh - 80px)", // Ladder length
|
||||||
width: "100px",
|
width: "40px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "flex-end",
|
alignItems: "flex-end",
|
||||||
zIndex: 999,
|
zIndex: 999,
|
||||||
|
pointerEvents: "none"
|
||||||
// smooth hide / show
|
|
||||||
opacity: visible ? 1 : 0,
|
|
||||||
transform: visible ? "translateX(0)" : "translateX(40px)",
|
|
||||||
pointerEvents: visible ? "auto" : "none",
|
|
||||||
transition: "opacity 0.8s ease, transform 0.8s ease"
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Ladder background */}
|
{/* The Visual Ladder */}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
width: "40px",
|
width: "30px",
|
||||||
background:
|
background: "repeating-linear-gradient(to bottom, #444 0, #444 4px, transparent 4px, transparent 30px)",
|
||||||
"repeating-linear-gradient(to bottom, #666 0, #666 8px, transparent 8px, transparent 30px)",
|
borderLeft: "4px solid #444",
|
||||||
borderLeft: "8px solid #666",
|
borderRight: "4px solid #444",
|
||||||
borderRight: "8px solid #666",
|
|
||||||
zIndex: 1
|
zIndex: 1
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Character */}
|
{/* The Character Container */}
|
||||||
<div
|
<div
|
||||||
ref={charRef}
|
ref={charRef}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
bottom: 45,
|
bottom: 0, // Starts at the bottom of the ladderRef
|
||||||
left: "50%",
|
left: "50%",
|
||||||
marginLeft: "-100px",
|
marginLeft: "-90px",
|
||||||
width: "200px",
|
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
willChange: "transform",
|
willChange: "transform"
|
||||||
transition: "transform 0.15s linear"
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* CLIMBING */}
|
<style>{`
|
||||||
{animation === "climbing" && (
|
.falling {
|
||||||
<img
|
transition: transform 0.7s cubic-bezier(0.55, 0.055, 0.675, 0.19) !important;
|
||||||
src="./climber21.png"
|
}
|
||||||
alt="Climbing"
|
`}</style>
|
||||||
style={{ width: "400px", height: "200px" }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* IDLE */}
|
<div ref={idleRef} style={{ display: 'block' }}>
|
||||||
{animation === "idle" && (
|
<dotlottie-wc src="https://lottie.host/13145c5f-8acc-4ea9-8842-58a458ef5553/b1W7KoG3lt.lottie" autoplay loop style={{ width: '180px' }} />
|
||||||
<img
|
</div>
|
||||||
src="./Idle3.png"
|
|
||||||
alt="Idle"
|
<div ref={climbRef} style={{ display: 'none' }}>
|
||||||
style={{
|
<dotlottie-wc src="https://lottie.host/97e44afd-2200-44a5-8d60-8db614c54362/cqzrUWVWdo.lottie" autoplay loop style={{ width: '180px' }} />
|
||||||
marginLeft: "10px",
|
</div>
|
||||||
width: "400px",
|
|
||||||
height: "200px"
|
<div ref={jumpRef} style={{ display: 'none' }}>
|
||||||
}}
|
<dotlottie-wc src="https://lottie.host/5fc8dea4-e109-4c3b-813d-afd4da96973b/fdslVCb92l.lottie" autoplay style={{ width: '180px' }} />
|
||||||
/>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,6 +46,7 @@ interface ContentItem {
|
|||||||
export default function MainHero() {
|
export default function MainHero() {
|
||||||
const [content, setContent] = useState<ContentItem[]>([])
|
const [content, setContent] = useState<ContentItem[]>([])
|
||||||
const [currentIndex, setCurrentIndex] = useState(0)
|
const [currentIndex, setCurrentIndex] = useState(0)
|
||||||
|
console.log("content", content)
|
||||||
|
|
||||||
// Fetch content from API
|
// Fetch content from API
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
793
package-lock.json
generated
793
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
|
"@lottiefiles/dotlottie-wc": "^0.8.11",
|
||||||
"@radix-ui/react-accordion": "1.2.2",
|
"@radix-ui/react-accordion": "1.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "1.1.4",
|
"@radix-ui/react-alert-dialog": "1.1.4",
|
||||||
"@radix-ui/react-aspect-ratio": "1.1.1",
|
"@radix-ui/react-aspect-ratio": "1.1.1",
|
||||||
@ -40,6 +41,9 @@
|
|||||||
"@radix-ui/react-toggle": "1.1.1",
|
"@radix-ui/react-toggle": "1.1.1",
|
||||||
"@radix-ui/react-toggle-group": "1.1.1",
|
"@radix-ui/react-toggle-group": "1.1.1",
|
||||||
"@radix-ui/react-tooltip": "1.1.6",
|
"@radix-ui/react-tooltip": "1.1.6",
|
||||||
|
"@react-three/drei": "^10.7.7",
|
||||||
|
"@react-three/fiber": "^9.4.2",
|
||||||
|
"@types/three": "^0.182.0",
|
||||||
"@vercel/analytics": "latest",
|
"@vercel/analytics": "latest",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@ -61,6 +65,7 @@
|
|||||||
"sonner": "^1.7.4",
|
"sonner": "^1.7.4",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"three": "^0.182.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "3.25.76"
|
"zod": "3.25.76"
|
||||||
},
|
},
|
||||||
|
|||||||
BIN
public/Idle3.png
BIN
public/Idle3.png
Binary file not shown.
|
Before Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
Loading…
Reference in New Issue
Block a user