diff --git a/apps/web/public/images/products/hash-bags.png b/apps/web/public/images/products/hash-bags.png
new file mode 100644
index 0000000..b971970
Binary files /dev/null and b/apps/web/public/images/products/hash-bags.png differ
diff --git a/apps/web/src/app/apps/page.tsx b/apps/web/src/app/apps/page.tsx
index 76ddc56..0af9008 100644
--- a/apps/web/src/app/apps/page.tsx
+++ b/apps/web/src/app/apps/page.tsx
@@ -19,6 +19,18 @@ export const metadata: Metadata = {
};
const apps = [
+ {
+ name: "Hash Bags",
+ tagline: "Non-custodial multi-chain wallet. Wownero-first.",
+ icon: "/images/products/hash-bags.png",
+ color: "text-emerald-600 dark:text-emerald-400",
+ borderColor: "border-emerald-500/20",
+ href: "/products/hash-bags",
+ links: [
+ { platform: "google" as const, href: "https://play.google.com/store/apps/details?id=com.suchsoftware.hashwallet" },
+ { platform: "apple" as const, href: null },
+ ],
+ },
{
name: "Bloomword",
tagline: "Grow your vocab: a daily word game with a living 3D tree",
diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css
index b71bc59..f4b32a1 100644
--- a/apps/web/src/app/globals.css
+++ b/apps/web/src/app/globals.css
@@ -327,3 +327,40 @@
html[data-splash="skip"] #splash-overlay {
display: none !important;
}
+
+/* Splash handover burst: the four pillar cells disperse as the mark hands over to the page.
+ The blobs sit where the monogram's four cells were (a 2x2 grid about the centre), so this
+ reads as the logo coming apart rather than as a generic flash.
+
+ ONLY transform and opacity animate, both compositor properties. This deliberately avoids
+ animating filter/background: the matrix animation pegged a core doing exactly that, because
+ a filter change re-renders the whole filter graph every frame. Soft edges come from a static
+ radial-gradient that is painted once and then only transformed. */
+@keyframes splash-burst {
+ from { transform: translate(-50%, -50%) scale(0.35); opacity: var(--burst-peak); }
+ to { transform: translate(-50%, -50%) scale(3.1); opacity: 0; }
+}
+.splash-burst-blob {
+ position: absolute;
+ width: 34vmin;
+ height: 34vmin;
+ border-radius: 9999px;
+ opacity: 0;
+ will-change: transform, opacity;
+ animation: splash-burst 420ms cubic-bezier(0.16, 0.84, 0.44, 1) forwards;
+ /* Half-transparent by construction: the gradient's own alpha never reaches 1, and
+ --burst-peak holds the whole layer under half opacity. */
+ --burst-peak: 0.42;
+}
+.dark .splash-burst-blob { --burst-peak: 0.5; }
+.splash-burst-s { background: radial-gradient(circle, rgb(26 92 56 / 0.55), transparent 70%); }
+.splash-burst-u { background: radial-gradient(circle, rgb(184 54 112 / 0.55), transparent 70%); }
+.splash-burst-c { background: radial-gradient(circle, rgb(23 106 146 / 0.55), transparent 70%); }
+.splash-burst-h { background: radial-gradient(circle, rgb(18 133 126 / 0.55), transparent 70%); }
+.dark .splash-burst-s { background: radial-gradient(circle, rgb(53 146 92 / 0.55), transparent 70%); }
+.dark .splash-burst-u { background: radial-gradient(circle, rgb(232 104 158 / 0.55), transparent 70%); }
+.dark .splash-burst-c { background: radial-gradient(circle, rgb(74 173 224 / 0.55), transparent 70%); }
+.dark .splash-burst-h { background: radial-gradient(circle, rgb(58 212 198 / 0.55), transparent 70%); }
+@media (prefers-reduced-motion: reduce) {
+ .splash-burst-blob { display: none; }
+}
diff --git a/apps/web/src/app/products/hash-bags/page.tsx b/apps/web/src/app/products/hash-bags/page.tsx
new file mode 100644
index 0000000..efeba21
--- /dev/null
+++ b/apps/web/src/app/products/hash-bags/page.tsx
@@ -0,0 +1,142 @@
+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";
+
+const DESCRIPTION =
+ "Open-source, non-custodial wallet for Monero, Wownero, Bitcoin, and nine more chains. Wownero-first, with Ledger, Trezor, and BitBox support. Free on Android; iOS and desktop coming soon.";
+
+export const metadata: Metadata = {
+ title: "Hash Bags: Non-Custodial Multi-Chain Crypto Wallet | Such Software",
+ description: DESCRIPTION,
+ alternates: { canonical: "/products/hash-bags" },
+ openGraph: {
+ title: "Hash Bags: Non-Custodial Multi-Chain Crypto Wallet | Such Software",
+ description: DESCRIPTION,
+ type: "website",
+ images: ["/images/branding/OG_banner_v2_light.png"],
+ },
+ twitter: {
+ card: "summary_large_image",
+ images: ["/images/branding/OG_banner_v2_light.png"],
+ },
+};
+
+const chains = [
+ { name: "Monero", ticker: "XMR" },
+ { name: "Wownero", ticker: "WOW" },
+ { name: "Bitcoin", ticker: "BTC" },
+ { name: "Litecoin", ticker: "LTC, incl. MWEB" },
+ { name: "Bitcoin Cash", ticker: "BCH" },
+ { name: "Dogecoin", ticker: "DOGE" },
+ { name: "Nano", ticker: "XNO" },
+ { name: "Ethereum", ticker: "ETH + ERC-20s" },
+ { name: "Polygon", ticker: "POL" },
+ { name: "Base", ticker: "BASE" },
+ { name: "Arbitrum", ticker: "ARB" },
+ { name: "BNB Smart Chain", ticker: "BSC" },
+];
+
+export default function HashBagsPage() {
+ return (
+
+
+
+
+
+
+ ← Back to Products
+
+
+
+
+
+ Hash Bags
+
+
+
+
+
+ A wallet for your coins. Hash Bags is an open-source, non-custodial, multi-chain
+ crypto wallet: your keys and your funds stay on your device, and we never hold
+ either. Free on Android today, with iOS and desktop on the way.
+
+
+
+
+
+
+ Live on Google Play. The iOS build is in review, and macOS, Linux, and Windows
+ follow after that.
+
+
+
+
+
Why it exists
+
+
+ Cake Wallet ended Wownero support in early 2026. Hash Bags is a fork that keeps
+ Wownero a first-class mobile experience, maintained by the project's founder,
+ while shipping a slimmer and more focused multi-coin wallet without the chains and
+ integrations we would rather not maintain or recommend.
+
+
+
+
+
+
Twelve chains
+
+ {chains.map((c) => (
+
+
{c.name}
+
{c.ticker}
+
+ ))}
+
+
+
+
+
Hardware wallets
+
+ Ledger, Trezor, and BitBox are supported, so the keys can stay off the phone
+ entirely if that is how you prefer to hold them.
+
+
+
+
+
+ ← All products
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/app/products/page.tsx b/apps/web/src/app/products/page.tsx
index df48767..eafa345 100644
--- a/apps/web/src/app/products/page.tsx
+++ b/apps/web/src/app/products/page.tsx
@@ -31,6 +31,14 @@ const products: Product[] = [
icon: "/images/products/custom-websites.svg",
releaseDate: "2025-01-01",
},
+ {
+ slug: "hash-bags",
+ title: "Hash Bags",
+ description: "Open-source, non-custodial wallet for Monero, Wownero, Bitcoin, and nine more chains. Wownero-first, with Ledger, Trezor, and BitBox support. Android now; iOS and desktop coming.",
+ color: "emerald",
+ icon: "/images/products/hash-bags.png",
+ releaseDate: "2026-08-17",
+ },
{
slug: "smirk-wallet",
title: "Smirk Wallet",
diff --git a/apps/web/src/app/sitemap.ts b/apps/web/src/app/sitemap.ts
index 2caf56f..c42d583 100644
--- a/apps/web/src/app/sitemap.ts
+++ b/apps/web/src/app/sitemap.ts
@@ -17,6 +17,7 @@ export default function sitemap(): MetadataRoute.Sitemap {
{ url: `${base}/products/neroswap`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/webshops`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/custom-websites`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
+ { url: `${base}/products/hash-bags`, lastModified: updated, changeFrequency: "weekly", priority: 0.8 },
{ url: `${base}/products/smirk-wallet`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/bauhaus-echo`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
{ url: `${base}/products/vegan-iq`, lastModified: updated, changeFrequency: "monthly", priority: 0.8 },
diff --git a/apps/web/src/components/landing/hero-splash.tsx b/apps/web/src/components/landing/hero-splash.tsx
index 1bf68bf..afa99e7 100644
--- a/apps/web/src/components/landing/hero-splash.tsx
+++ b/apps/web/src/components/landing/hero-splash.tsx
@@ -12,6 +12,16 @@ const SPLASH_MS = 1337;
// gate stacked on the first.
const FADE_MS = 260;
+// The four monogram cells, in the 2x2 grid the mark ends on: S and C in the left column,
+// U and H in the right. The burst throws one blob out of each, in that letter's own colour,
+// so the handover reads as the logo dispersing rather than as an unrelated flash.
+const BURST_CELLS = [
+ { key: "s", left: "44%", top: "44%" },
+ { key: "u", left: "56%", top: "44%" },
+ { key: "c", left: "44%", top: "56%" },
+ { key: "h", left: "56%", top: "56%" },
+] as const;
+
/**
* First-load splash: the pillar signature.
*
@@ -77,30 +87,55 @@ export function HeroSplash({ onEnter, leaving }: { onEnter: () => void; leaving?
};
}, [leaving]);
+ const fade = { transitionDuration: `${FADE_MS}ms`, opacity: fadeOut ? 0 : 1 };
+
return (
- {/* The mark is the only content, so it is the LCP candidate: an is a valid
- LCP element where an inline
);
}
diff --git a/apps/web/src/components/landing/home-shell.tsx b/apps/web/src/components/landing/home-shell.tsx
index cd7dc0f..df8e100 100644
--- a/apps/web/src/components/landing/home-shell.tsx
+++ b/apps/web/src/components/landing/home-shell.tsx
@@ -140,7 +140,8 @@ export function HomeShell({ children }: { children: ReactNode }) {
setTimeout(() => {
setSplashUp(false);
tryRelease();
- }, 300); // the overlay's 260ms cross-fade, plus a frame
+ }, 540); // the 420ms burst plus its 75ms stagger, not just the 260ms cross-fade:
+ // unmounting on the fade alone cut the blobs off mid-flight
};
return (