Compare commits
5 Commits
main
...
feature/an
| Author | SHA1 | Date | |
|---|---|---|---|
| 0230c8cf28 | |||
| 5ce7feb651 | |||
| 5abe47681b | |||
| 98c7939fad | |||
| 5abc04c382 |
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,25 +2,31 @@ import type React from "react"
|
|||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
import { Analytics } from "@vercel/analytics/next"
|
import { Analytics } from "@vercel/analytics/next"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
|
import LadderClimb from "@/components/ladderclimber"
|
||||||
|
import Navbar from "@/components/navbar"
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "School For Schools",
|
title: "School For Schools",
|
||||||
description: "Combining expertise in education with cutting-edge technology",
|
description: "Combining expertise in education with cutting-edge technology",
|
||||||
generator: "v0.app",
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode
|
|
||||||
}>) {
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<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}
|
{children}
|
||||||
<Analytics />
|
<Analytics />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
19
app/page.tsx
19
app/page.tsx
@ -8,14 +8,15 @@ import Footer from "@/components/footer"
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<main className="w-full">
|
<>
|
||||||
<Navbar />
|
<main className="w-full">
|
||||||
<MainHero />
|
<MainHero />
|
||||||
<HeroSection /> {/* This is the 'Why SFS' section */}
|
<HeroSection /> {/* This is the 'Why SFS' section */}
|
||||||
<OurOfferings />
|
<OurOfferings />
|
||||||
<Testimonials />
|
<Testimonials />
|
||||||
<TrustedBy />
|
<TrustedBy />
|
||||||
<Footer />
|
<Footer />
|
||||||
</main>
|
</main>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
187
components/ladderclimber.tsx
Normal file
187
components/ladderclimber.tsx
Normal 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>
|
||||||
|
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -33,6 +33,9 @@ const SHAPES: Record<string, ShapeData> = {
|
|||||||
wheel: {
|
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",
|
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",
|
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 (
|
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="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">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-0 items-center">
|
||||||
|
|
||||||
{/* --- Left Column: Text --- */}
|
{/* --- 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">
|
<h1 className="text-5xl md:text-6xl lg:text-7xl font-bold text-[#353535] leading-tight">
|
||||||
Learning
|
Learning
|
||||||
</h1>
|
</h1>
|
||||||
|
|||||||
@ -75,7 +75,7 @@ export default function Navbar() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
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="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">
|
<div className="flex justify-between items-center h-20">
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
|
|||||||
@ -71,9 +71,9 @@ export default function TrustedBy() {
|
|||||||
})
|
})
|
||||||
if (!res.ok) throw new Error(`Trusted-by API status ${res.status}`)
|
if (!res.ok) throw new Error(`Trusted-by API status ${res.status}`)
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|
||||||
if (!data.logos) return
|
if (!data.logos) return
|
||||||
|
|
||||||
const json = data.logos
|
const json = data.logos
|
||||||
const entries = Array.isArray(json?.data) ? json.data : []
|
const entries = Array.isArray(json?.data) ? json.data : []
|
||||||
|
|
||||||
@ -120,7 +120,15 @@ export default function TrustedBy() {
|
|||||||
loadLogos()
|
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 (
|
return (
|
||||||
<section className="w-full bg-white py-16">
|
<section className="w-full bg-white py-16">
|
||||||
@ -136,36 +144,57 @@ export default function TrustedBy() {
|
|||||||
{renderedLogos.length > 0 && (
|
{renderedLogos.length > 0 && (
|
||||||
<div className="relative max-w-6xl mx-auto px-4">
|
<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">
|
<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
|
{renderedLogos.length > 3 ? (
|
||||||
className="flex items-center gap-8 md:gap-10"
|
<motion.div
|
||||||
animate={{ x: ["0%", "-50%"] }}
|
className="flex items-center gap-8 md:gap-10"
|
||||||
transition={{
|
animate={{ x: ["0%", "-50%"] }}
|
||||||
x: {
|
transition={{
|
||||||
repeat: Infinity,
|
x: {
|
||||||
repeatType: "loop",
|
repeat: Infinity,
|
||||||
duration: 18,
|
repeatType: "loop",
|
||||||
ease: "linear",
|
duration: 18,
|
||||||
},
|
ease: "linear",
|
||||||
}}
|
},
|
||||||
style={{ width: "max-content" }}
|
}}
|
||||||
>
|
style={{ width: "max-content" }}
|
||||||
{renderedLogos.concat(renderedLogos).map((item, idx) => (
|
>
|
||||||
<div
|
{renderedLogos.concat(renderedLogos).map((item, idx) => (
|
||||||
key={`${item.id}-${idx}`}
|
<div
|
||||||
className="min-w-[220px] md:min-w-[240px] flex items-center justify-center"
|
key={`${item.id}-${idx}`}
|
||||||
style={{ minHeight: "120px" }}
|
className="min-w-[220px] md:min-w-[240px] flex items-center justify-center"
|
||||||
>
|
style={{ minHeight: "120px" }}
|
||||||
<Image
|
>
|
||||||
src={item.src}
|
<Image
|
||||||
alt={item.alt}
|
src={item.src}
|
||||||
width={180}
|
alt={item.alt}
|
||||||
height={140}
|
width={180}
|
||||||
className="object-contain h-[120px] md:h-[140px]"
|
height={140}
|
||||||
style={{ width: "auto", maxWidth: "180px" }}
|
className="object-contain h-[120px] md:h-[140px]"
|
||||||
/>
|
style={{ width: "auto", maxWidth: "180px" }}
|
||||||
</div>
|
/>
|
||||||
))}
|
</div>
|
||||||
</motion.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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
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",
|
||||||
@ -50,7 +54,7 @@
|
|||||||
"framer-motion": "^12.23.26",
|
"framer-motion": "^12.23.26",
|
||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "16.0.7",
|
"next": "16.0.10",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.2.0",
|
"react": "19.2.0",
|
||||||
"react-day-picker": "9.8.0",
|
"react-day-picker": "9.8.0",
|
||||||
@ -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"
|
||||||
},
|
},
|
||||||
@ -74,4 +79,4 @@
|
|||||||
"tw-animate-css": "1.3.3",
|
"tw-animate-css": "1.3.3",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
public/Idle1.png
Normal file
BIN
public/Idle1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 77 KiB |
BIN
public/climber2.png
Normal file
BIN
public/climber2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
public/climber21.png
Normal file
BIN
public/climber21.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
public/ladder.png
Normal file
BIN
public/ladder.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"schoolforschools": [
|
"schoolforschools": [
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 1,
|
||||||
"documentId": "jlrxnaecrbjlxgeh7jyc9hlk",
|
"documentId": "jlrxnaecrbjlxgeh7jyc9hlk",
|
||||||
"sfssubtitle": "Never Stops",
|
"sfssubtitle": "Never Stops",
|
||||||
"sfsdescription": "With SFS, progress doesn't pause. From ERPs to robotics, we build ecosystems that keep learning alive, every day, everywhere.",
|
"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"
|
"publishedAt": "2025-12-10T13:37:16.992Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 2,
|
||||||
"documentId": "a431b3vgmfdwjk9ygbfppq9t",
|
"documentId": "a431b3vgmfdwjk9ygbfppq9t",
|
||||||
"sfssubtitle": "to Evolve",
|
"sfssubtitle": "to Evolve",
|
||||||
"sfsdescription": "We aim to give educators the tools to keep growing and turning classrooms into spaces that evolve with every lesson.",
|
"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"
|
"publishedAt": "2025-12-10T13:38:33.664Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 3,
|
||||||
"documentId": "st3op26bnimu0trxahm0ntpf",
|
"documentId": "st3op26bnimu0trxahm0ntpf",
|
||||||
"sfssubtitle": "at Our Core",
|
"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.",
|
"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"
|
"publishedAt": "2025-12-10T13:39:11.684Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 8,
|
"id": 4,
|
||||||
"documentId": "o6fl35rof2e5ek60jgvguvfz",
|
"documentId": "o6fl35rof2e5ek60jgvguvfz",
|
||||||
"sfssubtitle": "Builds Bridges ",
|
"sfssubtitle": "Builds Bridges ",
|
||||||
"sfsdescription": "SFS connects schools, systems, and teachers, creating seamless links between people and technology.",
|
"sfsdescription": "SFS connects schools, systems, and teachers, creating seamless links between people and technology.",
|
||||||
"createdAt": "2025-12-10T13:39:48.372Z",
|
"createdAt": "2025-12-10T13:39:48.372Z",
|
||||||
"updatedAt": "2025-12-10T13:39:48.372Z",
|
"updatedAt": "2025-12-10T13:39:48.372Z",
|
||||||
"publishedAt": "2025-12-10T13:39:48.378Z"
|
"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": []
|
"whyschoolforschools": []
|
||||||
|
|||||||
@ -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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -38,4 +38,4 @@
|
|||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user