Fix critical accessibility and SEO issues

- Fix homepage heading hierarchy (h2 -> h1)
- Add page-specific metadata to services and about pages
- Fix outline-none on form inputs for keyboard accessibility
- Delete sync conflict files
- Add group class back to bento cards for hover animations
- Use raw CSS for glassmorphism to fix Tailwind opacity errors
- Add TODO.md with production checklist
- Add semantic CSS classes (.section-container, .glass-card)
- Update Tailwind config for opacity modifier support
This commit is contained in:
jwinterm
2026-01-16 15:03:12 -05:00
parent b40193c086
commit ffaea03e58
24 changed files with 485 additions and 4566 deletions
+131
View File
@@ -0,0 +1,131 @@
# Such Software - Production Checklist
## 🔴 Before Production Deployment
### 1. Cloudflare Turnstile Configuration
The contact form uses Cloudflare Turnstile for bot protection. You need to set up the secret key for production:
1. Go to [Cloudflare Dashboard](https://dash.cloudflare.com/) → Turnstile
2. Create a new site or use existing widget
3. Copy the **Site Key** (public) and **Secret Key** (private)
4. Set environment variables:
```bash
# In your production environment (Vercel, etc.)
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
TURNSTILE_SECRET_KEY=your_secret_key_here
```
⚠️ **Warning:** The current implementation returns `true` if no secret key is set. This is fine for development but must be configured in production.
See: `apps/web/src/lib/turnstile.ts`
---
## 🟡 Medium Priority Improvements
### 2. Extract Shared Header Component
The header is duplicated across three pages:
- `apps/web/src/app/page.tsx`
- `apps/web/src/app/services/page.tsx`
- `apps/web/src/app/about/page.tsx`
Create a shared component at `apps/web/src/components/layout/header.tsx`.
### 3. Add `prefers-reduced-motion` Support
The NebulaField and DynamicBackground components run animations without checking user preferences.
File: `packages/ui/src/procedural/nebula-field.tsx`
Add:
```tsx
const prefersReducedMotion = typeof window !== 'undefined'
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
: false;
if (prefersReducedMotion) {
return <div className="absolute inset-0 bg-gradient-to-b from-slate-100 to-slate-200 dark:from-slate-900 dark:to-slate-800" />;
}
```
### 4. Add Skip-to-Content Link
For keyboard accessibility, add a skip link in `apps/web/src/app/layout.tsx`:
```tsx
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-lg">
Skip to main content
</a>
```
### 5. Add `aria-hidden` to Decorative Icons
In `apps/web/src/app/about/page.tsx`, add `aria-hidden="true"` to decorative icons:
```tsx
<FaMapMarkerAlt aria-hidden="true" />
<FaPhone aria-hidden="true" />
<FaBuilding aria-hidden="true" />
```
### 6. Type Server Action Properly
In `apps/web/src/actions/submit-contact.ts`, the `prevState` parameter uses `any`. Update to:
```tsx
export async function submitContactForm(
prevState: { success: boolean; message: string; errors?: Record<string, string> },
formData: FormData
): Promise<{ success: boolean; message: string; errors?: Record<string, string> }>
```
### 7. Rate Limiting
Consider adding rate limiting to the contact form submission to prevent abuse.
Options: Upstash Ratelimit, Vercel Edge Config, or Cloudflare Workers.
---
## 🟢 Nice-to-Have
### 8. Add Structured Data (JSON-LD)
Add Organization schema to `layout.tsx` for better SEO:
```tsx
<script type="application/ld+json">
{JSON.stringify({
"@context": "https://schema.org",
"@type": "Organization",
"name": "Such Software LLC",
"url": "https://such.software",
"address": {
"@type": "PostalAddress",
"streetAddress": "110 E State St, Suite 300",
"addressLocality": "Kennett Square",
"addressRegion": "PA",
"postalCode": "19348",
"addressCountry": "US"
}
})}
</script>
```
### 9. Add Canonical URLs
Add canonical meta tags to prevent duplicate content issues.
### 10. Remove Console Logs
Remove `console.log` statements before production:
- `apps/web/src/components/contact/turnstile-widget.tsx`
---
## ✅ Recently Completed
- [x] Deleted sync conflict files
- [x] Fixed homepage `<h1>` heading hierarchy
- [x] Added page metadata to services and about pages
- [x] Fixed `outline-none` accessibility issue on form inputs
- [x] Established semantic CSS architecture (`.section-container`, `.glass-card`)
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+16
View File
@@ -0,0 +1,16 @@
{
"name": "Such Software",
"short_name": "Such Software",
"description": "Next-generation web applications engineered for scale, compliance, and performance.",
"start_url": "/",
"display": "standalone",
"background_color": "#000000",
"theme_color": "#000000",
"icons": [
{
"src": "/images/favicon_192.png",
"sizes": "192x192",
"type": "image/png"
}
]
}
+18 -6
View File
@@ -1,17 +1,29 @@
import { DynamicBackground } from "@/components/landing/dynamic-background"; import { DynamicBackground } from "@/components/landing/dynamic-background";
import { ThemeToggle } from "@/components/theme-toggle"; import { ThemeToggle } from "@/components/theme-toggle";
import { FounderSignature } from "@/components/about/founder-signature"; import { FounderSignature } from "@/components/about/founder-signature";
import { SiteLogo } from "@/components/layout/site-logo";
import Link from "next/link"; import Link from "next/link";
import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa"; import { FaMapMarkerAlt, FaPhone, FaBuilding, FaExternalLinkAlt } from "react-icons/fa";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "About | Such Software",
description: "Full-service software provider in Kennett Square, PA. Founded by John Winter Murphy, specializing in accessible web engineering and WCAG 2.2 compliance.",
openGraph: {
title: "About | Such Software",
description: "Full-service software provider in Kennett Square, PA. Founded by John Winter Murphy.",
type: "website",
},
};
export default function AboutPage() { export default function AboutPage() {
return ( return (
<main className="relative flex flex-col items-center overflow-hidden"> <main className="relative flex flex-col items-center overflow-hidden">
<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"> <header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-3xl sticky top-0 rounded-b-3xl border-b border-border/50">
<div className="flex items-center gap-6"> <div className="flex items-center gap-8">
<Link href="/" className="text-2xl font-bold tracking-tighter">Such Software</Link> <SiteLogo />
<nav className="hidden md:flex gap-4 text-sm font-medium"> <nav className="hidden md:flex gap-6 text-sm font-medium">
<Link href="/services" className="hover:text-primary transition-colors">Services</Link> <Link href="/services" className="hover:text-primary transition-colors">Services</Link>
<Link href="/about" className="text-primary transition-colors">About</Link> <Link href="/about" className="text-primary transition-colors">About</Link>
</nav> </nav>
@@ -19,10 +31,10 @@ export default function AboutPage() {
<ThemeToggle /> <ThemeToggle />
</header> </header>
<div className="z-10 w-full max-w-4xl mx-auto py-20 px-4"> <div className="z-10 w-full max-w-6xl mx-auto py-20 px-4">
<h1 className="text-4xl md:text-5xl font-bold mb-10 text-center">About Us</h1> <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="section-container">
<div className="prose dark:prose-invert max-w-none mb-12"> <div className="prose dark:prose-invert max-w-none mb-12">
<p className="text-xl leading-relaxed"> <p className="text-xl leading-relaxed">
+26
View File
@@ -74,3 +74,29 @@
@apply bg-background text-foreground; @apply bg-background text-foreground;
} }
} }
@layer components {
/* High-contrast content masking for accessibility and streamer protection */
.mask-backdrop {
background-color: hsl(var(--card) / 0.98);
backdrop-filter: blur(64px);
-webkit-backdrop-filter: blur(64px);
border-width: 2px;
border-color: hsl(var(--border) / 0.5);
box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
position: relative;
overflow: hidden;
transition: all 300ms;
}
.section-container {
@apply mask-backdrop rounded-[2.5rem] p-10 md:p-16 text-card-foreground;
}
.glass-card {
@apply mask-backdrop rounded-[2rem] p-8 text-card-foreground;
}
.glass-card:hover {
border-color: hsl(var(--primary) / 0.3);
}
}
+14 -1
View File
@@ -7,7 +7,20 @@ import { DynamicBackground } from "@/components/landing/dynamic-background";
const inter = Inter({ subsets: ["latin"] }); const inter = Inter({ subsets: ["latin"] });
// ... (metadata omitted) export const metadata: Metadata = {
title: "Such Software | Digital Craftsmanship",
description: "Next-generation web applications engineered for scale, compliance, and performance.",
icons: {
icon: [
{ url: "/images/favicon_192.png", sizes: "192x192", type: "image/png" },
],
shortcut: "/images/favicon_192.png",
apple: [
{ url: "/images/favicon_192.png", sizes: "192x192", type: "image/png" },
],
},
manifest: "/manifest.json",
};
export default function RootLayout({ export default function RootLayout({
children, children,
+19 -39
View File
@@ -4,6 +4,7 @@ import { PortfolioGrid } from "@/components/portfolio/bento-grid";
import { ContactForm } from "@/components/contact/contact-form"; import { ContactForm } from "@/components/contact/contact-form";
import { FounderSignature } from "@/components/about/founder-signature"; import { FounderSignature } from "@/components/about/founder-signature";
import { ServicesPreview } from "@/components/landing/services-preview"; import { ServicesPreview } from "@/components/landing/services-preview";
import { SiteLogo } from "@/components/layout/site-logo";
import Link from "next/link"; import Link from "next/link";
import { Button } from "@repo/ui/components/button"; import { Button } from "@repo/ui/components/button";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
@@ -12,10 +13,10 @@ export default function Home() {
return ( return (
<main className="relative flex flex-col items-center justify-between overflow-hidden"> <main className="relative flex flex-col items-center justify-between overflow-hidden">
<header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 pb-2"> <header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-3xl sticky top-0 rounded-b-3xl border-b border-border/50">
<div className="flex items-center gap-6"> <div className="flex items-center gap-8">
<h1 className="text-2xl font-bold tracking-tighter">Such Software</h1> <SiteLogo />
<nav className="hidden md:flex gap-4 text-sm font-medium"> <nav className="hidden md:flex gap-6 text-sm font-medium">
<Link href="/services" className="hover:text-primary transition-colors">Services</Link> <Link href="/services" className="hover:text-primary transition-colors">Services</Link>
<Link href="/about" className="hover:text-primary transition-colors">About</Link> <Link href="/about" className="hover:text-primary transition-colors">About</Link>
</nav> </nav>
@@ -24,9 +25,9 @@ export default function Home() {
</header> </header>
<div className="z-10 w-full max-w-7xl mx-auto flex flex-col items-center pt-20 pb-10 px-4 text-center"> <div className="z-10 w-full max-w-7xl mx-auto flex flex-col items-center pt-20 pb-10 px-4 text-center">
<h2 className="text-5xl md:text-7xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-emerald-500 pb-2"> <h1 className="text-5xl md:text-7xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-blue-500 to-emerald-500 pb-2">
Digital Craftsmanship Digital Craftsmanship
</h2> </h1>
<p className="max-w-2xl text-xl text-muted-foreground mb-8"> <p className="max-w-2xl text-xl text-muted-foreground mb-8">
Next-generation web applications engineered for scale, compliance, and performance. Next-generation web applications engineered for scale, compliance, and performance.
</p> </p>
@@ -47,9 +48,9 @@ export default function Home() {
<ServicesPreview /> <ServicesPreview />
<div className="w-full max-w-6xl mx-auto mb-16 px-4"> <div className="w-full max-w-6xl mx-auto mb-16 px-4">
<div className="text-left mb-8 px-2 border-l-2 border-blue-500/50 pl-6"> <div className="text-left mb-8 px-2 border-l-2 border-blue-500/50 section-container">
<h3 className="text-2xl font-bold mb-2">Selected Works</h3> <h3 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Selected Works</h3>
<p className="text-muted-foreground text-lg max-w-3xl"> <p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
A curated gallery of recent projects where we've pushed the boundaries of A curated gallery of recent projects where we've pushed the boundaries of
performance, accessibility, and user experience. performance, accessibility, and user experience.
</p> </p>
@@ -57,42 +58,21 @@ export default function Home() {
<PortfolioGrid /> <PortfolioGrid />
</div> </div>
<div className="w-full max-w-6xl mx-auto mb-16 px-4"> <div className="w-full max-w-6xl mx-auto mb-20 px-4">
<div className="text-left mb-8 px-2 border-l-2 border-purple-500/50 pl-6"> <div className="text-left mb-12 px-2 border-l-2 border-purple-500/50 section-container">
<h3 className="text-2xl font-bold mb-2">Inclusive by Design</h3> <h3 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Inclusive by Design</h3>
<p className="text-muted-foreground text-lg max-w-3xl"> <p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
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.1 compliance and high-performance across all devices. audited for WCAG 2.2 compliance and high-performance across all devices.
</p> </p>
</div> </div>
<div className="grid md:grid-cols-2 gap-8">
<Card className="p-8 bg-card/50 backdrop-blur-sm border-purple-500/10 hover:border-purple-500/30 transition-all group">
<h4 className="text-xl font-bold mb-4 flex items-center gap-2">
<span className="w-8 h-8 rounded-full bg-purple-500/10 flex items-center justify-center text-purple-500 text-sm">01</span>
A11y First Architecture
</h4>
<p className="text-muted-foreground text-sm">
Semantic HTML, ARIA patterns, and keyboard navigation aren't afterthoughts—they are the foundation of our build process.
</p>
</Card>
<Card className="p-8 bg-card/50 backdrop-blur-sm border-blue-500/10 hover:border-blue-500/30 transition-all group">
<h4 className="text-xl font-bold mb-4 flex items-center gap-2">
<span className="w-8 h-8 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500 text-sm">02</span>
Edge Performance
</h4>
<p className="text-muted-foreground text-sm">
By leveraging edge computing and static generation, we ensure your site is usable even on 3G connections and older hardware.
</p>
</Card>
</div>
</div>
<FounderSignature /> <FounderSignature />
</div>
<div id="contact" className="w-full mt-20 mb-20 flex flex-col items-center"> <div id="contact" className="w-full mt-20 mb-20 flex flex-col items-center">
<div className="w-full max-w-6xl text-left mb-12 px-2 border-l-2 border-emerald-500/50 pl-6"> <div className="w-full max-w-6xl text-left mb-12 px-2 border-l-2 border-emerald-500/50 section-container">
<h3 className="text-2xl font-bold mb-2">Start a Project</h3> <h3 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Start a Project</h3>
<p className="text-muted-foreground text-lg max-w-3xl"> <p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
Ready to build something exceptional? Drop us a line and let's discuss Ready to build something exceptional? Drop us a line and let's discuss
how we can help you scale your digital presence. how we can help you scale your digital presence.
</p> </p>
+49 -37
View File
@@ -1,8 +1,20 @@
import { DataGrid } from "@repo/ui/procedural/data-grid"; import { DataGrid } from "@repo/ui/procedural/data-grid";
import { ThemeToggle } from "@/components/theme-toggle"; import { ThemeToggle } from "@/components/theme-toggle";
import { Button } from "@repo/ui/components/button"; import { Button } from "@repo/ui/components/button";
import { SiteLogo } from "@/components/layout/site-logo";
import Link from "next/link"; import Link from "next/link";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Services | Such Software",
description: "Medusa v2 development, custom web applications, and cryptocurrency consulting. High-performance headless commerce, real-time platforms, and Web3 integration.",
openGraph: {
title: "Services | Such Software",
description: "Medusa v2 development, custom web applications, and cryptocurrency consulting.",
type: "website",
},
};
export default function ServicesPage() { export default function ServicesPage() {
return ( return (
@@ -11,10 +23,10 @@ export default function ServicesPage() {
{/* Option B: Data Grid Background for Technical Feel */} {/* Option B: Data Grid Background for Technical Feel */}
<DataGrid /> <DataGrid />
<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"> <header className="z-50 w-full max-w-7xl mx-auto flex justify-between items-center p-6 bg-background/50 backdrop-blur-3xl sticky top-0 border-b border-border/50 rounded-b-3xl">
<div className="flex items-center gap-6"> <div className="flex items-center gap-8">
<Link href="/" className="text-2xl font-bold tracking-tighter">Such Software</Link> <SiteLogo />
<nav className="hidden md:flex gap-4 text-sm font-medium"> <nav className="hidden md:flex gap-6 text-sm font-medium">
<Link href="/services" className="text-primary transition-colors">Services</Link> <Link href="/services" className="text-primary transition-colors">Services</Link>
<Link href="/about" className="hover:text-primary transition-colors">About</Link> <Link href="/about" className="hover:text-primary transition-colors">About</Link>
</nav> </nav>
@@ -25,7 +37,7 @@ export default function ServicesPage() {
<div className="z-10 w-full max-w-5xl mx-auto py-20 px-4"> <div className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
{/* Context Header */} {/* Context Header */}
<div className="mb-20"> <div className="mb-20">
<div className="text-left mb-8 px-2 border-l-2 border-emerald-500/50 pl-6"> <div className="text-left mb-8 px-2 border-l-2 border-emerald-500/50 section-container">
<h1 className="text-4xl md:text-6xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 to-blue-500"> <h1 className="text-4xl md:text-6xl font-bold mb-6 bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 to-blue-500">
Specialized Solutions Specialized Solutions
</h1> </h1>
@@ -39,7 +51,7 @@ export default function ServicesPage() {
{/* Medusa / Webshops Section */} {/* Medusa / Webshops Section */}
<div className="mb-24"> <div className="mb-24">
<div className="text-left mb-8 px-2 border-l-2 border-blue-500/50 pl-6"> <div className="text-left mb-8 px-2 border-l-2 border-blue-500/50 section-container mb-10">
<h2 className="text-3xl font-bold mb-2">Medusa v2 Development</h2> <h2 className="text-3xl font-bold mb-2">Medusa v2 Development</h2>
<p className="text-muted-foreground text-lg max-w-3xl"> <p className="text-muted-foreground text-lg max-w-3xl">
We build proprietary, headless commerce engines that scale with your business logic. We build proprietary, headless commerce engines that scale with your business logic.
@@ -48,23 +60,23 @@ export default function ServicesPage() {
</div> </div>
<div className="grid md:grid-cols-3 gap-6"> <div className="grid md:grid-cols-3 gap-6">
<Card className="p-6 bg-card/80 backdrop-blur-sm border-indigo-500/10 hover:border-indigo-500/30 transition-all"> <Card className="glass-card hover:border-indigo-500/30">
<h3 className="text-xl font-bold mb-2">Unlimited Data Modeling</h3> <h3 className="text-xl font-bold mb-3">Unlimited Data Modeling</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
Natively model complex entities like "DNA Sequences" or "Royalty Logic" Natively model complex entities like "DNA Sequences" or "Royalty Logic"
directly in the database without hacky metadata. directly in the database without hacky metadata.
</p> </p>
</Card> </Card>
<Card className="p-6 bg-card/80 backdrop-blur-sm border-indigo-500/10 hover:border-indigo-500/30 transition-all"> <Card className="glass-card hover:border-indigo-500/30">
<h3 className="text-xl font-bold mb-2">Zero Capital Upfront</h3> <h3 className="text-xl font-bold mb-3">Zero Capital Upfront</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
Our "Toast" style partnership means we win when you win. Low friction onboarding Our "Toast" style partnership means we win when you win. Low friction onboarding
with performance-based incentives. with performance-based incentives.
</p> </p>
</Card> </Card>
<Card className="p-6 bg-card/80 backdrop-blur-sm border-indigo-500/10 hover:border-indigo-500/30 transition-all"> <Card className="glass-card hover:border-indigo-500/30">
<h3 className="text-xl font-bold mb-2">Headless SEO</h3> <h3 className="text-xl font-bold mb-3">Headless SEO</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
Custom Next.js frontends engineered for perfect lighthouse scores and Custom Next.js frontends engineered for perfect lighthouse scores and
superior organic search conversion. superior organic search conversion.
</p> </p>
@@ -74,24 +86,24 @@ export default function ServicesPage() {
{/* Custom Apps Section */} {/* Custom Apps Section */}
<div className="mb-24"> <div className="mb-24">
<div className="text-left mb-8 px-2 border-l-2 border-purple-500/50 pl-6"> <div className="text-left mb-8 px-2 border-l-2 border-purple-500/50 section-container mb-10">
<h2 className="text-3xl font-bold mb-2">Custom Apps & Interactive Websites</h2> <h2 className="text-3xl font-bold mb-2">Custom Apps & Interactive Websites</h2>
<p className="text-muted-foreground text-lg max-w-2xl"> <p className="text-muted-foreground text-lg max-w-2xl leading-relaxed">
Whether it's a real-time collaborative tool or a high-end brand experience, Whether it's a real-time collaborative tool or a high-end brand experience,
we build performant web applications that delight users. we build performant web applications that delight users.
</p> </p>
</div> </div>
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<Card className="p-6 bg-card/80 backdrop-blur-sm border-purple-500/10 hover:border-purple-500/30 transition-all"> <Card className="glass-card hover:border-purple-500/30">
<h3 className="text-xl font-bold mb-2">Real-time Platforms</h3> <h3 className="text-xl font-bold mb-3">Real-time Platforms</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
Socket.io and WebRTC integration for collaborative tools, dashboards, Socket.io and WebRTC integration for collaborative tools, dashboards,
and live data visualization. and live data visualization.
</p> </p>
</Card> </Card>
<Card className="p-6 bg-card/80 backdrop-blur-sm border-purple-500/10 hover:border-purple-500/30 transition-all"> <Card className="glass-card hover:border-purple-500/30">
<h3 className="text-xl font-bold mb-2">Brand Motion</h3> <h3 className="text-xl font-bold mb-3">Brand Motion</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
Framermotion and Three.js powered interfaces that tell your brand's story Framermotion and Three.js powered interfaces that tell your brand's story
through motion and depth. through motion and depth.
</p> </p>
@@ -101,24 +113,24 @@ export default function ServicesPage() {
{/* Crypto Section */} {/* Crypto Section */}
<div className="mb-24"> <div className="mb-24">
<div className="text-left mb-8 px-2 border-l-2 border-cyan-500/50 pl-6"> <div className="text-left mb-8 px-2 border-l-2 border-cyan-500/50 section-container mb-10">
<h2 className="text-3xl font-bold mb-2">Cryptocurrency & Payment Consulting</h2> <h2 className="text-3xl font-bold mb-2">Cryptocurrency & Payment Consulting</h2>
<p className="text-muted-foreground text-lg max-w-2xl"> <p className="text-muted-foreground text-lg max-w-2xl leading-relaxed">
Navigate the complex world of global finance. We bridge the gap between Navigate the complex world of global finance. We bridge the gap between
traditional banking and decentralized protocols. traditional banking and decentralized protocols.
</p> </p>
</div> </div>
<div className="grid md:grid-cols-2 gap-6"> <div className="grid md:grid-cols-2 gap-6">
<Card className="p-6 bg-card/80 backdrop-blur-sm border-cyan-500/10 hover:border-cyan-500/30 transition-all"> <Card className="glass-card hover:border-cyan-500/30">
<h3 className="text-xl font-bold mb-2">Payment Orchestration</h3> <h3 className="text-xl font-bold mb-3">Payment Orchestration</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
Multi-PSP routing, automated reconciliation, and fraud prevention for Multi-PSP routing, automated reconciliation, and fraud prevention for
high-volume merchants. high-volume merchants.
</p> </p>
</Card> </Card>
<Card className="p-6 bg-card/80 backdrop-blur-sm border-cyan-500/10 hover:border-cyan-500/30 transition-all"> <Card className="glass-card hover:border-cyan-500/30">
<h3 className="text-xl font-bold mb-2">Web3 Integration</h3> <h3 className="text-xl font-bold mb-3">Web3 Integration</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
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. interactions for modern dApps.
</p> </p>
@@ -126,16 +138,16 @@ export default function ServicesPage() {
</div> </div>
</div> </div>
<div className="mt-20 p-12 rounded-2xl bg-gradient-to-br from-slate-900 via-slate-800 to-indigo-950 text-white text-center shadow-2xl relative overflow-hidden group"> <div className="mt-20 p-16 rounded-[2.5rem] bg-gradient-to-br from-slate-900 via-slate-800 to-indigo-950 text-white text-center shadow-2xl relative overflow-hidden group border-2 border-white/5">
<div className="absolute inset-0 bg-blue-500/5 opacity-0 group-hover:opacity-100 transition-opacity" /> <div className="absolute inset-0 bg-blue-500/10 opacity-0 group-hover:opacity-100 transition-opacity" />
<h2 className="text-3xl font-bold mb-4 relative z-10">Start building the future</h2> <h2 className="text-4xl font-bold mb-6 relative z-10 tracking-tight">Start building the future</h2>
<p className="mb-8 text-slate-300 max-w-xl mx-auto relative z-10"> <p className="mb-10 text-slate-300 text-lg max-w-2xl mx-auto relative z-10 leading-relaxed font-medium">
We are currently accepting a limited number of partners for performance-based development. We are currently accepting a limited number of partners for performance-based development.
Let's see if we're a fit. Let's see if we're a fit.
</p> </p>
<Link href="/#contact" className="relative z-10"> <Link href="/#contact" className="relative z-10">
<Button size="lg" variant="secondary" className="rounded-full px-12 py-6 text-lg hover:scale-105 transition-transform"> <Button size="lg" variant="secondary" className="rounded-2xl px-12 py-8 text-xl font-bold hover:scale-105 transition-transform bg-white text-slate-900 hover:bg-slate-100">
Request a Demo Request a Consultation
</Button> </Button>
</Link> </Link>
</div> </div>
@@ -1,48 +1,82 @@
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import Image from "next/image";
export const FounderSignature = () => { export const FounderSignature = () => {
return ( return (
<section className="w-full max-w-4xl mx-auto py-20 px-4"> <div className="grid md:grid-cols-[1fr,440px] gap-12 lg:gap-20 items-start">
<div className="grid md:grid-cols-2 gap-12 items-center"> {/* Left Column: Bio */}
<div className="space-y-6"> <div className="space-y-8 text-left section-container">
<h2 className="text-3xl font-bold tracking-tight">Accessibility as Architecture</h2> <div className="flex items-center gap-6 relative z-10">
<div className="prose dark:prose-invert"> <div className="relative w-24 h-24 rounded-full overflow-hidden border-2 border-primary/10 bg-white dark:bg-zinc-900 shadow-xl ring-4 ring-primary/5">
<p className="text-muted-foreground leading-relaxed"> <Image
"During my three years at <span className="text-foreground font-semibold">Cecil College</span>, I didn't just teach web design—I re-engineered how it was taught. src="/images/logo_w_circle_trans.jpg"
</p> alt="Such Software Icon"
<p className="text-muted-foreground leading-relaxed mt-4"> fill
Transitioning the curriculum toward 'Digital Arts and Media Design,' I embedded inclusive design principles into the foundation of every course. I taught students that a website isn't truly 'live' until it is accessible to everyone. className="object-cover scale-[1.02]"
</p> />
<p className="text-muted-foreground leading-relaxed mt-4">
This wasn't just academic theory; it was a rigorous discipline of semantic HTML, ARIA compliance, and cognitive load management. Today, I bring that same academic rigor to Such Software’s commercial projects. We don't retroactively fix accessibility; we architect for it from line one."
</p>
</div> </div>
<div className="pt-4 border-t border-border"> <div>
<p className="font-mono text-sm uppercase tracking-widest text-primary">Founder & Principle Engineer</p> <p className="font-mono text-xs uppercase tracking-[0.4em] text-primary/80 mb-1">Founder & Principal Engineer</p>
<h2 className="text-3xl font-bold tracking-tight">John Winter Murphy</h2>
</div> </div>
</div> </div>
<Card className="relative p-8 bg-muted/30 border-dashed"> <div className="prose dark:prose-invert max-w-none">
<div className="absolute -top-4 -right-4 w-24 h-24 bg-blue-500/10 rounded-full blur-2xl" /> <p className="text-muted-foreground leading-relaxed italic border-l-4 border-primary/20 pl-6 text-lg">
<div className="absolute -bottom-4 -left-4 w-32 h-32 bg-purple-500/10 rounded-full blur-2xl" /> "During my three years at <span className="text-foreground font-semibold">Cecil College</span>, I didn't just teach web engineering—I re-engineered how it was taught.
</p>
<p className="text-muted-foreground leading-relaxed mt-6">
Transitioning the curriculum toward from PDFs and Powerpoints to web-based content, I focused on transforming complex technical lessons into highly accessible HTML using <span className="text-foreground font-semibold underline decoration-primary/50">Quarto markdown</span> as our source. This allowed us to consistently generate semantic content including accessible equations and guaranteed descriptive alt text for every visual asset.
</p>
<p className="text-muted-foreground leading-relaxed mt-4 italic border-l-4 border-primary/20 pl-6 text-lg">
This wasn't just academic theory; it was a rigorous discipline of semantic architecture, ARIA compliance, and cognitive load management. Today, I bring that same technical rigor to Such Software’s commercial projects. We don't retroactively fix accessibility; we architect for it from line one."
</p>
</div>
</div>
<h3 className="tex-lg font-semibold mb-4">Core Competencies</h3> {/* Right Column: Cards 01, 02, 03 */}
<ul className="space-y-3 font-mono text-sm"> <div className="space-y-6 lg:-mt-4">
<Card className="glass-card hover:border-purple-500/30">
<h4 className="text-base font-bold mb-3 flex items-center gap-2">
<span className="w-6 h-6 rounded-full bg-purple-500/10 flex items-center justify-center text-purple-500 text-[10px]">01</span>
A11y First Architecture
</h4>
<p className="text-muted-foreground text-sm leading-relaxed font-medium">
Semantic HTML, ARIA patterns, and keyboard navigation aren't afterthoughts—they are the foundation of our build process.
</p>
</Card>
<Card className="glass-card hover:border-blue-500/30">
<h4 className="text-base font-bold mb-3 flex items-center gap-2">
<span className="w-6 h-6 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500 text-[10px]">02</span>
Edge Performance
</h4>
<p className="text-muted-foreground text-sm leading-relaxed font-medium">
By leveraging edge computing and static generation, we ensure your site is usable even on 3G connections and older hardware.
</p>
</Card>
<Card className="glass-card hover:border-emerald-500/30 border-dashed">
<h4 className="text-base font-bold mb-4 flex items-center gap-2">
<span className="text-emerald-500 text-[10px]">03</span>
Core Competencies
</h4>
<ul className="grid grid-cols-1 gap-2 font-mono text-[11px] uppercase tracking-wider text-muted-foreground/80">
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
<span className="text-green-500">✓</span> WCAG 2.2 AA Compliance <span className="text-emerald-500 text-sm">✓</span> WCAG 2.2 AA Compliance
</li> </li>
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
<span className="text-green-500">✓</span> Section 508 & ADA <span className="text-emerald-500 text-sm">✓</span> Section 508 & ADA
</li> </li>
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
<span className="text-green-500">✓</span> Semantic HTML5 Architecture <span className="text-emerald-500 text-sm">✓</span> Semantic HTML5 Architecture
</li> </li>
<li className="flex items-center gap-2"> <li className="flex items-center gap-2">
<span className="text-green-500">✓</span> Playwright A11y Automation <span className="text-emerald-500 text-sm">✓</span> Playwright A11y Automation
</li> </li>
</ul> </ul>
</Card> </Card>
</div> </div>
</section> </div>
); );
}; };
@@ -1,15 +1,19 @@
'use client'; 'use client';
import { useFormState, useFormStatus } from 'react-dom'; // Note: check react-dom version for useFormState support or import from hook import { useFormState, useFormStatus } from 'react-dom';
// Next.js 14 uses react-dom for these
import { submitContactForm } from '@/actions/submit-contact'; import { submitContactForm } from '@/actions/submit-contact';
import { TurnstileWidget } from './turnstile-widget'; import { TurnstileWidget } from './turnstile-widget';
import { useState } from 'react'; import { useState, useEffect } from 'react';
import { Button } from '@repo/ui/components/button'; import { Button } from '@repo/ui/components/button';
function SubmitButton() { function SubmitButton() {
const { pending } = useFormStatus(); const { pending } = useFormStatus();
return ( return (
<Button type="submit" disabled={pending}> <Button
type="submit"
disabled={pending}
size="lg"
className="w-full md:w-64 rounded-xl text-lg font-semibold"
>
{pending ? "Sending..." : "Send Message"} {pending ? "Sending..." : "Send Message"}
</Button> </Button>
); );
@@ -20,38 +24,84 @@ export function ContactForm() {
const [token, setToken] = useState<string>(''); const [token, setToken] = useState<string>('');
return ( return (
<form action={formAction} className="space-y-4 max-w-md mx-auto p-4 border rounded-lg bg-card text-card-foreground"> <form
<h3 className="text-xl font-bold">Contact Us</h3> action={formAction}
<div> className="space-y-8 w-full max-w-6xl mx-auto section-container"
<label htmlFor="name" className="block text-sm font-medium">Name</label> >
<input name="name" id="name" required className="w-full mt-1 p-2 border rounded bg-background" /> <div className="absolute top-0 right-0 w-64 h-64 bg-primary/5 rounded-full blur-3xl -mr-32 -mt-32" />
{state?.errors?.name && <p className="text-red-500 text-xs">{state.errors.name}</p>} <div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-500/5 rounded-full blur-3xl -ml-32 -mb-32" />
<div className="text-left mb-10 relative z-10">
<h3 className="text-3xl font-bold tracking-tight">Project Inquiry</h3>
<p className="text-muted-foreground text-lg mt-3 max-w-2xl text-balance">
Tell us about your technical challenges. From Medusa v2 migrations to real-time custom engines, we're ready to architect your solution.
</p>
</div> </div>
<div> <div className="grid md:grid-cols-2 gap-8 relative z-10">
<label htmlFor="email" className="block text-sm font-medium">Email</label> <div className="space-y-3">
<input name="email" id="email" type="email" required className="w-full mt-1 p-2 border rounded bg-background" /> <label htmlFor="name" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Full Name</label>
{state?.errors?.email && <p className="text-red-500 text-xs">{state.errors.email}</p>} <input
name="name"
id="name"
placeholder="Jamie Doe"
required
className="w-full p-4 border-2 border-border/50 rounded-2xl bg-background/50 focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg"
/>
{state?.errors?.name && <p className="text-red-500 text-sm px-1">{state.errors.name}</p>}
</div> </div>
<div> <div className="space-y-3">
<label htmlFor="message" className="block text-sm font-medium">Message</label> <label htmlFor="email" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Work Email</label>
<textarea name="message" id="message" required className="w-full mt-1 p-2 border rounded bg-background h-32" /> <input
{state?.errors?.message && <p className="text-red-500 text-xs">{state.errors.message}</p>} name="email"
id="email"
type="email"
placeholder="jamie@example.com"
required
className="w-full p-4 border-2 border-border/50 rounded-2xl bg-background/50 focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg"
/>
{state?.errors?.email && <p className="text-red-500 text-sm px-1">{state.errors.email}</p>}
</div>
</div>
<div className="space-y-3 relative z-10">
<label htmlFor="message" className="text-sm font-semibold tracking-wide uppercase text-muted-foreground ml-1">Project Details</label>
<textarea
name="message"
id="message"
placeholder="What are you building? Mention any specific constraints, timelines, or technologies..."
required
className="w-full p-6 border-2 border-border/50 rounded-2xl bg-background/50 h-56 resize-none focus:ring-4 focus:ring-primary/10 focus:border-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary transition-all text-lg leading-relaxed"
/>
{state?.errors?.message && <p className="text-red-500 text-sm px-1">{state.errors.message}</p>}
</div> </div>
<input type="hidden" name="token" value={token} /> <input type="hidden" name="token" value={token} />
<div className="my-4"> <div className="flex flex-col md:flex-row items-center justify-between gap-8 pt-4 relative z-10 border-t border-border/50">
<div className="scale-100 flex items-center gap-3">
<TurnstileWidget onVerify={setToken} /> <TurnstileWidget onVerify={setToken} />
<p className="text-[10px] text-muted-foreground max-w-[120px] leading-tight">
Protected by Cloudflare Turnstile to ensure human delivery.
</p>
</div>
<div className="w-full md:w-auto">
<SubmitButton />
</div>
</div> </div>
<SubmitButton />
{state?.message && ( {state?.message && (
<p className={`text-sm ${state.success ? 'text-green-500' : 'text-red-500'}`}> <div className={`mt-8 p-6 rounded-2xl text-base border-l-4 relative z-10 ${
{state.message} state.success
</p> ? "bg-emerald-500/10 border-emerald-500 text-emerald-700 dark:text-emerald-400"
: "bg-red-500/10 border-red-500 text-red-700 dark:text-red-400"
}`}>
<div className="flex items-center gap-3">
<span className="text-xl">{state.success ? "✓" : "⚠"}</span>
<p className="font-medium">{state.message}</p>
</div>
</div>
)} )}
</form> </form>
); );
@@ -2,33 +2,33 @@ import { Card } from "@repo/ui/components/card";
export function ServicesPreview() { export function ServicesPreview() {
return ( return (
<div className="w-full max-w-6xl mx-auto px-4 mt-8 mb-16"> <div className="w-full max-w-6xl mx-auto px-4 mt-8 mb-16 relative z-10">
<div className="text-left mb-8 px-2 border-l-2 border-emerald-500/50 pl-6"> <div className="text-left mb-8 px-2 border-l-2 border-emerald-500/50 section-container">
<h3 className="text-2xl font-bold mb-2">High-Performance Webshops</h3> <h3 className="text-3xl font-bold mb-3 tracking-tight">High-Performance Webshops</h3>
<p className="text-muted-foreground text-lg max-w-3xl"> <p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
We build commerce infrastructure that scales with you. From complex supply chains We build commerce infrastructure that scales with you. From complex supply chains
to global payments, we handle the technical heavy lifting so you can focus on your brand. to global payments, we handle the technical heavy lifting so you can focus on your brand.
</p> </p>
</div> </div>
<div className="grid md:grid-cols-3 gap-6"> <div className="grid md:grid-cols-3 gap-6">
<Card className="p-6 bg-card/50 backdrop-blur-sm border-indigo-500/20 hover:border-indigo-500/40 transition-colors text-left"> <Card className="glass-card hover:border-indigo-500/40 text-left">
<h3 className="text-xl font-bold mb-2">Native Crypto & Payments</h3> <h3 className="text-xl font-bold mb-3">Native Crypto & Payments</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
Deep expertise in multi-currency checkout, fiat-to-crypto onboarding, and Deep expertise in multi-currency checkout, fiat-to-crypto onboarding, and
DeFi integration. We build payments that work everywhere, for everyone. DeFi integration. We build payments that work everywhere, for everyone.
</p> </p>
</Card> </Card>
<Card className="p-6 bg-card/50 backdrop-blur-sm border-indigo-500/20 hover:border-indigo-500/40 transition-colors text-left"> <Card className="glass-card hover:border-indigo-500/40 text-left">
<h3 className="text-xl font-bold mb-2">Zero Capital Upfront</h3> <h3 className="text-xl font-bold mb-3">Zero Capital Upfront</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
We operate on a "Toast" style partnership model. No massive setup fees or We operate on a "Toast" style partnership model. No massive setup fees or
stiff monthly subscriptions—we only win when you're making sales. stiff monthly subscriptions—we only win when you're making sales.
</p> </p>
</Card> </Card>
<Card className="p-6 bg-card/50 backdrop-blur-sm border-indigo-500/20 hover:border-indigo-500/40 transition-colors text-left"> <Card className="glass-card hover:border-indigo-500/40 text-left">
<h3 className="text-xl font-bold mb-2">Headless Performance</h3> <h3 className="text-xl font-bold mb-3">Headless Performance</h3>
<p className="text-sm text-muted-foreground"> <p className="text-base text-muted-foreground leading-relaxed">
We couple the robust Medusa backend with a Next.js frontend, ensuring your shop We couple the robust Medusa backend with a Next.js frontend, ensuring your shop
scores 100/100 on Core Web Vitals and SEO. scores 100/100 on Core Web Vitals and SEO.
</p> </p>
+2 -1
View File
@@ -1,6 +1,7 @@
'use client'; 'use client';
import Link from "next/link"; import Link from "next/link";
import { SiteLogo } from "./site-logo";
import { FaXTwitter, FaTelegram, FaReddit, FaPhone } from "react-icons/fa6"; import { FaXTwitter, FaTelegram, FaReddit, FaPhone } from "react-icons/fa6";
export function Footer() { export function Footer() {
@@ -9,7 +10,7 @@ export function Footer() {
<div className="container px-4 md:px-6 mx-auto"> <div className="container px-4 md:px-6 mx-auto">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8"> <div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div className="space-y-4"> <div className="space-y-4">
<h3 className="text-lg font-bold">Such Software</h3> <SiteLogo />
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Full-service software provider in the heart of Mushroom Country. Full-service software provider in the heart of Mushroom Country.
</p> </p>
@@ -0,0 +1,29 @@
'use client';
import Image from "next/image";
import Link from "next/link";
export function SiteLogo() {
return (
<Link href="/" className="flex items-center gap-3 group">
<div className="relative w-52 h-14 lg:w-64 lg:h-16">
{/* Dark mode logo (white text) */}
<Image
src="/images/logo_dark_w_name_trans.png"
alt="Such Software"
fill
className="object-contain hidden dark:block"
priority
/>
{/* Light mode logo (black text) */}
<Image
src="/images/logo_light_w_name_trans.png"
alt="Such Software"
fill
className="object-contain block dark:hidden"
priority
/>
</div>
</Link>
);
}
@@ -1,67 +0,0 @@
import { Card } from "@repo/ui/components/card";
import Image from "next/image";
export const PortfolioGrid = () => {
return (
<section className="grid grid-cols-1 md:grid-cols-4 auto-rows-[300px] gap-4 max-w-7xl mx-auto p-4 z-10 relative">
{/* Hero Project: r/CryptoCurrency */}
<Card className="md:col-span-2 md:row-span-2 relative group overflow-hidden border-0 bg-slate-900/70 backdrop-blur-sm">
<div className="absolute inset-0 z-10 bg-gradient-to-t from-black/80 to-transparent p-6 flex flex-col justify-end">
<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>
<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 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="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 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 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 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>
);
};
@@ -5,29 +5,31 @@ export const PortfolioGrid = () => {
return ( return (
<section className="grid grid-cols-1 md:grid-cols-4 auto-rows-[300px] gap-6 w-full max-w-7xl mx-auto p-4 z-10 relative"> <section className="grid grid-cols-1 md:grid-cols-4 auto-rows-[300px] gap-6 w-full max-w-7xl mx-auto p-4 z-10 relative">
{/* Hero Project: r/CryptoCurrency */} {/* Hero Project: r/CryptoCurrency */}
<Card className="md:col-span-2 md:row-span-2 relative group overflow-hidden border-0 bg-slate-900/70 backdrop-blur-sm min-h-[300px]"> <Card className="md:col-span-2 md:row-span-2 glass-card group min-h-[300px] rounded-[2.5rem] !bg-slate-950/90">
<div className="absolute inset-0 z-20 bg-gradient-to-t from-black/90 via-black/20 to-transparent p-8 flex flex-col justify-end pointer-events-none"> <div className="absolute inset-0 z-20 bg-gradient-to-t from-black/95 via-black/20 to-transparent p-12 flex flex-col justify-end items-center text-center pointer-events-none pb-20">
<h3 className="text-3xl font-bold text-white shadow-black drop-shadow-xl">r/CryptoCurrency</h3> <h3 className="text-4xl font-bold text-white shadow-black drop-shadow-xl tracking-tight">r/CryptoCurrency</h3>
<p className="text-gray-200 shadow-black drop-shadow-md max-w-sm mt-2 font-medium">Governance dashboard for 10M+ members. Real-time DAO metrics.</p> <p className="text-gray-200 shadow-black drop-shadow-md max-w-md mt-3 font-medium text-lg leading-relaxed">
Governance dashboard for 10M+ members. Real-time DAO metrics.
</p>
</div> </div>
<div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-slate-800/50"> <div className="absolute inset-x-10 top-10 bottom-48 rounded-2xl overflow-hidden z-10 bg-slate-800/30">
<Image <Image
src="/images/r-cryptocurrency.png" src="/images/r-cryptocurrency.png"
alt="r/CryptoCurrency" alt="r/CryptoCurrency"
fill fill
className="object-contain p-4 opacity-70 transition-transform duration-1000 group-hover:scale-105" className="object-contain p-8 opacity-80 transition-transform duration-1000 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 50vw" sizes="(max-width: 768px) 100vw, 50vw"
/> />
</div> </div>
</Card> </Card>
{/* Secondary: Occupy Wallets */} {/* Secondary: Occupy Wallets */}
<Card className="md:col-span-2 relative group overflow-hidden bg-zinc-100/70 backdrop-blur-sm border-0 min-h-[300px]"> <Card className="md:col-span-2 glass-card group min-h-[300px] rounded-[2.5rem]">
<div className="absolute inset-0 z-20 p-8 flex flex-col justify-center items-center text-center pointer-events-none"> <div className="absolute inset-0 z-20 p-8 flex flex-col justify-center items-center text-center pointer-events-none">
<h3 className="text-2xl font-bold text-zinc-900 drop-shadow-sm bg-white/60 backdrop-blur-md rounded-xl px-6 py-2">Occupy Wallets</h3> <h3 className="text-2xl font-bold text-zinc-900 dark:text-white drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-6 py-2">Occupy Wallets</h3>
<p className="text-zinc-900 font-bold drop-shadow-sm bg-white/60 backdrop-blur-md rounded-xl px-6 py-2 mt-4">High-fidelity gallery for fiscal graffiti.</p> <p className="text-zinc-900 dark:text-zinc-200 font-bold drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-6 py-2 mt-4">High-fidelity gallery for fiscal graffiti.</p>
</div> </div>
<div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-zinc-200/50"> <div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-zinc-200/50 dark:bg-zinc-800/50">
<Image <Image
src="/images/occupy-wallets.png" src="/images/occupy-wallets.png"
alt="Occupy Wallets" alt="Occupy Wallets"
@@ -39,7 +41,7 @@ export const PortfolioGrid = () => {
</Card> </Card>
{/* Tertiary: Monerogue & Wowne.ro (Grouped) */} {/* 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 min-h-[300px]"> <Card className="md:col-span-1 glass-card group min-h-[300px] rounded-[2.5rem] !bg-orange-50/95 dark:!bg-orange-950/95 !border-orange-500/20">
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none"> <div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
<h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Privacy Labs</h3> <h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Privacy Labs</h3>
<ul className="mt-2 space-y-1 text-sm text-foreground font-semibold"> <ul className="mt-2 space-y-1 text-sm text-foreground font-semibold">
@@ -59,7 +61,7 @@ export const PortfolioGrid = () => {
</Card> </Card>
{/* Quaternary: MoonPlace */} {/* Quaternary: MoonPlace */}
<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 min-h-[300px]"> <Card className="md:col-span-1 glass-card group min-h-[300px] rounded-[2.5rem] !bg-indigo-50/95 dark:!bg-indigo-950/95 !border-indigo-500/20">
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none"> <div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
<h3 className="text-xl font-bold text-indigo-700 dark:text-indigo-400">MoonPlace</h3> <h3 className="text-xl font-bold text-indigo-700 dark:text-indigo-400">MoonPlace</h3>
<p className="text-sm mt-1 text-foreground font-semibold">Collaborative pixel canvas engine.</p> <p className="text-sm mt-1 text-foreground font-semibold">Collaborative pixel canvas engine.</p>
+4 -4
View File
@@ -18,13 +18,13 @@ const config: Config = {
}, },
extend: { extend: {
colors: { colors: {
border: "hsl(var(--border))", border: "hsl(var(--border) / <alpha-value>)",
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
background: "hsl(var(--background))", background: "hsl(var(--background) / <alpha-value>)",
foreground: "hsl(var(--foreground))", foreground: "hsl(var(--foreground))",
primary: { primary: {
DEFAULT: "hsl(var(--primary))", DEFAULT: "hsl(var(--primary) / <alpha-value>)",
foreground: "hsl(var(--primary-foreground))", foreground: "hsl(var(--primary-foreground))",
}, },
secondary: { secondary: {
@@ -48,7 +48,7 @@ const config: Config = {
foreground: "hsl(var(--popover-foreground))", foreground: "hsl(var(--popover-foreground))",
}, },
card: { card: {
DEFAULT: "hsl(var(--card))", DEFAULT: "hsl(var(--card) / <alpha-value>)",
foreground: "hsl(var(--card-foreground))", foreground: "hsl(var(--card-foreground))",
}, },
}, },
@@ -1,151 +0,0 @@
'use client';
import { motion, useAnimation } from "framer-motion";
import { useEffect, useState, useCallback } from "react";
import { cn } from "../lib/utils";
import { Monitor } from "lucide-react";
interface NebulaProps {
className?: string;
density?: number;
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
const targetY = 20; // moved up
useEffect(() => {
setMounted(true);
const newPaths = Array.from({ length: density }).map(() => {
// Determine random edge start point
const edge = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left
let startX = 50, startY = 50;
// Push start point well outside viewbox to ensure full coverage
switch(edge) {
case 0: startX = Math.random() * 100; startY = -20; break; // Top
case 1: startX = 120; startY = Math.random() * 100; break; // Right
case 2: startX = Math.random() * 100; startY = 120; break; // Bottom
case 3: startX = -20; startY = Math.random() * 100; break; // Left
}
// End at target point with small variance
const endX = targetX + (Math.random() - 0.5) * 5;
const endY = targetY + (Math.random() - 0.5) * 5;
// Control point for curve
const cpX = (startX + endX) / 2 + (Math.random() - 0.5) * 50;
const cpY = (startY + endY) / 2 + (Math.random() - 0.5) * 50;
return `M ${startX} ${startY} Q ${cpX} ${cpY} ${endX} ${endY}`;
});
setPaths(newPaths);
}, [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;
return (
<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 */}
<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}
className={cn(
"transition-colors duration-1000",
themeMode === 'light' ? "text-slate-400" : "text-emerald-500"
)}
/>
</motion.div>
<svg
viewBox="0 0 100 100"
preserveAspectRatio="none"
className="w-full h-full opacity-60 dark:opacity-80 absolute inset-0"
>
{paths.map((d, i) => (
<StreamLine
key={i}
d={d}
themeMode={themeMode}
onHit={handleHit}
width={themeMode === 'light' ? 0.1 : 0.2}
/>
))}
</svg>
</div>
);
};
File diff suppressed because it is too large Load Diff