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";
export const metadata: Metadata = {
title: "About | Such Software",
description: "Software studio in Kennett Square, PA. Founded by John Winter Murphy, specializing in accessible web engineering and WCAG 2.2 compliance.",
title: "About: A Kennett Square, PA Software Studio | Such Software",
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: {
canonical: "/about",
},
openGraph: {
title: "About | Such Software",
description: "Software studio in Kennett Square, PA. Founded by John Winter Murphy, specializing in accessible web engineering and WCAG 2.2 compliance.",
title: "About: A Kennett Square, PA Software Studio | Such Software",
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",
},
};
+4 -4
View File
@@ -7,12 +7,12 @@ import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Apps | Such Software",
description: "Download our apps on App Store and Google Play.",
title: "Our Apps: Games & Tools for iOS, Android & Web | Such Software",
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" },
openGraph: {
title: "Such Software Apps",
description: "Download our apps on App Store and Google Play.",
title: "Our Apps: Games & Tools for iOS, Android & Web | Such Software",
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",
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 { MDXRemote } from "next-mdx-remote/rsc";
import { getAllSlugs, getPost } from "@/lib/blog";
import { JsonLd, articleLd, breadcrumbLd } from "@/components/seo/json-ld";
type Props = { params: Promise<{ slug: string }> };
@@ -50,6 +51,12 @@ export default async function BlogPost({ params }: Props) {
return (
<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 />
<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">
+4 -4
View File
@@ -6,12 +6,12 @@ import type { Metadata } from "next";
import { getAllPosts } from "@/lib/blog";
export const metadata: Metadata = {
title: "Blog | Such Software",
description: "News, updates, and technical articles from Such Software.",
title: "Blog: Build Notes on Crypto, Commerce & Apps | 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" },
openGraph: {
title: "Blog | Such Software",
description: "News, updates, and technical articles from Such Software.",
title: "Blog: Build Notes on Crypto, Commerce & Apps | 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",
},
};
+18 -6
View File
@@ -9,14 +9,14 @@ const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
metadataBase: new URL("https://such.software"),
title: "Such Software | Precision Engineering for Everyone",
description: "E-commerce infrastructure, custom app development, and accessible web applications. Engineered for scale, compliance, and performance.",
title: "Custom E-Commerce, App & Crypto Development | Such Software",
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: {
canonical: "/",
},
openGraph: {
title: "Such Software",
description: "E-commerce infrastructure, custom app development, and accessible web applications.",
title: "Such Software: Custom E-Commerce, Apps & Crypto Development",
description: "A Kennett Square, PA software studio building custom online stores, web apps, mobile games, and Monero & crypto tools.",
type: "website",
siteName: "Such Software",
images: [
@@ -30,8 +30,8 @@ export const metadata: Metadata = {
},
twitter: {
card: "summary_large_image",
title: "Such Software",
description: "E-commerce infrastructure, custom app development, and accessible web applications.",
title: "Such Software: Custom E-Commerce, Apps & Crypto Development",
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"],
},
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({
children,
}: Readonly<{
@@ -98,6 +106,10 @@ export default function RootLayout({
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(websiteData) }}
/>
</head>
<body className={inter.className}>
<ThemeProvider
@@ -2,17 +2,18 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { StoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Image from "next/image";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Bauhaus Echo | Such Software",
description: "Visual memory puzzle game inspired by Bauhaus design. Five modes including a pressure-free Zen sandbox. Android, iOS, Web, Desktop.",
title: "Bauhaus Echo: Visual Memory Puzzle Game | Such Software",
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" },
openGraph: {
title: "Bauhaus Echo | Such Software",
description: "Visual memory puzzle game inspired by Bauhaus design. Five modes including a pressure-free Zen sandbox. Android, iOS, Web, Desktop.",
title: "Bauhaus Echo: Visual Memory Puzzle Game | Such Software",
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",
images: [{ url: "/images/products/bauhaus-echo.png", width: 512, height: 512, alt: "Bauhaus Echo" }],
},
@@ -29,6 +30,19 @@ const gameModes = [
export default function BauhausEchoPage() {
return (
<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 />
<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">
@@ -2,6 +2,7 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import { JsonLd, breadcrumbLd } from "@/components/seo/json-ld";
import Link from "next/link";
import type { Metadata } from "next";
@@ -19,6 +20,11 @@ export const metadata: Metadata = {
export default function CustomWebsitesPage() {
return (
<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 />
<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">
+26 -4
View File
@@ -3,17 +3,18 @@ import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import Link from "next/link";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Neroswap — Aggregated DEX & CEX Orderbook Data | Such Software",
title: "Neroswap: No-KYC Monero & Bitcoin Price Aggregator | Such Software",
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" },
openGraph: {
title: "Neroswap — DEX & CEX Aggregator",
title: "Neroswap: No-KYC Monero & Bitcoin Price Aggregator",
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",
url: "https://such.software/products/neroswap",
},
@@ -95,6 +96,20 @@ const features = [
export default function NeroswapPage() {
return (
<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 />
<div
id="main-content"
@@ -214,6 +229,13 @@ export default function NeroswapPage() {
<Button variant="outline">Read the Blog Post</Button>
</Link>
</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>
<MobileNav />
</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.",
alternates: { canonical: "/products" },
openGraph: {
title: "Products | Such Software",
description: "Tools and applications built by Such Software.",
title: "Our Products & Apps: Crypto Tools, Games & Commerce | 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",
},
};
@@ -35,7 +35,7 @@ const products = [
{
slug: "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",
icon: "/images/products/occupy-wallets.png",
},
@@ -70,7 +70,7 @@ const products = [
{
slug: "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",
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 { Button } from "@repo/ui/components/button";
import Link from "next/link";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Smirk Wallet | Such Software",
description: "Non-custodial browser wallet for social tipping. Send BTC, LTC, XMR, WOW, and GRIN by Telegram or Discord username.",
title: "Smirk Wallet: Non-Custodial Crypto Tipping by Username | Such Software",
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" },
openGraph: {
title: "Smirk Wallet | Such Software",
description: "Non-custodial browser wallet for social tipping. Send BTC, LTC, XMR, WOW, and GRIN by Telegram or Discord username.",
title: "Smirk Wallet: Non-Custodial Crypto Tipping by 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",
},
};
@@ -27,6 +28,20 @@ const currencies = [
export default function SmirkWalletPage() {
return (
<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 />
<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">
@@ -130,6 +145,13 @@ export default function SmirkWalletPage() {
<Button variant="outline">GitHub</Button>
</a>
</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>
<MobileNav />
</main>
@@ -2,16 +2,17 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { SuchMoonLaunchStoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Such Moon Launch | 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.",
title: "Such Moon Launch: Pixel Rocket Arcade Game | Such Software",
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" },
openGraph: {
title: "Such Moon Launch | 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.",
title: "Such Moon Launch: Pixel Rocket Arcade Game | Such Software",
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",
},
};
@@ -41,6 +42,20 @@ const weapons = [
export default function SuchMoonLaunchPage() {
return (
<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 />
<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">
+18 -4
View File
@@ -2,17 +2,18 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { StoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Image from "next/image";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Suchoice | 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.",
title: "Suchoice: Spin the Wheel & AI Decision Maker App | Such Software",
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" },
openGraph: {
title: "Suchoice | 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.",
title: "Suchoice: Spin the Wheel & AI Decision Maker App | Such Software",
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",
images: [{ url: "/images/products/suchoice.png", width: 512, height: 512, alt: "Suchoice" }],
},
@@ -30,6 +31,19 @@ const features = [
export default function SuchoicePage() {
return (
<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 />
<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">
+22 -6
View File
@@ -2,25 +2,28 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { StoreButtons } from "@/components/store-buttons";
import { JsonLd, breadcrumbLd, softwareAppLd } from "@/components/seo/json-ld";
import Image from "next/image";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Vegan IQ | 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.",
title: "Vegan IQ: Plant-Based Trivia Game | Such Software",
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" },
openGraph: {
title: "Vegan IQ | 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.",
title: "Vegan IQ: Plant-Based Trivia Game | Such Software",
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",
images: [{ url: "/images/products/vegan-iq.png", width: 512, height: 512, alt: "Vegan IQ" }],
},
};
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: "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: "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." },
@@ -30,6 +33,19 @@ const features = [
export default function VeganIqPage() {
return (
<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 />
<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">
@@ -51,7 +67,7 @@ export default function VeganIqPage() {
<div className="prose dark:prose-invert max-w-none mb-12">
<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.
</p>
</div>
+22 -8
View File
@@ -2,16 +2,17 @@ import { Header } from "@/components/layout/header";
import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { Button } from "@repo/ui/components/button";
import { JsonLd, breadcrumbLd } from "@/components/seo/json-ld";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Webshops | Such Software",
description: "Headless Medusa commerce engines with revenue-share pricing. No upfront costs, aligned incentives.",
title: "Custom Online Stores & E-Commerce Development | Such Software",
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" },
openGraph: {
title: "Webshops | Such Software",
description: "Headless Medusa commerce engines with revenue-share pricing. No upfront costs, aligned incentives.",
title: "Custom Online Stores & E-Commerce Development | Such Software",
description: "Custom online stores with no upfront cost and revenue-share pricing. Own your data and escape template limits. Built on Medusa.",
type: "website",
},
};
@@ -19,20 +20,27 @@ export const metadata: Metadata = {
export default function WebshopsPage() {
return (
<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 />
<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">
← Back to Products
</Link>
<h1 className="text-4xl md:text-5xl font-bold mb-6 text-emerald-600 dark:text-emerald-400">
Webshops
<h1 className="text-4xl md:text-5xl font-bold mb-2 text-emerald-600 dark:text-emerald-400">
Custom Online Stores
</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">
<p className="text-xl text-muted-foreground leading-relaxed">
We build headless commerce platforms on Medusa v2 with a revenue-share model.
No massive setup fees, no monthly retainers. We only win when you make sales.
We build custom online stores with a revenue-share model: no large setup fee, no monthly
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>
</div>
@@ -81,6 +89,12 @@ export default function WebshopsPage() {
<Link href="/about#contact">
<Button size="lg">Get in Touch</Button>
</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>
<MobileNav />
@@ -3,6 +3,7 @@ import { MobileNav } from "@/components/layout/mobile-nav";
import { Card } from "@repo/ui/components/card";
import { WownerogueLinks } from "@/components/store-buttons";
import Link from "next/link";
import { JsonLd, breadcrumbLd } from "@/components/seo/json-ld";
import type { Metadata } from "next";
export const metadata: Metadata = {
@@ -70,6 +71,11 @@ const steps = [
export default function WowneroguePage() {
return (
<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) }} />
<Header />
<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";
export const metadata: Metadata = {
title: "Services | Such Software",
description: "Medusa v2 development, custom web applications, and cryptocurrency consulting. High-performance headless commerce, real-time platforms, and Web3 integration.",
title: "Medusa, Web App & Crypto Development Services | Such Software",
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: {
canonical: "/services",
},
openGraph: {
title: "Services | Such Software",
description: "Medusa v2 development, custom web applications, and cryptocurrency consulting.",
title: "Medusa, Web App & Crypto Development Services | Such Software",
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",
},
};
+6 -6
View File
@@ -5,12 +5,12 @@ import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Support | Such Software",
description: "Get help with Bauhaus Echo or any Such Software product. Report bugs, request features, or send feedback.",
title: "Support & Feedback | Such Software",
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" },
openGraph: {
title: "Support | Such Software",
description: "Get help with Bauhaus Echo or any Such Software product. Report bugs, request features, or send feedback.",
title: "Support & Feedback | Such Software",
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",
},
};
@@ -39,13 +39,13 @@ export default function SupportPage() {
<div className="section-container border-l-4 border-emerald-500">
<h3 className="font-bold mb-2">Feature Requests</h3>
<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>
</div>
<div className="section-container border-l-4 border-amber-500">
<h3 className="font-bold mb-2">General Feedback</h3>
<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>
</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` },
},
};
}