From f51a1257c531f500c7763acfd77ad0fcaa70489e Mon Sep 17 00:00:00 2001 From: jwinterm Date: Mon, 1 Jun 2026 10:21:06 -0400 Subject: [PATCH] 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 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. --- apps/web/src/app/about/page.tsx | 8 +- apps/web/src/app/apps/page.tsx | 8 +- apps/web/src/app/blog/[slug]/page.tsx | 7 ++ apps/web/src/app/blog/page.tsx | 8 +- apps/web/src/app/layout.tsx | 24 +++-- .../src/app/products/bauhaus-echo/page.tsx | 22 ++++- .../src/app/products/custom-websites/page.tsx | 6 ++ apps/web/src/app/products/neroswap/page.tsx | 30 +++++- apps/web/src/app/products/page.tsx | 8 +- .../src/app/products/smirk-wallet/page.tsx | 30 +++++- .../app/products/such-moon-launch/page.tsx | 23 ++++- apps/web/src/app/products/suchoice/page.tsx | 22 ++++- apps/web/src/app/products/vegan-iq/page.tsx | 28 ++++-- apps/web/src/app/products/webshops/page.tsx | 30 ++++-- apps/web/src/app/products/wownerogue/page.tsx | 6 ++ apps/web/src/app/services/page.tsx | 8 +- apps/web/src/app/support/page.tsx | 12 +-- apps/web/src/components/seo/json-ld.tsx | 92 +++++++++++++++++++ 18 files changed, 306 insertions(+), 66 deletions(-) create mode 100644 apps/web/src/components/seo/json-ld.tsx diff --git a/apps/web/src/app/about/page.tsx b/apps/web/src/app/about/page.tsx index 0384e4b..cf4af54 100644 --- a/apps/web/src/app/about/page.tsx +++ b/apps/web/src/app/about/page.tsx @@ -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", }, }; diff --git a/apps/web/src/app/apps/page.tsx b/apps/web/src/app/apps/page.tsx index adb77a7..d6a7dea 100644 --- a/apps/web/src/app/apps/page.tsx +++ b/apps/web/src/app/apps/page.tsx @@ -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" }], }, diff --git a/apps/web/src/app/blog/[slug]/page.tsx b/apps/web/src/app/blog/[slug]/page.tsx index fd2bac6..08559d0 100644 --- a/apps/web/src/app/blog/[slug]/page.tsx +++ b/apps/web/src/app/blog/[slug]/page.tsx @@ -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"> diff --git a/apps/web/src/app/blog/page.tsx b/apps/web/src/app/blog/page.tsx index 8e72a0d..c2114e4 100644 --- a/apps/web/src/app/blog/page.tsx +++ b/apps/web/src/app/blog/page.tsx @@ -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", }, }; diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index 150a4f3..7274c2a 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -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 diff --git a/apps/web/src/app/products/bauhaus-echo/page.tsx b/apps/web/src/app/products/bauhaus-echo/page.tsx index 4f932df..16bab1c 100644 --- a/apps/web/src/app/products/bauhaus-echo/page.tsx +++ b/apps/web/src/app/products/bauhaus-echo/page.tsx @@ -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"> diff --git a/apps/web/src/app/products/custom-websites/page.tsx b/apps/web/src/app/products/custom-websites/page.tsx index e88ac57..90a2836 100644 --- a/apps/web/src/app/products/custom-websites/page.tsx +++ b/apps/web/src/app/products/custom-websites/page.tsx @@ -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"> diff --git a/apps/web/src/app/products/neroswap/page.tsx b/apps/web/src/app/products/neroswap/page.tsx index 219cf45..061ee21 100644 --- a/apps/web/src/app/products/neroswap/page.tsx +++ b/apps/web/src/app/products/neroswap/page.tsx @@ -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> diff --git a/apps/web/src/app/products/page.tsx b/apps/web/src/app/products/page.tsx index 8dc124c..65909a0 100644 --- a/apps/web/src/app/products/page.tsx +++ b/apps/web/src/app/products/page.tsx @@ -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", }, diff --git a/apps/web/src/app/products/smirk-wallet/page.tsx b/apps/web/src/app/products/smirk-wallet/page.tsx index 536fd4a..a8d092b 100644 --- a/apps/web/src/app/products/smirk-wallet/page.tsx +++ b/apps/web/src/app/products/smirk-wallet/page.tsx @@ -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> diff --git a/apps/web/src/app/products/such-moon-launch/page.tsx b/apps/web/src/app/products/such-moon-launch/page.tsx index 6f7f347..9cc98a0 100644 --- a/apps/web/src/app/products/such-moon-launch/page.tsx +++ b/apps/web/src/app/products/such-moon-launch/page.tsx @@ -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"> diff --git a/apps/web/src/app/products/suchoice/page.tsx b/apps/web/src/app/products/suchoice/page.tsx index 1e6a684..700ab87 100644 --- a/apps/web/src/app/products/suchoice/page.tsx +++ b/apps/web/src/app/products/suchoice/page.tsx @@ -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"> diff --git a/apps/web/src/app/products/vegan-iq/page.tsx b/apps/web/src/app/products/vegan-iq/page.tsx index 22c3678..8b30dfa 100644 --- a/apps/web/src/app/products/vegan-iq/page.tsx +++ b/apps/web/src/app/products/vegan-iq/page.tsx @@ -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> diff --git a/apps/web/src/app/products/webshops/page.tsx b/apps/web/src/app/products/webshops/page.tsx index ea2fedd..eaba277 100644 --- a/apps/web/src/app/products/webshops/page.tsx +++ b/apps/web/src/app/products/webshops/page.tsx @@ -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 /> diff --git a/apps/web/src/app/products/wownerogue/page.tsx b/apps/web/src/app/products/wownerogue/page.tsx index 549dd57..6d278de 100644 --- a/apps/web/src/app/products/wownerogue/page.tsx +++ b/apps/web/src/app/products/wownerogue/page.tsx @@ -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"> diff --git a/apps/web/src/app/services/page.tsx b/apps/web/src/app/services/page.tsx index daddc4f..ed732f3 100644 --- a/apps/web/src/app/services/page.tsx +++ b/apps/web/src/app/services/page.tsx @@ -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", }, }; diff --git a/apps/web/src/app/support/page.tsx b/apps/web/src/app/support/page.tsx index ab9183e..98840f6 100644 --- a/apps/web/src/app/support/page.tsx +++ b/apps/web/src/app/support/page.tsx @@ -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're always looking for ways to improve. + Have an idea to make your app or game better? We'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> diff --git a/apps/web/src/components/seo/json-ld.tsx b/apps/web/src/components/seo/json-ld.tsx new file mode 100644 index 0000000..1d799cd --- /dev/null +++ b/apps/web/src/components/seo/json-ld.tsx @@ -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` }, + }, + }; +}