Live on Google Play (com.suchsoftware.hashwallet); the iOS build is in review
and desktop follows, so both render through StoreButtons' existing null-href
"Coming Soon" state rather than claiming availability we do not have.
Content is taken from the project's own README and store-listing draft rather
than invented: open-source, non-custodial, Wownero-first, twelve chains, and
Ledger/Trezor/BitBox support. The "why it exists" section is the real reason,
which is the interesting part of this product: Cake Wallet ended Wownero
support in early 2026 and this fork keeps it first-class.
Icon is the shipped play-icon-512, already square and opaque, so it takes the
site's rounded-[22%] cleanly and cannot show a transparent corner wedge.
Also linked out to hash.boats, listed on /products, and added to the sitemap.
Subtle and half-transparent, per the brief. Four blobs, one per monogram cell,
each in that letter's own colour, thrown outward from where the letter was: the
mark comes apart into its own palette rather than flashing something unrelated.
The peak opacity is 0.42 light / 0.5 dark and the gradients never reach full
alpha, so it stays a wash rather than a curtain.
Only transform and opacity animate. Both are compositor properties, so this
does not repaint per frame. That is deliberate: animating a filter is what
pegged a core on the matrix animation, 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 only ever transformed.
The overlay could no longer fade as one piece. Ground and mark fade in 260ms
while the burst runs 420ms plus a 25ms per-blob stagger, so fading the
container would have taken the burst down with it halfway through. They fade
independently now and the shell unmounts at 540ms instead of 300ms, which is
the burst's real length rather than the cross-fade's.
Hidden entirely under prefers-reduced-motion, though those visitors never
reach the splash anyway.
Two separate problems, one of them a real bug.
THE FLASH. The splash is server-rendered, so on a refresh the browser painted
it and only then did hydration read sessionStorage, decide this visitor had
already entered, and remove it. That one painted frame is the flash, and React
cannot prevent it: even useLayoutEffect runs after the server HTML is painted.
So the decision moved before paint. A tiny synchronous script in <head> reads
the session flag and prefers-reduced-motion and stamps the root element; a
plain CSS rule hides the overlay in the frame it would otherwise appear in.
The overlay is `fixed`, so hiding it shifts no layout. Verified with a
Playwright run: after reload the root is already stamped at readyState
"interactive", the overlay computes to display:none, and the splash is not
visible in any of twelve sampled frames.
THE SPLASH ITSELF. It is now only the pillar signature: four dots become the
four pillar symbols -- commerce, crypto, games, education -- become the word
SUCH, then the page takes over. One generated asset (morph_splash*.svg) whose
CSS timeline is scaled to land exactly on 1337ms, from such-graphics 628fcc1.
SPLASH_MS here and motion.splash.ms there are the same number by construction:
a gate that closes early cuts the last letter mid-draw.
Everything else on the splash is gone -- tagline, supporting line, the four
corner nav links, the chevron, the water-refraction filter and its pre-warm.
The corner links were unreachable inside a gate this short anyway, and the
header carries them. splash-logo-data.ts goes with them, now orphaned.
Perf, measured on a local production build: mobile 97, desktop 98, unchanged
from what production scores today. CLS stays ~0. LCP moves 0.9s -> 1.2s on
mobile, because the mark is now a 12KB request where the old logo was an
inlined data URI. Both theme variants are fetched (3.7KB each gzipped) since
`dark:hidden` hides but does not prevent the download, so half of that is
waste. Fixable by inlining as data URIs the way splash-logo-data.ts did, at
the cost of ~30KB in the bundle; not done here because the score did not move.
New store icons from bauhaus-echo/assets/store/app_icon_512.png and
suchoice/assets/icon_1024.png, both resampled to 512 and both square and fully
opaque. Bauhaus drops 320KB -> 72KB, Suchoice 259KB -> 142KB.
The corner artifact had a measurable cause. Five icons bake their own rounded
corners as transparency (suchoice 20.5%, smirk-wallet 20.1%, vegan-iq 19.3%,
mushroom-capital 17.0%, such-moon-launch 15.2% of their width); the rest are
square. CSS then rounded them again, at a radius that differed per page:
21.4% on /apps at 56px, 25% at 64px, and 20% at 80px.
20% is BELOW suchoice's 20.5% baked corner, so on /products/suchoice the CSS
clip fell inside the transparent corner and a wedge of card background showed
through at each corner. That is the "different coloured stuff next to the
rounded corners": not a rendering bug, an arithmetic one.
Every square app icon now uses one percentage radius, rounded-[22%]. It is
above every baked radius in the set, so no transparent corner can ever be
exposed, and being a percentage it stays identical at 56, 64 and 80px instead
of drifting with the pixel size. The two new icons are square, so they cannot
produce the artifact at any radius.
The non-square occupy-wallets banner keeps a fixed radius: a percentage on a
non-square box goes elliptical.
Copied through from such-graphics db616f8. The launch arc's feet ran to y=178,
inside the frame's corner arcs (centred (26,174) and (174,174), r=12), so on
/apps the leg, its cap and its node dot all sat on the corner and the mark read
as broken. The feet now stop at the glyph baseline, y=164.
Generated, not hand-edited: the pack is canonical and a regen would have
overwritten a local fix.
The splash now runs its water-ripple enter 1.5s after paint instead of waiting
to be clicked. A click, scroll, or arrow key still fast-forwards it, and the
once-per-session flag and reduced-motion skip are unchanged.
Auto-advancing on its own would have cost the Lighthouse score badly: mobile
100 -> 73, desktop 97 -> 73. The cause was not LCP, which never moved. It was
CLS going 0 -> 1.0, and the reason is worth writing down.
The splash was `relative min-h-[100svh]`, a full viewport of height IN document
flow, and only became `fixed` on the way out. Dismissing it therefore pulled a
whole screen out of the flow and shoved the hero up: a layout shift of almost
exactly 1.0. Clicking hid that from CLS, because shifts within 500ms of user
input are excused. Nothing else did.
Which means this was already shipping to real people. Visitors with
prefers-reduced-motion, and anyone returning within a session, never click, so
they took the shift in full: production measured 74 on mobile with CLS 1.0.
The click-gate was masking a live bug, not preventing one.
The splash is now a fixed overlay from its first paint, so it is never in flow
and dismissing it cannot reflow anything. Measured on a local production build:
before -> after
mobile, auto-advance 73 -> 98 (CLS 1.0 -> 0.005)
mobile, reduced-motion 74 -> 98 (CLS 1.0 -> 0.005)
desktop, auto-advance 73 -> 98 (CLS 0.702 -> 0.011)
Mobile gives up 2 points against today's click-gated 100, entirely in Speed
Index (1.9s -> 4.4s), which is the unavoidable cost of holding a cover at all.
Desktop ends up ahead of where it started. The hold is one constant if you want
a different beat.
The scroll-past-to-dismiss IntersectionObserver is gone: an overlay never
leaves the viewport, so it could not fire. Scroll intent still enters, via the
wheel/touch/key handlers already in the splash, and the shell now locks page
scroll while the cover is up.
`next lint` is deprecated and disappears in Next 16. apps/web now runs
`eslint .` against eslint.config.mjs on ESLint 9, and the shared preset moves
to packages/config/eslint-preset.mjs. eslint-config-next 15 still ships only
an eslintrc-style config, so FlatCompat translates it; that keeps
next/core-web-vitals as the single source of rules.
The migration was done by hand rather than via @next/codemod, because the
codemod does not know about the shared @repo/config preset.
Two things worth knowing.
ESLint 9 only walks file extensions that some config names in `files`. The
first working version of this config linted three loose .js files, skipped
every .tsx in src/, and still exited 0 -- a green run that checked nothing.
The preset now names the extensions explicitly. Coverage went from 3 files to
82, and a deliberately broken canary component confirms rules still fire.
`@eslint/eslintrc` also needed a second scoped override: it does a default
import of minimatch and declares ^3.1.2, which the global ^9.0.7 audit
override broke. Same shape as the ajv fix in the previous commit.
The linter had two faults stacked on top of each other.
First, the global `ajv: ^8.17.1` override starved @eslint/eslintrc, which
needs ajv 6 and passes it `missingRefs`; ajv 8 rejects that option, so ESLint
died before loading a single rule. Scoped overrides give eslint and
@eslint/eslintrc ajv 6.12.6 and leave the audit override in place everywhere
else.
Second, underneath that, `extends: ["@repo/config/eslint-preset.js"]` could
never resolve: ESLint's shareable-config naming convention rewrites it to
`@repo/eslint-config-config/...`. require.resolve hands ESLint a real path.
With the linter running it reported 35 react/no-unescaped-entities errors
across 7 files. Those are escaped rather than silenced, using ESLint's own
coordinates so only flagged text nodes were touched. Lint now exits 0. Two
@next/next/no-img-element warnings remain on the splash logos, which are
deliberate: theme-paired and fetchPriority="high".
Em dashes are also gone from the six blog posts and the README (105
occurrences), same rules as the site copy: colons for definition labels,
commas for appositives, semicolons for independent clauses.
The hero h1 is now "Precision Engineering for Everyone". The concrete offer
moves into the supporting line, which keeps the search terms the h1 gives up
and no longer restates the splash's opening sentence.
The title's gradient drifts instead of sweeping: a 300% ramp over 18s with
symmetric easing, so there is no hard turnaround at either end of the travel.
Em dashes are gone from every tsx/ts file (87 lines, 104 occurrences), per
AGENTS.md rule 9. Label lists and titles take a colon; appositives take
commas; independent clauses take a semicolon or a period. A few sentences
were restructured where no single mark read well.
Metadata keeps "Crypto" deliberately: it is a real service line and a real
search term, and it is no longer in the visible headline.
- Voice unified on the title-tag register: the homepage h1 now says what we
sell (Custom E-commerce, Apps & Crypto Tools); 'Precision engineering for
everyone' is demoted to the supporting line. The structured-data and
manifest descriptions now match the OG description instead of carrying
their own registers.
- The off-palette cyan/blue gradients are gone: .hero-title and the new
.brand-gradient-text use the four-colour system (light palette on light,
dark palette on dark, per the pack). Splash cover text, services and
portfolio headings, the hero aura, and button shadows all follow.
- The splash now dismisses once 40% scrolled past instead of requiring the
whole screen to leave the viewport (it was already once-per-session and
reduced-motion-skipping).
- Portfolio: heading 'Portfolio', subtitle 'Products & projects', meta title
to match; grid grows to three columns on xl in a max-w-6xl container.
- anim3_matrix slimmed: single-layer glow, unfiltered crackle and orbs, and
the border cycles stroke colour instead of animating filter:hue-rotate --
the filter animation forced a full filter re-render per frame and pegged a
core on the page hosting it.
All branding and animation assets now come from packs/such-software/pack.json
(such-graphics feat/brand-full-blast) instead of hand-maintained files:
- anim1_pulse / anim3_matrix / anim_type_v4 (both themes): regenerated with
the corrected U bowl, measured pulse timing (the hand-authored node delays
ran ~17% fast), and CSS animation with real prefers-reduced-motion support.
- clean marks, lockups (clean_*_w_text), favicons (svg + 192px png), and the
OG banner: regenerated; the OG card is the pack's 1200x630 projection.
- splash-such: rebuilt from the pack -- the old one carried the divergent U,
an off-palette cyan frame, and the pre-correction radius. Now theme-aware:
two inlined data URIs (hero-splash shows the light palette on light, the
dark palette on dark) since the pack's dark hues are tuned for near-black.
- new assets available: games_pulse (the launch arch with the rocket, wired
into /apps), morph, ambient_{atom,mesh,leaf}, favicon_dots, favicon_live,
icon-192/512 + maskable-512 (wired into manifest.json), /favicon.ico.
- animation embeds switched <img> -> <object>: Chrome freezes CSS animations
inside SVG-as-img (the old files animated because they were SMIL); an
object hosts a real SVG document where CSS animation and the
reduced-motion query both work. Verified live in Chrome.
Swaps /images/products/bloomword.svg for bloomword.png across the apps
index, the products index, the Bloomword product page, the bento grid,
and both services.ts proof lists (games + education), so the icon
matches what actually ships on the App Store and Play listings.
The SVG is now unreferenced; leaving it in place for now.
The policy was stale (March) and materially inaccurate: it claimed data never
leaves the device and there are no leaderboards (false — device_id + display_name
sync to the backend), described AdMob ads + a Remove Ads purchase (the app shows
no ads as of v1.4), and omitted Firebase Analytics/Crashlytics/Messaging. Rewrite
to reflect actual data practices. Needs deploy for the in-app link to be accurate.
Hero: 'Four things, done well' -> 'What we build' (add pb-3 so the gradient-clip descenders
aren't cropped), and drop 'PhD-led radiation-science education' from the subtitle for
'education from radiation science to word games' (education now spans well past nuclear:
ZDD, Numchangers, Bloomword, Vegan IQ).
Middle: the near-empty 'Partnership-first' and 'Recent work' section-container cards (a
heading + one line inside p-20 padding) read sparse. Partnership-first is now a dense 3-step
row (Scope / Build / Run it together); Recent work drops its empty header box for a compact
heading above the Occupy Wallets card.
Apply the same 'list a product wherever it genuinely belongs' rule already used for
Evaluetron (commerce+crypto) and Bloomword/Vegan IQ (games+education): the Grin BTCPay
plugin is a crypto payment integration, and Barns & Neutrons + Numchangers are science/
math games we ship. Each keeps its original pillar and gains the second with a pillar-
angled blurb.
Bloomword is out on iOS + Android: added it to /apps (featured first) with store links,
put StoreButtons on /products/bloomword, and flipped all 'coming/launching' copy to 'free
on iOS and Android' (product page, products index, services games blurb, FAQ).
Education pillar: added Zero-Day Defender (cybersecurity edutainment, Coming soon) and
cross-listed Bloomword + Vegan IQ from Games & Apps — they're genuinely both a game we ship
and a game that teaches. Broadened the pillar lead + card to frame the edutainment shelf
(science, math, vocabulary, nutrition, cyber-safety) alongside the PhD radiation-science work.
Home: the big empty 'Selected Works' section-container card is gone in favour of a compact
centered heading, pulling the bento up ~300px so the work leads directly under the hero.
Hero juice (all LCP-safe + motion-safe): the H1 gets an animated gradient sheen (only the
gradient POSITION animates, so the largest paint is never delayed; verified opacity stays 1),
a soft breathing cyan aura sits behind it (aria-hidden), and the CTAs get a cyan glow + hover
lift.
Services: each pillar page (and the hub) now generates its own OG social card via the shared
gameOgImage helper with a 'Services' badge and the pillar accent, so sharing a pillar shows
a distinct card instead of the generic banner. Verified: distinct per-slug image URLs, valid
1200x630 PNGs.
Sitemap: bump lastModified 2026-06-01 -> 2026-07-12 (we've meaningfully changed the site).
/services becomes a hub (hero, four linked pillar cards, philosophy, recent work, FAQ,
CTA) and each pillar gets its own statically-generated page at /services/[pillar]
(commerce/games/crypto/education) with distinct SEO title+description+canonical, its own
H1, the products behind it, a pillar-scoped FAQ + FAQ JSON-LD, breadcrumb JSON-LD, and
'the other pillars' cross-links. dynamicParams=false so unknown slugs are a static 404.
The pillar/proof/FAQ data + colour maps move to lib/services.ts (single source of truth),
ProofCard/PillarCards/ServicesCta/EducationCredentials to components/services, so the hub,
subpages, home 'What We Do' preview, and sitemap can't drift. Home preview cards now link
to their pillar; sitemap gains the four subpage URLs. All pillar copy kept verbatim.
Verified: 4 subpages prebuilt, distinct titles/canonicals, right products per page,
click-through nav, education credentials block, bad slug -> 404.
The flat lockup becomes a live tile: a 3D smirk coin (transparent, sg_outline inset ink,
dark rim) that rests smirk-up, tosses end-over-end on hover with the suchoice coin-flip
feel, lands $-up, and half-flips home for a seamless loop. 518KB webm + transparent
poster; web_qa gated.
Scroll-enter: the triggering gesture's MOMENTUM outlives the 800ms wave (a trackpad
flick trails ~1.5s), and those ticks scrolled the revealed page so you landed below
the hero. The scroll lock now holds until the input goes quiet (250ms without a
wheel/touch event, 3s cap) and releases exactly at the top. Verified with a simulated
14-tick 1.5s flick: lands scrollY=0, and a fresh gesture afterwards scrolls normally.
Bloomword recut: the loop opened with a 1s bloom hold so hover felt dead (movement
backloaded). Now a 0.2s hold then a brisk 3x retract into the full regrow, closing bob
at the end; still frame0==frameN (seam 0.0000), 390KB.
Webshops tagline: 'One platform, unlimited possibilities.'
New shape (drops Bauhaus): Webshops 2x2 feature / Smirk + Bloomword stacked right /
Barns 2x1 + Vegan IQ bottom. Grid gains md:row-span-2.
Webshops reel rebuilt at 1.2:1 (864x720) to match the 2x2 tile box with PER-SHOP crop
offsets (artisan's headline is left-anchored, coaching's box sits right of center, the
rest center) so no shop's hero text gets clipped by the cover fit.
Vegan IQ re-rendered at 1600x900 with the card at 95% height (text was landing on too
few yuv420p samples at 720p and read pixelated); webm 488KB, seam still 0.0000.
New lineup (drops Occupy Wallets + Wownerogue): Webshops (span 2) leads with the
theming-morph reel — the 5 re-snapped post-glowup demo shops crossfading, proof that
one platform yields completely different storefronts; Bloomword gets a transparent
chroma-keyed tree vignette (full bloom at rest, hover retracts + regrows, palindrome
seam 0.0); Vegan IQ gets a brand-panel card-flip reel from its own social pipeline
(logo -> real quiz card unanswered -> answered, seam 0.0); Barns/Smirk/Bauhaus stay.
HoverVignette grows fit=cover (opaque reels fill the tile, poster = tile art), an
optional H.264 mp4 source (Safari can't decode VP9), and on-screen autoplay for
no-hover devices (IntersectionObserver >= 0.5) so phones see the animations at all.
All four live tiles verified with Playwright: correct video plays on its tile's
hover, others stay paused.
The page after the splash was two screens of text before the first image. Selected
Works (the bento, with the live barns vignette) now leads and the services copy
supports it below, wrapped in the same scroll-reveal.
Splash: the first wheel tick, 24px touch drag, or scroll key now plays the SAME
water-ripple enter as a tap (scrolling past used to hard-cut after a full screen).
Listeners are passive, the entering guard dedupes, reduced-motion still gets the
instant dismiss.
Pre-warm: on requestIdleCallback (post-LCP, invisible to Lighthouse) a 1px element
paints through the splash-water filter so the graph compiles before first use (the
first-tap stutter), and the logo layer gets will-change ahead of the zoom.
Verified with Playwright against the production build: wheel/key/touch all enter,
session flag set, bento renders above What We Do.
The stroke was traced from the WHOLE extruded solid's alpha (front face + side walls),
so the line sat at the side wall's outer edge, offset from the glyphs — mushy letters.
Now a third render pass emits the FRONT FACES only (depth-0 TextMesh, z-seated at the
full mesh's front plane) and the stroke traces that mask: ink lands exactly on the
letterform crease and the darkened extrusion reads as the 3D body behind it. Also
gates the absorption flash out of mask passes (it could cross the mask threshold near
a strike and get inked). web_qa self-gate green (0.0% stripped, seam 0.0000, 512/600 KiB).
Per feedback: the whole-frame alpha stroke outlined the flying beta and the background
nuclei and only traced the outer silhouette (no internal creases). Now:
- Stroke from a WORDMARK-ONLY mask pass (nuclei + beta hidden) so only the letters get
the ink line — beta stays a soft blue glow, nuclei stay clean.
- Darken the extruded SIDE WALLS so the front-face→side CREASE reads as a toon edge that
a silhouette stroke can't reach.
outline2d gains an optional --mask (outline one element); mask-less still outlines
everything for a full cartoon look.
Root causes (all 3D->web pipeline law violations):
- Beta 'flashing / getting deleted': the MatterWave beta was BLEND_ADD (writes ~no
alpha) so it was stripped by the transparent capture, AND its off-screen auto-free
killed the packet a third of the way through its flight. Now alpha-safe (MIX under
BN_ALPHA), pinned alive for the full flight (no_free), bigger + slower + brightness-
capped so it reads as a coherent BLUE de Broglie packet the whole way in.
- 'No black outline': the 3D inverted-hull shell was sub-pixel + washed by the fresnel
rim (invisible on light, flecky on dark). Replaced with a 2D silhouette INK STROKE
from the alpha matte (sg_outline, outline2d.py) — uniform, gap-free, and INSET so the
black line sits ON the yellow fill and reads on BOTH themes from ONE asset.
- Softened the ripple pulse so the crest stays a blue tint, not a white clip.
Seam diff frame0==frameN = 0.0 (perfectly seamless).
Clean rest (frame 0 no longer catches a wrapped ripple: 2 strikes decay before
the seam), no white blowout (pulse strength down + blue core + lower HDR-bloom),
thinner outline, bigger wordmark.
Two fixes to the barns hover vignette after seeing it live:
1. The beta was STRIPPED by the transparent capture: the flash/particle used
additive blending, which writes ~no alpha, so it vanished in the alpha webm
(only the opaque letters survived = that brief white "lightning"). Switched the
flash to alpha-blend and pushed the absorption onto the opaque letters (strong
blue surface-ripple pulse), so the beta now survives on BOTH light and dark.
2. Light + dark mode need different ink: a near-black outline vanishes on a dark
tile; a light outline vanishes on a light tile. Render two baked assets
(barns-light = dark ink, barns-dark = cream ink) and HoverVignette picks by
resolved theme (next-themes). Added a clean rest poster (no active strike) via
a BN_POSTER render so at-rest looks right.
Extruded ChakraPetch wordmark now has an inverted-hull ink outline (crisp toon
edges) and 3 staggered beta strikes across the 5s loop with overlapping ripples,
so the tile stays alive instead of flashing once. Rendered on the 1080 Ti.
At rest the card chrome (opaque bg, 64px frost, border, shadow) is now nearly
invisible so tiles read as just the title + the 3D render/image floating on the
page (especially clean on mobile, where every tile is 'at rest'). The glass
lights up + the colour tint paints on hover / focus / tap. Title and image keep
their visibility.
Add HoverVignette: a transparent alpha-WebM 3D render that plays over a tile on
hover/focus. Poster-first, preload=none, reduced-motion-safe, Safari-degrades to
the poster (no VP9-alpha). Feature tiles drop the static screenshot (the poster
is the rest state) and grow + lift above neighbours on hover so the animation is
the focus.
Rework the grid to six gap-free tiles: drop Neroswap, shrink Wownerogue to span 1.
First vignette: Barns & Neutrons — extruded 3D "BARNS & NEUTRONS" in the brand
ChakraPetch face with ambient drifting nuclei and a beta strike that fires a
surface-ripple + blue absorption flash across the letters. Rendered transparent
on the 1080 Ti via such-graphics.
Services page was a long undifferentiated scroll of six ad-hoc sections.
Reframe it as four clear pillars, each led by an intro and backed by the
real products behind it:
- Commerce & web: Webshops, custom builds, Grin BTCPay plugin, Evaluetron
- Games & apps: Suchoice, Vegan IQ, Bloomword, Bauhaus Echo, Such Moon Launch
- Crypto & payments: consulting, Smirk Wallet, Hash Bags, Evaluetron
- Education & science: Barns & Neutrons, nuclear training, Numchangers, creds
Add a pillar jump-nav so the page reads as four areas, and a ProofCard that
links shipped products to their detail pages (image tile) and shows
pre-release ones as informational tiles with a status pill.
Also refresh the theming-morph reel with the seamless-loop build (crossfades
the last shop back to shop #1 instead of hard-cutting at the <video loop>).
The five demo storefronts were re-themed to look like different companies
(distinct hero styles, card styles, density, grid, and homepage composition),
so re-snap the hero cards and regenerate the theming-morph reel from the
fresh shots.
Add a "Every surface, not just the homepage" gallery: six live surfaces
across different shops (yoga + coaching booking calendars, an editorial
product page, a dense catalog grid, a crypto product, a themed cart) so the
page shows platform depth the homepage crops hide.
Call out booking/scheduling explicitly: a new "Booking & Scheduling" feature
card, a booking FAQ (feeds FAQ JSON-LD), and a booking-calendar line in the
surfaces lead.
Demo images downsized to lean 1280w JPGs (png -> jpg).
The 'render once, serve cheap pixels' showcase, live. A 7s reel of the same
platform themed as five real demo brands (artisan -> merch -> crypto -> yoga
-> coaching), inside a decorative browser frame, at the top of the webshops
page.
- ShowcaseVideo: poster-first, preload='none' (no download until played),
plays only while in the viewport (IntersectionObserver), pauses offscreen,
and prefers-reduced-motion -> poster only (video never loads). muted +
playsInline for mobile autoplay-on-view.
- BrowserFrame: static decorative chrome so the reel reads as a real shop.
- 16:9 assets ~330-355KB (webm/mp4) + 50KB poster, all within the motion budget.
- bento-grid: the Occupy Wallets tile is height-constrained (object-contain)
to ~240px wide, but sizes='33vw' made Next serve a 750w file into a 241px
slot (~62 KiB waste). Cap the sizes hint to the real display size.
- blog: add the 'a month later' chapter to the animated-hero post — the
Turnstile 450KB below-the-fold weight, deferred, mobile 75 -> 93 / desktop 99.
The contact form's Turnstile widget pulled ~450KB of Cloudflare challenge
scripts eagerly on every page load, including the homepage where the form
sits far below the fold. On slow-4G mobile that download competes with the
critical path during load (the biggest single drag on the mobile PageSpeed
score; desktop was unaffected, hence the 97-vs-75 split). The widget now
mounts only once its container is within 800px of the viewport; the existing
placeholder reserves layout, and on /contact it still loads immediately.
- Remove the "not controlled modeling for hire" framing from the homepage
services preview + both services-page occurrences (FAQ answer + education
section).
- Wrap the services FAQ in a section-container so it stops breaking out of the
section rhythm; constrain answer width to max-w-3xl.
- Point "Request a Consultation" at /contact (the contact-form shortcut page)
instead of the fragile /#contact cross-page hash.
- /products/webshops: lead with per-tenant theming; add the real platform
features (Printful POD, non-custodial crypto to own wallet, Stripe Connect
with item-margin-only fee framing, SIWE/wallet login, Telegram + email
notifications, automated tax); fix the incorrect "Vercel/Railway" hosting
line to fully-managed self-hosting; remove PayPal/Coinbase (not live).
- Live Demos gallery (new component) with real screenshots of the 5 themed
demo storefronts.
- Add three projects to the portfolio: Bloomword (now live, bloomword.earth),
Meatspace (in dev, meatspace.place), Mushroom Capital (coming soon,
mushroom.capital) with an icon, a detail page (VideoGame + FAQ JSON-LD),
and a sitemap entry each.
- Copy: avoid em dashes (colon/semicolon/comma) across the products area.
Verified: tsc clean, next build green (50/50 static pages).
Server-rendered /products/such-moon-launch/leaderboard backed by the public
scores API. Level tabs (1-12), Fastest / Most Moonrocks boards, Endless Mode
wave board, responsive table with graceful empty/error states. Linked from
the product page. Ships with the 1.1 release.
The marketing ToS predates the multi-tenant Medusa platform launch
and didn't mention it. Visitors arriving at /terms after viewing a
client storefront have no way to find the legal relationship clearly
spelled out.
- Section 2 (Description of Services): add the platform + custom dev /
consulting line items so the list reflects what we actually do today
- New Section 6 (Multi-Tenant Commerce Platform): states clearly that
Such Software is NOT the merchant of record for purchases made on
Merchant Client storefronts, that each Merchant Client has their
own signed Platform Services Agreement, that card payments route
through Stripe Connect with the Merchant Client holding their own
Stripe account, and that crypto payments are non-custodial (we
never take possession of customer funds). Directs customers with
purchase questions to contact the Merchant Client directly.
- Renumbered sections 7-12 (now 8-13). Last-updated date bumped to
2026-06-08.
Verified consistent with the operator-side documentation in the
medusa-multi-tenant-platform repo: doc/legal_model.md, doc/architecture.md
"Liability Model" section, and doc/tenant_guide.md "Refunds &
Chargebacks" section all match this language.
The previous commit (e1ff601) put a {/* */} comment between JSX attributes, which is
a syntax error, and it shipped because the commit wasn't gated on the build. Removed.
- Cap the two wildly oversized bento source PNGs so Next can't serve giant variants:
occupy-wallets 1200x837/393K -> 700x488/167K, neroswap 1584x672/1.8M -> 700x297/404K.
(bauhaus/vegan are already 512px and self-cap; privacy-labs was already small.)
- Fix the bento <Image> sizes: object-contain height-constrains the image so it renders
~360px wide in either span; 66vw/100vw made Next serve up to ~750-1200w. Now
'(max-width:768px) 90vw, 33vw'.
- Bump turbo 2.7.4 -> 2.9.16 (GHSA-hcf7-66rw-9f5r, GHSA-3qcw-2rhx-2726). pnpm audit
now clean.
- Monitor terminal: ~4 blinks, type 'such', backspace, ~4 blinks, fade.
- Rewrite the blog post so it tells the accurate story end to end (not an update):
the LCP breakdown showed render delay with TTFB 0 and a free main thread, so it
was neither the network nor JavaScript. It was render-blocking CSS, fixed by
inlining the stylesheet. Server components / dropping framer are kept as honest
supporting steps, not the gate. Swap the architecture figure for a CSS
critical-path figure; mobile now a steady 100.
LCP breakdown showed TTFB 0ms and Element render delay ~2,320ms: the data-URI logo
was ready instantly but couldn't paint until the render-blocking CSS request finished,
and that round-trip varied wildly on throttled 4G (LCP 2.3s -> 6.8s, same code). This
is why the score bounced 71<->96 with no code change.
- experimental.inlineCss: fold the 64 KB CSS into the HTML so there is no separate
render-blocking stylesheet request on the critical path. Verified: homepage HTML
now has the CSS inline and zero <link rel=stylesheet>.
- Drop the two header-logo image preloads (both theme variants) that were stealing
slow-4G bandwidth from the critical path; the header wordmark is not the LCP and is
hidden during the splash.
- Remove framer-motion from the two components that pulled it into the homepage's
initial bundle: HeroSplash (click-to-enter wash -> CSS keyframe; reduced-motion
check moved into the handler) and ScrollReveal (whileInView -> IntersectionObserver
+ CSS transition). framer now only lives in the lazy nebula chunk, which never loads
during the splash. First Load JS on / : 174 kB -> 138 kB.
- Monitor terminal: blink ~7 times before typing 'such' (so it's catchable even when
the splash is skipped on refresh), then type/hold/backspace/blink/fade as before.
Desktop behavior unchanged; both converted components still respect reduced-motion.
- Streamers were rendering and hitting the monitor but invisible on phones:
preserveAspectRatio=none squishes the stroke horizontally on a narrow viewport,
so 0.12 went sub-pixel. Fatten 2.5x on mobile so they actually show.
- Terminal cursor now types 'such', backspaces, blinks ~6 times, then fades out
completely instead of blinking forever (it was distracting).
- Replace the centered dash with a tiny CSS terminal in the screen: types 'such'
once, backspaces, then the caret blinks on the left (steps() width typewriter).
- Impact pulse is snappier and brighter (0.9s ping, scale 1.14, bright cyan glow)
so streamer hits actually read; the screen color shifts with it.
- Pointer parallax dialed down to a subtle 6px with a gentler spring, fixing the
desktop 'shift up-left after load' (it was easing toward the cursor on first move).
- Revert the mobile streamer thinning/density cut from the prior commit: it made
them too sparse to see. Mobile now gets the full set at full weight, like desktop.
- Mobile jump fix: compute the monitor anchor from the title's document-absolute top
(scroll-independent) and only recompute on a real width change, so the address-bar
resize that fires mid-scroll no longer snaps it. Mobile parallax now lifts the field
up and away (faster than scroll) instead of drifting down over the text.
- Pointer parallax (desktop only): the field nudges toward the cursor, spring-damped
so it can never move abruptly. No mousemove on touch, so no mobile cost.
- Live monitor: a CSS blinking terminal cursor inside the screen (opacity only =
compositor); it shifts color on each streamer hit, reinforcing the impact pulse.
- Lighter mobile field: streamer density 8->5 and lines ~15% thinner on phones.
Desktop motion unchanged. (CSS/SMIL streamer rewrite deferred to its own commit.)
A phone has no left gutter like desktop, and the nebula was position:fixed, so
scrolling slid the text up into the pinned monitor (overlap + jumps). Keep the
monitor to the left of the title and keep the parallax, but on mobile the field
glides up and out with the hero (slower-than-scroll lag for depth) and fades as it
leaves, so it never crosses the content below. Only the monitor/streamer layer
fades; the base gradient stays. Extra top room (pt-44) keeps it clear of the header
at rest. Desktop unchanged.
Dials: yRangeMobile (parallax), heroOpacity range (fade), pt-44 (top room).
On phones the fixed monitor icon and its scroll parallax had no whitespace to live
in, so it drifted over the title/tagline and under the header. Fall back to the
static Cherenkov glow under 768px (same path as reduced-motion); desktop keeps the
full monitor + streamers.
- New post 'My Animated Splash Was Tanking Our Mobile PageSpeed Score' (draft:false):
the LCP investigation, the data-URI diagnostic, and the real fix (shrink the
use-client boundary / server-render the static parts). Two SVG diagrams.
- Such Moon Launch now live on iOS: App Store link wired on the apps and product
pages.
The data-URI logo still had LCP ~5s with zero network request, proving the
bottleneck is render delay (JS), not the image. So:
- page.tsx is now a Server Component. The hero, tagline, buttons, ServicesPreview,
PortfolioGrid, and FounderSignature render as static server HTML (they had no
client features, so they now hydrate to nothing).
- Splash state machine + header/nav/background gating + nebula position + scroll
restoration moved into a small client island (home-shell.tsx) that takes the
static content as children. Nebula reads the title via getElementById.
- Dedupe @types/react to 19 across the workspace (pnpm override) so the
server->client children boundary type-checks.
- The high-priority <link rel=preload as=image> stole bandwidth from render-
blocking CSS on 4G, pushing FCP 1.1s -> 2.2s and the score 82 -> 70. Removed.
- TBT is ~30ms (not main-thread bound), so the FCP->LCP gap was the logo's
network fetch. Inline the mark as a data URI: zero request, paints with FCP.
LCP should drop toward FCP and stop swinging run-to-run.
- Splash is now a one-time intro per session: once entered (scroll or click) a
refresh skips it, so you can't scroll back up into it mid-page.
- Preload the splash logo <img> (the mobile LCP element) at high priority.
- Draft blog post on keeping an animated hero without tanking Core Web Vitals
(draft: true, for review).
- Logo is a plain <img fetchPriority=high> in a plain div (no framer-motion,
no drop-shadow filter) so it is a clean, statically-painted LCP candidate.
Water filter + a CSS zoom apply only on enter.
- Tracer target nudged to computerX/Y +1.5 (between the low-right +2.5 and the
high-left +0.5).
- Inline <svg> is not an LCP candidate; on mobile the tagline is hidden so the
logo was the only large element -> NO_LCP. Serve the mark as
/images/branding/splash-such.svg and render it via <img> (a valid LCP
candidate, painted from SSR HTML).
- Transition: start the cross-fade on click (shimmer plays as the splash fades
into the content) and shorten durations 1.2s -> 0.8s; snappier and blended.
- Tracer target back to the midpoint (computerX+0.5, computerY+0.5).
- Drop the two animated glow rings: stroke-dashoffset is non-composited, so
they repainted every frame next to the logo and prevented Lighthouse from
settling on an LCP (mobile NO_LCP). The static box + pulsing glow remain.
- Splash now cross-fades into the content: on enter it becomes a fixed overlay
and fades opacity to 0 over the revealed page, instead of a hard cut.
- Nebula tracer target moved up-and-left (computerX-1, computerY-1.5) so the
streamers converge on the monitor screen, not below-right of it.
- Removed the now-unused splash-* keyframes.
- Revert the nebula density/parallax change that disturbed the carefully
tuned streamer convergence (it gave no perf benefit; the nebula is behind
the splash and not in the load trace).
- Logo is now fully static (no float/breath) so it's a stable LCP candidate
on mobile; the rings, glow pulse, and staged button fade-ins still carry
the motion. (Desktop TBT win from the framer->CSS conversion is retained.)
- The splash's always-on animations (logo float/breath, the two traveling
glow rings) move from framer-motion (JS rAF every frame on the main thread)
to CSS keyframes (browser/compositor). framer now only drives the one-shot
click-to-enter water exit.
- Nebula: density 8 -> 4 and dropped the scroll parallax (the Forced-reflow
source); reduced-motion still gets the static background.
The logo stays painted at first render (LCP-safe); the tagline, corner
buttons, and Explore hint now stagger-fade in via Tailwind animate-in
(CSS/compositor, no JS main-thread cost). motion-safe only.
The splash logo (the LCP element) started at opacity:0 and only faded in
after framer-motion hydrated, pushing mobile LCP to ~7s. Render it visible
immediately; the float/breath animations still layer on top.
- Shorter, non-truncating <title>/OG titles for products, apps, wownerogue,
smirk, neroswap, suchoice, barns-and-neutrons, webshops.
- TODO.md removed from the repo and gitignored (internal working notes).
Reduced-motion users now land straight on the main hero (desktop and mobile);
the splash is no longer shown to them, not just de-animated. Set before paint
via useIsoLayoutEffect so it never flashes.
- /contact redirects straight to the home contact form; product CTAs that
pointed at the broken /about#contact now use /contact.
- Bento: custom e-commerce (Occupy Wallets) top-left, Smirk Wallet top-right,
Wownerogue + Neroswap in the middle row; 'art brand' -> 'artist'.
Drop the expanding circles. Clicking the logo now runs an SVG
feTurbulence + feDisplacementMap filter over the mark, ramped by a rAF
loop, so it ripples like a disturbed liquid surface and settles, with a
soft Cherenkov wash + backdrop blur, then enters. Reduced-motion still
enters instantly.
- Hide the cover tagline and subheader on mobile (logo + corner icons carry
it); only the labeled pills/tagline show at sm+.
- On enter, fade the logo, tagline, buttons, and Explore cue out while the
click-triggered wave plays.
- Waterier transition: backdrop-blur wash plus four expanding Cherenkov
ripples; reduced-motion still enters instantly.
- Mobile: corner nav buttons become icon-only (Portfolio/Apps/Services/About)
with aria-labels; subheader hidden on mobile so nothing overlaps the logo.
- More ambient motion: logo float plus a second counter-rotating glow ring.
- Clicking the logo (or Explore) triggers a Cherenkov wave (wash + expanding
rings) before entering; reduced-motion enters instantly.
- Beveled glass buttons.
- Splash: full-viewport animated SUCH logo cover with four corner nav
buttons; reveals the site once "entered" (scroll-through or Explore) and
does not resurrect on scroll-up. Original hero restored beneath it
(title-anchored monitor pulse + Cherenkov-blue background).
- Logo: traveling Cherenkov glow ring + gentle letter breath (SVG/Framer,
reduced-motion safe); beveled glass corner buttons.
- Services: new "Nuclear & Radiation Modeling" section + "Consulting"
umbrella (payments, crypto, nuclear), MCNP Google Scholar link, and a
not-a-licensed-PE clarification.
- About/founder bio: LLNL credibility + accessibility, art101.io link.
- Products: add Barns & Neutrons, Alpha Doc, Decay Theory (detail pages,
OG cards, SVG icons); client-side sortable grid with coming-soon at the
bottom. New Neroswap + Custom Websites icons (de-duplicated).
- SEO: game pages added to sitemap.
- Copy: dial back em-dashes to colons/commas/parens on home + services.
Rework the per-product OG cards to a single template with a graphic slot: real
app icons for the apps that have one (Bauhaus Echo, Suchoice, Vegan IQ, Such Moon
Launch, Smirk), and thematic SVG glyphs for the rest (Wownerogue dungeon glyphs,
Neroswap candlesticks, a shopping bag for Webshops, a browser window for Custom
Websites). Same layout and accent system so the set reads as one family. Paths
are unchanged, so the page metadata already points at these.
- Add FAQPage structured data + visible FAQ sections to Wownerogue, Webshops,
Neroswap, and Services (covers high-intent 'provably fair', 'no-KYC', and
'revenue share' style queries; visible Q&A matches the schema).
- Generate branded 1200x630 OG cards per product and wire them into each
product page's openGraph + twitter (summary_large_image), replacing the
square app icons that previously got cropped in social previews.
- Sitemap: stop stamping every static page with new Date() on each build; use
a single content-update date so freshness signals are not diluted.
- Rewrite title tags + meta descriptions site-wide to front-load search intent
(home, products index, services, apps, support, about, blog, and every product
page); keep brand suffix, fix overlong/truncated descriptions, use colons not
em dashes. Home keeps the visible 'Precision Engineering' hero; only the <title>
changed. De-jargon Webshops away from leading with 'headless'.
- Add reusable JSON-LD helper (JsonLd + breadcrumb/softwareApp/article builders).
WebSite schema site-wide; BreadcrumbList on all product + blog pages;
SoftwareApplication on the apps/games; BlogPosting on posts.
- Cross-link the Monero/Wownero cluster (Neroswap, Smirk, Wownerogue, Such Moon
Launch) and link products to services.
- Data fixes: Vegan IQ corrected to 1,000+ questions with Daily Challenge and
Weekly Quiz (page + index); generalize Support page off a single game.
- New /products/wownerogue page (SEO metadata + VideoGame JSON-LD) and
wownerogue-launch blog post covering the fairness, block-timing, and payout design.
- Drop the retired r/CryptoCurrency and MoonPlace references from the portfolio grid,
products index, and custom-websites page; feature Wownerogue + Neroswap instead.
- Add WownerogueLinks component (live instances + GitHub source) and wire it into MDX.
- Remove now-orphaned r-cryptocurrency and moonplace images; add product page to sitemap.
Apple rejected the v1.0 submission under 5.1.2(i) because App Privacy
declared tracking that the app does not actually perform. The app
ships without an App Tracking Transparency prompt, so AdMob falls
back to non-personalized ads on iOS and never accesses IDFA.
Rewrite the iOS subsection of the privacy policy to reflect this
accurately (no ATT request, no IDFA access, non-personalized ads
only on iOS). Also drop the now-misleading "Settings -> Privacy ->
Tracking -> Disable tracking for Such Moon Launch" instruction since
that toggle only exists for apps that have requested ATT.
Bump the Last Updated date to May 20, 2026.
Build was failing because the new MDX post tried to `import` the
SuchMoonLaunchStoreButtons component — next-mdx-remote/rsc doesn't
support imports inside .mdx; components must be passed via the
`components` prop. Wire the helper into [slug]/page.tsx and drop the
import line from the .mdx.
Dependency bumps to clear the 14 outstanding Dependabot advisories
(9 high, 4 moderate, 1 low):
- apps/web/next: ^15.5.15 → ^15.5.18 (covers 12 next.js advisories)
- pnpm overrides: pin postcss ^8.5.14 (clears 1117015 XSS via </style>)
- pnpm overrides: pin fast-uri ^3.1.2 (clears 1117870 + 1117884 path
traversal / host confusion)
pnpm audit: 14 vulnerabilities → 0.
Visible-name rebrand for the game ahead of Play Store + App Store launch.
The Wownero theme stays in the game itself; this is purely about the
product name.
- Rename slug: /products/wownero-moon-launch → /products/such-moon-launch
- Rename blog post file + flip from draft → published with rewritten
origin story (first game ever → Tabby Labs test app → full rebuild
with tilt-to-steer controls + 11 levels)
- New "The Story" section on the product page
- StoreButtons: accept null href to render disabled "Coming Soon"
buttons; add SuchMoonLaunchStoreButtons preset (Google + Apple null
pending review, web link present, itch removed — no itch page exists)
- Add Such Moon Launch entry to /apps with coming-soon store buttons
- Bump privacy + terms last-updated dates to May 18, 2026
- Theme color: orange → amber to match the new brand palette
- Sweep all remaining "Wownero Moon Launch" / "wownero-moon-launch"
references in product listing, sitemap, top-level privacy index, and
website-relaunch blog post
Forces flatted, minimatch, picomatch, ajv, and brace-expansion to
patched versions across the transitive graph (ESLint, Tailwind, and
Playwright toolchains). Both prod and full audits now report no
known vulnerabilities.
New ScrollReveal client component wraps section-containers and cards with
a subtle opacity + y-translate fade-in triggered by IntersectionObserver.
Bails out to an unanimated render when prefers-reduced-motion is set.
- home: Selected Works, Inclusive by Design, Start a Project
- services: each of the four verticals and the Recent Work card
- portfolio: each product card, slight stagger by column index
- about: Leadership section and Get in Touch
- apps: each app card, stagger by index
- Brand Motion card rewritten with concrete capability language
- Revenue-share card clarified: no upfront fees, no retainers, low % after launch
- Recent Work section added with Occupy Wallets as the first case study
- Bumped next to ^15.5.15 to patch 2 high and 3 moderate advisories
- Added pnpm override for yaml ^2.8.3 to resolve the transitive next-mdx-remote
stack-overflow advisory. Production audit now reports no known vulnerabilities.
- Home hero subhead rewritten to name commerce/websites/apps/consulting
plus in-house mobile apps and games; primary CTA points to /products
- Portfolio page (/products) title renamed and each product card now shows
a 56px icon beside the title
- Favicon served inverse to the browser color scheme so the tile contrasts
with the chrome instead of blending into it