mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-17 05:32:05 +02:00
feat: enhance landing page, add about page and footer. refactor nebula animation.
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
"next-themes": "^0.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { DynamicBackground } from "@/components/landing/dynamic-background";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { FounderSignature } from "@/components/about/founder-signature";
|
||||
import Link from "next/link";
|
||||
import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa";
|
||||
|
||||
export default function AboutPage() {
|
||||
return (
|
||||
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground overflow-hidden">
|
||||
<DynamicBackground />
|
||||
|
||||
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-sm sticky top-0">
|
||||
<div className="flex items-center gap-6">
|
||||
<Link href="/" className="text-2xl font-bold tracking-tighter">Such Software</Link>
|
||||
<nav className="hidden md:flex gap-4 text-sm font-medium">
|
||||
<Link href="/services" className="hover:text-primary transition-colors">Services</Link>
|
||||
<Link href="/about" className="text-primary transition-colors">About</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</header>
|
||||
|
||||
<div className="z-10 w-full max-w-4xl mx-auto py-20 px-4">
|
||||
<h1 className="text-4xl md:text-5xl font-bold mb-10 text-center">About Us</h1>
|
||||
|
||||
<div className="bg-card/80 backdrop-blur-md rounded-2xl p-8 md:p-12 shadow-xl border border-border/50">
|
||||
|
||||
<div className="prose dark:prose-invert max-w-none mb-12">
|
||||
<p className="text-xl leading-relaxed">
|
||||
Founded in 2021 by John Winter Murphy in Chester County, PA, and operating in Kennett Square since July 2025,
|
||||
we are a full-service software provider in the heart of Mushroom Country.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid md:grid-cols-2 gap-10 mb-12">
|
||||
<div>
|
||||
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
|
||||
<FaBuilding className="text-primary" />
|
||||
Headquarters
|
||||
</h3>
|
||||
<address className="not-italic text-muted-foreground space-y-1 pl-7 border-l-2 border-border ml-2">
|
||||
<p>Such Software LLC</p>
|
||||
<p>110 E State St</p>
|
||||
<p>Suite 300</p>
|
||||
<p>Kennett Square, PA 19348</p>
|
||||
<p>USA</p>
|
||||
</address>
|
||||
|
||||
<div className="mt-6 flex flex-col gap-3 ml-2">
|
||||
<a
|
||||
href="https://maps.app.goo.gl/JeGx1rHFcriMdngn9"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-3 text-primary hover:underline hover:text-primary/80 transition-colors"
|
||||
>
|
||||
<FaMapMarkerAlt /> View on Google Maps <FaExternalLinkAlt className="w-3 h-3" />
|
||||
</a>
|
||||
<a
|
||||
href="tel:+14438779940"
|
||||
className="flex items-center gap-3 text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<FaPhone /> +1 (443) 877-9940
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-center items-center bg-muted/30 rounded-xl p-6">
|
||||
<div className="text-center">
|
||||
<h3 className="text-lg font-semibold mb-2">Connect</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">Find us on your favorite platforms</p>
|
||||
{/* Social links are in the footer, but we can highlight them here or leave for footer */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border/50 pt-10">
|
||||
<h3 className="text-2xl font-bold mb-6 text-center">Leadership</h3>
|
||||
<FounderSignature />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import { Inter } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { ThemeProvider } from "@/components/theme-provider";
|
||||
import { Footer } from "@/components/layout/footer";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
@@ -25,6 +26,7 @@ export default function RootLayout({
|
||||
disableTransitionOnChange
|
||||
>
|
||||
{children}
|
||||
<Footer />
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { PortfolioGrid } from "@/components/portfolio/bento-grid";
|
||||
import { ContactForm } from "@/components/contact/contact-form";
|
||||
import { FounderSignature } from "@/components/about/founder-signature";
|
||||
import { ServicesPreview } from "@/components/landing/services-preview";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@repo/ui/components/button";
|
||||
|
||||
@@ -31,7 +32,7 @@ export default function Home() {
|
||||
Next-generation web applications engineered for scale, compliance, and performance.
|
||||
</p>
|
||||
|
||||
<div className="flex gap-4 mb-20">
|
||||
<div className="flex gap-4 mb-10">
|
||||
<Link href="/services">
|
||||
<Button size="lg" className="rounded-full">
|
||||
Explore Services
|
||||
@@ -44,6 +45,8 @@ export default function Home() {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<ServicesPreview />
|
||||
|
||||
<div className="grid gap-4 w-full">
|
||||
<h3 className="text-3xl font-bold text-left w-full mt-10 mb-6 px-4">Selected Works</h3>
|
||||
<PortfolioGrid />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Card } from "@repo/ui/components/card";
|
||||
|
||||
export function ServicesPreview() {
|
||||
return (
|
||||
<div className="grid md:grid-cols-3 gap-6 w-full max-w-6xl mx-auto px-4 mt-8 mb-16">
|
||||
<Card className="p-6 bg-card/50 backdrop-blur-sm border-indigo-500/20 hover:border-indigo-500/40 transition-colors text-left">
|
||||
<h3 className="text-xl font-bold mb-2">Unlimited Data Modeling</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Need a product with "DNA Sequence", "Artist Bio", and "Royalty Logic"?
|
||||
We model that natively in the database, not via hacky meta-fields.
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-card/50 backdrop-blur-sm border-indigo-500/20 hover:border-indigo-500/40 transition-colors text-left">
|
||||
<h3 className="text-xl font-bold mb-2">Workflow Orchestration</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Automate complex business logic—like splitting an order between a print-on-demand provider
|
||||
and a local warehouse automatically using Medusa Workflows.
|
||||
</p>
|
||||
</Card>
|
||||
<Card className="p-6 bg-card/50 backdrop-blur-sm border-indigo-500/20 hover:border-indigo-500/40 transition-colors text-left">
|
||||
<h3 className="text-xl font-bold mb-2">Headless Performance</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
We couple the robust Medusa backend with a Next.js frontend, ensuring your shop
|
||||
scores 100/100 on Core Web Vitals and SEO.
|
||||
</p>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
'use client';
|
||||
|
||||
import Link from "next/link";
|
||||
import { FaXTwitter, FaTelegram, FaReddit, FaPhone } from "react-icons/fa6";
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="w-full border-t border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 py-12">
|
||||
<div className="container px-4 md:px-6 mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-bold">Such Software</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Full-service software provider in the heart of Mushroom Country.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-bold">Contact</h3>
|
||||
<address className="text-sm text-muted-foreground not-italic space-y-2">
|
||||
<p>Such Software LLC</p>
|
||||
<p>110 E State St, Suite 300</p>
|
||||
<p>Kennett Square, PA 19348</p>
|
||||
<p>USA</p>
|
||||
<div className="pt-2 flex items-center gap-2">
|
||||
<FaPhone className="h-4 w-4" />
|
||||
<a href="tel:+14438779940" className="hover:text-primary transition-colors">+1 (443) 877-9940</a>
|
||||
</div>
|
||||
</address>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-bold">Links</h3>
|
||||
<nav className="flex flex-col gap-2 text-sm text-muted-foreground">
|
||||
<Link href="/services" className="hover:text-primary transition-colors">Services</Link>
|
||||
<Link href="/about" className="hover:text-primary transition-colors">About</Link>
|
||||
<a href="https://maps.app.goo.gl/JeGx1rHFcriMdngn9" target="_blank" rel="noopener noreferrer" className="hover:text-primary transition-colors">
|
||||
Google Business
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-bold">Connect</h3>
|
||||
<div className="flex gap-4">
|
||||
<a href="https://x.com/such_software" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-primary transition-colors">
|
||||
<FaXTwitter className="h-5 w-5" />
|
||||
<span className="sr-only">X (Twitter)</span>
|
||||
</a>
|
||||
<a href="https://t.me/j_winter_m" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-primary transition-colors">
|
||||
<FaTelegram className="h-5 w-5" />
|
||||
<span className="sr-only">Telegram</span>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/SuchSoftware" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-primary transition-colors">
|
||||
<FaReddit className="h-5 w-5" />
|
||||
<span className="sr-only">Reddit</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-12 pt-8 border-t border-border/40 text-center text-sm text-muted-foreground">
|
||||
<p>© {new Date().getFullYear()} Such Software LLC. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -10,35 +10,57 @@ export const PortfolioGrid = () => {
|
||||
<h3 className="text-3xl font-bold text-white">r/CryptoCurrency</h3>
|
||||
<p className="text-gray-200">Governance dashboard for 10M+ members. Real-time DAO metrics.</p>
|
||||
</div>
|
||||
{/* Placeholder image since we don't have the assets yet */}
|
||||
<div className="absolute inset-0 bg-slate-800/50" />
|
||||
<Image
|
||||
src="/images/r-cryptocurrency.png"
|
||||
alt="r/CryptoCurrency Governance Dashboard"
|
||||
fill
|
||||
className="object-cover opacity-60 transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Secondary: Occupy Wallets */}
|
||||
<Card className="md:col-span-2 relative group overflow-hidden bg-zinc-100/70 backdrop-blur-sm">
|
||||
<div className="absolute inset-0 z-10 p-6 flex flex-col justify-center items-center text-center">
|
||||
<h3 className="text-2xl font-bold text-zinc-900">Occupy Wallets</h3>
|
||||
<p className="text-zinc-600">High-fidelity gallery for fiscal graffiti.</p>
|
||||
<h3 className="text-2xl font-bold text-zinc-900 drop-shadow-sm">Occupy Wallets</h3>
|
||||
<p className="text-zinc-800 font-medium drop-shadow-sm">High-fidelity gallery for fiscal graffiti.</p>
|
||||
</div>
|
||||
<Image
|
||||
src="/images/occupy-wallets.png"
|
||||
alt="Occupy Wallets Gallery"
|
||||
fill
|
||||
className="object-cover opacity-40 transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Tertiary: Monerogue & Wowne.ro (Grouped) */}
|
||||
<Card className="md:col-span-1 relative group overflow-hidden bg-orange-50/70 dark:bg-orange-950/70 border-orange-500/20 backdrop-blur-sm">
|
||||
<div className="p-6">
|
||||
<div className="absolute inset-0 z-10 p-6 bg-gradient-to-br from-background/90 to-transparent">
|
||||
<h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Privacy Labs</h3>
|
||||
<ul className="mt-2 space-y-2 text-sm">
|
||||
<ul className="mt-2 space-y-2 text-sm text-foreground">
|
||||
<li className="flex items-center gap-2">Monerogue <span className="text-xs opacity-50">(Game)</span></li>
|
||||
<li className="flex items-center gap-2">Wowne.ro <span className="text-xs opacity-50">(WASM)</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
<Image
|
||||
src="/images/privacy-labs.png"
|
||||
alt="Privacy Labs Projects"
|
||||
fill
|
||||
className="object-cover opacity-30 transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Quaternary: MoonPlace */}
|
||||
<Card className="md:col-span-1 relative group bg-indigo-50/70 dark:bg-indigo-950/70 border-indigo-500/20 backdrop-blur-sm">
|
||||
<div className="p-6">
|
||||
<Card className="md:col-span-1 relative group overflow-hidden bg-indigo-50/70 dark:bg-indigo-950/70 border-indigo-500/20 backdrop-blur-sm">
|
||||
<div className="absolute inset-0 z-10 p-6 bg-gradient-to-br from-background/90 to-transparent">
|
||||
<h3 className="text-xl font-bold text-indigo-700 dark:text-indigo-400">MoonPlace</h3>
|
||||
<p className="text-sm mt-2">Collaborative pixel canvas engine.</p>
|
||||
<p className="text-sm mt-2 text-foreground">Collaborative pixel canvas engine.</p>
|
||||
</div>
|
||||
<Image
|
||||
src="/images/moonplace.png"
|
||||
alt="MoonPlace Pixel Canvas"
|
||||
fill
|
||||
className="object-cover opacity-30 transition-transform duration-500 group-hover:scale-105"
|
||||
/>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
import { motion, useAnimation } from "framer-motion";
|
||||
import { useEffect, useState, useCallback } from "react";
|
||||
import { cn } from "../lib/utils";
|
||||
import { Monitor } from "lucide-react";
|
||||
|
||||
@@ -11,9 +11,65 @@ interface NebulaProps {
|
||||
themeMode?: 'light' | 'dark'; // customized prop
|
||||
}
|
||||
|
||||
interface StreamLineProps {
|
||||
d: string;
|
||||
themeMode: 'light' | 'dark';
|
||||
onHit: () => void;
|
||||
width: number;
|
||||
}
|
||||
|
||||
const StreamLine = ({ d, themeMode, onHit, width }: StreamLineProps) => {
|
||||
const controls = useAnimation();
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
const animate = async () => {
|
||||
// Random initial delay
|
||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 4000));
|
||||
|
||||
while (isMounted) {
|
||||
const duration = 2 + Math.random() * 4;
|
||||
|
||||
controls.set({ pathLength: 0, opacity: 0 });
|
||||
|
||||
await controls.start({
|
||||
pathLength: [0, 1],
|
||||
opacity: [0, 1, 1, 0],
|
||||
transition: {
|
||||
duration,
|
||||
ease: "easeInOut",
|
||||
times: [0, 0.1, 0.9, 1]
|
||||
}
|
||||
});
|
||||
|
||||
if (isMounted) onHit();
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, Math.random() * 2000));
|
||||
}
|
||||
};
|
||||
animate();
|
||||
return () => { isMounted = false; };
|
||||
}, [controls, onHit]);
|
||||
|
||||
return (
|
||||
<motion.path
|
||||
d={d}
|
||||
stroke="currentColor"
|
||||
strokeWidth={width}
|
||||
fill="none"
|
||||
animate={controls}
|
||||
className={cn(
|
||||
"transition-colors duration-500",
|
||||
themeMode === 'light' ? "text-slate-400" : "text-emerald-500"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const NebulaField = ({ className, density = 20, themeMode = 'dark' }: NebulaProps) => {
|
||||
const [paths, setPaths] = useState<string[]>([]);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const monitorControls = useAnimation();
|
||||
|
||||
// Configuration for convergence point (percent of container)
|
||||
const targetX = 25; // moved right
|
||||
@@ -45,7 +101,15 @@ export const NebulaField = ({ className, density = 20, themeMode = 'dark' }: Neb
|
||||
return `M ${startX} ${startY} Q ${cpX} ${cpY} ${endX} ${endY}`;
|
||||
});
|
||||
setPaths(newPaths);
|
||||
}, [density, targetX, targetY]);
|
||||
}, [density]); // Removed targetX/Y dep as constant
|
||||
|
||||
const handleHit = useCallback(() => {
|
||||
monitorControls.start({
|
||||
scale: [1, 1.15, 1],
|
||||
filter: ["brightness(1)", "brightness(1.5)", "brightness(1)"],
|
||||
transition: { duration: 0.3 }
|
||||
});
|
||||
}, [monitorControls]);
|
||||
|
||||
if (!mounted) return null;
|
||||
|
||||
@@ -53,9 +117,10 @@ export const NebulaField = ({ className, density = 20, themeMode = 'dark' }: Neb
|
||||
<div className={cn("absolute inset-0 overflow-hidden pointer-events-none z-0", className)}>
|
||||
|
||||
{/* "Computer" Icon positioned via inline styles to match physics target exactly */}
|
||||
<div
|
||||
className="absolute -translate-x-1/2 -translate-y-1/2 z-10 opacity-20 dark:opacity-40 animate-pulse"
|
||||
<motion.div
|
||||
className="absolute -translate-x-1/2 -translate-y-1/2 z-10 opacity-30 dark:opacity-50"
|
||||
style={{ left: `${targetX}%`, top: `${targetY}%` }}
|
||||
animate={monitorControls}
|
||||
>
|
||||
<Monitor
|
||||
size={64}
|
||||
@@ -64,7 +129,7 @@ export const NebulaField = ({ className, density = 20, themeMode = 'dark' }: Neb
|
||||
themeMode === 'light' ? "text-slate-400" : "text-emerald-500"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<svg
|
||||
viewBox="0 0 100 100"
|
||||
@@ -72,27 +137,12 @@ export const NebulaField = ({ className, density = 20, themeMode = 'dark' }: Neb
|
||||
className="w-full h-full opacity-60 dark:opacity-80 absolute inset-0"
|
||||
>
|
||||
{paths.map((d, i) => (
|
||||
<motion.path
|
||||
<StreamLine
|
||||
key={i}
|
||||
d={d}
|
||||
stroke="currentColor"
|
||||
strokeWidth={themeMode === 'light' ? 0.1 : 0.2}
|
||||
fill="none"
|
||||
initial={{ pathLength: 0, opacity: 0 }}
|
||||
animate={{
|
||||
pathLength: [0, 1], // Draw from start to end
|
||||
opacity: [0, 1, 0] // Fade in, then out at the end
|
||||
}}
|
||||
transition={{
|
||||
duration: 5 + Math.random() * 5, // Faster, more data-stream like (was 20s)
|
||||
repeat: Infinity,
|
||||
ease: "easeInOut",
|
||||
delay: Math.random() * 5
|
||||
}}
|
||||
className={cn(
|
||||
"transition-colors duration-500",
|
||||
themeMode === 'light' ? "text-slate-400" : "text-emerald-500"
|
||||
)}
|
||||
themeMode={themeMode}
|
||||
onHit={handleHit}
|
||||
width={themeMode === 'light' ? 0.1 : 0.2}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
|
||||
Generated
+12
@@ -50,6 +50,9 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.3.1(react@18.3.1)
|
||||
react-icons:
|
||||
specifier: ^5.5.0
|
||||
version: 5.5.0(react@18.3.1)
|
||||
zod:
|
||||
specifier: ^3.22.4
|
||||
version: 3.25.76
|
||||
@@ -1626,6 +1629,11 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.3.1
|
||||
|
||||
react-icons@5.5.0:
|
||||
resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@@ -3657,6 +3665,10 @@ snapshots:
|
||||
react: 18.3.1
|
||||
scheduler: 0.23.2
|
||||
|
||||
react-icons@5.5.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react@18.3.1:
|
||||
|
||||
Reference in New Issue
Block a user