From 7023658e5227f1f8e7b350fff6df94fded3f2d31 Mon Sep 17 00:00:00 2001 From: gregemax Date: Tue, 30 Jun 2026 14:00:47 +0100 Subject: [PATCH 1/4] feat: add branded not-found and error routes (#125) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/app/not-found.tsx — Server Component; Helio orb + solar 404 label + display heading + back-to-home pill link. Replaces the Next.js default for all unknown routes. - src/app/error.tsx — Client Component (required by Next.js); logs the error, surfaces error.digest as a ref, offers Try again (reset()) and Go home actions. Replaces the framework default crash screen. Both pages use design-system tokens (--font-display, --font-body, --font-data, --solar, --ink, --radius-pill, --dur-press, --ease-out) and honour the TopBar/Footer shell via the root layout. --- src/app/error.tsx | 147 ++++++++++++++++++++++++++++++++++++++++++ src/app/not-found.tsx | 91 ++++++++++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 src/app/error.tsx create mode 100644 src/app/not-found.tsx diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..209fe20 --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,147 @@ +'use client' + +import { useEffect } from 'react' +import { Helio } from '../brand/Helio' + +/** + * App-level error boundary — runtime errors in any route segment bubble here + * instead of the framework default crash screen. + * + * Must be a Client Component (Next.js requirement for error.tsx). + * Logs the error to the console and offers a recovery action. + */ +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string } + reset: () => void +}) { + useEffect(() => { + // In production wire this to your error-reporting service (e.g. Sentry). + console.error('[Heliobond] unhandled error:', error) + }, [error]) + + return ( +
+ + +

+ Something went wrong +

+ +

+ An unexpected error occurred +

+ +

+ The application hit an unexpected problem. You can try recovering, or go + back to the home page. +

+ + {error.digest && ( +

+ Error ref: {error.digest} +

+ )} + {!error.digest &&
} + +
+ + + + Go home + +
+
+ ) +} diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 0000000..3c8fdd9 --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,91 @@ +import Link from 'next/link' +import { Helio } from '../brand/Helio' + +/** + * App-level 404 — unknown routes fall here instead of the framework default. + * Branded: Helio orb at reduced size, display-type heading, solar accent, back + * to the landing CTA. Pure Server Component — no client hooks needed. + */ +export default function NotFound() { + return ( +
+ + +

+ 404 +

+ +

+ Page not found +

+ +

+ The route you followed doesn't exist — it may have moved or never existed. +

+ + + Back to Heliobond + +
+ ) +} From f281a800da2e9e3beb65981835672ae104f89e60 Mon Sep 17 00:00:00 2001 From: gregemax Date: Tue, 30 Jun 2026 14:04:53 +0100 Subject: [PATCH 2/4] feat: pixel-perfect ProjectCardSkeleton for project grid loading (#117) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The skeleton now mirrors the real ProjectCard structure exactly so there is zero layout shift when live data lands: • Hero: 168 px (matches card image area) + location-badge pill ghost positioned at bottom-left (absolute, same as the real badge) • Title: 22 px high block at 68% width (font-display h3 equivalent) • Scores: two 84 × 84 px circles (exact ScoreGauge size) with gap: 18 • Footer: funded label+value stack on the left, verified chip on the right, above a 1 px var(--ink-12) top border — identical to real card Animation delays cascade 50–300 ms top-to-bottom (hb-pulse keyframe, already in app.css). CSSProperties helper keeps the inline styles DRY. prefers-reduced-motion is handled globally by the base reset. --- src/screens/Explore.tsx | 117 ++++++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/src/screens/Explore.tsx b/src/screens/Explore.tsx index 946f256..3cd6ac6 100644 --- a/src/screens/Explore.tsx +++ b/src/screens/Explore.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from 'react' +import { useEffect, useState, type CSSProperties } from 'react' import { useTranslations } from 'next-intl' import { ProjectCard, Tag } from '../components' import { HB_DATA, type Project, type ProjectType } from '../data' @@ -169,7 +169,28 @@ export function Explore({ onOpen }: ExploreProps) { ) } +/** + * ProjectCardSkeleton — pixel-for-pixel placeholder for ProjectCard. + * + * Mirrors the real card's DOM structure so the layout doesn't shift when + * live data replaces the skeleton: + * • 168 px hero with a location-badge pill ghost in the bottom-left + * • 20 px display-weight title line (h3 equivalent) + * • Two 84 px circular score-gauge ghosts (matching ScoreGauge size) + * • Footer row: funded block on the left, verified chip on the right, + * separated by a 1 px top border — identical to the real card footer + * + * aria-hidden — all content is presentational; screen readers skip it. + * Animation delays are staggered by 100 ms per row so the shimmer + * cascades top-to-bottom rather than flashing all at once. + */ function ProjectCardSkeleton() { + const pulse = (delay = '0s'): CSSProperties => ({ + background: 'var(--ink-06)', + animation: `hb-pulse 1.4s ease-in-out ${delay} infinite`, + borderRadius: 6, + }) + return (