draw rawdrop sharp

design & development est. 2017

Prague Escorts18+

Stack

Figma + Adobe Illustrator + Vite + Tailwind CSS + Alpine.js + Laravel Blade + PostCSS

Client

pragueescorts.org

View prototype

Overview

Solo redesign of a legacy adult directory. The real problem wasn't any single page — it was 14+ routes drifting apart — so I rebuilt the product on one token-based system (100+ tokens, 55+ components) and shipped it as production Blade + Alpine on the existing backend. The linked prototype walks the live flows.

Platform overview

I mapped every flow — listing, profiles, agency, auth, and the edge states — in Figma before writing any code. The point wasn't to draw screens; it was to design the system that screens come from, so each page became an assembly of known parts instead of a fresh set of decisions.

Prague Escorts — Platform overview 1
Homepage — search, filters, model grid, and directory sections, all assembled from the shared library

Context

An adult directory running on a Laravel backend I couldn't replace — so the redesign had to slot onto existing routes and data, not fight them. I owned it end to end: UX flows in Figma, a token layer in the middle, production Blade + Alpine.js on top. One person, one system, so design intent survived all the way into the codebase.

Problem & brief

The legacy UI was a template patchwork — cards, spacing, and type re-decided per page across 14+ routes, so nothing read as one product and every fix leaked new regressions. I made a deliberate call: stop shipping pages, build the component library first. Filters and agency tooling had no shared foundation to stand on. Slower to start, far cheaper after the first few screens.

Before / after

Same routes, same crops — only the system changed. I kept the comparison honest so the story is about UI structure and hierarchy, not flattering angles: the legacy side is the cost of per-page decisions, the redesign is what one card component and a single spacing scale actually buy you.

Prague Escorts — Before / after 1
Listing — legacy filters sat buried in a noisy sidebar with hand-tuned cards; the redesign surfaces faceted filters, collapses everything to one card component, and puts all 14+ routes on a single spacing scale
Prague Escorts — Before / after 2
Profile — legacy stacked a cluttered sidebar against an ad-hoc gallery; the redesign uses a sticky left column so the hero and gallery stay in view while the long profile scrolls, on shared surface and type rules

Design system

This is the actual product — every screen is just an instance of it. I treated tokens as the single source of truth for color, type, surfaces, and status, so a change propagates everywhere instead of being re-typed per page, and documented it as a live showcase rather than a static sheet so design and build read from the same reference. 100+ tokens, 55+ components — including the empty and error states most directories only discover at launch.

Prague Escorts — Design system 1
Color, surface, and status tokens — tags, badges, gradients, and alert patterns built on the primary/accent/text scales
Prague Escorts — Design system 2
Type scale and the 12 empty/edge-case patterns — designed up front so blank and broken states never fall back to default ugliness
Prague Escorts — Design system 3
Components — rate states, validated inputs, card variants, shadows, and dropdowns, each a single source reused across routes

Key screens

Listing is where users actually spend their time, so the filtering had to be powerful without spawning a second, mobile-only build to maintain. I designed one card grid and one filter panel that simply reflow, and let Alpine.js toggle that panel into a drawer at narrow widths — same markup, same components, no parallel UI to keep in sync.

Prague Escorts — Key screens 1
Tablet listing — sort control and model grid; filters tuck behind a single panel trigger to keep the grid the focus
Prague Escorts — Key screens 2
Filters expanded — physical attributes, services, and categories on the same form components used everywhere else

Model profile

A profile is dense — gallery, stats, services, reviews, contact — so the risk is a wall of data. I anchored a sticky media column beside the scrollable content, keeping the photos and key actions in view while the details scroll, and built every block from the same library as listing so a returning user already knows how to read it.

Prague Escorts — Model profile 1
Hero and gallery — media tabs, badges, location, and agency links; this column stays sticky while the rest scrolls
Prague Escorts — Model profile 2
Physical data and services — attribute tables and tiered service groups, kept scannable on shared tokens
Prague Escorts — Model profile 3
Interview unlock, contact actions, and reviews — verification and feedback patterns that build trust before contact

