From 5c1e3d597f2a868ac9850a3bdab07ea4bd13c17c Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 20 Apr 2026 13:28:03 -0400 Subject: [PATCH] Add scroll-reveal animations across home, services, portfolio, about, apps New ScrollReveal client component wraps section-containers and cards with a subtle opacity + y-translate fade-in triggered by IntersectionObserver. Bails out to an unanimated render when prefers-reduced-motion is set. - home: Selected Works, Inclusive by Design, Start a Project - services: each of the four verticals and the Recent Work card - portfolio: each product card, slight stagger by column index - about: Leadership section and Get in Touch - apps: each app card, stagger by index --- apps/web/src/app/about/page.tsx | 25 ++++---- apps/web/src/app/apps/page.tsx | 37 ++++++----- apps/web/src/app/page.tsx | 19 +++--- apps/web/src/app/products/page.tsx | 43 +++++++------ apps/web/src/app/services/page.tsx | 31 ++++----- apps/web/src/components/scroll-reveal.tsx | 77 +++++++++++++++++++++++ 6 files changed, 160 insertions(+), 72 deletions(-) create mode 100644 apps/web/src/components/scroll-reveal.tsx diff --git a/apps/web/src/app/about/page.tsx b/apps/web/src/app/about/page.tsx index baa14c2..0384e4b 100644 --- a/apps/web/src/app/about/page.tsx +++ b/apps/web/src/app/about/page.tsx @@ -2,6 +2,7 @@ import { FounderSignature } from "@/components/about/founder-signature"; import { ContactForm } from "@/components/contact/contact-form"; import { Header } from "@/components/layout/header"; import { MobileNav } from "@/components/layout/mobile-nav"; +import { ScrollReveal } from "@/components/scroll-reveal"; import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa"; import { FaXTwitter, FaTelegram, FaDiscord, FaBluesky, FaTiktok, FaYoutube, FaInstagram, FaLinkedinIn, FaFacebookF, FaPinterestP } from "react-icons/fa6"; import type { Metadata } from "next"; @@ -117,20 +118,22 @@ export default function AboutPage() { -
+

Leadership

+
+ +
+ + +
+

Get in Touch

+

+ Have a project in mind? We'd love to hear about it. +

+
- - - -
-

Get in Touch

-

- Have a project in mind? We'd love to hear about it. -

