Tweak copy and add links

This commit is contained in:
jwinterm
2026-06-01 11:10:31 -04:00
parent 4b2ae5646c
commit 254e969c36
7 changed files with 165 additions and 167 deletions
+17 -17
View File
@@ -19,19 +19,6 @@ export const metadata: Metadata = {
};
const apps = [
{
name: "Such Moon Launch",
tagline: "Vry rocket. Much landing. Wow!",
icon: "/images/products/such-moon-launch.png",
color: "text-amber-600 dark:text-amber-400",
borderColor: "border-amber-500/20",
href: "/products/such-moon-launch",
links: [
// null = render disabled "Coming Soon" button; flip to real URLs once live.
{ platform: "google" as const, href: null },
{ platform: "apple" as const, href: null },
],
},
{
name: "Vegan IQ",
tagline: "Plant-based trivia backed by real data",
@@ -68,20 +55,33 @@ const apps = [
{ platform: "apple" as const, href: "https://apps.apple.com/us/app/suchoice/id6759626658" },
],
},
{
name: "Such Moon Launch",
tagline: "Vry rocket. Much landing. Wow!",
icon: "/images/products/such-moon-launch.png",
color: "text-amber-600 dark:text-amber-400",
borderColor: "border-amber-500/20",
href: "/products/such-moon-launch",
links: [
// Google Play live; Apple in review (null renders a disabled "Coming Soon").
{ platform: "google" as const, href: "https://play.google.com/store/apps/details?id=com.suchsoftware.suchmoonlaunch" },
{ platform: "apple" as const, href: null },
],
},
];
export default function LinksPage() {
return (
<main className="relative min-h-screen flex flex-col items-center bg-background text-foreground">
<Header />
<div id="main-content" className="z-10 w-full max-w-lg mx-auto py-20 px-4 pb-24 md:pb-20">
<div id="main-content" className="z-10 w-full max-w-3xl mx-auto py-20 px-4 pb-24 md:pb-20">
<h1 className="text-3xl font-bold mb-2 text-center">Such Software</h1>
<p className="text-muted-foreground text-center mb-10">Our apps</p>
<div className="space-y-8">
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
{apps.map((app, i) => (
<ScrollReveal key={app.name} delay={i * 0.08}>
<div className={`rounded-xl border ${app.borderColor} bg-card p-6`}>
<ScrollReveal key={app.name} delay={(i % 2) * 0.08}>
<div className={`h-full rounded-xl border ${app.borderColor} bg-card p-6`}>
<div className="flex items-center gap-4 mb-3">
<Image
src={app.icon}
+14 -14
View File
@@ -18,20 +18,6 @@ export const metadata: Metadata = {
};
const products = [
{
slug: "wownerogue",
title: "Wownerogue",
description: "A provably-fair multiplayer roguelike synced to Monero and Wownero block times. Play free in the browser, escape the dungeon, earn on-chain payouts.",
color: "orange",
icon: "/images/products/privacy-labs.png",
},
{
slug: "neroswap",
title: "Neroswap",
description: "Aggregated DEX & CEX orderbook data. Compare Monero, Wownero, and Bitcoin prices across 6 exchanges in real time.",
color: "yellow",
icon: "/images/products/neroswap.png",
},
{
slug: "webshops",
title: "Webshops",
@@ -81,6 +67,20 @@ const products = [
color: "amber",
icon: "/images/products/such-moon-launch.png",
},
{
slug: "wownerogue",
title: "Wownerogue",
description: "A provably-fair multiplayer roguelike synced to Monero and Wownero block times. Play free in the browser, escape the dungeon, earn on-chain payouts.",
color: "orange",
icon: "/images/products/privacy-labs.png",
},
{
slug: "neroswap",
title: "Neroswap",
description: "Aggregated DEX & CEX orderbook data. Compare Monero, Wownero, and Bitcoin prices across 6 exchanges in real time.",
color: "yellow",
icon: "/images/products/neroswap.png",
},
];
const colorClasses: Record<string, string> = {
+18 -14
View File
@@ -117,20 +117,24 @@ export default function WowneroguePage() {
<h2 className="text-2xl font-bold mb-6">Two Worlds</h2>
<div className="grid md:grid-cols-2 gap-6 mb-12">
<Card className="glass-card border-orange-500/20">
<h3 className="text-xl font-bold mb-2">play.wowne.ro</h3>
<p className="text-muted-foreground text-sm">
The Wownero mainnet instance: free to play, with paid entry for the Hall of Champions.
No crypto payouts, all the tension. The friendly place to learn the game.
</p>
</Card>
<Card className="glass-card border-orange-500/20">
<h3 className="text-xl font-bold mb-2">monerogue.app</h3>
<p className="text-muted-foreground text-sm">
The Monero stagenet instance, where escaping pays out stagenet XMR from a house wallet.
Stagenet coins have no real value, so it is a safe sandbox for the full payout loop.
</p>
</Card>
<a href="https://play.wowne.ro" target="_blank" rel="noopener noreferrer">
<Card className="glass-card border-orange-500/20 hover:border-orange-500/50 transition-colors cursor-pointer h-full">
<h3 className="text-xl font-bold mb-2 text-orange-600 dark:text-orange-400">play.wowne.ro &rarr;</h3>
<p className="text-muted-foreground text-sm">
The Wownero mainnet instance: free to play, with paid entry for the Hall of Champions.
No crypto payouts, all the tension. The friendly place to learn the game.
</p>
</Card>
</a>
<a href="https://monerogue.app" target="_blank" rel="noopener noreferrer">
<Card className="glass-card border-orange-500/20 hover:border-orange-500/50 transition-colors cursor-pointer h-full">
<h3 className="text-xl font-bold mb-2 text-orange-600 dark:text-orange-400">monerogue.app &rarr;</h3>
<p className="text-muted-foreground text-sm">
The Monero stagenet instance, where escaping pays out stagenet XMR from a house wallet.
Stagenet coins have no real value, so it is a safe sandbox for the full payout loop.
</p>
</Card>
</a>
</div>
<h2 className="text-2xl font-bold mb-6">How a Run Works</h2>
@@ -44,7 +44,7 @@ export const FounderSignature = () => {
He previously co-founded <span className="text-foreground font-semibold">Patrn LLC</span> (art101.io), a series of art-history NFT drops, and <span className="text-foreground font-semibold">Tabby Labs</span>, which was developing a cryptocurrency wallet with an interactive world inside.
</p>
<p className="text-muted-foreground leading-relaxed mt-4">
He most recently taught engineering at <span className="text-foreground font-semibold">Cecil College</span> and senior-level materials engineering (microfabrication, nanomaterials) at <span className="text-foreground font-semibold">Frostburg State University</span>. Active in cryptocurrency since 2014; <a href="https://github.com/jwinterm" target="_blank" rel="noopener noreferrer" className="text-foreground font-semibold underline decoration-primary/50 hover:text-primary transition-colors">GitHub</a> for the receipts.
He most recently taught engineering at <span className="text-foreground font-semibold">Cecil College</span> and senior-level materials engineering (microfabrication, nanomaterials) at <span className="text-foreground font-semibold">Frostburg State University</span>. Active in cryptocurrency since 2014, with a public track record on <a href="https://github.com/jwinterm" target="_blank" rel="noopener noreferrer" className="text-foreground font-semibold underline decoration-primary/50 hover:text-primary transition-colors">GitHub</a>.
</p>
</div>
</div>
@@ -2,103 +2,97 @@ import { Card } from "@repo/ui/components/card";
import Image from "next/image";
import Link from "next/link";
// Literal class strings per color so Tailwind's JIT keeps them (no dynamic interpolation).
const cardBg: Record<string, string> = {
orange: "!bg-orange-50/95 dark:!bg-orange-950/95 !border-orange-500/20",
yellow: "!bg-yellow-50/95 dark:!bg-yellow-950/95 !border-yellow-500/20",
amber: "!bg-amber-50/95 dark:!bg-amber-950/95 !border-amber-500/20",
emerald: "!bg-emerald-50/95 dark:!bg-emerald-950/95 !border-emerald-500/20",
blue: "!bg-blue-50/95 dark:!bg-blue-950/95 !border-blue-500/20",
green: "!bg-green-50/95 dark:!bg-green-950/95 !border-green-500/20",
};
const titleColor: Record<string, string> = {
orange: "text-orange-700 dark:text-orange-400",
yellow: "text-yellow-700 dark:text-yellow-400",
amber: "text-amber-700 dark:text-amber-400",
emerald: "text-emerald-700 dark:text-emerald-400",
blue: "text-blue-700 dark:text-blue-400",
green: "text-green-700 dark:text-green-400",
};
const imgBorder: Record<string, string> = {
orange: "border-orange-500/10",
yellow: "border-yellow-500/10",
amber: "border-amber-500/10",
emerald: "border-emerald-500/10",
blue: "border-blue-500/10",
green: "border-green-500/10",
};
const spanClass: Record<number, string> = {
1: "md:col-span-1",
2: "md:col-span-2",
};
type Item = {
name: string;
tagline: string;
color: keyof typeof titleColor;
span: 1 | 2;
image: string;
alt: string;
href: string;
external?: boolean;
};
// Order matters: spans 2,1,1,2,2,1 tile a 3-column grid with no gaps while alternating the
// wide side row to row for a balanced bento look.
const items: Item[] = [
{ name: "Wownerogue", tagline: "Provably-fair roguelike, synced to crypto block times.", color: "orange", span: 2, image: "/images/products/privacy-labs.png", alt: "Screenshot of the Wownerogue roguelike dungeon-crawler", href: "/products/wownerogue" },
{ name: "Neroswap", tagline: "DEX & CEX orderbook aggregator.", color: "yellow", span: 1, image: "/images/products/neroswap.png", alt: "Screenshot of the Neroswap orderbook aggregator", href: "https://neroswap.com", external: true },
{ name: "Smirk Wallet", tagline: "Non-custodial browser wallet with social tipping.", color: "amber", span: 1, image: "/images/products/smirk-wallet.png", alt: "Smirk Wallet browser extension", href: "https://smirk.cash", external: true },
{ name: "Occupy Wallets", tagline: "Custom e-commerce storefront we build and host for an art brand.", color: "emerald", span: 2, image: "/images/products/occupy-wallets.png", alt: "Occupy Wallets online store and gallery", href: "https://occupywallets.art", external: true },
{ name: "Bauhaus Echo", tagline: "Visual memory puzzle game.", color: "blue", span: 2, image: "/images/products/bauhaus-echo.png", alt: "Bauhaus Echo visual memory puzzle game", href: "/products/bauhaus-echo" },
{ name: "Vegan IQ", tagline: "Plant-based trivia game.", color: "green", span: 1, image: "/images/products/vegan-iq.png", alt: "Vegan IQ plant-based trivia game", href: "/products/vegan-iq" },
];
function BentoCard({ item, delay }: { item: Item; delay: number }) {
const inner = (
<Card className={`h-full glass-card group min-h-[300px] rounded-[2.5rem] ${cardBg[item.color]} cursor-pointer`}>
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
<h3 className={`text-xl font-bold ${titleColor[item.color]}`}>{item.name}</h3>
<p className="text-sm mt-1 text-foreground font-semibold">{item.tagline}</p>
</div>
<div className={`absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border ${imgBorder[item.color]} shadow-inner`}>
<Image
src={item.image}
alt={item.alt}
fill
className="object-contain p-2 opacity-60 transition-transform duration-1000 group-hover:scale-105"
sizes={item.span === 2 ? "(max-width: 768px) 100vw, 66vw" : "(max-width: 768px) 100vw, 33vw"}
/>
</div>
</Card>
);
const className = `bento-item ${spanClass[item.span]}`;
const style = { animationDelay: `${delay}ms` };
return item.external ? (
<a href={item.href} target="_blank" rel="noopener noreferrer" className={className} style={style}>
{inner}
</a>
) : (
<Link href={item.href} className={className} style={style}>
{inner}
</Link>
);
}
export const PortfolioGrid = () => {
return (
<section className="grid grid-cols-1 md:grid-cols-4 auto-rows-[300px] gap-6 w-full max-w-7xl mx-auto p-4 z-10 relative">
{/* Row 1: Wownerogue (2x1) */}
<Link href="/products/wownerogue" className="bento-item md:col-span-2" style={{ animationDelay: "0ms" }}>
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-orange-50/95 dark:!bg-orange-950/95 !border-orange-500/20 cursor-pointer">
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
<h3 className="text-xl font-bold text-orange-700 dark:text-orange-400">Wownerogue</h3>
<p className="text-sm mt-1 text-foreground font-semibold">Provably-fair multiplayer roguelike, synced to Monero and Wownero blocks.</p>
</div>
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-orange-500/10 shadow-inner">
<Image
src="/images/products/privacy-labs.png"
alt="Screenshot of the Wownerogue and Monerogue roguelike dungeon-crawler game"
fill
className="object-contain p-2 opacity-60 transition-transform duration-1000 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 50vw"
/>
</div>
</Card>
</Link>
{/* Row 1: Smirk Wallet (2x1) */}
<Link href="https://smirk.cash" target="_blank" rel="noopener noreferrer" className="bento-item md:col-span-2" style={{ animationDelay: "75ms" }}>
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-amber-50/95 dark:!bg-amber-950/95 !border-amber-500/20 cursor-pointer">
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
<h3 className="text-xl font-bold text-amber-700 dark:text-amber-400">Smirk Wallet</h3>
<p className="text-sm mt-1 text-foreground font-semibold">Non-custodial browser wallet with encrypted social tipping.</p>
</div>
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-amber-500/10 shadow-inner flex items-center justify-center">
<Image
src="/images/products/smirk-wallet.png"
alt="Screenshot of Smirk Wallet browser extension showing multi-currency support for BTC, LTC, XMR, WOW, and GRIN"
fill
className="object-contain p-4 opacity-60 transition-transform duration-1000 group-hover:scale-110"
sizes="(max-width: 768px) 100vw, 50vw"
/>
</div>
</Card>
</Link>
{/* Row 2: Neroswap (2x1) */}
<Link href="https://neroswap.com" target="_blank" rel="noopener noreferrer" className="bento-item md:col-span-2" style={{ animationDelay: "150ms" }}>
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-yellow-50/95 dark:!bg-yellow-950/95 !border-yellow-500/20 cursor-pointer">
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
<h3 className="text-xl font-bold text-yellow-700 dark:text-yellow-400">Neroswap</h3>
<p className="text-sm mt-1 text-foreground font-semibold">DEX &amp; CEX orderbook aggregator.</p>
</div>
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-yellow-500/10 shadow-inner">
<Image
src="/images/products/neroswap.png"
alt="Screenshot of Neroswap DEX and CEX orderbook aggregator interface"
fill
className="object-cover opacity-50 transition-transform duration-1000 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 50vw"
/>
</div>
</Card>
</Link>
{/* Row 2: Bauhaus Echo (1x1) */}
<Link href="/products/bauhaus-echo" className="bento-item md:col-span-1" style={{ animationDelay: "225ms" }}>
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] !bg-blue-50/95 dark:!bg-blue-950/95 !border-blue-500/20 cursor-pointer">
<div className="absolute inset-x-0 top-0 z-20 p-6 bg-gradient-to-b from-background/95 to-transparent pointer-events-none">
<h3 className="text-xl font-bold text-blue-700 dark:text-blue-400">Bauhaus Echo</h3>
<p className="text-sm mt-1 text-foreground font-semibold">Visual memory puzzle game.</p>
</div>
<div className="absolute inset-x-4 bottom-4 top-24 rounded-lg overflow-hidden z-10 bg-black/5 dark:bg-white/5 border border-blue-500/10 shadow-inner">
<Image
src="/images/products/bauhaus-echo.png"
alt="Screenshot of Bauhaus Echo visual memory puzzle game with geometric tiles"
fill
className="object-contain p-2 opacity-50 transition-transform duration-1000 group-hover:scale-110"
sizes="(max-width: 768px) 100vw, 25vw"
/>
</div>
</Card>
</Link>
{/* Row 2: Occupy Wallets (1x1) */}
<Link href="https://occupywallets.art" target="_blank" rel="noopener noreferrer" className="bento-item md:col-span-1" style={{ animationDelay: "300ms" }}>
<Card className="h-full glass-card group min-h-[300px] rounded-[2.5rem] cursor-pointer">
<div className="absolute inset-0 z-20 p-6 flex flex-col justify-center items-center text-center pointer-events-none">
<h3 className="text-2xl font-bold text-zinc-900 dark:text-white drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-6 py-2">Occupy Wallets</h3>
<p className="text-zinc-900 dark:text-zinc-200 font-bold drop-shadow-sm bg-white/60 dark:bg-black/60 backdrop-blur-3xl rounded-xl px-4 py-2 mt-4 text-sm">High-fidelity gallery for fiscal graffiti.</p>
</div>
<div className="absolute inset-4 rounded-xl overflow-hidden z-10 bg-zinc-200/50 dark:bg-zinc-800/50">
<Image
src="/images/products/occupy-wallets.png"
alt="Screenshot of Occupy Wallets gallery displaying illustrated artwork with political and economic commentary"
fill
className="object-contain p-4 opacity-60 transition-transform duration-1000 group-hover:scale-105"
sizes="(max-width: 768px) 100vw, 25vw"
/>
</div>
</Card>
</Link>
<section className="grid grid-cols-1 md:grid-cols-3 auto-rows-[300px] gap-6 w-full max-w-7xl mx-auto p-4 z-10 relative">
{items.map((item, i) => (
<BentoCard key={item.name} item={item} delay={i * 75} />
))}
</section>
);
};
+2 -4
View File
@@ -72,13 +72,11 @@ export function SuchoiceStoreButtons() {
}
export function SuchMoonLaunchStoreButtons() {
// Both stores submitted 2026-05-18; pending review. Flip null → real URLs
// once the app is live (Apple assigns the appstore.com /id<NUM> at first
// approval; Google URL is predictable as id=com.suchsoftware.suchmoonlaunch).
// Google Play is live; Apple still in review (flip null → real URL once approved).
return (
<StoreButtons
links={[
{ platform: "google", href: null },
{ platform: "google", href: "https://play.google.com/store/apps/details?id=com.suchsoftware.suchmoonlaunch" },
{ platform: "apple", href: null },
]}
extraLinks={[