SEO: keyword-led titles/descriptions, structured data, internal linking

- Rewrite title tags + meta descriptions site-wide to front-load search intent
  (home, products index, services, apps, support, about, blog, and every product
  page); keep brand suffix, fix overlong/truncated descriptions, use colons not
  em dashes. Home keeps the visible 'Precision Engineering' hero; only the <title>
  changed. De-jargon Webshops away from leading with 'headless'.
- Add reusable JSON-LD helper (JsonLd + breadcrumb/softwareApp/article builders).
  WebSite schema site-wide; BreadcrumbList on all product + blog pages;
  SoftwareApplication on the apps/games; BlogPosting on posts.
- Cross-link the Monero/Wownero cluster (Neroswap, Smirk, Wownerogue, Such Moon
  Launch) and link products to services.
- Data fixes: Vegan IQ corrected to 1,000+ questions with Daily Challenge and
  Weekly Quiz (page + index); generalize Support page off a single game.
This commit is contained in:
jwinterm
2026-06-01 10:21:06 -04:00
parent d8db411824
commit f51a1257c5
18 changed files with 306 additions and 66 deletions
+4 -4
View File
@@ -8,14 +8,14 @@ import { FaXTwitter, FaTelegram, FaDiscord, FaBluesky, FaTiktok, FaYoutube, FaIn
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "About | Such Software", title: "About: A Kennett Square, PA Software Studio | Such Software",
description: "Software studio in Kennett Square, PA. Founded by John Winter Murphy, specializing in accessible web engineering and WCAG 2.2 compliance.", description: "Such Software is a Kennett Square, PA studio founded by John Winter Murphy, building accessible web apps, crypto tools, and games with WCAG 2.2 compliance.",
alternates: { alternates: {
canonical: "/about", canonical: "/about",
}, },
openGraph: { openGraph: {
title: "About | Such Software", title: "About: A Kennett Square, PA Software Studio | Such Software",
description: "Software studio in Kennett Square, PA. Founded by John Winter Murphy, specializing in accessible web engineering and WCAG 2.2 compliance.", description: "Such Software is a Kennett Square, PA studio founded by John Winter Murphy, building accessible web apps, crypto tools, and games with WCAG 2.2 compliance.",
type: "website", type: "website",
}, },
}; };
+4 -4
View File
@@ -7,12 +7,12 @@ import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Apps | Such Software", title: "Our Apps: Games & Tools for iOS, Android & Web | Such Software",
description: "Download our apps on App Store and Google Play.", description: "Download Such Software's apps and games: Bauhaus Echo, Suchoice, Vegan IQ, Such Moon Launch, and more. Free on the App Store, Google Play, and the web.",
alternates: { canonical: "/apps" }, alternates: { canonical: "/apps" },
openGraph: { openGraph: {
title: "Such Software Apps", title: "Our Apps: Games & Tools for iOS, Android & Web | Such Software",
description: "Download our apps on App Store and Google Play.", description: "Download Such Software's apps and games: Bauhaus Echo, Suchoice, Vegan IQ, Such Moon Launch, and more. Free on the App Store, Google Play, and the web.",
type: "website", type: "website",
images: [{ url: "/images/branding/OG_banner_v2_light.png", width: 1200, height: 630, alt: "Such Software" }], images: [{ url: "/images/branding/OG_banner_v2_light.png", width: 1200, height: 630, alt: "Such Software" }],
}, },
+7
View File
@@ -13,6 +13,7 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { MDXRemote } from "next-mdx-remote/rsc"; import { MDXRemote } from "next-mdx-remote/rsc";
import { getAllSlugs, getPost } from "@/lib/blog"; import { getAllSlugs, getPost } from "@/lib/blog";
import { JsonLd, articleLd, breadcrumbLd } from "@/components/seo/json-ld";
type Props = { params: Promise<{ slug: string }> }; type Props = { params: Promise<{ slug: string }> };
@@ -50,6 +51,12 @@ export default async function BlogPost({ params }: Props) {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={articleLd({ title: post.title, description: post.excerpt, slug, date: post.date })} />
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Blog", path: "/blog" },
{ name: post.title, path: `/blog/${slug}` },
])} />
<Header /> <Header />
<article id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <article id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/blog" className="text-sm text-muted-foreground hover:text-emerald-600 dark:hover:text-emerald-400 mb-8 inline-flex items-center gap-2 transition-colors"> <Link href="/blog" className="text-sm text-muted-foreground hover:text-emerald-600 dark:hover:text-emerald-400 mb-8 inline-flex items-center gap-2 transition-colors">
+4 -4
View File
@@ -6,12 +6,12 @@ import type { Metadata } from "next";
import { getAllPosts } from "@/lib/blog"; import { getAllPosts } from "@/lib/blog";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Blog | Such Software", title: "Blog: Build Notes on Crypto, Commerce & Apps | Such Software",
description: "News, updates, and technical articles from Such Software.", description: "Technical write-ups from Such Software on provably-fair games, Monero tooling, custom e-commerce, and the apps we ship.",
alternates: { canonical: "/blog" }, alternates: { canonical: "/blog" },
openGraph: { openGraph: {
title: "Blog | Such Software", title: "Blog: Build Notes on Crypto, Commerce & Apps | Such Software",
description: "News, updates, and technical articles from Such Software.", description: "Technical write-ups from Such Software on provably-fair games, Monero tooling, custom e-commerce, and the apps we ship.",
type: "website", type: "website",
}, },
}; };
+18 -6
View File
@@ -9,14 +9,14 @@ const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = { export const metadata: Metadata = {
metadataBase: new URL("https://such.software"), metadataBase: new URL("https://such.software"),
title: "Such Software | Precision Engineering for Everyone", title: "Custom E-Commerce, App & Crypto Development | Such Software",
description: "E-commerce infrastructure, custom app development, and accessible web applications. Engineered for scale, compliance, and performance.", description: "Such Software is a Kennett Square, PA studio building custom online stores, web apps, mobile games, and Monero & crypto tools. Start a project today.",
alternates: { alternates: {
canonical: "/", canonical: "/",
}, },
openGraph: { openGraph: {
title: "Such Software", title: "Such Software: Custom E-Commerce, Apps & Crypto Development",
description: "E-commerce infrastructure, custom app development, and accessible web applications.", description: "A Kennett Square, PA software studio building custom online stores, web apps, mobile games, and Monero & crypto tools.",
type: "website", type: "website",
siteName: "Such Software", siteName: "Such Software",
images: [ images: [
@@ -30,8 +30,8 @@ export const metadata: Metadata = {
}, },
twitter: { twitter: {
card: "summary_large_image", card: "summary_large_image",
title: "Such Software", title: "Such Software: Custom E-Commerce, Apps & Crypto Development",
description: "E-commerce infrastructure, custom app development, and accessible web applications.", description: "A Kennett Square, PA software studio building custom online stores, web apps, mobile games, and Monero & crypto tools.",
images: ["/images/branding/OG_banner_v2_light.png"], images: ["/images/branding/OG_banner_v2_light.png"],
}, },
icons: { icons: {
@@ -86,6 +86,14 @@ const structuredData = {
], ],
}; };
const websiteData = {
"@context": "https://schema.org",
"@type": "WebSite",
name: "Such Software",
url: "https://such.software",
publisher: { "@type": "Organization", name: "Such Software LLC" },
};
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
@@ -98,6 +106,10 @@ export default function RootLayout({
type="application/ld+json" type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }} dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/> />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteData) }}
/>
</head> </head>
<body className={inter.className}> <body className={inter.className}>
<ThemeProvider <ThemeProvider
@@ -2,17 +2,18 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav"; import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { StoreButtons } from "@/components/store-buttons"; import { StoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Bauhaus Echo | Such Software", title: "Bauhaus Echo: Visual Memory Puzzle Game | Such Software",
description: "Visual memory puzzle game inspired by Bauhaus design. Five modes including a pressure-free Zen sandbox. Android, iOS, Web, Desktop.", description: "A Bauhaus-inspired memory puzzle game with 5 modes and a pressure-free Zen sandbox. Play free on iOS, Android, web, and desktop.",
alternates: { canonical: "/products/bauhaus-echo" }, alternates: { canonical: "/products/bauhaus-echo" },
openGraph: { openGraph: {
title: "Bauhaus Echo | Such Software", title: "Bauhaus Echo: Visual Memory Puzzle Game | Such Software",
description: "Visual memory puzzle game inspired by Bauhaus design. Five modes including a pressure-free Zen sandbox. Android, iOS, Web, Desktop.", description: "A Bauhaus-inspired memory puzzle game with 5 modes and a pressure-free Zen sandbox. Play free on iOS, Android, web, and desktop.",
type: "website", type: "website",
images: [{ url: "/images/products/bauhaus-echo.png", width: 512, height: 512, alt: "Bauhaus Echo" }], images: [{ url: "/images/products/bauhaus-echo.png", width: 512, height: 512, alt: "Bauhaus Echo" }],
}, },
@@ -29,6 +30,19 @@ const gameModes = [
export default function BauhausEchoPage() { export default function BauhausEchoPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Bauhaus Echo", path: "/products/bauhaus-echo" },
])} />
<JsonLd data={softwareAppLd({
name: "Bauhaus Echo",
path: "/products/bauhaus-echo",
description: "A Bauhaus-inspired memory puzzle game with 5 modes and a pressure-free Zen sandbox. Play free on iOS, Android, web, and desktop.",
category: "GameApplication",
operatingSystem: "Android, iOS, Web, Windows, macOS, Linux",
price: "0",
})} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block"> <Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
@@ -2,6 +2,7 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav"; import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button"; import { Button } from "@repo/ui/components/button";
import { JsonLd, breadcrumbLd } from "@/components/seo/json-ld";
import Link from "next/link"; import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
@@ -19,6 +20,11 @@ export const metadata: Metadata = {
export default function CustomWebsitesPage() { export default function CustomWebsitesPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Custom Web Apps", path: "/products/custom-websites" },
])} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block"> <Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
+26 -4
View File
@@ -3,17 +3,18 @@ import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button"; import { Button } from "@repo/ui/components/button";
import Link from "next/link"; import Link from "next/link";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Neroswap — Aggregated DEX & CEX Orderbook Data | Such Software", title: "Neroswap: No-KYC Monero & Bitcoin Price Aggregator | Such Software",
description: description:
"Real-time aggregated orderbook data across decentralized and centralized exchanges. Compare Monero, Wownero, and Bitcoin prices across BasicSwap, Retoswap, Eigenwallet, Nonlogs, CexSwap, and AltQuick — no KYC, no custody.", "Compare live Monero, Wownero, and Bitcoin prices across 6 DEX and no-KYC CEX orderbooks in real time. No accounts, no custody, no tracking.",
alternates: { canonical: "/products/neroswap" }, alternates: { canonical: "/products/neroswap" },
openGraph: { openGraph: {
title: "Neroswap — DEX & CEX Aggregator", title: "Neroswap: No-KYC Monero & Bitcoin Price Aggregator",
description: description:
"Compare crypto prices across 6 exchanges in real time. Privacy-focused, self-sovereign, no KYC.", "Compare live Monero, Wownero, and Bitcoin prices across 6 DEX and no-KYC CEX orderbooks in real time. No accounts, no custody, no tracking.",
type: "website", type: "website",
url: "https://such.software/products/neroswap", url: "https://such.software/products/neroswap",
}, },
@@ -95,6 +96,20 @@ const features = [
export default function NeroswapPage() { export default function NeroswapPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Neroswap", path: "/products/neroswap" },
])} />
<JsonLd data={softwareAppLd({
name: "Neroswap",
path: "/products/neroswap",
description: "Compare live Monero, Wownero, and Bitcoin prices across 6 DEX and no-KYC CEX orderbooks in real time. No accounts, no custody, no tracking.",
category: "FinanceApplication",
operatingSystem: "Web browser",
price: "0",
offerUrl: "https://neroswap.com",
})} />
<Header /> <Header />
<div <div
id="main-content" id="main-content"
@@ -214,6 +229,13 @@ export default function NeroswapPage() {
<Button variant="outline">Read the Blog Post</Button> <Button variant="outline">Read the Blog Post</Button>
</Link> </Link>
</div> </div>
<p className="text-sm text-muted-foreground mt-12">
Related:{" "}
<Link href="/products/smirk-wallet" className="text-yellow-600 dark:text-yellow-400 underline">Smirk Wallet</Link>,{" "}
<Link href="/products/wownerogue" className="text-yellow-600 dark:text-yellow-400 underline">Wownerogue</Link>, and our{" "}
<Link href="/services" className="text-yellow-600 dark:text-yellow-400 underline">crypto consulting</Link>.
</p>
</div> </div>
<MobileNav /> <MobileNav />
</main> </main>
+4 -4
View File
@@ -11,8 +11,8 @@ export const metadata: Metadata = {
description: "Explore Such Software's products: Wownerogue Monero roguelike, Neroswap crypto aggregator, Smirk tipping wallet, headless Webshops, and our mobile games.", description: "Explore Such Software's products: Wownerogue Monero roguelike, Neroswap crypto aggregator, Smirk tipping wallet, headless Webshops, and our mobile games.",
alternates: { canonical: "/products" }, alternates: { canonical: "/products" },
openGraph: { openGraph: {
title: "Products | Such Software", title: "Our Products & Apps: Crypto Tools, Games & Commerce | Such Software",
description: "Tools and applications built by Such Software.", description: "Explore Such Software's products: Wownerogue Monero roguelike, Neroswap crypto aggregator, Smirk tipping wallet, headless Webshops, and our mobile games.",
type: "website", type: "website",
}, },
}; };
@@ -35,7 +35,7 @@ const products = [
{ {
slug: "webshops", slug: "webshops",
title: "Webshops", title: "Webshops",
description: "Headless Medusa commerce with revenue-share pricing. No upfront costs.", description: "Custom online stores on revenue-share pricing. No upfront cost. Built on headless Medusa.",
color: "emerald", color: "emerald",
icon: "/images/products/occupy-wallets.png", icon: "/images/products/occupy-wallets.png",
}, },
@@ -70,7 +70,7 @@ const products = [
{ {
slug: "vegan-iq", slug: "vegan-iq",
title: "Vegan IQ", title: "Vegan IQ",
description: "Test your plant-based knowledge with 2000+ trivia questions across 8 categories. Verified data, swipe gameplay. Android and iOS.", description: "Test your plant-based knowledge with 1,000+ trivia questions across 8 categories, plus a daily challenge and weekly quiz. Android and iOS.",
color: "green", color: "green",
icon: "/images/products/vegan-iq.png", icon: "/images/products/vegan-iq.png",
}, },
@@ -3,15 +3,16 @@ import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button"; import { Button } from "@repo/ui/components/button";
import Link from "next/link"; import Link from "next/link";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Smirk Wallet | Such Software", title: "Smirk Wallet: Non-Custodial Crypto Tipping by Username | Such Software",
description: "Non-custodial browser wallet for social tipping. Send BTC, LTC, XMR, WOW, and GRIN by Telegram or Discord username.", description: "Tip BTC, LTC, XMR, WOW, and GRIN on Telegram or Discord by username. A non-custodial browser wallet: your keys never leave your device.",
alternates: { canonical: "/products/smirk-wallet" }, alternates: { canonical: "/products/smirk-wallet" },
openGraph: { openGraph: {
title: "Smirk Wallet | Such Software", title: "Smirk Wallet: Non-Custodial Crypto Tipping by Username",
description: "Non-custodial browser wallet for social tipping. Send BTC, LTC, XMR, WOW, and GRIN by Telegram or Discord username.", description: "Tip BTC, LTC, XMR, WOW, and GRIN on Telegram or Discord by username. A non-custodial browser wallet: your keys never leave your device.",
type: "website", type: "website",
}, },
}; };
@@ -27,6 +28,20 @@ const currencies = [
export default function SmirkWalletPage() { export default function SmirkWalletPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Smirk Wallet", path: "/products/smirk-wallet" },
])} />
<JsonLd data={softwareAppLd({
name: "Smirk Wallet",
path: "/products/smirk-wallet",
description: "Tip BTC, LTC, XMR, WOW, and GRIN on Telegram or Discord by username. A non-custodial browser wallet: your keys never leave your device.",
category: "FinanceApplication",
operatingSystem: "Chrome, Firefox (browser extension)",
price: "0",
offerUrl: "https://smirk.cash",
})} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block"> <Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
@@ -130,6 +145,13 @@ export default function SmirkWalletPage() {
<Button variant="outline">GitHub</Button> <Button variant="outline">GitHub</Button>
</a> </a>
</div> </div>
<p className="text-sm text-muted-foreground mt-12">
Related:{" "}
<Link href="/products/neroswap" className="text-amber-600 dark:text-amber-400 hover:underline">Neroswap</Link>,{" "}
<Link href="/products/wownerogue" className="text-amber-600 dark:text-amber-400 hover:underline">Wownerogue</Link>, and{" "}
<Link href="/products/such-moon-launch" className="text-amber-600 dark:text-amber-400 hover:underline">Such Moon Launch</Link>.
</p>
</div> </div>
<MobileNav /> <MobileNav />
</main> </main>
@@ -2,16 +2,17 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav"; import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { SuchMoonLaunchStoreButtons } from "@/components/store-buttons"; import { SuchMoonLaunchStoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Link from "next/link"; import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Such Moon Launch | Such Software", title: "Such Moon Launch: Pixel Rocket Arcade Game | Such Software",
description: "Vry rocket. Much landing. Wow! A pixel-art rocket-flying arcade with 11 levels, 4 weapons, and an endless mode. Wownero-themed. Android, iOS, Web, Desktop.", description: "Fly a rocket across the solar system in this Wownero-themed pixel arcade. 11 levels, 4 weapons, tilt-to-steer, endless mode. Free on iOS, Android, web, desktop.",
alternates: { canonical: "/products/such-moon-launch" }, alternates: { canonical: "/products/such-moon-launch" },
openGraph: { openGraph: {
title: "Such Moon Launch | Such Software", title: "Such Moon Launch: Pixel Rocket Arcade Game | Such Software",
description: "Vry rocket. Much landing. Wow! A pixel-art rocket-flying arcade with 11 levels, 4 weapons, and an endless mode. Wownero-themed.", description: "Fly a rocket across the solar system in this Wownero-themed pixel arcade. 11 levels, 4 weapons, tilt-to-steer, endless mode. Free on iOS, Android, web, desktop.",
type: "website", type: "website",
}, },
}; };
@@ -41,6 +42,20 @@ const weapons = [
export default function SuchMoonLaunchPage() { export default function SuchMoonLaunchPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Such Moon Launch", path: "/products/such-moon-launch" },
])} />
<JsonLd data={softwareAppLd({
name: "Such Moon Launch",
path: "/products/such-moon-launch",
description: "Fly a rocket across the solar system in this Wownero-themed pixel arcade. 11 levels, 4 weapons, tilt-to-steer, endless mode. Free on iOS, Android, web, desktop.",
category: "GameApplication",
operatingSystem: "Android, iOS, Web, Windows, macOS, Linux",
price: "0",
offerUrl: "https://moonlaunch.such.software",
})} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block"> <Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
+18 -4
View File
@@ -2,17 +2,18 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav"; import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { StoreButtons } from "@/components/store-buttons"; import { StoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Suchoice | Such Software", title: "Suchoice: Spin the Wheel & AI Decision Maker App | Such Software",
description: "Six ways to decide: AI photo reader, spin the wheel, coin flip, 3D dice, head-to-head duel, and city explorer. Android and iOS.", description: "Can't decide? Snap a photo and let AI read your options, spin the wheel, flip a coin, or roll 3D dice. The fun decision-maker app for iOS and Android.",
alternates: { canonical: "/products/suchoice" }, alternates: { canonical: "/products/suchoice" },
openGraph: { openGraph: {
title: "Suchoice | Such Software", title: "Suchoice: Spin the Wheel & AI Decision Maker App | Such Software",
description: "Six ways to decide: AI photo reader, spin the wheel, coin flip, 3D dice, head-to-head duel, and city explorer. Android and iOS.", description: "Can't decide? Snap a photo and let AI read your options, spin the wheel, flip a coin, or roll 3D dice. The fun decision-maker app for iOS and Android.",
type: "website", type: "website",
images: [{ url: "/images/products/suchoice.png", width: 512, height: 512, alt: "Suchoice" }], images: [{ url: "/images/products/suchoice.png", width: 512, height: 512, alt: "Suchoice" }],
}, },
@@ -30,6 +31,19 @@ const features = [
export default function SuchoicePage() { export default function SuchoicePage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Suchoice", path: "/products/suchoice" },
])} />
<JsonLd data={softwareAppLd({
name: "Suchoice",
path: "/products/suchoice",
description: "Can't decide? Snap a photo and let AI read your options, spin the wheel, flip a coin, or roll 3D dice. The fun decision-maker app for iOS and Android.",
category: "LifestyleApplication",
operatingSystem: "Android, iOS",
price: "0",
})} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block"> <Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
+22 -6
View File
@@ -2,25 +2,28 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav"; import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { StoreButtons } from "@/components/store-buttons"; import { StoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Vegan IQ | Such Software", title: "Vegan IQ: Plant-Based Trivia Game | Such Software",
description: "Test your plant-based knowledge with 1000+ trivia questions across 8 categories. Swipe and tap gameplay with verified data. Android and iOS.", description: "Test your plant-based knowledge with 1,000+ trivia questions across 8 categories, plus a daily challenge and weekly quiz. Free vegan trivia for iOS and Android.",
alternates: { canonical: "/products/vegan-iq" }, alternates: { canonical: "/products/vegan-iq" },
openGraph: { openGraph: {
title: "Vegan IQ | Such Software", title: "Vegan IQ: Plant-Based Trivia Game | Such Software",
description: "Test your plant-based knowledge with 1000+ trivia questions across 8 categories. Swipe and tap gameplay with verified data. Android and iOS.", description: "Test your plant-based knowledge with 1,000+ trivia questions across 8 categories, plus a daily challenge and weekly quiz. Free vegan trivia for iOS and Android.",
type: "website", type: "website",
images: [{ url: "/images/products/vegan-iq.png", width: 512, height: 512, alt: "Vegan IQ" }], images: [{ url: "/images/products/vegan-iq.png", width: 512, height: 512, alt: "Vegan IQ" }],
}, },
}; };
const features = [ const features = [
{ name: "1000+ Questions", description: "Curated trivia questions backed by verified data from USDA, Poore & Nemecek, NIH, and FAOSTAT. No AI-generated content." }, { name: "1,000+ Questions", description: "Curated trivia questions backed by verified data from USDA, Poore & Nemecek, NIH, and FAOSTAT. No AI-generated content." },
{ name: "8 Categories", description: "Nutrition, environment, ingredients, food history, animal welfare, health, cooking, and culture." }, { name: "8 Categories", description: "Nutrition, environment, ingredients, food history, animal welfare, health, cooking, and culture." },
{ name: "Daily Challenge", description: "A fresh set of questions every day. Build a streak and keep your knowledge sharp." },
{ name: "Weekly Quiz", description: "New themed quiz each week with fresh content to test how far you've come." },
{ name: "Swipe & Tap", description: "Swipe right for true, left for false. Tap your answer for multiple choice. Fast, intuitive gameplay." }, { name: "Swipe & Tap", description: "Swipe right for true, left for false. Tap your answer for multiple choice. Fast, intuitive gameplay." },
{ name: "Dark & Light Mode", description: "Play in whichever theme you prefer. Automatic or manual switching." }, { name: "Dark & Light Mode", description: "Play in whichever theme you prefer. Automatic or manual switching." },
{ name: "SFX & Haptics", description: "Satisfying sound effects and haptic feedback on correct and incorrect answers." }, { name: "SFX & Haptics", description: "Satisfying sound effects and haptic feedback on correct and incorrect answers." },
@@ -30,6 +33,19 @@ const features = [
export default function VeganIqPage() { export default function VeganIqPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Vegan IQ", path: "/products/vegan-iq" },
])} />
<JsonLd data={softwareAppLd({
name: "Vegan IQ",
path: "/products/vegan-iq",
description: "Test your plant-based knowledge with 1,000+ trivia questions across 8 categories, plus a daily challenge and weekly quiz. Free vegan trivia for iOS and Android.",
category: "GameApplication",
operatingSystem: "Android, iOS",
price: "0",
})} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block"> <Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
@@ -51,7 +67,7 @@ export default function VeganIqPage() {
<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 text-muted-foreground leading-relaxed"> <p className="text-xl text-muted-foreground leading-relaxed">
Think you know your vegan facts? Vegan IQ puts your plant-based knowledge to the test with over 1000 trivia questions across 8 categories. Think you know your vegan facts? Vegan IQ puts your plant-based knowledge to the test with over 1,000 trivia questions across 8 categories.
Every question is backed by verified data from sources like the USDA, Poore & Nemecek (2018), NIH, and FAOSTAT. Every question is backed by verified data from sources like the USDA, Poore & Nemecek (2018), NIH, and FAOSTAT.
</p> </p>
</div> </div>
+22 -8
View File
@@ -2,16 +2,17 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav"; import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button"; import { Button } from "@repo/ui/components/button";
import { JsonLd, breadcrumbLd } from "@/components/seo/json-ld";
import Link from "next/link"; import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Webshops | Such Software", title: "Custom Online Stores & E-Commerce Development | Such Software",
description: "Headless Medusa commerce engines with revenue-share pricing. No upfront costs, aligned incentives.", description: "Custom online stores with no upfront cost and revenue-share pricing. Own your data, escape template limits, and hit 100/100 Lighthouse. Built on Medusa.",
alternates: { canonical: "/products/webshops" }, alternates: { canonical: "/products/webshops" },
openGraph: { openGraph: {
title: "Webshops | Such Software", title: "Custom Online Stores & E-Commerce Development | Such Software",
description: "Headless Medusa commerce engines with revenue-share pricing. No upfront costs, aligned incentives.", description: "Custom online stores with no upfront cost and revenue-share pricing. Own your data and escape template limits. Built on Medusa.",
type: "website", type: "website",
}, },
}; };
@@ -19,20 +20,27 @@ export const metadata: Metadata = {
export default function WebshopsPage() { export default function WebshopsPage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Custom Online Stores", path: "/products/webshops" },
])} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
<Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block"> <Link href="/products" className="text-sm text-muted-foreground hover:text-foreground mb-8 inline-block">
← Back to Products ← Back to Products
</Link> </Link>
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-emerald-600 dark:text-emerald-400"> <h1 className="text-4xl md:text-5xl font-bold mb-2 text-emerald-600 dark:text-emerald-400">
Webshops Custom Online Stores
</h1> </h1>
<p className="text-base text-muted-foreground mb-6">Webshops built and run on revenue share.</p>
<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 text-muted-foreground leading-relaxed"> <p className="text-xl text-muted-foreground leading-relaxed">
We build headless commerce platforms on Medusa v2 with a revenue-share model. We build custom online stores with a revenue-share model: no large setup fee, no monthly
No massive setup fees, no monthly retainers. We only win when you make sales. retainer. We only win when you make sales. Under the hood it is a headless Medusa v2
backend with a Next.js storefront, so you own your data and your business logic.
</p> </p>
</div> </div>
@@ -81,6 +89,12 @@ export default function WebshopsPage() {
<Link href="/about#contact"> <Link href="/about#contact">
<Button size="lg">Get in Touch</Button> <Button size="lg">Get in Touch</Button>
</Link> </Link>
<p className="text-sm text-muted-foreground mt-6">
See also our{" "}
<Link href="/services" className="text-emerald-600 dark:text-emerald-400 hover:underline">development services</Link>
{" "}and{" "}
<Link href="/products/custom-websites" className="text-emerald-600 dark:text-emerald-400 hover:underline">custom web apps</Link>.
</p>
</div> </div>
</div> </div>
<MobileNav /> <MobileNav />
@@ -3,6 +3,7 @@ import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card"; import { Card } from "@repo/ui/components/card";
import { WownerogueLinks } from "@/components/store-buttons"; import { WownerogueLinks } from "@/components/store-buttons";
import Link from "next/link"; import Link from "next/link";
import { JsonLd, breadcrumbLd } from "@/components/seo/json-ld";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
@@ -70,6 +71,11 @@ const steps = [
export default function WowneroguePage() { export default function WowneroguePage() {
return ( return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground"> <main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<JsonLd data={breadcrumbLd([
{ name: "Home", path: "/" },
{ name: "Products", path: "/products" },
{ name: "Wownerogue", path: "/products/wownerogue" },
])} />
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} /> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
<Header /> <Header />
<div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20"> <div id="main-content" className="z-10 w-full max-w-4xl mx-auto py-20 px-4 pb-24 md:pb-20">
+4 -4
View File
@@ -9,14 +9,14 @@ import { Card } from "@repo/ui/components/card";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Services | Such Software", title: "Medusa, Web App & Crypto Development Services | Such Software",
description: "Medusa v2 development, custom web applications, and cryptocurrency consulting. High-performance headless commerce, real-time platforms, and Web3 integration.", description: "Custom e-commerce on Medusa v2, real-time web apps, and Monero/crypto consulting. A Kennett Square, PA studio building for scale, accessibility, and performance.",
alternates: { alternates: {
canonical: "/services", canonical: "/services",
}, },
openGraph: { openGraph: {
title: "Services | Such Software", title: "Medusa, Web App & Crypto Development Services | Such Software",
description: "Medusa v2 development, custom web applications, and cryptocurrency consulting.", description: "Custom e-commerce on Medusa v2, real-time web apps, and Monero/crypto consulting. A Kennett Square, PA studio building for scale, accessibility, and performance.",
type: "website", type: "website",
}, },
}; };
+6 -6
View File
@@ -5,12 +5,12 @@ import Link from "next/link";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Support | Such Software", title: "Support & Feedback | Such Software",
description: "Get help with Bauhaus Echo or any Such Software product. Report bugs, request features, or send feedback.", description: "Get help with any Such Software app or game. Report a bug, request a feature, or send feedback. We read every message.",
alternates: { canonical: "/support" }, alternates: { canonical: "/support" },
openGraph: { openGraph: {
title: "Support | Such Software", title: "Support & Feedback | Such Software",
description: "Get help with Bauhaus Echo or any Such Software product. Report bugs, request features, or send feedback.", description: "Get help with any Such Software app or game. Report a bug, request a feature, or send feedback. We read every message.",
type: "website", type: "website",
}, },
}; };
@@ -39,13 +39,13 @@ export default function SupportPage() {
<div className="section-container border-l-4 border-emerald-500"> <div className="section-container border-l-4 border-emerald-500">
<h3 className="font-bold mb-2">Feature Requests</h3> <h3 className="font-bold mb-2">Feature Requests</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Have an idea to make the game better? We&apos;re always looking for ways to improve. Have an idea to make your app or game better? We&apos;re always looking for ways to improve.
</p> </p>
</div> </div>
<div className="section-container border-l-4 border-amber-500"> <div className="section-container border-l-4 border-amber-500">
<h3 className="font-bold mb-2">General Feedback</h3> <h3 className="font-bold mb-2">General Feedback</h3>
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
Enjoying the game? Hate something? All feedback is welcome and helps us prioritize. Enjoying one of our apps? Hate something? All feedback is welcome and helps us prioritize.
</p> </p>
</div> </div>
</div> </div>
+92
View File
@@ -0,0 +1,92 @@
/**
* JSON-LD helpers. `JsonLd` renders a structured-data <script>; the builders return
* schema.org objects so pages can stay declarative. Server components only (no client JS).
*/
const SITE = "https://such.software";
export function JsonLd({ data }: { data: object }) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
);
}
/** Breadcrumb trail for a deep page, e.g. Home › Products › Neroswap. */
export function breadcrumbLd(items: { name: string; path: string }[]) {
return {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: items.map((it, i) => ({
"@type": "ListItem",
position: i + 1,
name: it.name,
item: `${SITE}${it.path}`,
})),
};
}
type SoftwareAppOpts = {
name: string;
path: string;
description: string;
/** schema.org applicationCategory, e.g. "GameApplication", "FinanceApplication". */
category: string;
operatingSystem: string;
/** Price as a string; "0" for free. */
price?: string;
/** External play/download URL the offer points at (defaults to the on-site path). */
offerUrl?: string;
/** Optional store rating, rendered as AggregateRating when provided. */
rating?: { value: string; count: string };
};
/** SoftwareApplication node: drives app/game rich results (and star ratings when rated). */
export function softwareAppLd(opts: SoftwareAppOpts) {
const data: Record<string, unknown> = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: opts.name,
url: `${SITE}${opts.path}`,
description: opts.description,
applicationCategory: opts.category,
operatingSystem: opts.operatingSystem,
offers: {
"@type": "Offer",
price: opts.price ?? "0",
priceCurrency: "USD",
url: opts.offerUrl ?? `${SITE}${opts.path}`,
},
publisher: { "@type": "Organization", name: "Such Software" },
};
if (opts.rating) {
data.aggregateRating = {
"@type": "AggregateRating",
ratingValue: opts.rating.value,
ratingCount: opts.rating.count,
};
}
return data;
}
/** BlogPosting node for an article page. */
export function articleLd(opts: { title: string; description: string; slug: string; date: string }) {
const url = `${SITE}/blog/${opts.slug}`;
return {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: opts.title,
description: opts.description,
url,
mainEntityOfPage: url,
datePublished: opts.date,
dateModified: opts.date,
author: { "@type": "Organization", name: "Such Software" },
publisher: {
"@type": "Organization",
name: "Such Software",
logo: { "@type": "ImageObject", url: `${SITE}/images/branding/clean_dark_w_text.svg` },
},
};
}