From 8d4bbaea0a79b113c2f9632323e3c6c2572e23da Mon Sep 17 00:00:00 2001 From: thesn10 <38666407+thesn10@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:18:07 +0200 Subject: [PATCH] Add listing detail desktop design spec --- ...026-07-03-listing-detail-desktop-design.md | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-03-listing-detail-desktop-design.md diff --git a/docs/superpowers/specs/2026-07-03-listing-detail-desktop-design.md b/docs/superpowers/specs/2026-07-03-listing-detail-desktop-design.md new file mode 100644 index 0000000..353b670 --- /dev/null +++ b/docs/superpowers/specs/2026-07-03-listing-detail-desktop-design.md @@ -0,0 +1,279 @@ +# Listing detail page — desktop/web design pass + +## Problem + +The listing detail screen (`app/src/app/listing/[id].tsx`) looks noticeably worse on +web/desktop than the HTML design prototype (`prototype/Volana.dc.html`, "LISTING +DETAIL" section, lines 423-543): content runs edge-to-edge with no max-width, the +hero is an oversized flat-color box with a tiny emoji, typography doesn't scale up +for desktop, the sidebar isn't visually a distinct card, and several shared +components it depends on (`TopNav`, `ListingCard`) have the same issues since they're +reused across other screens. + +A prior request included a written analysis of the gap with concrete numbers +(container width, font sizes, colors). That analysis was produced without access to +the prototype's source and got several concrete details wrong (see "Corrections" +below) — the prototype HTML/CSS and the current app code are the source of truth for +this spec, not that analysis. + +## Scope + +Primary target: `app/src/app/listing/[id].tsx`. Because two of the fixes (navbar, +"customers also bought" cards) live in shared components also used by +`(tabs)/index.tsx`, `(tabs)/browse.tsx`, and `(tabs)/orders.tsx`, this spec also +covers: +- `app/src/components/TopNav.tsx` +- `app/src/components/ListingCard.tsx` +- `app/src/components/StatusChip.tsx` +- `app/src/components/ResolverCard.tsx` +- A new `app/src/components/PageContainer.tsx` +- `app/src/global.css` (theme) +- `app/src/app/_layout.tsx` (font loading) + +Out of scope: on-chain/wallet logic, `(tabs)/index.tsx` and `(tabs)/browse.tsx` +layout beyond what `ListingCard` changes give them for free, `WalletConnectSheet`, +`CheckoutSheet`, the mobile/desktop nav breakpoint (768px) itself, nav +blur/translucency. + +## Corrections vs. the original analysis + +Verified against `prototype/Volana.dc.html` and the actual HeroUI Native +docs/source in `.heroui-docs/` and `node_modules/heroui-native/src`: + +- **Max-width is 1320px, not 1440px.** The prototype uses `max-width:1320px` + consistently for the nav, hero, and listing-detail wrapper. +- **The app's theme colors already match the prototype.** `app/src/global.css`'s + `--background`, `--surface`, `--surface-secondary`, `--accent`, `--success`, + `--warning`, `--danger` were already copied from the prototype's own + `--c-bg`/`--c-surf`/`--c-a`/etc. CSS variables. The hex codes given in the + original analysis prompt do not match the prototype and must not be used. +- **The hero background is not teal.** The prototype layers a + `rgba(255,255,255,.08)` radial highlight (top-left) and a black-to-transparent + linear fade at the bottom over the listing's own per-product `bg` color + (`mock.ts`), not a fixed teal color. +- **Sidebar width is a fixed 340px, not 380-420px.** +- The prototype has **zero responsive/mobile CSS** — it's a desktop-only mockup + (confirmed: no `@media` queries touch layout). All mobile-specific sizing + (hero aspect ratio, typography minimums, breakpoint choice) is this spec's own + design judgment, not derived from the prototype. + +## Theme (`app/src/global.css`) + +Extend the existing `@layer theme` blocks (do not replace the existing +background/surface/accent/success/warning/danger values — they're already correct): + +- Add `--border` overrides (currently unset, silently falling back to HeroUI's + generic default gray instead of the prototype's border color): + - dark: `--border: #252548` + - light: `--border: #e2dff0` +- Add a second muted tier. The prototype uses two distinct de-emphasis levels — + `--c-t2` for body/description text (already mapped to HeroUI's `--muted`) and a + dimmer `--c-t3` for micro-labels, captions, wallet/listing IDs, fee fine-print + (currently has no equivalent). Add a new semantic color following HeroUI's + documented "Adding Custom Colors" pattern: + ```css + @layer theme { + @variant dark { --subtle: #605c88; } + @variant light { --subtle: #9991b8; } + } + @theme inline { + --color-subtle: var(--subtle); + } + ``` + Usable as `className="text-subtle"` (Typography's `color` prop only accepts + `default|muted`, so subtle text is applied via `className`, not the `color` prop). +- No radius changes. Base `--radius` is already `8px` → `rounded-xl` (12px) and + `rounded-2xl` (16px) already match the spec's button/card radius targets via + existing Tailwind utility classes; just use those classes consistently instead of + ``'s default `rounded-3xl` (24px). + +## Fonts + +The prototype loads DM Sans (body) and DM Mono (addresses, fees, IDs) from Google +Fonts; the app currently uses HeroUI's default system font throughout. + +- Add `@expo-google-fonts/dm-sans` and `@expo-google-fonts/dm-mono` (both exist on + npm, confirmed). +- Load `DMSans_400Regular`, `DMSans_500Medium`, `DMSans_600SemiBold`, + `DMSans_700Bold`, `DMMono_400Regular`, `DMMono_500Medium` via `useFonts` in + `app/src/app/_layout.tsx`, following the existing app-level loading-gate pattern + (render nothing / keep splash screen until `fontsLoaded`). +- Override HeroUI's font CSS variables per its documented mechanism: + ```css + @theme { + --font-normal: 'DMSans_400Regular'; + --font-medium: 'DMSans_500Medium'; + --font-semibold: 'DMSans_600SemiBold'; + } + ``` +- Monospace elements (resolver fee %, wallet addresses, listing IDs) use + `` + (HeroUI's monospace preset, stripped of its default chip-style + background/padding) with an explicit `style={{ fontFamily: 'DMMono_500Medium' }}` + override — HeroUI's `type="code"` font selection is hardcoded to generic + `'monospace'`/`'Menlo'` in its source (`text.constants.ts`) and isn't + theme-variable-driven, so the loaded DM Mono font must be applied directly via + `style` (which takes precedence over `className` per this project's styling + conventions). + +## `PageContainer` (new component) + +`app/src/components/PageContainer.tsx`: +```tsx +interface PageContainerProps { + children: ReactNode; + className?: string; +} + +export function PageContainer({ children, className }: PageContainerProps) { + return ( + + {children} + + ); +} +``` +(Exact class-merge helper — `cn`/`twMerge`/plain template string — decided at +implementation time to match whatever convention the rest of the codebase uses.) + +Used to wrap: +- `TopNav`'s inner content (the nav bar itself stays edge-to-edge with its + `border-b`; only the row of logo/search/links/wallet-button inside it is + constrained) +- The entire `ListingDetailScreen` body (hero, two-column detail layout, and + "Customers also bought" section all inside one container, mirroring the + prototype's single wrapping div) + +## Listing detail layout (`app/src/app/listing/[id].tsx`) + +**Two-column split:** change the breakpoint from `md:flex-row` (768px) to +`lg:flex-row` (1024px) so there's room for the 340px sidebar before switching to +two columns. Main content column `flex-1`, sidebar fixed `lg:w-[340px]`. Below +`lg`: single column, sidebar stacks under the hero (today's existing behavior, +just at a higher breakpoint). + +**Sticky sidebar:** keep the existing web-only `position: sticky` mechanism, but +gate it behind a `useWindowDimensions` width check (same pattern +`app/src/app/(tabs)/_layout.tsx` already uses for its own breakpoint) so it only +activates at `>=1024`. Update `top` offset to match the new desktop nav height +(~76px) plus spacing (~92px total). + +**Hero:** +- `style={{ aspectRatio: isWide ? 2.4 : 1.6 }}` (RN's `aspectRatio` style works + cross-platform, no separate mobile/desktop height branches needed) — `isWide` + reuses the same width check as the sticky sidebar. +- `rounded-2xl overflow-hidden`, background color from `listing.bg` (unchanged, + per-listing data). +- Layered on top via `react-native-svg` (already a project dependency, no new + install needed): a `RadialGradient` white-8%-to-transparent highlight + (top-left-biased) and a bottom linear fade to black-transparent — replicating the + prototype's actual two-layer gradient technique exactly. +- Emoji: `text-[96px] lg:text-[160px]`, centered. + +**Typography scale** (Tailwind arbitrary values layered on HeroUI's `type` presets, +which supply weight/line-height defaults; size is overridden responsively): +- Title: `text-2xl lg:text-[38px] leading-tight font-bold` +- Description: `text-[15px] lg:text-[17px]`, `color="muted"` +- Sidebar price: `text-[30px] lg:text-[42px] font-extrabold` +- USD subline: `text-[15px] lg:text-[18px] text-subtle` +- Section micro-labels ("Pay with", "Dispute Protection", "About this listing"): + `text-[11px] font-semibold uppercase tracking-wider text-subtle` — defined once + as a local class-string constant in `[id].tsx` and reused at its ~4 call sites + (not a new shared component; not reused outside this screen yet) + +**Purchase sidebar card:** +- `rounded-2xl border border-border bg-surface p-6 lg:p-8`, existing `` + usage between price / pay-with / dispute-protection / CTA blocks, re-padded to the + new scale. +- Stock badge uses `StatusChip`'s new `variant="outline"` (see below). +- CTA: `