diff --git a/public/icons/cat-communication.svg b/public/icons/cat-communication.svg
new file mode 100644
index 0000000..e4cbe2b
--- /dev/null
+++ b/public/icons/cat-communication.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/icons/cat-devtools.svg b/public/icons/cat-devtools.svg
new file mode 100644
index 0000000..fa66a19
--- /dev/null
+++ b/public/icons/cat-devtools.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/icons/cat-games.svg b/public/icons/cat-games.svg
new file mode 100644
index 0000000..a06eaaf
--- /dev/null
+++ b/public/icons/cat-games.svg
@@ -0,0 +1,9 @@
+
diff --git a/public/icons/cat-media.svg b/public/icons/cat-media.svg
new file mode 100644
index 0000000..d933510
--- /dev/null
+++ b/public/icons/cat-media.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/icons/cat-other.svg b/public/icons/cat-other.svg
new file mode 100644
index 0000000..fffaf5c
--- /dev/null
+++ b/public/icons/cat-other.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/icons/cat-privacy.svg b/public/icons/cat-privacy.svg
new file mode 100644
index 0000000..7ed4970
--- /dev/null
+++ b/public/icons/cat-privacy.svg
@@ -0,0 +1,7 @@
+
diff --git a/public/icons/cat-utilities.svg b/public/icons/cat-utilities.svg
new file mode 100644
index 0000000..b79d22d
--- /dev/null
+++ b/public/icons/cat-utilities.svg
@@ -0,0 +1,8 @@
+
diff --git a/public/icons/cat-wallets.svg b/public/icons/cat-wallets.svg
new file mode 100644
index 0000000..35f95ee
--- /dev/null
+++ b/public/icons/cat-wallets.svg
@@ -0,0 +1,9 @@
+
diff --git a/public/style.css b/public/style.css
index 9e53bb5..14e1dc4 100644
--- a/public/style.css
+++ b/public/style.css
@@ -168,7 +168,9 @@ footer .fine{max-width:660px;margin:.8rem auto 0;line-height:1.7}
display:flex;flex-direction:column;gap:.6rem;
}
.cat:hover{transform:translateY(-3px);border-color:rgba(214,255,61,.35);background:var(--panel2)}
-.cat .ico{font-size:26px;line-height:1;filter:grayscale(.2)}
+.cat .ico{width:40px;height:40px;display:block;transition:transform .18s ease}
+.cat:hover .ico{transform:rotate(-3deg)}
+.cat.empty .ico{opacity:.75}
.cat h3{margin:.1rem 0 0;font-size:1.02rem}
.cat .count{color:var(--dim);font-size:12px;letter-spacing:.02em;margin-top:auto;padding-top:.4rem}
.cat.empty .count::before{content:"— ";color:var(--dim)}
diff --git a/service/pages-browse.js b/service/pages-browse.js
index 1e06f38..98ed9a8 100644
--- a/service/pages-browse.js
+++ b/service/pages-browse.js
@@ -30,22 +30,26 @@ import { softwareApplicationLd } from './seo.js';
* allow-list in listings.js. The browse grid picks up the new row
* automatically.
*/
+// `icon` is a path to an SVG under /static/icons/. Rendered via ${escapeHtml(c.blurb)} ${escapeHtml(cat.blurb)} at the
+// three sizes the templates use (26 px in the grid, 40 px in the category
+// hero, 1em in the inline chip on listing detail pages). The icons share a
+// family with the sibling .x marks: 12-radius dark square + acid-green mark.
export const CATEGORIES = [
- { slug: 'communication', icon: '💬', title: 'Communication',
+ { slug: 'communication', icon: '/static/icons/cat-communication.svg', title: 'Communication',
blurb: `Messengers, voice, boards, group chats. Clients that route through peers rather than someone else's server.` },
- { slug: 'privacy', icon: '🛡️', title: 'Privacy & security',
+ { slug: 'privacy', icon: '/static/icons/cat-privacy.svg', title: 'Privacy & security',
blurb: `Tor and mix-net tooling, key and vault managers, disk crypto. The things a conventional store delists first when a government writes.` },
- { slug: 'wallets', icon: '💸', title: 'Wallets & payments',
+ { slug: 'wallets', icon: '/static/icons/cat-wallets.svg', title: 'Wallets & payments',
blurb: `BCH wallets, cross-chain bridges, point-of-sale terminals. The chain publishes the listing and the chain settles the payment.` },
- { slug: 'games', icon: '🎮', title: 'Games',
+ { slug: 'games', icon: '/static/icons/cat-games.svg', title: 'Games',
blurb: `Standalone titles, on-chain leaderboards, in-app purchases that reach the publisher instead of a 30% toll.` },
- { slug: 'devtools', icon: '🧰', title: 'Developer tools',
+ { slug: 'devtools', icon: '/static/icons/cat-devtools.svg', title: 'Developer tools',
blurb: `SDKs, libraries, indexers, faucets, protocol utilities. The pieces other apps on this list are built out of.` },
- { slug: 'media', icon: '🎧', title: 'Media & publishing',
+ { slug: 'media', icon: '/static/icons/cat-media.svg', title: 'Media & publishing',
blurb: `Readers, players, one-file blogs, podcast hosts. Clients that find their audience without going through an aggregator.` },
- { slug: 'utilities', icon: '⚙️', title: 'Utilities',
+ { slug: 'utilities', icon: '/static/icons/cat-utilities.svg', title: 'Utilities',
blurb: `File tools, format converters, backup, calculators. The small sharp things that solve one problem and do not ask for an account first.` },
- { slug: 'other', icon: '✨', title: 'Everything else',
+ { slug: 'other', icon: '/static/icons/cat-other.svg', title: 'Everything else',
blurb: `Anything that does not fit above. New categories are proposed in-band — any listing can declare one and clients will offer to add it.` },
];
@@ -77,7 +81,7 @@ export function renderCategoriesGrid({ verbose = true } = {}) {
const emptyCls = n === 0 ? ' empty' : '';
const countLabel = `${n} ${n === 1 ? 'listing' : 'listings'}`;
return `
-
${escapeHtml(c.title)}
${verbose ? `
${escapeHtml(title)}
${escapeHtml(cat.title)}`
: '';
const scanReport = safeParse(row.scan_report);
@@ -150,7 +154,10 @@ export function appPage(req, publisher, slug) {
const iconHtml = row.icon
? `
`
- : `