# 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: `