A premium barbershop booking experience with real-time time-slot scheduling, cinematic animations, and an Express Priority Pass β the special paid booking that lets customers skip the queue entirely.
π¬ Jump to the Feature Walkthrough Β· β‘ Express Pass Β· π API Β· π Deploy
- About The Project
- π¬ Live Demo β Feature Walkthrough
- 1. Animated Hero & CSS Barber Pole
- 2. Infinite Marquee Ribbon
- 3. Services Menu β Hover-Tilt Cards
- 4. π The Special Booking β Express Priority Pass
- 5. Step 1 β Pick Your Service
- 6. Step 2 β Pick Your Date & Time Slot
- 7. Step 3 β Express Toggle, Live Ticket & Details
- 8. Confirmation β Confetti & Booking Code
- 9. The Shop Gallery
- 10. Mobile-First & Butter-Smooth
- π§ The Booking Engine β How It Works
- π§± Tech Stack
- π Project Structure
- π API Reference
- π Getting Started
- βοΈ Tuning The Shop
- βοΈ Deployment
- πΊοΈ Roadmap
- π€ Contributing & License
Fade & Blade is a full booking experience for a modern barbershop β not just a landing page with a phone number. Customers browse the service menu, pick a day and an exact 30-minute time slot, watch the available chairs update in real time, and lock in their appointment with a unique FB-XXXXXX confirmation code.
The signature feature is the Express Priority Pass β a special booking tier with an extra charge (+$12) that buys a guaranteed zero-wait experience: reserved chairs that standard bookings can never touch, a 5-minute guaranteed start, and a hot-towel finish on the house. Even when every standard chair is sold out, Express seats stay open β and the UI actively offers the upgrade when a slot is full.
Every pixel was designed to feel like the shop itself: espresso-dark interiors, amber-gold light, a spinning barber pole, and motion everywhere β staggered text reveals, tilt-on-hover cards, animated totals, and a confetti burst on confirmation.
Every feature below is captured from the running app. Follow along top-to-bottom and you have seen the entire customer journey.
The first thing you see is a full-viewport hero over a warm shop interior. The headline "SHARP LOOKS, ZERO WAIT." enters with a staggered Bebas Neue reveal β each line slides up with a spring easing curve a beat after the previous one. On the right sits a pure-CSS barber pole (barber-pole.tsx) whose red/white/amber stripes scroll endlessly via a pole-spin keyframe β no GIF, no video, just gradients and @keyframes.
What is happening under the hood:
- Scroll-aware navbar β hides as you scroll down, reappears with a backdrop blur the moment you scroll up (Framer Motion
useScroll). - Floating micro-details β a soft
floatykeyframe animates the sparkle icon; aglow-pulsekeyframe breathes life into the amber accents. - Trust strip β live stats (
12K+ cuts delivered,4.9β rating,5 min express start) anchor the two CTAs: Book Your Chair (smooth-scrolls to the booking flow) and Express Pass β skip the line (pre-selects the express upgrade via the Zustand store).
Directly under the hero, an infinitely scrolling ribbon loops the shop's promises β WALK-INS WELCOME β¦ HOT TOWEL FINISH β¦ 5-MIN EXPRESS START β¦ MASTER BARBERS β¦ β powered by a GPU-friendly marquee-x transform keyframe that never pauses the main thread.
Six services are seeded into SQLite and served from GET /api/services. Each card features:
- 3D hover tilt β the card rotates toward your cursor with a spring physics transition (Framer Motion), plus an amber border glow.
- "Most Wanted" badges for popular services, icon chips, duration & price rows.
- One-click booking β the "Book this" shortcut pre-fills the service in the booking flow and scrolls you straight to Step 1.
| Service | Price | Duration |
|---|---|---|
| Signature Haircut | $28 | 45 min |
| Premium Skin Fade | $32 | 45 min |
| Beard Sculpt & Trim | $18 | 30 min |
| Royal Hot Towel Shave | $30 | 40 min |
| Cut + Beard Combo | $42 | 60 min |
| Kids Cut (u12) | $16 | 30 min |
This is the special paid booking the shop is built around. For a flat +$12 per visit, the Express Pass rewrites the rules of the queue:
| πͺ Standard Booking | β‘ Express Priority Pass (+$12) | |
|---|---|---|
| Chairs | 3 shared chairs per slot | 2 chairs reserved for Express only |
| Wait time | First free slot | Guaranteed start within 5 minutes |
| Availability | Blocked when the 3 standard chairs are full | Still bookable when standard is sold out |
| Perks | β | Hot towel finish + complimentary drink |
The section presents this with an animated "Priority Boarding" card β a visual queue that shows standard customers waiting (W A Tβ¦) while the Express badge flashes YOU Β· FIRST. Benefits roll in with staggered scroll-reveal animations (reveal.tsx uses useInView so animations fire exactly when the section enters the viewport).
π Why it matters: the express inventory is enforced server-side.
POST /api/bookingscounts express bookings per slot againstEXPRESS_CHAIRS_PER_SLOT = 2, so standard traffic can never steal a priority chair β and when a standard slot fills up, the API returns aSLOT_FULL_TRY_EXPRESShint that the UI turns into a one-tap upgrade toast.
The booking panel is a 3-step wizard (booking.tsx) with animated step pills. Step 1 lists the live services from the database with prices and durations. Selecting a card animates the panel forward (Framer Motion AnimatePresence slide + fade) to the time picker. The step tracker at the top fills with amber as you progress β completed steps get a check, the active step glows.
The heart of the time-slot system:
- 14-day date rail β horizontally scrollable day chips (
TODAY 13,TMRW 14, β¦), generated client-side, validated server-side. - Live capacity per slot β every 30-minute slot from 10:00 AM to 7:00 PM queries
GET /api/availability?date=YYYY-MM-DDand renders remaining chairs ("3 left") under each time. - Smart slot states β past slots are disabled; fully-booked slots dim with a "Full" tag; slots with zero standard chairs but free express seats show an amber β‘ Express-only state that invites the upgrade.
- Refresh button re-syncs availability without losing your selection.
The details step is where the special booking comes alive:
- Express Priority Pass toggle β one flip and the whole panel changes mood: the pass card lights up amber, benefits list reload in, and the ticket on the right recalculates $32 β $44 with an animated number roll. The line item breaks the price down:
Service $32 + Express Pass +$12. - Your Ticket β a live summary card (service, duration, date, arrival time, priority status) that updates instantly with every change, like a boarding pass being printed.
- Validated form β name, phone (min 7 chars), optional email & notes, all validated client-side with react-hook-form + zod and re-validated server-side on submit.
- Free-cancellation hint β "up to 2 hours before your slot. Pay at the shop β card, cash or wallet."
On success the server returns the created booking with a unique human-friendly code (FB- + 6 characters from a confusion-safe alphabet β no O/0, no I/1). The screen celebrates with:
- An SVG checkmark that draws itself (stroke-dashoffset animation) inside a pulsing ring,
- A confetti burst rendered by the booking success component,
- A toast: "Booking confirmed π" β plus a "Book another slot" action that resets the wizard while keeping your contact details.
Every booking is retrievable anytime via GET /api/bookings?code=FB-XXXXXX.
An asymmetric masonry grid of the shop β leather chairs, warm bulbs, hot-towel smoke. Images zoom subtly on hover behind a gradient caption ("BEARD SCULPTING", "THE FLOOR") and reveal on scroll with the shared reveal.tsx wrapper.
The entire experience is responsive: the navbar collapses into an animated hamburger sheet, the date rail swipes, the ticket stacks below the form, touch targets stay β₯ 44px, and the footer always sticks to the bottom of the viewport β never floats, never overlaps.
All business rules live in one file (src/lib/constants.ts) and are enforced server-side in the API layer, so the client can be trusted for UX but never for inventory.
Opening hours 10:00 AM β 8:00 PM (MonβSun)
Slot length 30 minutes (last booking starts 7:00 PM)
Booking window next 14 days, including today
Standard capacity 3 chairs per slot β enforced by POST /api/bookings
Express capacity 2 chairs per slot β never shared with standard
Express fee flat $12, added to the service price
Confirmation code FB-XXXXXX, confusion-safe alphabet (no O/0, I/1)
Double-booking blocked per (date, timeSlot, chair-pool) with HTTP 409
Slot decision logic (server-side, POST /api/bookings):
Requested express?
βββ YES β count express bookings in slot
β βββ < 2 β β
confirm (price = service + $12)
β βββ β₯ 2 β β 409 "Express seats fully reserved"
βββ NO β count standard bookings in slot
βββ < 3 β β
confirm (price = service)
βββ β₯ 3 β β 409 SLOT_FULL_TRY_EXPRESS
β UI offers a one-tap Express upgrade β‘
A booking is stored with its price breakdown (expressFee, totalPrice) so receipts and audits stay consistent.
| Layer | Technology | Why |
|---|---|---|
| Framework | Next.js 16 (App Router) + React 19 | Server components, route handlers, image optimization |
| Language | TypeScript 5 (strict) | End-to-end type safety from schema to UI |
| Styling | Tailwind CSS 4 + CSS custom properties | Dark espresso/amber theme as design tokens |
| UI Kit | shadcn/ui (Radix primitives) | Accessible dialogs, toasts, forms out of the box |
| Animation | Framer Motion 12 + custom @keyframes |
Springs, staggering, scroll reveals, AnimatePresence step transitions |
| State | Zustand | Cross-component pre-selection (service / express shortcuts) |
| Forms | react-hook-form + zod | Client & server validation from one schema style |
| Database | Prisma ORM + SQLite | Zero-config persistence, perfect for a single shop |
| Icons | Lucide | Crisp, consistent iconography |
| Toasts | Sonner | Confirmation & upgrade hints |
BARBER-SSSS/
βββ prisma/
β βββ schema.prisma # Service & Booking models (SQLite)
βββ db/ # SQLite database file (generated)
βββ public/
β βββ images/ # Hero, gallery & express imagery
β βββ logo.svg
βββ docs/
β βββ screenshots/ # Captures used in this README
βββ src/
β βββ app/
β β βββ page.tsx # Single-page composition of all sections
β β βββ layout.tsx # Fonts (Bebas Neue / Geist), metadata, Toaster
β β βββ globals.css # Theme tokens + marquee / pole / shimmer keyframes
β β βββ api/
β β βββ services/route.ts # GET β catalogue (auto-seeds on first hit)
β β βββ availability/route.ts # GET β per-slot remaining chairs
β β βββ bookings/route.ts # POST β create Β· GET ?code= β lookup
β βββ components/
β β βββ barber/ # βοΈ The whole experience
β β β βββ navbar.tsx # Scroll-aware, blur, mobile sheet
β β β βββ hero.tsx # Staggered reveal, stats, CTAs
β β β βββ barber-pole.tsx # Pure-CSS spinning pole
β β β βββ marquee.tsx # Infinite ribbon
β β β βββ services.tsx # Hover-tilt service cards
β β β βββ express.tsx # Priority Boarding card + benefits
β β β βββ booking.tsx # 3-step wizard orchestrator
β β β βββ booking-parts.tsx # Date rail, slot grid, ticket, success view
β β β βββ reveal.tsx # Shared scroll-reveal wrapper
β β β βββ gallery.tsx # Masonry gallery
β β β βββ footer.tsx # Sticky footer
β β βββ ui/ # shadcn/ui primitives
β βββ hooks/ # use-toast, use-mobile
β βββ lib/
β βββ constants.ts # ποΈ ALL business rules in one place
β βββ slots.ts # Slot generation, past-slot & date-window guards
β βββ booking-store.ts # Zustand β express/service pre-selection
β βββ seed.ts # Idempotent service catalogue seeding
β βββ db.ts # Prisma singleton
β βββ types.ts # Shared DTOs (ServiceDto, SlotInfo, β¦)
βββ .env.example # Environment template
βββ package.json
Base URL: /api
Returns the active service catalogue. Auto-seeds the six services on first request if the table is empty.
Returns every slot of the day with remaining capacity for both pools.
// 200 OK
{
"date": "2026-10-14",
"slots": [
{ "slot": "10:00", "label": "10:00 AM",
"regularLeft": 2, "expressLeft": 2,
"regularAvailable": true, "expressAvailable": true },
{ "slot": "11:00", "label": "11:00 AM",
"regularLeft": 0, "expressLeft": 1,
"regularAvailable": false, "expressAvailable": true } // β‘ express-only
]
}Creates a booking. Validates the date window, opening hours, past slots, service existence and per-slot chair capacity β with a special SLOT_FULL_TRY_EXPRESS hint when standard is full but express is not.
// Request
{
"serviceId": "clxβ¦",
"date": "2026-10-14",
"timeSlot": "11:00",
"isExpress": true, // β‘ the special booking β adds $12
"customerName": "Alex Carter",
"phone": "+1 555 010 2288",
"email": "[email protected]", // optional
"notes": "Skin fade, tight lineup" // optional
}
// 201 Created
{
"booking": {
"code": "FB-T2MRGN",
"isExpress": true,
"expressFee": 12,
"totalPrice": 44,
"status": "CONFIRMED",
"service": { "name": "Premium Skin Fade", "duration": 45, "price": 32 }
}
}
// 409 Conflict (standard full, express open)
{ "error": "Standard chairs are fully booked for this slot. Express seats may still be open β upgrade for $12 to lock it in.", "code": "SLOT_FULL_TRY_EXPRESS" }Looks a booking up by its confirmation code β powers the "where's my appointment" flow.
- Node.js 20+ and Bun (or npm/pnpm β scripts shown with Bun)
- Git
# 1 β clone
git clone https://github.com/ankush24570/BARBER-SSSS.git
cd BARBER-SSSS
# 2 β install dependencies
bun install
# 3 β configure environment
cp .env.example .env # points Prisma at ./db/custom.db
# 4 β create the database from the schema
bun run db:push
# 5 β start the dev server
bun run devOpen http://localhost:3000. The service catalogue seeds itself on the first page load β no seed command needed.
π‘ The app is fully self-contained: SQLite means no external database server, and the catalogue seeds automatically.
Everything the owner would want to change lives in src/lib/constants.ts β one edit re-prices the whole experience, UI and API included:
| Constant | Default | Effect |
|---|---|---|
OPEN_HOUR / CLOSE_HOUR |
10 / 20 |
Slot grid start & end |
SLOT_MINUTES |
30 |
Granularity of the time picker |
CHAIRS_PER_SLOT |
3 |
Standard capacity per slot |
EXPRESS_CHAIRS_PER_SLOT |
2 |
Reserved express chairs per slot |
EXPRESS_FEE |
$12 |
The special booking surcharge |
BOOKING_DAYS_AHEAD |
14 |
How far ahead customers can book |
SERVICES (src/lib/seed.ts) |
6 items | The menu β name, price, duration, icon |
- Push this repo to GitHub (already done β ) and click New Project on vercel.com.
- Add the environment variable
DATABASE_URL=file:/tmp/custom.db(or swap Prisma's provider to Postgres for durable storage). - Deploy β the build runs
next buildout of the box.
β οΈ SQLite on serverless: serverless filesystems are ephemeral. For production traffic, switchprisma/schema.prismatoprovider = "postgresql"and pointDATABASE_URLat Neon/Supabase β no code changes needed beyond the provider line andbun run db:push.
bun run build # produces the standalone server
bun run start # serves on $PORT with the SQLite file persisted on disk- SMS / WhatsApp confirmation via Twilio
- Barber assignment & staff calendars
- Express Pass subscriptions (monthly zero-wait membership)
- Admin dashboard β daily agenda, capacity editing, no-show flags
- Online payment capture (Stripe) with cancellation refunds
Contributions are welcome β fork the repo, create a feature branch, and open a PR describing the why and the how.
MIT License Β© 2026 ankush24570
β Star this repo if it saved you a haircut queue.
Built with Next.js 16 Β· Tailwind 4 Β· Framer Motion Β· Prisma