- -
+
diff --git a/apps/web/src/app/apps/page.tsx b/apps/web/src/app/apps/page.tsx index 25903d7..1a1d6d2 100644 --- a/apps/web/src/app/apps/page.tsx +++ b/apps/web/src/app/apps/page.tsx @@ -1,5 +1,6 @@ import { Header } from "@/components/layout/header"; import { MobileNav } from "@/components/layout/mobile-nav"; +import { ScrollReveal } from "@/components/scroll-reveal"; import { StoreButtons } from "@/components/store-buttons"; import Image from "next/image"; import Link from "next/link"; @@ -65,25 +66,27 @@ export default function LinksPage() {

Our apps

- {apps.map((app) => ( -
-
- {`${app.name} -
- -

{app.name}

- -

{app.tagline}

+ {apps.map((app, i) => ( + +
+
+ {`${app.name} +
+ +

{app.name}

+ +

{app.tagline}

+
+
- -
+ ))}
diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 760c8a1..a6c86af 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -7,6 +7,7 @@ import { ServicesPreview } from "@/components/landing/services-preview"; import { Header } from "@/components/layout/header"; import { MobileNav } from "@/components/layout/mobile-nav"; import { DynamicBackground } from "@/components/landing/dynamic-background"; +import { ScrollReveal } from "@/components/scroll-reveal"; import Link from "next/link"; import { Button } from "@repo/ui/components/button"; import { useRef, useState, useEffect } from "react"; @@ -82,8 +83,8 @@ export default function Home() {
- -
+ +

Selected Works

@@ -91,28 +92,28 @@ export default function Home() {

-
+ -
+

Inclusive by Design

- We believe the web is for everyone. Every line of code we write is + We believe the web is for everyone. Every line of code we write is audited for WCAG 2.2 compliance and high-performance across all devices.

-
+ -
-
+ +

Start a Project

Have a project in mind? Let's talk about what you need and how we can help.

-
+
diff --git a/apps/web/src/app/products/page.tsx b/apps/web/src/app/products/page.tsx index 9e865e2..45d6401 100644 --- a/apps/web/src/app/products/page.tsx +++ b/apps/web/src/app/products/page.tsx @@ -1,5 +1,6 @@ import { Header } from "@/components/layout/header"; import { MobileNav } from "@/components/layout/mobile-nav"; +import { ScrollReveal } from "@/components/scroll-reveal"; import { Card } from "@repo/ui/components/card"; import Image from "next/image"; import Link from "next/link"; @@ -112,26 +113,28 @@ export default function ProductsPage() {
- {products.map((product) => ( - - -
- {`${product.title} -

- {product.title} -

-
-

- {product.description} -

-
- + {products.map((product, i) => ( + + + +
+ {`${product.title} +

+ {product.title} +

+
+

+ {product.description} +

+
+ +
))}
diff --git a/apps/web/src/app/services/page.tsx b/apps/web/src/app/services/page.tsx index 0d9c43d..daddc4f 100644 --- a/apps/web/src/app/services/page.tsx +++ b/apps/web/src/app/services/page.tsx @@ -2,6 +2,7 @@ import { DataGrid } from "@repo/ui/procedural/data-grid"; import { Button } from "@repo/ui/components/button"; import { Header } from "@/components/layout/header"; import { MobileNav } from "@/components/layout/mobile-nav"; +import { ScrollReveal } from "@/components/scroll-reveal"; import Image from "next/image"; import Link from "next/link"; import { Card } from "@repo/ui/components/card"; @@ -38,7 +39,7 @@ export default function ServicesPage() { {/* Philosophy Card */} -
+

Partnership-First Development

@@ -46,10 +47,10 @@ export default function ServicesPage() { we provide the engineering. We design systems that grow with your business.

-
+ {/* E-Commerce Section */} -
+

Custom E-Commerce Engines

@@ -80,10 +81,10 @@ export default function ServicesPage() {

-
+ {/* Custom Apps Section */} -
+

Custom Apps & Interactive Websites

@@ -95,7 +96,7 @@ export default function ServicesPage() {

Real-time Platforms

- Socket.io and WebRTC integration for collaborative tools, dashboards, + Socket.io and WebRTC integration for collaborative tools, dashboards, and live data visualization.

@@ -106,10 +107,10 @@ export default function ServicesPage() {

-
+ {/* Crypto Section */} -
+

Cryptocurrency & Payment Consulting

@@ -120,22 +121,22 @@ export default function ServicesPage() {

Payment Orchestration

- Multi-PSP routing, automated reconciliation, and fraud prevention for + Multi-PSP routing, automated reconciliation, and fraud prevention for high-volume merchants.

Web3 Integration

- Non-custodial wallet auth, on/off ramp solutions, and smart contract + Non-custodial wallet auth, on/off ramp solutions, and smart contract interactions for modern dApps.

-
+ {/* Recent Work */} -
+

Recent Work

@@ -167,9 +168,9 @@ export default function ServicesPage() { With us since day one. Five years on WordPress, migrating to a custom Medusa v2 storefront this spring.

-
+
-
+

Let's build together

@@ -181,7 +182,7 @@ export default function ServicesPage() { Request a Consultation -

+
diff --git a/apps/web/src/components/scroll-reveal.tsx b/apps/web/src/components/scroll-reveal.tsx new file mode 100644 index 0000000..03e6221 --- /dev/null +++ b/apps/web/src/components/scroll-reveal.tsx @@ -0,0 +1,77 @@ +'use client'; + +import { motion, useReducedMotion, type Variants } from 'framer-motion'; +import { createElement, type ReactNode } from 'react'; + +type Direction = 'up' | 'down' | 'left' | 'right' | 'none'; +type Tag = 'div' | 'section' | 'article' | 'li'; + +interface ScrollRevealProps { + children: ReactNode; + direction?: Direction; + delay?: number; + duration?: number; + distance?: number; + amount?: number; + className?: string; + as?: Tag; +} + +const axisFor = (direction: Direction, distance: number) => { + switch (direction) { + case 'up': return { x: 0, y: distance }; + case 'down': return { x: 0, y: -distance }; + case 'left': return { x: distance, y: 0 }; + case 'right': return { x: -distance, y: 0 }; + case 'none': return { x: 0, y: 0 }; + } +}; + +export function ScrollReveal({ + children, + direction = 'up', + delay = 0, + duration = 0.6, + distance = 24, + amount = 0.2, + className, + as = 'div', +}: ScrollRevealProps) { + const prefersReducedMotion = useReducedMotion(); + + if (prefersReducedMotion) { + return createElement(as, { className }, children); + } + + const offset = axisFor(direction, distance); + const variants: Variants = { + hidden: { opacity: 0, ...offset }, + visible: { + opacity: 1, + x: 0, + y: 0, + transition: { + duration, + delay, + ease: [0.16, 1, 0.3, 1], + }, + }, + }; + + const common = { + className, + initial: 'hidden', + whileInView: 'visible', + viewport: { once: true, amount }, + variants, + children, + }; + + const Component = + as === 'section' ? motion.section : + as === 'article' ? motion.article : + as === 'li' ? motion.li : + motion.div; + + return createElement(Component as never, common); +}