services: split the four pillars into dedicated subpages

/services becomes a hub (hero, four linked pillar cards, philosophy, recent work, FAQ,
CTA) and each pillar gets its own statically-generated page at /services/[pillar]
(commerce/games/crypto/education) with distinct SEO title+description+canonical, its own
H1, the products behind it, a pillar-scoped FAQ + FAQ JSON-LD, breadcrumb JSON-LD, and
'the other pillars' cross-links. dynamicParams=false so unknown slugs are a static 404.

The pillar/proof/FAQ data + colour maps move to lib/services.ts (single source of truth),
ProofCard/PillarCards/ServicesCta/EducationCredentials to components/services, so the hub,
subpages, home 'What We Do' preview, and sitemap can't drift. Home preview cards now link
to their pillar; sitemap gains the four subpage URLs. All pillar copy kept verbatim.

Verified: 4 subpages prebuilt, distinct titles/canonicals, right products per page,
click-through nav, education credentials block, bad slug -> 404.
This commit is contained in:
jwinterm
2026-07-12 13:47:19 -04:00
parent 20e0962fbc
commit 1e5719a1da
9 changed files with 471 additions and 308 deletions
+113
View File
@@ -0,0 +1,113 @@
import { DataGrid } from "@repo/ui/procedural/data-grid";
import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { ScrollReveal } from "@/components/scroll-reveal";
import Link from "next/link";
import { notFound } from "next/navigation";
import type { Metadata } from "next";
import { JsonLd, breadcrumbLd, faqLd } from "@/components/seo/json-ld";
import { ProofCard } from "@/components/services/proof-card";
import { PillarCards } from "@/components/services/pillar-cards";
import { ServicesCta } from "@/components/services/services-cta";
import { EducationCredentials } from "@/components/services/education-credentials";
import { pillars, pillarBySlug, proofFor, faqs, borderL } from "@/lib/services";
type Props = { params: Promise<{ pillar: string }> };
// Prebuild the four pillar pages at build time (static); anything else is a static 404
// rather than an on-demand render.
export const dynamicParams = false;
export function generateStaticParams() {
return pillars.map((p) => ({ pillar: p.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { pillar: slug } = await params;
const pillar = pillarBySlug(slug);
if (!pillar) return {};
return {
title: pillar.metaTitle,
description: pillar.metaDescription,
alternates: { canonical: `/services/${pillar.slug}` },
openGraph: { title: pillar.metaTitle, description: pillar.metaDescription, type: "website" },
};
}
export default async function PillarPage({ params }: Props) {
const { pillar: slug } = await params;
const pillar = pillarBySlug(slug);
if (!pillar) notFound();
const proof = proofFor(pillar.slug);
const pillarFaqs = faqs.filter((f) => f.pillars.includes(pillar.slug));
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground overflow-hidden">
<JsonLd
data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Services", path: "/services" },
{ name: pillar.label, path: `/services/${pillar.slug}` },
])}
/>
{pillarFaqs.length > 0 && <JsonLd data={faqLd(pillarFaqs.map(({ q, a }) => ({ q, a })))} />}
<DataGrid />
<Header />
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
{/* Breadcrumb */}
<nav aria-label="Breadcrumb" className="mb-8 text-sm text-muted-foreground">
<Link href="/services" className="hover:text-foreground transition-colors">Services</Link>
<span className="mx-2" aria-hidden="true">/</span>
<span className="text-foreground">{pillar.label}</span>
</nav>
{/* Pillar header */}
<ScrollReveal className="mb-14">
<div className={`text-left section-container border-l-4 ${borderL[pillar.accent]}`}>
<h1 className="text-4xl md:text-5xl font-bold mb-4">{pillar.title}</h1>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">{pillar.lead}</p>
</div>
</ScrollReveal>
{/* The products behind this pillar */}
<ScrollReveal className="mb-16">
<div className="grid md:grid-cols-2 gap-6">
{proof.map((p) => (
<ProofCard key={p.title} p={p} accent={pillar.accent} />
))}
</div>
{pillar.slug === "education" && <EducationCredentials />}
</ScrollReveal>
{/* Pillar-specific FAQ */}
{pillarFaqs.length > 0 && (
<ScrollReveal className="mb-16">
<div className="section-container border-l-4 border-l-indigo-500">
<h2 className="text-2xl font-bold mb-6 tracking-tight">Questions</h2>
<div className="space-y-6">
{pillarFaqs.map((f) => (
<div key={f.q}>
<h3 className="text-lg font-bold mb-1">{f.q}</h3>
<p className="text-muted-foreground leading-relaxed max-w-3xl">{f.a}</p>
</div>
))}
</div>
</div>
</ScrollReveal>
)}
{/* Explore the other pillars */}
<ScrollReveal className="mb-20">
<PillarCards exclude={pillar.slug} heading="The other pillars" />
</ScrollReveal>
{/* CTA */}
<ScrollReveal>
<ServicesCta />
</ScrollReveal>
</div>
<MobileNav />
</main>
);
}
+21 -268
View File
@@ -1,241 +1,52 @@
import { DataGrid } from "@repo/ui/procedural/data-grid"; import { DataGrid } from "@repo/ui/procedural/data-grid";
import { Button } from "@repo/ui/components/button";
import { Header } from "@/components/layout/header"; import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav"; import { MobileNav } from "@/components/layout/mobile-nav";
import { ScrollReveal } from "@/components/scroll-reveal"; import { ScrollReveal } from "@/components/scroll-reveal";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link";
import { Card } from "@repo/ui/components/card";
import { JsonLd, faqLd } from "@/components/seo/json-ld"; import { JsonLd, faqLd } from "@/components/seo/json-ld";
import { PillarCards } from "@/components/services/pillar-cards";
import { ServicesCta } from "@/components/services/services-cta";
import { faqs } from "@/lib/services";
import { Card } from "@repo/ui/components/card";
import type { Metadata } from "next"; import type { Metadata } from "next";
const faqs = [
{ q: "What does Such Software build?", a: "Four things, from one small team: custom e-commerce and web apps on revenue share, games and mobile apps we ship ourselves, cryptocurrency and payments software (integration only, never custody), and PhD-led radiation-science education. The pillars below break each down with the products behind them." },
{ q: "Do you offer nuclear and radiation education?", a: "Yes. Our founder holds a PhD in radiation-detector materials and spent six years at Lawrence Livermore National Laboratory, plus shielding and spent-fuel work at Holtec. We teach radiation-science fundamentals and Monte Carlo methods, run GEANT4 training, and speak on these topics. Our GEANT4 radiation labs are published open-source (MIT / CC-BY). This is education and published methods." },
{ q: "Where are you based?", a: "We are a software studio in Kennett Square, Pennsylvania, working with clients remotely across the US and beyond." },
{ q: "Do you build for accessibility?", a: "Yes. We engineer front-ends to meet WCAG 2.2 AA and audit against it, alongside Core Web Vitals, as part of every project." },
{ q: "How does a project start?", a: "Reach out through the contact form with what you need. We scope it, propose an approach, and from there it can run as a fixed project or a performance-based revenue-share partnership." },
{ q: "Do you take revenue-share work?", a: "Yes. For a limited number of partners we build and operate products (such as webshops) for a share of revenue instead of a large upfront fee, so our incentives line up with yours." },
];
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Web, Games, Crypto & Nuclear-Science Education | Such Software", title: "Web, Games, Crypto & Nuclear-Science Education | Such Software",
description: "Four pillars from one studio: custom e-commerce and web apps on revenue share, games and mobile apps, crypto and payments software (non-custodial), and PhD-led radiation-science education and GEANT4 training. Kennett Square, PA.", description:
alternates: { "Four pillars from one studio: custom e-commerce and web apps on revenue share, games and mobile apps, crypto and payments software (non-custodial), and PhD-led radiation-science education and GEANT4 training. Kennett Square, PA.",
canonical: "/services", alternates: { canonical: "/services" },
},
openGraph: { openGraph: {
title: "Web, Games, Crypto & Nuclear-Science Education | Such Software", title: "Web, Games, Crypto & Nuclear-Science Education | Such Software",
description: "Four pillars from one studio: custom e-commerce and web apps on revenue share, games and mobile apps, crypto and payments software (non-custodial), and PhD-led radiation-science education and GEANT4 training.", description:
"Four pillars from one studio: custom e-commerce and web apps on revenue share, games and mobile apps, crypto and payments software (non-custodial), and PhD-led radiation-science education and GEANT4 training.",
type: "website", type: "website",
}, },
}; };
// ---- Pillar model -------------------------------------------------------
type Pillar = {
id: string;
label: string;
accent: string; // tailwind colour stem, e.g. "blue"
title: string;
lead: string;
};
const pillars: Pillar[] = [
{
id: "commerce",
label: "Commerce & Web",
accent: "blue",
title: "Commerce & custom web",
lead: "Proprietary, headless commerce and web apps that you own, built to your business logic instead of a template's limits. Often on revenue share: we build, host, and run it, and earn a share once you are live.",
},
{
id: "games",
label: "Games & Apps",
accent: "purple",
title: "Games & apps",
lead: "We do not just consult on software, we ship it. A growing catalog of games and mobile apps built in-house across iOS, Android, web, and desktop, on Godot and Next.js.",
},
{
id: "crypto",
label: "Crypto & Payments",
accent: "cyan",
title: "Crypto & payments",
lead: "Software and integration for payments and cryptocurrency. The client, a licensed processor, or the user's own wallet holds the funds; we build the system around it and never take custody.",
},
{
id: "education",
label: "Education & Science",
accent: "amber",
title: "Education & science",
lead: "PhD-led radiation-science teaching, training, and speaking from years at a national lab, plus learning-through-play games that teach real science and math. We publish our methods open-source.",
},
];
// Border-left colour per accent (literal strings so Tailwind's JIT keeps them).
const borderL: Record<string, string> = {
blue: "border-l-blue-500",
purple: "border-l-purple-500",
cyan: "border-l-cyan-500",
amber: "border-l-amber-500",
};
const chipAccent: Record<string, string> = {
blue: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
purple: "bg-purple-500/15 text-purple-700 dark:text-purple-300",
cyan: "bg-cyan-500/15 text-cyan-700 dark:text-cyan-300",
amber: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
};
const hoverBorder: Record<string, string> = {
blue: "hover:border-blue-500/30",
purple: "hover:border-purple-500/30",
cyan: "hover:border-cyan-500/30",
amber: "hover:border-amber-500/30",
};
// ---- Proof items (real products behind each pillar) ---------------------
type Proof = {
title: string;
blurb: string;
img?: string; // /images/products/*
href?: string; // internal detail page or external
tag?: string; // status pill: "Live", "Open source", "Beta", "Early", "Flagship"
};
const commerceProof: Proof[] = [
{ title: "Webshops", blurb: "Our multi-tenant commerce platform: fully-themed storefronts, print-on-demand, non-custodial crypto checkout, and automated tax, on revenue share.", img: "/images/products/occupy-wallets.png", href: "/products/webshops", tag: "Flagship" },
{ title: "Custom builds", blurb: "Real-time web apps, multiplayer experiences, and live dashboards on Next.js, Socket.io, and WebRTC.", img: "/images/products/custom-websites.svg", href: "/products/custom-websites", tag: "Live" },
{ title: "Grin BTCPay plugin", blurb: "An open-source BTCPay Server plugin we built to accept Grin (Mimblewimble) payments, including auto-Tor invoice exchange.", tag: "Open source" },
{ title: "Evaluetron", blurb: "A self-hostable AI-compute platform: one core that runs a personal image bot or a paid multi-customer service, with an append-only credit ledger and pricing built in.", tag: "Building" },
];
const gamesProof: Proof[] = [
{ title: "Suchoice", blurb: "Stop overthinking decisions: snap a photo, let AI read the options, spin the wheel. iOS and Android.", img: "/images/products/suchoice.png", href: "/products/suchoice", tag: "Live" },
{ title: "Vegan IQ", blurb: "1,000+ plant-based trivia questions across 8 categories, with a daily challenge and weekly quiz. iOS and Android.", img: "/images/products/vegan-iq.png", href: "/products/vegan-iq", tag: "Live" },
{ title: "Bloomword", blurb: "A daily word game where every word you find grows a living 3D typographic tree. Free at bloomword.earth, launching on mobile.", img: "/images/products/bloomword.svg", href: "/products/bloomword", tag: "Live" },
{ title: "Bauhaus Echo", blurb: "A visual memory puzzle game inspired by Bauhaus design. Android, iOS, and web.", img: "/images/products/bauhaus-echo.png", href: "/products/bauhaus-echo", tag: "Live" },
{ title: "Such Moon Launch", blurb: "Vry rocket, much landing, wow: a Wownero-themed pixel rocket arcade with 11 levels and tilt-to-steer. Everywhere.", img: "/images/products/such-moon-launch.png", href: "/products/such-moon-launch", tag: "Live" },
];
const cryptoProof: Proof[] = [
{ title: "Payments consulting", blurb: "Stripe and multi-processor integrations, reconciliation tooling, Monero and on-chain integration. The processor or wallet moves the money; we wire it in.", tag: "Service" },
{ title: "Smirk Wallet", blurb: "A non-custodial multi-asset browser wallet: send, receive, and tip by username on Telegram and Discord. BTC, LTC, XMR, WOW, GRIN.", img: "/images/products/smirk-wallet.png", href: "/products/smirk-wallet", tag: "Live" },
{ title: "Hash Bags", blurb: "A mobile multi-chain wallet forked from Cake Wallet, tuned for the privacy-coin ecosystem.", tag: "Beta" },
{ title: "Evaluetron", blurb: "Pay-for-compute done right: an append-only credit ledger, a job queue, and an evaluator that lets on-demand work preempt opportunistic work. Customers can never self-credit.", tag: "Building" },
];
const educationProof: Proof[] = [
{ title: "Barns & Neutrons", blurb: "A cozy exploration-puzzle game across the real Table of Nuclides: capture neutrons, coax decays, and learn honest nuclear physics. Our premiere visual work.", img: "/images/products/barns-and-neutrons.svg", href: "/products/barns-and-neutrons", tag: "Coming soon" },
{ title: "Nuclear consulting & training", blurb: "PhD-led radiation-science teaching: Monte Carlo methods with GEANT4, shielding and dose fundamentals, detector physics, and open-source labs.", href: "https://jwinterm.github.io/geant4-radiation-labs/", tag: "Service" },
{ title: "Numchangers", blurb: "A kids math game (ages 5-8) where numbers morph into dots, shapes, and 3D characters, so arithmetic becomes a visible action instead of a quiz. Godot, mobile-first.", tag: "Early" },
];
const tagStyle: Record<string, string> = {
Flagship: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
Live: "bg-green-500/15 text-green-700 dark:text-green-300",
Service: "bg-sky-500/15 text-sky-700 dark:text-sky-300",
"Open source": "bg-indigo-500/15 text-indigo-700 dark:text-indigo-300",
Beta: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
Building: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
Early: "bg-fuchsia-500/15 text-fuchsia-700 dark:text-fuchsia-300",
"Coming soon": "bg-fuchsia-500/15 text-fuchsia-700 dark:text-fuchsia-300",
};
function ProofCard({ p, accent }: { p: Proof; accent: string }) {
const inner = (
<Card className={`glass-card h-full !p-6 transition-colors ${hoverBorder[accent]}`}>
<div className="flex items-start gap-4">
{p.img ? (
<Image
src={p.img}
alt={p.title}
width={48}
height={48}
className="rounded-xl flex-shrink-0 h-12 w-12 object-cover bg-white/5"
/>
) : (
<div
aria-hidden="true"
className={`flex-shrink-0 h-12 w-12 rounded-xl flex items-center justify-center text-lg font-bold ${chipAccent[accent]}`}
>
{p.title.charAt(0)}
</div>
)}
<div className="min-w-0">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<h3 className="text-lg font-bold text-foreground">{p.title}</h3>
{p.tag && (
<span className={`text-[11px] font-medium px-2 py-0.5 rounded-full ${tagStyle[p.tag] ?? chipAccent[accent]}`}>
{p.tag}
</span>
)}
</div>
<p className="text-sm text-muted-foreground leading-relaxed">{p.blurb}</p>
{p.href && (
<span className="mt-2 inline-block text-sm font-medium text-foreground group-hover:underline">
Learn more →
</span>
)}
</div>
</div>
</Card>
);
if (!p.href) return <div>{inner}</div>;
const external = p.href.startsWith("http");
return external ? (
<a href={p.href} target="_blank" rel="noopener noreferrer" className="group block h-full">
{inner}
</a>
) : (
<Link href={p.href} className="group block h-full">
{inner}
</Link>
);
}
function proofFor(id: string): Proof[] {
if (id === "commerce") return commerceProof;
if (id === "games") return gamesProof;
if (id === "crypto") return cryptoProof;
return educationProof;
}
export default function ServicesPage() { export default function ServicesPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground overflow-hidden"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground overflow-hidden">
<JsonLd data={faqLd(faqs)} /> <JsonLd data={faqLd(faqs.map(({ q, a }) => ({ q, a })))} />
<DataGrid /> <DataGrid />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4"> <div id="main-content" className="z-10 w-full max-w-5xl mx-auto py-20 px-4">
{/* Page Header */} {/* Page header */}
<div className="text-center mb-12"> <div className="text-center mb-12">
<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">
Four things, done well Four things, done well
</h1> </h1>
<p className="text-xl text-muted-foreground max-w-2xl mx-auto"> <p className="text-xl text-muted-foreground max-w-2xl mx-auto">
Commerce and custom web, games and apps, crypto and payments, and Commerce and custom web, games and apps, crypto and payments, and
PhD-led radiation-science education. Each pillar comes with the PhD-led radiation-science education. Pick a pillar to see the products
products behind it. behind it.
</p> </p>
</div> </div>
{/* Pillar sub-nav: jump links so the page reads as four clear areas */} {/* The four pillars, each its own page */}
<nav aria-label="Service pillars" className="mb-16"> <ScrollReveal className="mb-20">
<ul className="flex flex-wrap justify-center gap-2 sm:gap-3"> <PillarCards />
{pillars.map((p) => ( </ScrollReveal>
<li key={p.id}>
<a
href={`#${p.id}`}
className="inline-block px-4 py-2 rounded-full text-sm font-medium mask-backdrop border border-white/10 text-foreground hover:border-white/30 transition-colors"
>
{p.label}
</a>
</li>
))}
</ul>
</nav>
{/* Philosophy */} {/* Philosophy */}
<ScrollReveal className="mb-20"> <ScrollReveal className="mb-20">
@@ -248,55 +59,7 @@ export default function ServicesPage() {
</div> </div>
</ScrollReveal> </ScrollReveal>
{/* Pillars */} {/* Recent work */}
{pillars.map((pillar) => (
<section
key={pillar.id}
id={pillar.id}
aria-labelledby={`${pillar.id}-heading`}
className="mb-24 scroll-mt-24"
>
<ScrollReveal>
<div className={`text-left mb-10 section-container border-l-4 ${borderL[pillar.accent]}`}>
<h2 id={`${pillar.id}-heading`} className="text-3xl font-bold mb-2">
{pillar.title}
</h2>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
{pillar.lead}
</p>
</div>
</ScrollReveal>
<ScrollReveal>
<div className="grid md:grid-cols-2 gap-6">
{proofFor(pillar.id).map((p) => (
<ProofCard key={p.title} p={p} accent={pillar.accent} />
))}
</div>
</ScrollReveal>
{/* Education pillar carries the founder credentials inline */}
{pillar.id === "education" && (
<ScrollReveal>
<Card className="glass-card border-amber-400/20 mt-6 !p-8">
<h3 className="text-lg font-bold mb-4">Why us, on the science</h3>
<ul className="space-y-2 text-base text-muted-foreground leading-relaxed">
<li><span className="font-semibold text-foreground">PhD in radiation-detector materials</span>, UT-Dallas (2014)</li>
<li><span className="font-semibold text-foreground">Lawrence Livermore National Laboratory</span> (2015-2021): detector systems, radioisotope battery program, MCNP5 publications</li>
<li><span className="font-semibold text-foreground">Holtec International</span>: radiation shielding and spent-fuel-to-dry-cask optimization</li>
<li><span className="font-semibold text-foreground">College-level instructor</span>: author of open-source GEANT4 radiation labs</li>
</ul>
<p className="text-xs text-muted-foreground/70 mt-5">
This is education and published open-source methods, not for-hire controlled
modeling. We are not a licensed professional-engineering (PE) firm and provide no
stamped engineering deliverables.
</p>
</Card>
</ScrollReveal>
)}
</section>
))}
{/* Recent Work */}
<ScrollReveal className="mb-24"> <ScrollReveal className="mb-24">
<div className="text-left mb-10 section-container border-l-4 border-l-amber-500"> <div className="text-left mb-10 section-container border-l-4 border-l-amber-500">
<h2 className="text-3xl font-bold mb-2">Recent work</h2> <h2 className="text-3xl font-bold mb-2">Recent work</h2>
@@ -332,7 +95,7 @@ export default function ServicesPage() {
</ScrollReveal> </ScrollReveal>
{/* FAQ */} {/* FAQ */}
<ScrollReveal className="mt-20"> <ScrollReveal className="mb-20">
<div className="section-container border-l-4 border-l-indigo-500"> <div className="section-container border-l-4 border-l-indigo-500">
<h2 className="text-3xl font-bold mb-8 tracking-tight">Frequently asked questions</h2> <h2 className="text-3xl font-bold mb-8 tracking-tight">Frequently asked questions</h2>
<div className="space-y-6"> <div className="space-y-6">
@@ -347,18 +110,8 @@ export default function ServicesPage() {
</ScrollReveal> </ScrollReveal>
{/* CTA */} {/* CTA */}
<ScrollReveal 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"> <ScrollReveal>
<div className="absolute inset-0 bg-blue-500/10 opacity-0 group-hover:opacity-100 transition-opacity" /> <ServicesCta />
<h2 className="text-4xl font-bold mb-6 relative z-10 tracking-tight">Let&apos;s build together</h2>
<p className="mb-10 text-slate-300 text-lg max-w-2xl mx-auto relative z-10 leading-relaxed font-medium">
We take on a limited number of revenue-share build partners. Tell us what you&apos;re
building and we&apos;ll run a scope review.
</p>
<Link href="/contact" className="relative z-10">
<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 Consultation
</Button>
</Link>
</ScrollReveal> </ScrollReveal>
</div> </div>
<MobileNav /> <MobileNav />
+4
View File
@@ -29,6 +29,10 @@ export default function sitemap(): MetadataRoute.Sitemap {
{ url: `${base}/products/meatspace`, lastModified: updated, changeFrequency: "weekly", priority: 0.7 }, { url: `${base}/products/meatspace`, lastModified: updated, changeFrequency: "weekly", priority: 0.7 },
{ url: `${base}/products/mushroom-capital`, lastModified: updated, changeFrequency: "weekly", priority: 0.7 }, { url: `${base}/products/mushroom-capital`, lastModified: updated, changeFrequency: "weekly", priority: 0.7 },
{ url: `${base}/services`, lastModified: updated, changeFrequency: "monthly", priority: 0.9 }, { url: `${base}/services`, lastModified: updated, changeFrequency: "monthly", priority: 0.9 },
{ url: `${base}/services/commerce`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/services/games`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/services/crypto`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/services/education`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/about`, lastModified: updated, changeFrequency: "monthly", priority: 0.7 }, { url: `${base}/about`, lastModified: updated, changeFrequency: "monthly", priority: 0.7 },
{ url: `${base}/support`, lastModified: updated, changeFrequency: "monthly", priority: 0.5 }, { url: `${base}/support`, lastModified: updated, changeFrequency: "monthly", priority: 0.5 },
{ url: `${base}/privacy`, lastModified: updated, changeFrequency: "yearly", priority: 0.3 }, { url: `${base}/privacy`, lastModified: updated, changeFrequency: "yearly", priority: 0.3 },
@@ -1,49 +1,54 @@
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import Link from "next/link";
// The four "What We Do" cards on the home page, each linking to its /services/<slug> pillar page.
const previews = [
{
href: "/services/commerce",
title: "E-Commerce & Hosting",
body: "Headless storefronts on Medusa and Next.js, built fast and hosted on our own infrastructure. Stripe Connect with a revenue-share model: you stay the merchant of record, and crypto settles straight to your own wallets.",
},
{
href: "/services/games",
title: "Custom App Development",
body: "Cross-platform mobile and web apps built to spec. We handle architecture, deployment, and ongoing support so you can focus on your business.",
},
{
href: "/services/crypto",
title: "Crypto & Payment Integration",
body: "Software and integration for multi-currency checkout and non-custodial crypto payments, in production since 2014. You or a licensed provider hold the funds; we never take custody.",
},
{
href: "/services/education",
title: "Nuclear & Radiation Education",
body: "Training, talks, and open-source GEANT4 labs on radiation transport and semiconductor detectors, led by a PhD with a Lawrence Livermore background. Published methods and teaching.",
},
];
export function ServicesPreview() { export function ServicesPreview() {
return ( return (
<div className="w-full max-w-6xl mx-auto px-4 mt-8 mb-16 relative z-10"> <div className="w-full max-w-6xl mx-auto px-4 mt-8 mb-16 relative z-10">
<div className="text-left mb-8 section-container border-l-4 border-l-emerald-500"> <div className="text-left mb-8 section-container border-l-4 border-l-emerald-500">
<h2 className="text-3xl font-bold mb-3 tracking-tight">What We Do</h2> <h2 className="text-3xl font-bold mb-3 tracking-tight">What We Do</h2>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed"> <p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
We build and host custom e-commerce stores, develop apps to spec, and ship our own We build and host custom e-commerce stores, develop apps to spec, and ship our own
titles and games. We handle your project end to end, from architecture to deployment. titles and games. We handle your project end to end, from architecture to deployment.
</p> </p>
</div> </div>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6"> <div className="grid md:grid-cols-2 lg:grid-cols-4 gap-6">
<Card className="glass-card hover:border-indigo-500/40 text-left"> {previews.map((p) => (
<h3 className="text-xl font-bold mb-3">E-Commerce &amp; Hosting</h3> <Link key={p.href} href={p.href} className="group block h-full">
<p className="text-base text-muted-foreground leading-relaxed"> <Card className="glass-card h-full hover:border-indigo-500/40 text-left transition-colors">
Headless storefronts on Medusa and Next.js, built fast and hosted on our own <h3 className="text-xl font-bold mb-3">{p.title}</h3>
infrastructure. Stripe Connect with a revenue-share model: you stay the merchant <p className="text-base text-muted-foreground leading-relaxed">{p.body}</p>
of record, and crypto settles straight to your own wallets. <span className="mt-3 inline-block text-sm font-medium text-foreground group-hover:underline">
</p> Learn more →
</span>
</Card> </Card>
<Card className="glass-card hover:border-indigo-500/40 text-left"> </Link>
<h3 className="text-xl font-bold mb-3">Custom App Development</h3> ))}
<p className="text-base text-muted-foreground leading-relaxed"> </div>
Cross-platform mobile and web apps built to spec. We handle architecture,
deployment, and ongoing support so you can focus on your business.
</p>
</Card>
<Card className="glass-card hover:border-indigo-500/40 text-left">
<h3 className="text-xl font-bold mb-3">Crypto &amp; Payment Integration</h3>
<p className="text-base text-muted-foreground leading-relaxed">
Software and integration for multi-currency checkout and non-custodial crypto
payments, in production since 2014. You or a licensed provider hold the funds;
we never take custody.
</p>
</Card>
<Card className="glass-card hover:border-indigo-500/40 text-left">
<h3 className="text-xl font-bold mb-3">Nuclear &amp; Radiation Education</h3>
<p className="text-base text-muted-foreground leading-relaxed">
Training, talks, and open-source GEANT4 labs on radiation transport and
semiconductor detectors, led by a PhD with a Lawrence Livermore background.
Published methods and teaching.
</p>
</Card>
</div>
</div> </div>
) );
} }
@@ -0,0 +1,21 @@
import { Card } from "@repo/ui/components/card";
// Founder science credentials, shown on the Education & Science pillar page.
export function EducationCredentials() {
return (
<Card className="glass-card border-amber-400/20 mt-6 !p-8">
<h3 className="text-lg font-bold mb-4">Why us, on the science</h3>
<ul className="space-y-2 text-base text-muted-foreground leading-relaxed">
<li><span className="font-semibold text-foreground">PhD in radiation-detector materials</span>, UT-Dallas (2014)</li>
<li><span className="font-semibold text-foreground">Lawrence Livermore National Laboratory</span> (2015-2021): detector systems, radioisotope battery program, MCNP5 publications</li>
<li><span className="font-semibold text-foreground">Holtec International</span>: radiation shielding and spent-fuel-to-dry-cask optimization</li>
<li><span className="font-semibold text-foreground">College-level instructor</span>: author of open-source GEANT4 radiation labs</li>
</ul>
<p className="text-xs text-muted-foreground/70 mt-5">
This is education and published open-source methods, not for-hire controlled
modeling. We are not a licensed professional-engineering (PE) firm and provide no
stamped engineering deliverables.
</p>
</Card>
);
}
@@ -0,0 +1,27 @@
import { Card } from "@repo/ui/components/card";
import Link from "next/link";
import { pillars, borderL, hoverBorder } from "@/lib/services";
// The four pillar cards, each linking to its /services/<slug> subpage. Used as the hub's
// primary nav and, with `exclude`, as the "explore the other pillars" block on a subpage.
export function PillarCards({ exclude, heading }: { exclude?: string; heading?: string }) {
const shown = pillars.filter((p) => p.slug !== exclude);
return (
<div>
{heading && <h2 className="text-2xl font-bold mb-6">{heading}</h2>}
<div className="grid sm:grid-cols-2 gap-6">
{shown.map((p) => (
<Link key={p.slug} href={`/services/${p.slug}`} className="group block h-full">
<Card className={`glass-card h-full !p-7 text-left border-l-4 ${borderL[p.accent]} transition-colors ${hoverBorder[p.accent]}`}>
<h3 className="text-xl font-bold mb-2 text-foreground">{p.title}</h3>
<p className="text-base text-muted-foreground leading-relaxed">{p.card}</p>
<span className="mt-3 inline-block text-sm font-medium text-foreground group-hover:underline">
Explore {p.label} →
</span>
</Card>
</Link>
))}
</div>
</div>
);
}
@@ -0,0 +1,58 @@
import { Card } from "@repo/ui/components/card";
import Image from "next/image";
import Link from "next/link";
import { chipAccent, hoverBorder, tagStyle, type Proof } from "@/lib/services";
// One "real product behind a pillar" card. Used on the hub and on every /services/[pillar] page.
export function ProofCard({ p, accent }: { p: Proof; accent: string }) {
const inner = (
<Card className={`glass-card h-full !p-6 transition-colors ${hoverBorder[accent]}`}>
<div className="flex items-start gap-4">
{p.img ? (
<Image
src={p.img}
alt={p.title}
width={48}
height={48}
className="rounded-xl flex-shrink-0 h-12 w-12 object-cover bg-white/5"
/>
) : (
<div
aria-hidden="true"
className={`flex-shrink-0 h-12 w-12 rounded-xl flex items-center justify-center text-lg font-bold ${chipAccent[accent]}`}
>
{p.title.charAt(0)}
</div>
)}
<div className="min-w-0">
<div className="flex items-center gap-2 mb-1 flex-wrap">
<h3 className="text-lg font-bold text-foreground">{p.title}</h3>
{p.tag && (
<span className={`text-[11px] font-medium px-2 py-0.5 rounded-full ${tagStyle[p.tag] ?? chipAccent[accent]}`}>
{p.tag}
</span>
)}
</div>
<p className="text-sm text-muted-foreground leading-relaxed">{p.blurb}</p>
{p.href && (
<span className="mt-2 inline-block text-sm font-medium text-foreground group-hover:underline">
Learn more →
</span>
)}
</div>
</div>
</Card>
);
if (!p.href) return <div>{inner}</div>;
const external = p.href.startsWith("http");
return external ? (
<a href={p.href} target="_blank" rel="noopener noreferrer" className="group block h-full">
{inner}
</a>
) : (
<Link href={p.href} className="group block h-full">
{inner}
</Link>
);
}
@@ -0,0 +1,26 @@
import { Button } from "@repo/ui/components/button";
import Link from "next/link";
// Shared closing CTA for the services hub and every pillar subpage.
export function ServicesCta({
title = "Let's build together",
blurb = "We take on a limited number of revenue-share build partners. Tell us what you're building and we'll run a scope review.",
}: {
title?: string;
blurb?: string;
}) {
return (
<div className="p-12 md: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/10 opacity-0 group-hover:opacity-100 transition-opacity" />
<h2 className="text-3xl md:text-4xl font-bold mb-6 relative z-10 tracking-tight">{title}</h2>
<p className="mb-10 text-slate-300 text-lg max-w-2xl mx-auto relative z-10 leading-relaxed font-medium">
{blurb}
</p>
<Link href="/contact" className="relative z-10">
<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 Consultation
</Button>
</Link>
</div>
);
}
+156
View File
@@ -0,0 +1,156 @@
// Single source of truth for the Services pillars + the real products behind each.
// Consumed by the /services hub, the /services/[pillar] subpages, the home "What We Do"
// preview, and the sitemap, so the four pillars can never drift between those surfaces.
export type Pillar = {
slug: "commerce" | "games" | "crypto" | "education";
label: string; // short nav label
accent: string; // tailwind colour stem, e.g. "blue"
title: string; // on-page H1 / section heading
lead: string; // the pillar's paragraph
// Per-subpage SEO. Kept distinct so each /services/<slug> ranks for its own terms.
metaTitle: string;
metaDescription: string;
// One line for the hub card, tighter than the lead.
card: string;
};
export const pillars: Pillar[] = [
{
slug: "commerce",
label: "Commerce & Web",
accent: "blue",
title: "Commerce & custom web",
lead: "Proprietary, headless commerce and web apps that you own, built to your business logic instead of a template's limits. Often on revenue share: we build, host, and run it, and earn a share once you are live.",
card: "Headless commerce and custom web apps you own, often on revenue share.",
metaTitle: "Custom E-Commerce & Web App Development | Such Software",
metaDescription:
"Headless, proprietary commerce and web apps built to your business logic on Medusa and Next.js, hosted on our infrastructure, often on a revenue-share partnership. Kennett Square, PA.",
},
{
slug: "games",
label: "Games & Apps",
accent: "purple",
title: "Games & apps",
lead: "We do not just consult on software, we ship it. A growing catalog of games and mobile apps built in-house across iOS, Android, web, and desktop, on Godot and Next.js.",
card: "A growing catalog of games and mobile apps we build and ship in-house.",
metaTitle: "Games & Mobile App Development | Such Software",
metaDescription:
"A studio that ships its own titles: games and cross-platform mobile apps built in-house on Godot and Next.js for iOS, Android, web, and desktop. Kennett Square, PA.",
},
{
slug: "crypto",
label: "Crypto & Payments",
accent: "cyan",
title: "Crypto & payments",
lead: "Software and integration for payments and cryptocurrency. The client, a licensed processor, or the user's own wallet holds the funds; we build the system around it and never take custody.",
card: "Payments and cryptocurrency integration. We never take custody of funds.",
metaTitle: "Crypto & Payments Integration Software (Non-Custodial) | Such Software",
metaDescription:
"Non-custodial payments and cryptocurrency software: Stripe and multi-processor integration, Monero and on-chain checkout, in production since 2014. The processor or wallet holds the funds. Kennett Square, PA.",
},
{
slug: "education",
label: "Education & Science",
accent: "amber",
title: "Education & science",
lead: "PhD-led radiation-science teaching, training, and speaking from years at a national lab, plus learning-through-play games that teach real science and math. We publish our methods open-source.",
card: "PhD-led radiation-science teaching and games that teach real science.",
metaTitle: "Nuclear & Radiation-Science Education, GEANT4 Training | Such Software",
metaDescription:
"PhD-led radiation-science education: Monte Carlo methods with GEANT4, shielding and detector physics, open-source labs, plus learning-through-play science and math games. Lawrence Livermore background.",
},
];
export function pillarBySlug(slug: string): Pillar | undefined {
return pillars.find((p) => p.slug === slug);
}
// ---- Proof items (real products behind each pillar) ---------------------
export type Proof = {
title: string;
blurb: string;
img?: string; // /images/products/*
href?: string; // internal detail page or external
tag?: string; // status pill
};
const commerceProof: Proof[] = [
{ title: "Webshops", blurb: "Our multi-tenant commerce platform: fully-themed storefronts, print-on-demand, non-custodial crypto checkout, and automated tax, on revenue share.", img: "/images/products/occupy-wallets.png", href: "/products/webshops", tag: "Flagship" },
{ title: "Custom builds", blurb: "Real-time web apps, multiplayer experiences, and live dashboards on Next.js, Socket.io, and WebRTC.", img: "/images/products/custom-websites.svg", href: "/products/custom-websites", tag: "Live" },
{ title: "Grin BTCPay plugin", blurb: "An open-source BTCPay Server plugin we built to accept Grin (Mimblewimble) payments, including auto-Tor invoice exchange.", tag: "Open source" },
{ title: "Evaluetron", blurb: "A self-hostable AI-compute platform: one core that runs a personal image bot or a paid multi-customer service, with an append-only credit ledger and pricing built in.", tag: "Building" },
];
const gamesProof: Proof[] = [
{ title: "Suchoice", blurb: "Stop overthinking decisions: snap a photo, let AI read the options, spin the wheel. iOS and Android.", img: "/images/products/suchoice.png", href: "/products/suchoice", tag: "Live" },
{ title: "Vegan IQ", blurb: "1,000+ plant-based trivia questions across 8 categories, with a daily challenge and weekly quiz. iOS and Android.", img: "/images/products/vegan-iq.png", href: "/products/vegan-iq", tag: "Live" },
{ title: "Bloomword", blurb: "A daily word game where every word you find grows a living 3D typographic tree. Free at bloomword.earth, launching on mobile.", img: "/images/products/bloomword.svg", href: "/products/bloomword", tag: "Live" },
{ title: "Bauhaus Echo", blurb: "A visual memory puzzle game inspired by Bauhaus design. Android, iOS, and web.", img: "/images/products/bauhaus-echo.png", href: "/products/bauhaus-echo", tag: "Live" },
{ title: "Such Moon Launch", blurb: "Vry rocket, much landing, wow: a Wownero-themed pixel rocket arcade with 11 levels and tilt-to-steer. Everywhere.", img: "/images/products/such-moon-launch.png", href: "/products/such-moon-launch", tag: "Live" },
];
const cryptoProof: Proof[] = [
{ title: "Payments consulting", blurb: "Stripe and multi-processor integrations, reconciliation tooling, Monero and on-chain integration. The processor or wallet moves the money; we wire it in.", tag: "Service" },
{ title: "Smirk Wallet", blurb: "A non-custodial multi-asset browser wallet: send, receive, and tip by username on Telegram and Discord. BTC, LTC, XMR, WOW, GRIN.", img: "/images/products/smirk-wallet.png", href: "/products/smirk-wallet", tag: "Live" },
{ title: "Hash Bags", blurb: "A mobile multi-chain wallet forked from Cake Wallet, tuned for the privacy-coin ecosystem.", tag: "Beta" },
{ title: "Evaluetron", blurb: "Pay-for-compute done right: an append-only credit ledger, a job queue, and an evaluator that lets on-demand work preempt opportunistic work. Customers can never self-credit.", tag: "Building" },
];
const educationProof: Proof[] = [
{ title: "Barns & Neutrons", blurb: "A cozy exploration-puzzle game across the real Table of Nuclides: capture neutrons, coax decays, and learn honest nuclear physics. Our premiere visual work.", img: "/images/products/barns-and-neutrons.svg", href: "/products/barns-and-neutrons", tag: "Coming soon" },
{ title: "Nuclear consulting & training", blurb: "PhD-led radiation-science teaching: Monte Carlo methods with GEANT4, shielding and dose fundamentals, detector physics, and open-source labs.", href: "https://jwinterm.github.io/geant4-radiation-labs/", tag: "Service" },
{ title: "Numchangers", blurb: "A kids math game (ages 5-8) where numbers morph into dots, shapes, and 3D characters, so arithmetic becomes a visible action instead of a quiz. Godot, mobile-first.", tag: "Early" },
];
export function proofFor(slug: string): Proof[] {
if (slug === "commerce") return commerceProof;
if (slug === "games") return gamesProof;
if (slug === "crypto") return cryptoProof;
return educationProof;
}
// ---- FAQ, tagged by pillar so each subpage shows the relevant ones -------
export type Faq = { q: string; a: string; pillars: Pillar["slug"][] };
export const faqs: Faq[] = [
{ q: "What does Such Software build?", a: "Four things, from one small team: custom e-commerce and web apps on revenue share, games and mobile apps we ship ourselves, cryptocurrency and payments software (integration only, never custody), and PhD-led radiation-science education. The pillars below break each down with the products behind them.", pillars: ["commerce", "games", "crypto", "education"] },
{ q: "Do you take revenue-share work?", a: "Yes. For a limited number of partners we build and operate products (such as webshops) for a share of revenue instead of a large upfront fee, so our incentives line up with yours.", pillars: ["commerce"] },
{ q: "Do you build for accessibility?", a: "Yes. We engineer front-ends to meet WCAG 2.2 AA and audit against it, alongside Core Web Vitals, as part of every project.", pillars: ["commerce", "games"] },
{ q: "Do you offer nuclear and radiation education?", a: "Yes. Our founder holds a PhD in radiation-detector materials and spent six years at Lawrence Livermore National Laboratory, plus shielding and spent-fuel work at Holtec. We teach radiation-science fundamentals and Monte Carlo methods, run GEANT4 training, and speak on these topics. Our GEANT4 radiation labs are published open-source (MIT / CC-BY). This is education and published methods.", pillars: ["education"] },
{ q: "Where are you based?", a: "We are a software studio in Kennett Square, Pennsylvania, working with clients remotely across the US and beyond.", pillars: ["commerce", "games", "crypto", "education"] },
{ q: "How does a project start?", a: "Reach out through the contact form with what you need. We scope it, propose an approach, and from there it can run as a fixed project or a performance-based revenue-share partnership.", pillars: ["commerce", "crypto"] },
];
// ---- Shared colour maps (literal strings so Tailwind's JIT keeps them) ---
export const borderL: Record<string, string> = {
blue: "border-l-blue-500",
purple: "border-l-purple-500",
cyan: "border-l-cyan-500",
amber: "border-l-amber-500",
};
export const chipAccent: Record<string, string> = {
blue: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
purple: "bg-purple-500/15 text-purple-700 dark:text-purple-300",
cyan: "bg-cyan-500/15 text-cyan-700 dark:text-cyan-300",
amber: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
};
export const hoverBorder: Record<string, string> = {
blue: "hover:border-blue-500/30",
purple: "hover:border-purple-500/30",
cyan: "hover:border-cyan-500/30",
amber: "hover:border-amber-500/30",
};
export const tagStyle: Record<string, string> = {
Flagship: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
Live: "bg-green-500/15 text-green-700 dark:text-green-300",
Service: "bg-sky-500/15 text-sky-700 dark:text-sky-300",
"Open source": "bg-indigo-500/15 text-indigo-700 dark:text-indigo-300",
Beta: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
Building: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
Early: "bg-fuchsia-500/15 text-fuchsia-700 dark:text-fuchsia-300",
"Coming soon": "bg-fuchsia-500/15 text-fuchsia-700 dark:text-fuchsia-300",
};