Entry screen is the app: phone frame with a live deck inside
The homepage was a marketing brochure -- a hero paragraph, a "three steps" explainer and a tag list. It described the gesture in prose while the actual Tinder deck sat behind /deck/[jobId], reachable only after signing in AND posting a job. Nobody opening the app ever saw the product. Now / renders a phone illustration with the real app running inside it: the same <Deck>, the same drag physics, real verified pros. On a phone the bezel collapses and the deck simply fills the viewport -- drawing a picture of a phone on a phone is absurd, and it would eat the width the cards need. - Removed the fixed app bar and bottom tab bar. AppShell now renders the screen title as an in-flow h1; the bar owned the only h1 on every screen, so dropping it silently would have left every page headingless. The /jobs "post" action moved from bar chrome into the content, since the tab bar was its only other route there. - getShowcaseDeck(): a deck with no job behind it. getDeck is job-scoped (joins jobs for category and location, anti-joins swipes), which an anonymous visitor has none of, so this centres on the launch city. Eligibility rules are copied verbatim -- nobody may appear in the shop window who could not appear on a real deck. - deck.showcase: the only public procedure in the router. list and swipe stay behind clientProcedure. It reads nothing about the caller and writes nothing, so a right swipe on the entry screen is purely local. No real tradesperson is contacted until a job is posted. - <Deck> filled a hardcoded 560px desktop box; it now fills its container. The card counter moved out from between the two action buttons so the thumb zone holds nothing but the two controls. Verified against the seeded database: 22 eligible pros returned, and all three seeded traps excluded for the right reason -- Pau Ribas (22km out, 5km radius), Unverified Ulla (pending), Away Arnau (not accepting). 7 new integration tests cover exactly that. Also corrects a label I had written as "Plumbers near you" -- the showcase deck is not category-filtered and shows every trade. Includes concurrent edits to the mobile shell, ui/ primitives and DESIGN.md made outside this session. typecheck, lint, build clean; 141 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
/**
|
||||
* A hardware illustration with the real app running inside it.
|
||||
*
|
||||
* This is the entry screen on a desktop browser: rather than describing the
|
||||
* product in prose, it shows the actual running app — the same <Deck>, the same
|
||||
* drag gesture, the same data — inside a phone. Everything inside `children` is
|
||||
* live and interactive, not a screenshot.
|
||||
*
|
||||
* On a phone there is no frame. Drawing a picture of a phone on a phone is
|
||||
* absurd, and it would waste the ~24px of width the deck actually needs, so
|
||||
* below `sm` the bezel collapses and the app simply fills the viewport.
|
||||
*/
|
||||
export function PhoneFrame({
|
||||
className,
|
||||
children,
|
||||
}: {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
// Phone: no frame at all, app fills the screen.
|
||||
'h-dvh w-full',
|
||||
// Tablet and up: a 390x844 device, the iPhone 14 logical viewport.
|
||||
'sm:h-[844px] sm:w-[390px] sm:shrink-0',
|
||||
// The bezel. ink-950 rather than pure black, per DESIGN.md §2.2.
|
||||
'sm:rounded-[3.25rem] sm:bg-ink-950 sm:p-[0.7rem] sm:shadow-lg',
|
||||
// A hairline of light along the top edge reads as a chamfer and stops
|
||||
// the bezel looking like a flat black rectangle.
|
||||
'sm:ring-1 sm:ring-inset sm:ring-white/12',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{/* The screen. overflow-hidden is what makes a swiped card disappear at
|
||||
the bezel instead of flying across the page. */}
|
||||
<div className="relative h-full w-full overflow-hidden bg-page sm:rounded-[2.6rem]">
|
||||
{/* Dynamic Island. Pointer-events-none so it never eats a drag that
|
||||
starts near the top of the card. */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute left-1/2 top-2 z-30 hidden h-[1.85rem] w-[6.2rem] -translate-x-1/2 rounded-pill bg-ink-950 sm:block"
|
||||
/>
|
||||
{children}
|
||||
{/* Home indicator. */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute bottom-2 left-1/2 z-30 hidden h-[0.3rem] w-[8.5rem] -translate-x-1/2 rounded-pill bg-ink-950/25 sm:block"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user