prometheus: SVG icons for the 8 categories
Replaces the emoji glyphs (💬🛡️💸🎮🧰🎧⚙️✨) in the category grid with dedicated SVG icons under /static/icons/cat-<slug>.svg, following the same visual family as the sibling .x marks (theseus, sirius, ariadne, pithos, aegis): 12-radius dark square + acid-green mark, legible at 16 px tab-icon size and 40 px grid size. Designs chosen to not blur with the Silent Mode product icons also rendered on the site: cat-communication speech bubble + three dots (not an envelope — too close to the Hephaestus letter imagery elsewhere) cat-privacy shield + checkmark (DISTINCT from aegis.svg's shield+chevron, so category + listing don't collide) cat-wallets stacked coins + B serif (hints at the BCH coin) cat-games stylised controller (d-pad + two action buttons) cat-devtools code brackets < / > (reads as "code" at 16 px where a wrench/hammer blurs with the Utilities gear) cat-media play triangle in a circle (equally "player" and "publish/broadcast") cat-utilities gear with hollow centre (less visual weight than the Media circle; the two side-by-side don't crowd each other) cat-other sparkle burst + two smaller sparkles (sibling of sirius.svg's 4-point mark, extras so the two don't look identical) pages-browse.js: CATEGORIES[].icon is now the path to the SVG, not an emoji string. The three places the icon rendered — grid card, category hero, and the inline chip on listing detail — all now use <img> at the size they need (40, 56, 14 respectively). The CSS entry `.cat .ico` loses the font-size + grayscale (text styling) and gains width/height for the image plus a tiny hover rotation that matches the existing lift transform. Also: when a listing has no `icon` of its own, the detail page's icon slot now falls back to the category's SVG (centered + padded) instead of a plain 📦 emoji, so the detail hero always has a vector icon.
8
public/icons/cat-communication.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Communication — speech bubble with a tail hooking down and left. -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<path d="M12 20 Q 12 12 20 12 L 44 12 Q 52 12 52 20 L 52 36 Q 52 44 44 44 L 26 44 L 16 52 L 18 44 Q 12 44 12 36 Z" fill="#d6ff3d"/>
|
||||
<circle cx="23" cy="28" r="2.5" fill="#0b0e14"/>
|
||||
<circle cx="32" cy="28" r="2.5" fill="#0b0e14"/>
|
||||
<circle cx="41" cy="28" r="2.5" fill="#0b0e14"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 489 B |
8
public/icons/cat-devtools.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Developer tools — code brackets < / >. Reads at 16 px as "code" rather
|
||||
than hammer/wrench shapes which blur with the Utilities gear. -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<path d="M22 20 L10 32 L22 44" fill="none" stroke="#d6ff3d" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M42 20 L54 32 L42 44" fill="none" stroke="#d6ff3d" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M37 16 L27 48" stroke="#d6ff3d" stroke-width="4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 618 B |
9
public/icons/cat-games.svg
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Games — a stylised game controller: rounded body + d-pad on the left,
|
||||
two action buttons on the right. -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<path d="M14 32 Q 14 20 24 20 L 40 20 Q 50 20 50 32 L 50 42 Q 50 50 42 48 L 36 44 L 28 44 L 22 48 Q 14 50 14 42 Z" fill="#d6ff3d"/>
|
||||
<path d="M20 32 L28 32 M24 28 L24 36" stroke="#0b0e14" stroke-width="3" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="30" r="3" fill="#0b0e14"/>
|
||||
<circle cx="45" cy="34" r="3" fill="#0b0e14"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 578 B |
7
public/icons/cat-media.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Media & publishing — a play triangle sitting inside a circle. Reads
|
||||
equally as "player" and "publish/broadcast". -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<circle cx="32" cy="32" r="22" fill="#d6ff3d"/>
|
||||
<path d="M27 22 L45 32 L27 42 Z" fill="#0b0e14"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 359 B |
8
public/icons/cat-other.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Everything else — one big sparkle plus two small ones. The sibling of
|
||||
sirius.svg's 4-point mark, with extras so the two don't look identical. -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<path d="M32 10 L36 26 L52 30 L36 34 L32 50 L28 34 L12 30 L28 26 Z" fill="#d6ff3d"/>
|
||||
<path d="M48 48 L50 42 L52 48 L50 54 Z" fill="#d6ff3d" opacity="0.75"/>
|
||||
<path d="M14 50 L16 44 L18 50 L16 56 Z" fill="#d6ff3d" opacity="0.75"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 521 B |
7
public/icons/cat-privacy.svg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Privacy & security — shield with a checkmark inside. Distinct from the
|
||||
Aegis icon (chevron) so a category card and a listing card don't blur. -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<path d="M32 8 L50 16 L50 32 C 50 44 42 52 32 56 C 22 52 14 44 14 32 L 14 16 Z" fill="#d6ff3d"/>
|
||||
<path d="M22 32 L29 39 L42 24" fill="none" stroke="#0b0e14" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 513 B |
8
public/icons/cat-utilities.svg
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Utilities — gear with 8 teeth. The hollow centre keeps the acid mass
|
||||
below the Media circle's visual weight so the two don't crowd each
|
||||
other in the grid. -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<path d="M32 10 L35 10 L37 16 L42 17 L46 13 L48 15 L46 21 L50 25 L56 23 L57 26 L52 30 L52 34 L57 38 L56 41 L50 39 L46 43 L48 49 L46 51 L42 47 L37 48 L35 54 L32 54 L29 54 L27 48 L22 47 L18 51 L16 49 L18 43 L14 39 L8 41 L7 38 L12 34 L12 30 L7 26 L8 23 L14 25 L18 21 L16 15 L18 13 L22 17 L27 16 L29 10 Z" fill="#d6ff3d"/>
|
||||
<circle cx="32" cy="32" r="8" fill="#0b0e14"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 676 B |
9
public/icons/cat-wallets.svg
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<!-- Wallets & payments — stacked coins with a small notch to hint at the BCH 'B'. -->
|
||||
<rect width="64" height="64" rx="12" fill="#0b0e14"/>
|
||||
<ellipse cx="32" cy="20" rx="18" ry="6" fill="#d6ff3d"/>
|
||||
<path d="M14 20 L14 44 Q 14 50 32 50 Q 50 50 50 44 L 50 20 Q 50 26 32 26 Q 14 26 14 20 Z" fill="#d6ff3d" opacity="0.5"/>
|
||||
<ellipse cx="32" cy="32" rx="18" ry="6" fill="#d6ff3d"/>
|
||||
<path d="M14 32 L14 44 Q 14 50 32 50 Q 50 50 50 44 L 50 32" fill="none" stroke="#d6ff3d" stroke-width="2.5" stroke-linejoin="round"/>
|
||||
<text x="32" y="36" text-anchor="middle" font-family="serif" font-weight="700" font-size="10" fill="#0b0e14">B</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 712 B |
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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 <img> 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 `<a class="cat${emptyCls}" href="/c/${c.slug}">
|
||||
<div class="ico">${c.icon}</div>
|
||||
<img class="ico" src="${c.icon}" alt="" width="40" height="40">
|
||||
<h3>${escapeHtml(c.title)}</h3>
|
||||
${verbose ? `<p>${escapeHtml(c.blurb)}</p>` : ''}
|
||||
<span class="count">${countLabel}</span>
|
||||
|
|
@ -102,7 +106,7 @@ export function categoryPage(req, slug) {
|
|||
here,
|
||||
body: `
|
||||
<header class="hero compact">
|
||||
<div class="mark" style="font-size:40px">${cat.icon}</div>
|
||||
<img class="mark" src="${cat.icon}" alt="" width="56" height="56" style="display:block;margin:0 auto 1rem">
|
||||
<h1>${escapeHtml(title)}</h1>
|
||||
<p class="tag">${escapeHtml(cat.blurb)}</p>
|
||||
</header>
|
||||
|
|
@ -136,7 +140,7 @@ export function appPage(req, publisher, slug) {
|
|||
|
||||
const cat = CATEGORY_BY_SLUG[row.category];
|
||||
const catChip = cat
|
||||
? `<a href="/c/${cat.slug}" class="pill p-upcoming" style="text-decoration:none">${cat.icon} ${escapeHtml(cat.title)}</a>`
|
||||
? `<a href="/c/${cat.slug}" class="pill p-upcoming" style="text-decoration:none;display:inline-flex;align-items:center;gap:4px"><img src="${cat.icon}" alt="" width="14" height="14" style="vertical-align:-2px"> ${escapeHtml(cat.title)}</a>`
|
||||
: '';
|
||||
|
||||
const scanReport = safeParse(row.scan_report);
|
||||
|
|
@ -150,7 +154,10 @@ export function appPage(req, publisher, slug) {
|
|||
const iconHtml = row.icon
|
||||
? `<img src="${escapeAttr(row.icon)}" alt="" width="80" height="80"
|
||||
style="width:80px;height:80px;border-radius:16px;border:1px solid var(--line);background:var(--panel);object-fit:cover">`
|
||||
: `<div style="width:80px;height:80px;border-radius:16px;border:1px solid var(--line);background:var(--panel);display:grid;place-items:center;font-size:36px">${cat?.icon || '📦'}</div>`;
|
||||
: cat?.icon
|
||||
? `<img src="${cat.icon}" alt="" width="80" height="80"
|
||||
style="width:80px;height:80px;border-radius:16px;border:1px solid var(--line);background:var(--panel);padding:12px">`
|
||||
: `<div style="width:80px;height:80px;border-radius:16px;border:1px solid var(--line);background:var(--panel);display:grid;place-items:center;font-size:36px">📦</div>`;
|
||||
|
||||
return {
|
||||
title: `${row.name} by ${row.publisher}`,
|
||||
|
|
|
|||