Agency profile

Agencies needed their own hub, but inventing a separate visual language would have fractured the product. So an agency page is deliberately the model profile's bigger sibling — same cards, same type, same review pattern — with a roster that simply reuses the directory grid. New surface, zero new components to maintain.

Prague Escorts — Agency profile 1
Agency hero — rating, location, facilities tags, and description on the same surface tokens as profiles
Prague Escorts — Agency profile 2
Model roster — the directory's listing cards reused verbatim, with the same sort control
Prague Escorts — Agency profile 3
Reviews and related agencies — tabbed feedback and cross-links from shared patterns

Hot offers

Hot offers is a commercial surface, so it had to feel premium without becoming a one-off to maintain. I reused the directory's card and filter system and pushed the difference into content — richer hero cards, pricing, and a 'create your own' CTA — proving the same components flex from a plain listing into a marketing page.

Prague Escorts — Hot offers 1
Offers grid — hero cards, pricing, and contact actions on the directory's own card and filter system
Prague Escorts — Hot offers 2
Load more and footer CTA — pagination, a create-your-own banner, and the global footer pattern

Auth

Auth is the first real interaction, and friction here costs signups directly. I built it from one set of form components with inline validation and a single gradient primary action, and split onboarding by role — client, model, or agency — up front, because each path is genuinely different and guessing later is worse than asking once.

States

Edge states are where directories usually feel broken — a removed model, a private gallery, a dead link. I treated them as first-class screens, not afterthoughts: each uses the same empty-state pattern from the system, says what happened in plain copy, and always offers a route back into the catalog instead of a dead end.

Create request & paywall

This is where the product makes money, so both flows had to convert without feeling cheap. The create-request form is chunked into sections with inline validation so a long form never reads as one, and the paywall is a focused modal weighing membership against a one-time pass — both built from the same cards and buttons as the rest of the site, so monetization feels like part of the product rather than a bolted-on upsell.

Prague Escorts — Create request & paywall 1
Create request — title, location, and contact fields with inline validation, chunked into digestible sections
Prague Escorts — Create request & paywall 2
Private gallery modal — membership vs. one-time pass, with clear pricing tiers and a single primary CTA

Mobile

There's no separate mobile site — and that's the point. Every phone layout falls out of the same tokens and components as tablet and desktop, so there's no parallel CSS to drift out of sync. The real work was deciding what collapses, stacks, or hides at each breakpoint — not rebuilding screens.

Prague Escorts — Mobile 1
Listing, profile, offers, and agency at phone width — the same components, just reflowed
Prague Escorts — Mobile 2
Mobile nav and search — drawer menu, search field, and the directory's ad cards unchanged

Dev handoff

Because I designed and built it, handoff is a straight line, not a translation. A Figma card's spec — type at H3 28/36, the exact color token — lands as a profile-card Blade partial using the same pe.* tokens defined in tailwind.config.js. Design and code share one vocabulary, so there's nothing to interpret and nothing to drift.

Prague Escorts — Dev handoff 1
Figma — the profile card component, type (H3 28/36), and color tokens in the inspect panel
Prague Escorts — Dev handoff 2
Blade + Tailwind — the same card as profile-card.blade.php props and pe.* tokens in config

Platform breadth

Zoomed out, the proof is the consistency: listing, profile, agency, auth, and states all read as one product because they're literally built from the same parts. This is the payoff of systemizing first — breadth without the entropy that sank the legacy UI.

Prague Escorts — Platform breadth 1
Full platform map — every screen composed from the same tokens and Blade components

Brand touchpoints

The system doesn't stop at the screen. I drew the wordmark and merch in Illustrator from the same palette and geometry as the UI, so the brand and the product feel like one decision instead of two.

Prague Escorts — Brand touchpoints 1
Wordmark and lockups
Prague Escorts — Brand touchpoints 2
Merch mockup