Compare commits

..

5 Commits

Author SHA1 Message Date
0230c8cf28 added 2025-12-22 15:44:39 +05:30
5ce7feb651 fixed bugs 2025-12-19 17:18:20 +05:30
5abe47681b fixed bugs 2025-12-19 17:06:39 +05:30
98c7939fad animation 2025-12-19 12:47:57 +05:30
5abc04c382 animation changes 2025-12-16 12:30:38 +05:30
17 changed files with 1111 additions and 100 deletions

View File

@ -124,9 +124,17 @@
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-width: none;
-ms-overflow-style: none;
}
*::-webkit-scrollbar {
display: none;
}
html,
body {
@apply bg-background text-foreground;
overflow-x: hidden;
}
}

View File

@ -2,22 +2,28 @@ import type React from "react"
import type { Metadata } from "next"
import { Analytics } from "@vercel/analytics/next"
import "./globals.css"
import LadderClimb from "@/components/ladderclimber"
import Navbar from "@/components/navbar"
export const metadata: Metadata = {
title: "School For Schools",
description: "Combining expertise in education with cutting-edge technology",
generator: "v0.app",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className="font-sans antialiased" style={{ fontFamily: "DIN Alternate, sans-serif" }} suppressHydrationWarning>
<body
className="font-sans antialiased scrollbar-hide"
style={{ fontFamily: "DIN Alternate, sans-serif" }}
>
<Navbar />
{/* LadderClimb is now global */}
<LadderClimb />
{/* All page content */}
{children}
<Analytics />
</body>

View File

@ -8,14 +8,15 @@ import Footer from "@/components/footer"
export default function Home() {
return (
<main className="w-full">
<Navbar />
<MainHero />
<HeroSection /> {/* This is the 'Why SFS' section */}
<OurOfferings />
<Testimonials />
<TrustedBy />
<Footer />
</main>
<>
<main className="w-full">
<MainHero />
<HeroSection /> {/* This is the 'Why SFS' section */}
<OurOfferings />
<Testimonials />
<TrustedBy />
<Footer />
</main>
</>
)
}

9
components/dotlottie.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
import * as React from 'react';
declare module 'react' {
namespace JSX {
interface IntrinsicElements {
'dotlottie-wc': any;
}
}
}

View File

@ -0,0 +1,187 @@
"use client"
import { useEffect, useRef } from "react"
// import "@lottiefiles/dotlottie-wc" // Removed static import to fix SSR error
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() {
const charRef = useRef<HTMLDivElement>(null)
const ladderRef = useRef<HTMLDivElement>(null)
const idleRef = useRef<HTMLDivElement>(null)
const climbRef = useRef<HTMLDivElement>(null)
const jumpRef = useRef<HTMLDivElement>(null)
const ladderMaxTravel = useRef(0)
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 = () => {
if (ladderRef.current) {
// 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
}
}
useEffect(() => {
import("@lottiefiles/dotlottie-wc")
updateDimensions()
window.addEventListener("resize", updateDimensions)
const handleScroll = () => {
if (!charRef.current) return
const scrollTop = window.scrollY
const docHeight = document.body.scrollHeight - window.innerHeight
let progress = docHeight > 0 ? scrollTop / docHeight : 0
// Clamp progress between 0 and 1
progress = Math.max(0, Math.min(1, progress))
// Calculate Target Y based on the ladder's actual height constraint
const targetY = progress * ladderMaxTravel.current
// Interrupt fall if user starts scrolling
if (isFalling.current) {
isFalling.current = false
charRef.current.classList.remove("falling")
}
// 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)
scrollTimeout.current = setTimeout(() => {
if (targetY > 2) {
triggerFall()
} else {
switchAnimation("idle")
}
}, STOP_DELAY)
}
window.addEventListener("scroll", handleScroll, { passive: true })
handleScroll()
return () => {
window.removeEventListener("scroll", handleScroll)
window.removeEventListener("resize", updateDimensions)
if (scrollTimeout.current) clearTimeout(scrollTimeout.current)
}
}, [])
return (
<>
<div
ref={ladderRef}
style={{
position: "fixed",
right: 10,
top: 80, // Top margin of the ladder rail
height: "calc(100vh - 80px)", // Ladder length
width: "40px",
display: "flex",
justifyContent: "center",
alignItems: "flex-end",
zIndex: 999,
pointerEvents: "none"
}}
>
{/* The Visual Ladder */}
<div
style={{
height: "100%",
width: "30px",
background: "repeating-linear-gradient(to bottom, #444 0, #444 4px, transparent 4px, transparent 30px)",
borderLeft: "4px solid #444",
borderRight: "4px solid #444",
zIndex: 1
}}
/>
{/* The Character Container */}
<div
ref={charRef}
style={{
position: "absolute",
bottom: 0, // Starts at the bottom of the ladderRef
left: "50%",
marginLeft: "-90px",
zIndex: 10,
willChange: "transform"
}}
>
<style>{`
.falling {
transition: transform 0.7s cubic-bezier(0.55, 0.055, 0.675, 0.19) !important;
}
`}</style>
<div ref={idleRef} style={{ display: 'block' }}>
<dotlottie-wc src="https://lottie.host/13145c5f-8acc-4ea9-8842-58a458ef5553/b1W7KoG3lt.lottie" autoplay loop style={{ width: '180px' }} />
</div>
<div ref={climbRef} style={{ display: 'none' }}>
<dotlottie-wc src="https://lottie.host/97e44afd-2200-44a5-8d60-8db614c54362/cqzrUWVWdo.lottie" autoplay loop style={{ width: '180px' }} />
</div>
<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>
</>
)
}

View File

@ -33,6 +33,9 @@ const SHAPES: Record<string, ShapeData> = {
wheel: {
path: "M305.5 0C264.34 0 213.51 58.76 197.5 78.62C181.49 58.76 130.66 0 89.5 0C40.15 0 0 40.15 0 89.5C0 138.85 40.15 179 89.5 179C130.66 179 181.49 120.24 197.5 100.38C213.51 120.24 264.34 179 305.5 179C354.85 179 395 138.85 395 89.5C395 40.15 354.85 0 305.5 0ZM15 89.5C15 57.56 35.2 30.26 63.5 19.69V159.31C35.2 148.74 15 121.44 15 89.5ZM78.5 163.18V15.82C82.09 15.29 85.76 15 89.5 15C100.38 15 112.92 20.82 125.5 29.46V149.54C112.92 158.18 100.38 164 89.5 164C85.76 164 82.09 163.72 78.5 163.18ZM140.5 138.02V40.99C159.44 56.97 176.92 76.76 187.02 89.51C176.93 102.26 159.45 122.05 140.5 138.03V138.02ZM207.98 89.5C218.07 76.75 235.55 56.96 254.5 40.98V138.01C235.56 122.03 218.08 102.24 207.98 89.49V89.5ZM269.5 149.54V29.46C282.08 20.82 294.62 15 305.5 15C309.24 15 312.91 15.28 316.5 15.82V163.18C312.91 163.71 309.24 164 305.5 164C294.62 164 282.08 158.18 269.5 149.54ZM331.5 159.31V19.69C359.8 30.26 380 57.56 380 89.5C380 121.44 359.8 148.74 331.5 159.31Z",
viewBox: "0 0 395 179",
},
gear: {
path: "M143.5 0C64.37 0 0 64.37 0 143.5C0 222.63 64.37 287 143.5 287C222.63 287 287 222.63 287 143.5C287 64.37 222.63 0 143.5 0ZM143.5 231C95.25 231 56 191.75 56 143.5C56 95.25 95.25 56 143.5 56C191.75 56 231 95.25 231 143.5C231 191.75 191.75 231 143.5 231ZM220.47 75.89L238.88 57.48C246.53 65.96 253.07 75.46 258.27 85.75L234.24 95.87C230.44 88.66 225.81 81.95 220.47 75.88V75.89ZM209.77 65.37C203.35 59.91 196.25 55.24 188.62 51.48L198.46 27.36C209.29 32.51 219.29 39.12 228.2 46.95L209.78 65.37H209.77ZM174.71 45.86C167.15 43.44 159.21 41.87 151 41.27V15.22C162.67 15.89 173.92 18.13 184.55 21.72L174.71 45.86ZM136 41.28C127.44 41.9 119.17 43.58 111.32 46.19L101.24 22.16C112.23 18.32 123.9 15.93 136.01 15.23V41.28H136ZM97.46 51.94C90.18 55.62 83.39 60.14 77.22 65.38L58.8 46.96C67.39 39.41 77 32.99 87.38 27.93L97.46 51.95V51.94ZM66.53 75.89C61.28 81.86 56.71 88.45 52.94 95.53L28.95 85.31C34.11 75.19 40.58 65.84 48.12 57.48L66.53 75.89ZM46.89 109.26C44.02 117.34 42.13 125.89 41.38 134.75H15.31C16.15 122.28 18.77 110.3 22.93 99.05L46.89 109.25V109.26ZM41.2 149.75C41.73 158.57 43.39 167.1 46.02 175.19L21.99 185.31C18.11 174.07 15.75 162.14 15.15 149.75H41.19H41.2ZM51.7 189.07C55.42 196.53 60.02 203.48 65.37 209.77L46.95 228.19C39.3 219.48 32.8 209.73 27.7 199.18L51.7 189.07ZM75.89 220.47C82.42 226.22 89.69 231.15 97.54 235.1L87.7 259.24C76.66 253.9 66.5 247.01 57.49 238.88L75.9 220.47H75.89ZM111.39 240.84C119.22 243.43 127.47 245.1 136 245.72V271.77C124.01 271.08 112.45 268.73 101.55 264.96L111.39 240.84ZM151 245.72C159.89 245.07 168.48 243.29 176.6 240.51L186.68 264.52C175.42 268.55 163.45 271.05 151 271.77V245.72ZM190.4 234.62C197.89 230.75 204.84 225.98 211.11 220.46L229.52 238.87C220.83 246.71 211.07 253.39 200.48 258.64L190.4 234.61V234.62ZM221.63 209.77C226.89 203.57 231.43 196.75 235.12 189.42L259.08 199.62C254.02 210 247.6 219.6 240.05 228.19L221.63 209.77ZM240.86 175.56C243.57 167.36 245.26 158.71 245.81 149.75H271.85C271.25 162.31 268.82 174.41 264.85 185.78L240.86 175.56ZM245.62 134.75C244.88 126.02 243.04 117.6 240.24 109.63L264.24 99.52C268.3 110.63 270.86 122.46 271.69 134.76H245.62V134.75Z", viewBox: "0 0 287 287",
}
}
@ -222,12 +225,12 @@ export default function MainHero() {
}
return (
<section className="relative w-full bg-[#EAEAEA] py-6 md:py-10 lg:py-16 overflow-hidden min-h-[360px] lg:min-h-[600px] flex items-center justify-center">
<section className="relative w-full bg-[#EAEAEA] py-20 md:py-20 lg:py-20 overflow-hidden min-h-[360px] lg:min-h-[600px] flex items-center justify-center">
<div className="w-full max-w-[1200px] mx-auto pl-4 pr-2 sm:pl-6 sm:pr-3 lg:pl-8 lg:pr-4">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-0 items-center">
{/* --- Left Column: Text --- */}
<div className="flex flex-col justify-center text-center lg:text-left z-10 lg:pl-12">
<div className="flex flex-col justify-center text-center z-10 lg:pl-12">
<h1 className="text-5xl md:text-6xl lg:text-7xl font-bold text-[#353535] leading-tight">
Learning
</h1>

View File

@ -75,7 +75,7 @@ export default function Navbar() {
}, [])
return (
<nav className="w-full bg-white border-b border-gray-200">
<nav className="fixed top-0 left-0 right-0 z-50 w-full bg-white border-b border-gray-200">
<div className="w-full pl-6 sm:pl-8 lg:pl-12 pr-4 sm:pr-6 lg:pr-8">
<div className="flex justify-between items-center h-20">
{/* Logo */}

View File

@ -120,7 +120,15 @@ export default function TrustedBy() {
loadLogos()
}, [])
const renderedLogos = logos
const twoImages = [
{
id: "4",
alt: "Headshot of a smiling young man with short brown hair and blue eyes.",
src: "http://160.187.167.213/uploads/image_7e177c57da.png"
},
];
const renderedLogos = logos.length > 0 ? logos : twoImages;
return (
<section className="w-full bg-white py-16">
@ -136,36 +144,57 @@ export default function TrustedBy() {
{renderedLogos.length > 0 && (
<div className="relative max-w-6xl mx-auto px-4">
<div className="relative rounded-3xl border border-gray-300 px-4 md:px-8 py-6 md:py-8 shadow-sm bg-white overflow-hidden">
<motion.div
className="flex items-center gap-8 md:gap-10"
animate={{ x: ["0%", "-50%"] }}
transition={{
x: {
repeat: Infinity,
repeatType: "loop",
duration: 18,
ease: "linear",
},
}}
style={{ width: "max-content" }}
>
{renderedLogos.concat(renderedLogos).map((item, idx) => (
<div
key={`${item.id}-${idx}`}
className="min-w-[220px] md:min-w-[240px] flex items-center justify-center"
style={{ minHeight: "120px" }}
>
<Image
src={item.src}
alt={item.alt}
width={180}
height={140}
className="object-contain h-[120px] md:h-[140px]"
style={{ width: "auto", maxWidth: "180px" }}
/>
</div>
))}
</motion.div>
{renderedLogos.length > 3 ? (
<motion.div
className="flex items-center gap-8 md:gap-10"
animate={{ x: ["0%", "-50%"] }}
transition={{
x: {
repeat: Infinity,
repeatType: "loop",
duration: 18,
ease: "linear",
},
}}
style={{ width: "max-content" }}
>
{renderedLogos.concat(renderedLogos).map((item, idx) => (
<div
key={`${item.id}-${idx}`}
className="min-w-[220px] md:min-w-[240px] flex items-center justify-center"
style={{ minHeight: "120px" }}
>
<Image
src={item.src}
alt={item.alt}
width={180}
height={140}
className="object-contain h-[120px] md:h-[140px]"
style={{ width: "auto", maxWidth: "180px" }}
/>
</div>
))}
</motion.div>
) : (
<div className="flex items-center justify-center gap-8 md:gap-12 py-4">
{renderedLogos.map((item) => (
<div
key={item.id}
className="flex items-center justify-center px-4"
style={{ minHeight: "120px" }}
>
<Image
src={item.src}
alt={item.alt}
width={180}
height={140}
className="object-contain h-[120px] md:h-[140px]"
style={{ width: "auto", maxWidth: "180px" }}
/>
</div>
))}
</div>
)}
</div>
</div>
)}

793
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@lottiefiles/dotlottie-wc": "^0.8.11",
"@radix-ui/react-accordion": "1.2.2",
"@radix-ui/react-alert-dialog": "1.1.4",
"@radix-ui/react-aspect-ratio": "1.1.1",
@ -40,6 +41,9 @@
"@radix-ui/react-toggle": "1.1.1",
"@radix-ui/react-toggle-group": "1.1.1",
"@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",
"autoprefixer": "^10.4.20",
"class-variance-authority": "^0.7.1",
@ -50,7 +54,7 @@
"framer-motion": "^12.23.26",
"input-otp": "1.4.1",
"lucide-react": "^0.454.0",
"next": "16.0.7",
"next": "16.0.10",
"next-themes": "^0.4.6",
"react": "19.2.0",
"react-day-picker": "9.8.0",
@ -61,6 +65,7 @@
"sonner": "^1.7.4",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"three": "^0.182.0",
"vaul": "^1.1.2",
"zod": "3.25.76"
},

BIN
public/Idle1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
public/climber2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
public/climber21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

BIN
public/ladder.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

View File

@ -1,7 +1,7 @@
{
"schoolforschools": [
{
"id": 2,
"id": 1,
"documentId": "jlrxnaecrbjlxgeh7jyc9hlk",
"sfssubtitle": "Never Stops",
"sfsdescription": "With SFS, progress doesn't pause. From ERPs to robotics, we build ecosystems that keep learning alive, every day, everywhere.",
@ -10,7 +10,7 @@
"publishedAt": "2025-12-10T13:37:16.992Z"
},
{
"id": 4,
"id": 2,
"documentId": "a431b3vgmfdwjk9ygbfppq9t",
"sfssubtitle": "to Evolve",
"sfsdescription": "We aim to give educators the tools to keep growing and turning classrooms into spaces that evolve with every lesson.",
@ -19,7 +19,7 @@
"publishedAt": "2025-12-10T13:38:33.664Z"
},
{
"id": 6,
"id": 3,
"documentId": "st3op26bnimu0trxahm0ntpf",
"sfssubtitle": "at Our Core",
"sfsdescription": "For SFS, education isn't a category, it's the code that shapes every product, platform, and idea we create.",
@ -28,13 +28,22 @@
"publishedAt": "2025-12-10T13:39:11.684Z"
},
{
"id": 8,
"id": 4,
"documentId": "o6fl35rof2e5ek60jgvguvfz",
"sfssubtitle": "Builds Bridges ",
"sfsdescription": "SFS connects schools, systems, and teachers, creating seamless links between people and technology.",
"createdAt": "2025-12-10T13:39:48.372Z",
"updatedAt": "2025-12-10T13:39:48.372Z",
"publishedAt": "2025-12-10T13:39:48.378Z"
},
{
"id": 5,
"documentId": "o6fl35rof2e5ek60jgvguvfz",
"sfssubtitle": "SFS is the Future",
"sfsdescription": "With SFS, we're not just building tools for schools. We're building the future of education, one lesson at a time.",
"createdAt": "2025-12-10T13:39:48.372Z",
"updatedAt": "2025-12-10T13:39:48.372Z",
"publishedAt": "2025-12-10T13:39:48.378Z"
}
],
"whyschoolforschools": []

View File

@ -1,38 +1 @@
[
{
"id": 2,
"documentId": "jlrxnaecrbjlxgeh7jyc9hlk",
"sfssubtitle": "Never Stops",
"sfsdescription": "With SFS, progress doesn't pause. From ERPs to robotics, we build ecosystems that keep learning alive, every day, everywhere.",
"createdAt": "2025-12-10T13:37:16.981Z",
"updatedAt": "2025-12-10T13:37:16.981Z",
"publishedAt": "2025-12-10T13:37:16.992Z"
},
{
"id": 4,
"documentId": "a431b3vgmfdwjk9ygbfppq9t",
"sfssubtitle": "to Evolve",
"sfsdescription": "We aim to give educators the tools to keep growing and turning classrooms into spaces that evolve with every lesson.",
"createdAt": "2025-12-10T13:38:33.657Z",
"updatedAt": "2025-12-10T13:38:33.657Z",
"publishedAt": "2025-12-10T13:38:33.664Z"
},
{
"id": 6,
"documentId": "st3op26bnimu0trxahm0ntpf",
"sfssubtitle": "at Our Core",
"sfsdescription": "For SFS, education isn't a category, it's the code that shapes every product, platform, and idea we create.",
"createdAt": "2025-12-10T13:39:11.675Z",
"updatedAt": "2025-12-10T13:39:11.675Z",
"publishedAt": "2025-12-10T13:39:11.684Z"
},
{
"id": 8,
"documentId": "o6fl35rof2e5ek60jgvguvfz",
"sfssubtitle": "Builds Bridges ",
"sfsdescription": "SFS connects schools, systems, and teachers, creating seamless links between people and technology.",
"createdAt": "2025-12-10T13:39:48.372Z",
"updatedAt": "2025-12-10T13:39:48.372Z",
"publishedAt": "2025-12-10T13:39:48.378Z"
}
]
[]