feat(apps): the Hash Bags icon draws itself on

An animated icon has to still BE the icon, so the geometry is copied verbatim
from the app's own icon source (hash-wallet
assets/images/hash_wallet_app_icon_source.svg): full-bleed #1a5c38, four white
lines at stroke-width 80 with square caps, horizontals at y=412/612, verticals
at x=412/612, all spanning 212..812. It draws on in 850ms and settles into a
frame that is pixel-for-pixel the static PNG.

Not hash.boats/splash.svg, which was the obvious candidate and is wrong twice:
its # is slanted where the shipped icon is upright, and it insets its green box
inside a transparent margin. The site rounds icons with a 22% CSS radius, so
that margin would have shown card background at the corners, which is the exact
artifact we just finished removing from the icon set.

CSS rather than SMIL, for the reason the brand pack already documents:
prefers-reduced-motion is a media query and cannot reach inside a SMIL
<animate>. Under reduced motion this is just the finished icon.

Draws once and holds, rather than looping. Five icons looping forever on a
directory page is a distraction, and an app icon's resting state should be an
app icon.

`animatedIcon` is optional per app and `unoptimized` is set only when it is
used, because the image optimizer would rasterise the SVG and throw the
animation away.
This commit is contained in:
jwinterm
2026-08-18 08:33:01 -04:00
parent b0bd6a46ef
commit b1590c3a2b
2 changed files with 51 additions and 1 deletions
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Hash Bags app icon, drawn on.
Geometry is copied verbatim from the app's own icon source
(src/hash-wallet/assets/images/hash_wallet_app_icon_source.svg): full-bleed #1a5c38,
four white lines at stroke-width 80 with square caps, horizontals at y=412/612 and
verticals at x=412/612, all spanning 212..812. Held identical on purpose, because an
animated icon that does not settle into the static icon reads as a different app.
FULL-BLEED, not the inset rounded box that hash.boats/splash.svg uses. The site rounds
icons itself with a 22% CSS radius, and an asset with its own transparent margin would
let the card background show through at the corners.
CSS rather than SMIL, for the same reason the brand pack gives: prefers-reduced-motion is
a media query, and a media query cannot reach inside a SMIL <animate>. Under reduced
motion this is simply the finished icon.
Draws once and holds. A perpetual loop on a page of five icons is a distraction, and this
has to end as a normal, still app icon.
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" width="1024" height="1024">
<title>Hash Bags</title>
<style>
.bar {
stroke-dasharray: 600;
stroke-dashoffset: 600;
animation: hb-draw 520ms cubic-bezier(0.33, 0, 0.2, 1) forwards;
}
@keyframes hb-draw { to { stroke-dashoffset: 0; } }
/* Same order the brand's own mark uses: the uprights first, then the bars across. */
.v1 { animation-delay: 0ms; }
.v2 { animation-delay: 110ms; }
.h1 { animation-delay: 220ms; }
.h2 { animation-delay: 330ms; }
@media (prefers-reduced-motion: reduce) {
.bar { animation: none; stroke-dashoffset: 0; }
}
</style>
<rect width="1024" height="1024" fill="#1a5c38"/>
<g stroke="#ffffff" stroke-width="80" stroke-linecap="square" fill="none">
<line class="bar v1" x1="412" y1="212" x2="412" y2="812"/>
<line class="bar v2" x1="612" y1="212" x2="612" y2="812"/>
<line class="bar h1" x1="212" y1="412" x2="812" y2="412"/>
<line class="bar h2" x1="212" y1="612" x2="812" y2="612"/>
</g>
</svg>
+4 -1
View File
@@ -23,6 +23,8 @@ const apps = [
name: "Hash Bags",
tagline: "Non-custodial multi-chain wallet. Wownero-first.",
icon: "/images/products/hash-bags.png",
// Draws itself on once, then settles into exactly the static icon.
animatedIcon: "/images/products/hash-bags-animated.svg",
color: "text-emerald-600 dark:text-emerald-400",
borderColor: "border-emerald-500/20",
href: "/products/hash-bags",
@@ -107,10 +109,11 @@ export default function LinksPage() {
<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}
src={app.animatedIcon ?? app.icon}
alt={`${app.name} icon`}
width={56}
height={56}
unoptimized={!!app.animatedIcon}
className="rounded-[22%]"
/>
<div>