Compare commits
No commits in common. "feature/animation" and "main" have entirely different histories.
feature/an
...
main
@ -124,17 +124,9 @@
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@ -2,28 +2,22 @@ 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 }: { children: React.ReactNode }) {
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body
|
||||
className="font-sans antialiased scrollbar-hide"
|
||||
style={{ fontFamily: "DIN Alternate, sans-serif" }}
|
||||
>
|
||||
|
||||
<Navbar />
|
||||
|
||||
{/* LadderClimb is now global */}
|
||||
<LadderClimb />
|
||||
|
||||
{/* All page content */}
|
||||
<body className="font-sans antialiased" style={{ fontFamily: "DIN Alternate, sans-serif" }} suppressHydrationWarning>
|
||||
{children}
|
||||
<Analytics />
|
||||
</body>
|
||||
|
||||
19
app/page.tsx
19
app/page.tsx
@ -8,15 +8,14 @@ import Footer from "@/components/footer"
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<main className="w-full">
|
||||
<MainHero />
|
||||
<HeroSection /> {/* This is the 'Why SFS' section */}
|
||||
<OurOfferings />
|
||||
<Testimonials />
|
||||
<TrustedBy />
|
||||
<Footer />
|
||||
</main>
|
||||
</>
|
||||
<main className="w-full">
|
||||
<Navbar />
|
||||
<MainHero />
|
||||
<HeroSection /> {/* This is the 'Why SFS' section */}
|
||||
<OurOfferings />
|
||||
<Testimonials />
|
||||
<TrustedBy />
|
||||
<Footer />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
9
components/dotlottie.d.ts
vendored
9
components/dotlottie.d.ts
vendored
@ -1,9 +0,0 @@
|
||||
import * as React from 'react';
|
||||
|
||||
declare module 'react' {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
'dotlottie-wc': any;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,187 +0,0 @@
|
||||
"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,9 +33,6 @@ 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",
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,12 +222,12 @@ export default function MainHero() {
|
||||
}
|
||||
|
||||
return (
|
||||
<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">
|
||||
<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">
|
||||
<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 z-10 lg:pl-12">
|
||||
<div className="flex flex-col justify-center text-center lg:text-left z-10 lg:pl-12">
|
||||
<h1 className="text-5xl md:text-6xl lg:text-7xl font-bold text-[#353535] leading-tight">
|
||||
Learning
|
||||
</h1>
|
||||
|
||||
@ -75,7 +75,7 @@ export default function Navbar() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<nav className="fixed top-0 left-0 right-0 z-50 w-full bg-white border-b border-gray-200">
|
||||
<nav className="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 */}
|
||||
|
||||
@ -120,15 +120,7 @@ export default function TrustedBy() {
|
||||
loadLogos()
|
||||
}, [])
|
||||
|
||||
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;
|
||||
const renderedLogos = logos
|
||||
|
||||
return (
|
||||
<section className="w-full bg-white py-16">
|
||||
@ -144,57 +136,36 @@ 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">
|
||||
{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>
|
||||
)}
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
|
||||
793
package-lock.json
generated
793
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -13,7 +13,6 @@
|
||||
},
|
||||
"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",
|
||||
@ -41,9 +40,6 @@
|
||||
"@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",
|
||||
@ -54,7 +50,7 @@
|
||||
"framer-motion": "^12.23.26",
|
||||
"input-otp": "1.4.1",
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "16.0.10",
|
||||
"next": "16.0.7",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "19.2.0",
|
||||
"react-day-picker": "9.8.0",
|
||||
@ -65,7 +61,6 @@
|
||||
"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
BIN
public/Idle1.png
Binary file not shown.
|
Before Width: | Height: | Size: 77 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"schoolforschools": [
|
||||
{
|
||||
"id": 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.",
|
||||
@ -10,7 +10,7 @@
|
||||
"publishedAt": "2025-12-10T13:37:16.992Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"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.",
|
||||
@ -19,7 +19,7 @@
|
||||
"publishedAt": "2025-12-10T13:38:33.664Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"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.",
|
||||
@ -28,22 +28,13 @@
|
||||
"publishedAt": "2025-12-10T13:39:11.684Z"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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": []
|
||||
|
||||
@ -1 +1,38 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"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"
|
||||
}
|
||||
]
|
||||
Loading…
Reference in New Issue
Block a user