// Entertainment app UI
Movie Ticket Booking App UI Design in Expo
This movie ticket booking app UI covers the full path from browsing to a held seat: a ranked movie chart, a poster-led detail page, showtimes by theatre, and a seat map you can actually tap.
- Screens
- 4
- Built with
- Expo SDK 58
- Fonts
- Inter, Bebas Neue
- License
- Free (MIT)

Movie Ticket Booking app screens

Home and movie chart
Primary tabs, a peeking poster carousel with chart rank overlays, booking stats and a floating BOOK action in the tab bar.

Movie detail
A full-bleed poster fades to black behind the title lockup, runtime, languages, synopsis and a single Book Now action.

Booking by theatre
Format chips and a poster row on black, then a white sheet with a date strip, city filters and showtime chips colour-coded by availability.

Seat selection
A curved screen, tappable seat grid with aisles and two price tiers, a legend, and a dark ticket stub with a hold action.
Copy the Movie Ticket Booking kit
Use the artifact as a design brief, the skill to generate the UI with an AI agent, or the code to run it right away.
Design artifact
The full design: tokens, screens and source
# Movie Ticket Booking: design artifact
This movie ticket booking app UI covers the full path from browsing to a held seat: a ranked movie chart, a poster-led detail page, showtimes by theatre, and a seat map you can actually tap.
- Type: Entertainment mobile app UI
- Screens: 4
- Artboard: 390 × 844
- Target: Expo SDK 58, Expo Router, TypeScript, expo-image, expo-linear-gradient, react-native-svg
- Live page: https://developermill.com/app-designs/movie-ticket-booking-app-ui-design
## Design direction
A cinematic black and white base with a single ember accent, tall poster cards and a dark ticket stub that anchors the checkout.
- Interactive seat map with classic and premium pricing
- Live ticket stub with seats, show time and total
- Peeking poster carousel with ranked overlays
- Date strip, format chips and theatre showtime cards
## Foundations
### Colour
| Token | Hex |
| --- | --- |
| Ember | `#CC3A0E` |
| Ink | `#141414` |
| Screen black | `#000000` |
| Gold rating | `#F0A52B` |
| Available | `#23A55A` |
| Hairline | `#E0E0E0` |
### Typography
- Typefaces: Inter, Bebas Neue
- Common sizes (px): 10, 11, 12, 13, 14, 15, 16, 17
### Shape and depth
- Common corner radii (px): 3, 4, 8, 9, 14, 999
- Shadows: `0 18px 36px rgba(196, 30, 30, 0.28)`, `0 -4px 18px rgba(0,0,0,0.06)`, `0 10px 22px rgba(204,58,14,0.4)`, `0 10px 24px rgba(204,58,14,0.35)`
## Screens
### 1. Home and movie chart
Primary tabs, a peeking poster carousel with chart rank overlays, booking stats and a floating BOOK action in the tab bar.
Preview: https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/home.webp
Copy on this screen:
- Movies
- Events / Benefits
- Activities
- Movie Chart
- Now Showing
- Coming Soon
- See all
- 2
- 1
- Iron Petal – The Movie: Akane Arc
- 16+
- Booking Rate: 28.4% • Cumulative: 1.4 million •
- 8,123
- Book Now
- Home
- Cine Talk
- BOOK
- Store
- More
### 2. Movie detail
A full-bleed poster fades to black behind the title lockup, runtime, languages, synopsis and a single Book Now action.
Preview: https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/detail.webp
Copy on this screen:
- Iron Petal – The Movie: Akane Arc
- Trailer (2)
- IRON PETAL
- THE MOVIE · AKANE ARC
- Japanese, Hindi
- Streaming Now
- Book Now
- 1h 42m • Action, Adventure, Anime, Fantasy • A • 13 Nov, 2026
- Japanese • Languages:
- Audio(2)
- ,
- Subtitles(1)
- Akane, a sword-for-hire with a blade that blooms, is drawn into a turf war when an old ally resurfaces with a debt she can’t …
- Read more…
### 3. Booking by theatre
Format chips and a poster row on black, then a white sheet with a date strip, city filters and showtime chips colour-coded by availability.
Preview: https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/showtimes.webp
Copy on this screen:
- Movies Showing
- |
- Booking by Theatre
- All
- SCREENX
- 4DX
- IMAX
- DOLBY
- 1
- Iron Petal – The Movie: Akane Arc
- 16+
- 1h 42m
- MON
- 16
- TUE
- 17
- WED
- 18
- THU
- 19
- FRI
- 20
- SAT
- 21
- Mumbai
- Special Theatre
- CV
- Cineverse, Skyline Mall, Borivali
- Non-cancellable.
- 4:25 PM
- 4:45 PM
- 7:35 PM
- · LUX
- 7:45 PM
- 10:50 PM
- 11:00 PM
- 11:05 PM
- Make favourite cinemas by tapping the
- 3/3
### 4. Seat selection
A curved screen, tappable seat grid with aisles and two price tiers, a legend, and a dark ticket stub with a hold action.
Preview: https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/seats.webp
Copy on this screen:
- Iron Petal – The Movie
- Cineverse, Skyline Mall, Borivali
- Mon, 16 Nov · 1h 42m · Japanese
- 4:25 PM
- 4:45 PM
- 7:35 PM
- LUX
- 7:45 PM
- 10:50 PM
- Screen this way
- CLASSIC
- ₹250
- PREMIUM RECLINER
- ₹420
- Classic
- Premium
- Taken
- Your pick
- Seats
- Show
- Mon · 7:35 PM
- Total
- Seats held for 10 minutes. Complete payment to confirm your tickets.
- Clear selection
## Full source
The complete design source for each screen. Images point at hosted demo files.
### Main
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Home – Movie Chart</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Bebas+Neue&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#1a1a1a}a:hover{color:#000}
button{font-family:inherit;cursor:pointer}
</style>
</helmet>
<div style="width: 390px; height: 844px; box-sizing: border-box; background: #ffffff; font-family: Inter, system-ui, sans-serif; color: #141414; position: relative; overflow: hidden; display: flex; flex-direction: column">
<!-- Header -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 58px 20px 14px">
<svg width="36" height="32" viewBox="0 0 36 32" role="img" aria-label="Reelo logo"><path d="M18 2 L34 28 Q35 30 33 30 L3 30 Q1 30 2 28 Z" fill="{{accent}}"></path><path d="M18 13 L25 25 L11 25 Z" fill="#ffffff"></path></svg>
<div style="display: flex; gap: 18px; align-items: center">
<button aria-label="My tickets" style="background: none; border: 0; padding: 0; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h16v4a2 2 0 0 0 0 4v4H4v-4a2 2 0 0 0 0-4z"></path><path d="M12 6v12" stroke-dasharray="2 2"></path></svg></button>
<button aria-label="Notifications" style="background: none; border: 0; padding: 0; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 16V11a6 6 0 0 1 12 0v5l1.5 2h-15z"></path><path d="M10 20a2 2 0 0 0 4 0"></path></svg></button>
<button aria-label="Search" style="background: none; border: 0; padding: 0; width: 28px; height: 28px; display: flex; align-items: center; justify-content: center"><svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="1.8" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="M16.5 16.5L21 21"></path></svg></button>
</div>
</div>
<!-- Primary tabs -->
<div style="display: flex; align-items: center; gap: 22px; padding: 0 20px">
<button style="display: flex; align-items: center; gap: 7px; background: #141414; color: #ffffff; border: 0; border-radius: 999px; padding: 9px 16px; font-size: 15px; font-weight: 600"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="5" width="18" height="15" rx="2"></rect><path d="M3 10h18M8 5l2 5M14 5l2 5"></path></svg>Movies</button>
<button style="background: none; border: 0; padding: 0; font-size: 14px; color: #5c5c5c">Events / Benefits</button>
<button style="background: none; border: 0; padding: 0; font-size: 14px; color: #5c5c5c">Activities</button>
</div>
<!-- Sub tabs -->
<div style="display: flex; align-items: center; gap: 18px; padding: 18px 20px 0; font-size: 13px; color: #6b6b6b">
<span style="color: #141414; font-weight: 600; padding-bottom: 6px; border-bottom: 2px solid #141414">Movie Chart</span>
<span style="padding-bottom: 8px">Now Showing</span>
<span style="padding-bottom: 8px">Coming Soon</span>
<span style="padding-bottom: 8px; display: flex; align-items: center; gap: 2px">See all <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#6b6b6b" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M9 6l6 6-6 6"></path></svg></span>
</div>
<!-- Carousel -->
<div style="position: relative; height: 330px; margin-top: 22px">
<div aria-hidden="true" style="position: absolute; left: -200px; top: 26px; width: 228px; height: 278px; border-radius: 26px; overflow: hidden"><img src="https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets/c27d246b2a.svg" alt="" style="width: 100%; height: 100%; object-fit: cover; display: block"></div>
<div aria-hidden="true" style="position: absolute; right: -200px; top: 26px; width: 228px; height: 278px; border-radius: 26px; overflow: hidden"><img src="https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets/c84f4e1efb.svg" alt="" style="width: 100%; height: 100%; object-fit: cover; display: block"><span style="position: absolute; left: 14px; top: 10px; font-family: 'Bebas Neue', sans-serif; font-size: 34px; color: #ffffff">2</span></div>
<div style="position: absolute; left: 50%; top: 0; transform: translateX(-50%); width: 252px; height: 330px; border-radius: 30px; overflow: hidden; box-shadow: 0 18px 36px rgba(196, 30, 30, 0.28); background: #6e0610">
<img src="https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets/18e23cdfc9.svg" alt="Iron Petal – The Movie: Akane Arc poster" style="position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block">
<span style="position: absolute; left: 18px; top: 10px; font-family: 'Bebas Neue', sans-serif; font-size: 44px; color: #ffffff; font-style: italic">1</span>
<button aria-label="Add to favourites" style="position: absolute; right: 14px; top: 14px; width: 36px; height: 36px; border: 0; background: none; padding: 0; display: flex; align-items: center; justify-content: center"><svg width="26" height="26" viewBox="0 0 24 24" fill="#ffffff" aria-hidden="true"><path d="M12 21s-7.5-4.6-9.5-9.2C1 8.2 3.3 4.5 7 4.5c2.1 0 3.7 1.1 5 2.9 1.3-1.8 2.9-2.9 5-2.9 3.7 0 6 3.7 4.5 7.3C19.5 16.4 12 21 12 21z"></path></svg></button>
</div>
</div>
<!-- Movie info -->
<div style="display: flex; flex-direction: column; align-items: center; padding: 22px 20px 0">
<div style="display: flex; align-items: center; gap: 8px">
<h1 style="margin: 0; font-size: 16px; font-weight: 700">Iron Petal – The Movie: Akane Arc</h1>
<span style="background: #f0a52b; color: #141414; font-size: 11px; font-weight: 800; border-radius: 5px; padding: 3px 5px">16+</span>
</div>
<div style="display: flex; align-items: center; gap: 6px; margin-top: 12px; font-size: 12px; color: #3d3d3d">
<svg width="13" height="13" viewBox="0 0 24 24" fill="{{accent}}" aria-hidden="true"><path d="M12 2c1 4 6 6 6 12a6 6 0 0 1-12 0c0-3 2-5 3-6 0 2 1 3 2 3 0-3 1-6 1-9z"></path></svg>
<span>Booking Rate: 28.4% • Cumulative: 1.4 million •</span>
<svg width="12" height="12" viewBox="0 0 24 24" fill="#c0392b" aria-hidden="true"><path d="M12 21s-7.5-4.6-9.5-9.2C1 8.2 3.3 4.5 7 4.5c2.1 0 3.7 1.1 5 2.9 1.3-1.8 2.9-2.9 5-2.9 3.7 0 6 3.7 4.5 7.3C19.5 16.4 12 21 12 21z"></path></svg>
<span>8,123</span>
</div>
<a href="Detail.dc.html" style="margin-top: 22px; background: #141414; color: #ffffff; text-decoration: none; border-radius: 999px; padding: 13px 34px; font-size: 15px; font-weight: 600">Book Now</a>
</div>
<!-- Bottom nav -->
<div style="position: absolute; left: 0; right: 0; bottom: 0; height: 92px; background: #ffffff; box-shadow: 0 -4px 18px rgba(0,0,0,0.06); display: flex; align-items: flex-start; justify-content: space-around; padding: 12px 8px 0; box-sizing: border-box">
<a href="Main.dc.html" style="display: flex; flex-direction: column; align-items: center; gap: 5px; text-decoration: none; color: #141414; font-size: 11px; width: 56px"><svg width="22" height="22" viewBox="0 0 24 24" fill="#141414" aria-hidden="true"><path d="M3 10.5L12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z"></path></svg>Home</a>
<a href="#" style="display: flex; flex-direction: column; align-items: center; gap: 5px; text-decoration: none; color: #141414; font-size: 11px; width: 56px"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16v11H9l-5 4z"></path><path d="M8 10.5h.01M12 10.5h.01M16 10.5h.01" stroke-width="2.6" stroke-linecap="round"></path></svg>Cine Talk</a>
<a href="Showtimes.dc.html" aria-label="Book tickets" style="margin-top: -32px; width: 68px; height: 78px; border-radius: 24px; background: {{accent}}; box-shadow: 0 10px 22px rgba(204,58,14,0.4); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; text-decoration: none; color: #ffffff; font-size: 12px; font-weight: 700"><svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><path d="M4 6h16v4a2 2 0 0 0 0 4v4H4v-4a2 2 0 0 0 0-4z"></path><path d="M9 10h6M9 14h6"></path></svg>BOOK</a>
<a href="#" style="display: flex; flex-direction: column; align-items: center; gap: 5px; text-decoration: none; color: #141414; font-size: 11px; width: 56px"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><path d="M6 10h12l-1.5 11h-9z"></path><path d="M7 10a3 3 0 0 1 3-4 3 3 0 0 1 4 0 3 3 0 0 1 3 4"></path></svg>Store</a>
<a href="#" style="display: flex; flex-direction: column; align-items: center; gap: 5px; text-decoration: none; color: #141414; font-size: 11px; width: 56px"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="1.8" aria-hidden="true"><circle cx="7.5" cy="7.5" r="3.5"></circle><circle cx="16.5" cy="7.5" r="3.5"></circle><circle cx="7.5" cy="16.5" r="3.5"></circle><circle cx="16.5" cy="16.5" r="3.5"></circle></svg>More</a>
</div>
<div aria-hidden="true" style="position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); width: 134px; height: 5px; border-radius: 3px; background: #141414"></div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#CC3A0E","options":["#CC3A0E","#C2185B","#1E5BD8"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
return { accent: this.props.accent ?? '#CC3A0E' };
}
}
</script>
</body>
</html>
```
### Detail
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Movie Detail</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Bebas+Neue&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#ff8a65}a:hover{color:#ffab91}
button{font-family:inherit;cursor:pointer}
</style>
</helmet>
<div style="width: 390px; height: 844px; box-sizing: border-box; background: #000000; font-family: Inter, system-ui, sans-serif; color: #ffffff; position: relative; overflow: hidden">
<!-- Poster hero -->
<div style="position: absolute; left: 0; top: 0; width: 390px; height: 590px; background: #2a0105">
<img src="https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets/18e23cdfc9.svg" alt="" style="position: absolute; left: 0; top: 0; width: 390px; height: 590px; object-fit: cover; object-position: top; display: block">
<div style="position: absolute; left: 0; right: 0; bottom: 0; height: 300px; background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 45%, #000000 100%)"></div>
</div>
<!-- Top bar -->
<div style="position: absolute; left: 16px; right: 16px; top: 56px; height: 44px; border-radius: 999px; background: rgba(40,0,4,0.55); display: flex; align-items: center; justify-content: space-between; padding: 0 6px">
<a href="Main.dc.html" aria-label="Back" style="width: 36px; height: 36px; display: flex; align-items: center; justify-content: center"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5l-7 7 7 7"></path></svg></a>
<span style="font-size: 13px; font-weight: 600">Iron Petal – The Movie: Akane Arc</span>
<button aria-label="Share" style="width: 36px; height: 36px; border: 0; background: none; padding: 0; display: flex; align-items: center; justify-content: center"><svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 4h6v6M20 4l-9 9"></path><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"></path></svg></button>
</div>
<!-- Trailer pill -->
<button style="position: absolute; left: 50%; top: 300px; transform: translateX(-50%); display: flex; align-items: center; gap: 7px; border: 0; border-radius: 999px; background: rgba(30,30,30,0.6); color: #ffffff; font-size: 13px; padding: 8px 14px 8px 8px"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="1.6" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M10 8.5l5 3.5-5 3.5z" fill="#ffffff"></path></svg>Trailer (2)</button>
<!-- Title block -->
<div style="position: absolute; left: 0; right: 0; top: 448px; display: flex; flex-direction: column; align-items: center">
<h1 style="margin: 0; font-family: 'Bebas Neue', sans-serif; font-weight: 400; font-size: 64px; line-height: 0.9; letter-spacing: 2px">IRON PETAL</h1>
<span style="font-size: 11px; letter-spacing: 3.5px; color: #ffc9c9; margin-top: 6px">THE MOVIE · AKANE ARC</span>
<span style="font-size: 14px; font-weight: 600; margin-top: 10px">Japanese, Hindi</span>
</div>
<!-- Booking + info -->
<div style="position: absolute; left: 20px; right: 20px; top: 612px; display: flex; flex-direction: column">
<span style="font-size: 13px; color: #e6e6e6">Streaming Now</span>
<a href="Showtimes.dc.html" style="margin-top: 14px; height: 48px; border-radius: 999px; background: {{accent}}; color: #ffffff; display: flex; align-items: center; justify-content: center; text-decoration: none; font-size: 15px; font-weight: 600; box-shadow: 0 10px 24px rgba(204,58,14,0.35)">Book Now</a>
<p style="margin: 16px 0 0; font-size: 12px; color: #e6e6e6">1h 42m • Action, Adventure, Anime, Fantasy • A • 13 Nov, 2026</p>
<p style="margin: 6px 0 0; font-size: 12px; color: #9a9a9a">Japanese • Languages: <span style="color: #ff8a65">Audio(2)</span>, <span style="color: #ff8a65">Subtitles(1)</span></p>
<p style="margin: 10px 0 0; font-size: 12px; line-height: 1.5; color: #d9d9d9">Akane, a sword-for-hire with a blade that blooms, is drawn into a turf war when an old ally resurfaces with a debt she can’t … <a href="#" style="text-decoration: none">Read more…</a></p>
</div>
<div aria-hidden="true" style="position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); width: 134px; height: 5px; border-radius: 3px; background: #ffffff"></div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#CC3A0E","options":["#CC3A0E","#C2185B","#1E5BD8"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
return { accent: this.props.accent ?? '#CC3A0E' };
}
}
</script>
</body>
</html>
```
### Showtimes
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Booking by Theatre</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Bebas+Neue&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#ffffff}a:hover{color:#ffffff}
button{font-family:inherit;cursor:pointer}
</style>
</helmet>
<div style="width: 390px; height: 844px; box-sizing: border-box; background: #000000; font-family: Inter, system-ui, sans-serif; color: #ffffff; position: relative; overflow: hidden">
<!-- Header -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 58px 20px 0">
<div style="font-size: 13px; color: #ffffff">Movies Showing <span style="color: #8a8a8a">|</span> <span style="color: #a6a6a6">Booking by Theatre</span></div>
<a href="Detail.dc.html" aria-label="Close" style="width: 28px; height: 28px; display: flex; align-items: center; justify-content: center"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="10"></circle><path d="M9 9l6 6M15 9l-6 6"></path></svg></a>
</div>
<!-- Format chips -->
<div style="display: flex; gap: 8px; padding: 18px 20px 0">
<button style="border: 0; border-radius: 999px; background: #ffffff; color: #000000; font-size: 13px; font-weight: 600; padding: 7px 14px">All</button>
<button style="border: 1px solid #4a4a4a; border-radius: 999px; background: none; color: #e0e0e0; font-size: 12px; font-weight: 600; padding: 7px 12px">SCREENX</button>
<button style="border: 1px solid #4a4a4a; border-radius: 999px; background: none; color: #e0e0e0; font-size: 12px; font-weight: 600; padding: 7px 12px">4DX</button>
<button style="border: 1px solid #4a4a4a; border-radius: 999px; background: none; color: #e0e0e0; font-size: 12px; font-weight: 600; padding: 7px 12px">IMAX</button>
<button style="border: 1px solid #4a4a4a; border-radius: 999px; background: none; color: #e0e0e0; font-size: 12px; font-weight: 600; padding: 7px 12px">DOLBY</button>
</div>
<!-- Poster row (placeholders for the three generated posters) -->
<div style="position: relative; height: 170px; margin-top: 22px">
<div aria-hidden="true" style="position: absolute; left: -2px; top: 12px; width: 84px; height: 146px; border-radius: 16px; overflow: hidden"><img src="https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets/c27d246b2a.svg" alt="" style="width: 100%; height: 100%; object-fit: cover; display: block"></div>
<div style="position: absolute; left: 50%; top: 0; transform: translateX(-50%); width: 120px; height: 170px; border-radius: 18px; overflow: hidden; background: #6d0610">
<img src="https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets/18e23cdfc9.svg" alt="Iron Petal poster" style="width: 100%; height: 100%; object-fit: cover; display: block">
<span style="position: absolute; left: 10px; top: 4px; font-family: 'Bebas Neue', sans-serif; font-size: 30px; font-style: italic">1</span>
</div>
<div aria-hidden="true" style="position: absolute; right: -2px; top: 12px; width: 84px; height: 146px; border-radius: 16px; overflow: hidden"><img src="https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets/c84f4e1efb.svg" alt="" style="width: 100%; height: 100%; object-fit: cover; display: block"></div>
</div>
<div style="display: flex; flex-direction: column; align-items: center; padding-top: 20px">
<div style="display: flex; align-items: center; gap: 8px">
<h1 style="margin: 0; font-size: 16px; font-weight: 700">Iron Petal – The Movie: Akane Arc</h1>
<span style="background: #f0a52b; color: #141414; font-size: 11px; font-weight: 800; border-radius: 5px; padding: 3px 5px">16+</span>
</div>
<span style="font-size: 12px; color: #b3b3b3; margin-top: 6px">1h 42m</span>
</div>
<!-- White sheet -->
<div style="position: absolute; left: 0; right: 0; top: 444px; bottom: 0; background: #ffffff; border-radius: 28px 28px 0 0; color: #141414; padding: 18px 16px 0">
<div style="display: flex; gap: 8px">
<button style="width: 50px; height: 56px; border: 0; border-radius: 14px; background: {{accent}}; color: #ffffff; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; font-size: 11px; font-weight: 700">MON<span style="font-size: 17px; font-weight: 700">16</span></button>
<button style="width: 50px; height: 56px; border: 1px solid #e0e0e0; border-radius: 14px; background: #ffffff; color: #141414; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; font-size: 11px; font-weight: 600">TUE<span style="font-size: 17px">17</span></button>
<button style="width: 50px; height: 56px; border: 1px solid #e0e0e0; border-radius: 14px; background: #ffffff; color: #141414; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; font-size: 11px; font-weight: 600">WED<span style="font-size: 17px">18</span></button>
<button style="width: 50px; height: 56px; border: 1px solid #e0e0e0; border-radius: 14px; background: #ffffff; color: #141414; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; font-size: 11px; font-weight: 600">THU<span style="font-size: 17px">19</span></button>
<button style="width: 50px; height: 56px; border: 1px solid #e0e0e0; border-radius: 14px; background: #ffffff; color: #141414; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; font-size: 11px; font-weight: 600">FRI<span style="font-size: 17px">20</span></button>
<button style="width: 50px; height: 56px; border: 1px solid #e0e0e0; border-radius: 14px; background: #ffffff; color: #141414; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; font-size: 11px; font-weight: 600">SAT<span style="font-size: 17px">21</span></button>
</div>
<div style="display: flex; gap: 8px; margin-top: 14px">
<button style="display: flex; align-items: center; gap: 5px; border: 0; border-radius: 999px; background: {{accent}}; color: #ffffff; font-size: 13px; font-weight: 600; padding: 7px 13px"><svg width="13" height="13" viewBox="0 0 24 24" fill="#ffffff" aria-hidden="true"><path d="M12 2a7 7 0 0 0-7 7c0 5 7 13 7 13s7-8 7-13a7 7 0 0 0-7-7zm0 9.5A2.5 2.5 0 1 1 12 6.5a2.5 2.5 0 0 1 0 5z"></path></svg>Mumbai</button>
<button style="border: 1px solid #e0e0e0; border-radius: 999px; background: #ffffff; color: #3d3d3d; font-size: 13px; padding: 7px 14px">All</button>
<button style="border: 1px solid #e0e0e0; border-radius: 999px; background: #ffffff; color: #3d3d3d; font-size: 13px; padding: 7px 14px">Special Theatre</button>
</div>
<!-- Theatre card -->
<div style="margin-top: 14px; border-radius: 18px; box-shadow: 0 4px 18px rgba(0,0,0,0.08); padding: 14px 14px 16px">
<div style="display: flex; align-items: center; gap: 10px">
<div style="width: 34px; height: 34px; border-radius: 10px; background: #fff3d6; color: #b07400; font-size: 11px; font-weight: 800; display: flex; align-items: center; justify-content: center">CV</div>
<div style="flex: 1">
<div style="font-size: 15px; font-weight: 600">Cineverse, Skyline Mall, Borivali</div>
<div style="font-size: 11px; color: #6b6b6b; margin-top: 1px">Non-cancellable.</div>
</div>
<button aria-label="Favourite this cinema" style="width: 36px; height: 36px; border: 0; background: none; padding: 0; display: flex; align-items: center; justify-content: center"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="1.8" aria-hidden="true"><path d="M12 20s-7-4.3-8.8-8.6C1.9 8 4 4.8 7.2 4.8c2 0 3.5 1 4.8 2.7 1.3-1.7 2.8-2.7 4.8-2.7 3.2 0 5.3 3.2 4 6.6C19 15.7 12 20 12 20z"></path></svg></button>
</div>
<div style="display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; margin-top: 14px">
<a href="Seats.dc.html" style="height: 30px; border-radius: 8px; background: #141414; border-left: 5px solid #23a55a; color: #ffffff; text-decoration: none; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600">4:25 PM</a>
<a href="Seats.dc.html" style="height: 30px; border-radius: 8px; background: #141414; border-left: 5px solid #23a55a; color: #ffffff; text-decoration: none; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600">4:45 PM</a>
<a href="Seats.dc.html" style="height: 30px; border-radius: 8px; background: linear-gradient(90deg, #f0a52b 0 50%, #141414 50% 100%); color: #ffffff; text-decoration: none; display: flex; align-items: center; justify-content: center; gap: 3px; font-size: 11px; font-weight: 600">7:35 PM<span style="font-size: 7px">· LUX</span></a>
<a href="Seats.dc.html" style="height: 30px; border-radius: 8px; background: linear-gradient(90deg, #f0a52b 0 50%, #141414 50% 100%); color: #ffffff; text-decoration: none; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600">7:45 PM</a>
<a href="Seats.dc.html" style="height: 30px; border-radius: 8px; background: #141414; border-left: 5px solid #23a55a; color: #ffffff; text-decoration: none; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600">10:50 PM</a>
<a href="Seats.dc.html" style="position: relative; height: 30px; border-radius: 8px; background: #141414; border-left: 5px solid #23a55a; color: #ffffff; text-decoration: none; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600">11:00 PM<span aria-label="Late show" style="position: absolute; right: -5px; top: -7px; width: 16px; height: 16px; border-radius: 50%; background: {{accent}}; display: flex; align-items: center; justify-content: center"><svg width="9" height="9" viewBox="0 0 24 24" fill="#ffffff" aria-hidden="true"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z"></path></svg></span></a>
<a href="Seats.dc.html" style="position: relative; height: 30px; border-radius: 8px; background: #141414; border-left: 5px solid #23a55a; color: #ffffff; text-decoration: none; display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600">11:05 PM<span aria-label="Late show" style="position: absolute; right: -5px; top: -7px; width: 16px; height: 16px; border-radius: 50%; background: {{accent}}; display: flex; align-items: center; justify-content: center"><svg width="9" height="9" viewBox="0 0 24 24" fill="#ffffff" aria-hidden="true"><path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z"></path></svg></span></a>
</div>
</div>
<!-- Next card peek -->
<div aria-hidden="true" style="margin-top: 12px; height: 60px; border-radius: 18px; box-shadow: 0 4px 18px rgba(0,0,0,0.08)"></div>
<!-- Tip toast -->
<div style="position: absolute; left: 12px; right: 12px; bottom: 30px; height: 44px; border-radius: 12px; background: #ffffff; box-shadow: 0 -2px 16px rgba(0,0,0,0.1); display: flex; align-items: center; justify-content: space-between; padding: 0 12px; font-size: 12px">
<span style="display: flex; align-items: center; gap: 4px">Make favourite cinemas by tapping the <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="2" aria-hidden="true"><path d="M12 20s-7-4.3-8.8-8.6C1.9 8 4 4.8 7.2 4.8c2 0 3.5 1 4.8 2.7 1.3-1.7 2.8-2.7 4.8-2.7 3.2 0 5.3 3.2 4 6.6C19 15.7 12 20 12 20z"></path></svg></span>
<span style="display: flex; align-items: center; gap: 6px; color: #3d3d3d">3/3 <button aria-label="Dismiss tip" style="width: 20px; height: 20px; border: 0; border-radius: 50%; background: #ececec; padding: 0; display: flex; align-items: center; justify-content: center"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#3d3d3d" stroke-width="3" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"></path></svg></button></span>
</div>
</div>
<div aria-hidden="true" style="position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); width: 134px; height: 5px; border-radius: 3px; background: #141414"></div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#CC3A0E","options":["#CC3A0E","#C2185B","#1E5BD8"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
renderVals() {
return { accent: this.props.accent ?? '#CC3A0E' };
}
}
</script>
</body>
</html>
```
### Seats
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Select Seats</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
body{margin:0}
a{color:#141414}a:hover{color:#000000}
button{font-family:inherit;cursor:pointer}
button:disabled{cursor:not-allowed}
</style>
</helmet>
<div style="width: 390px; height: 844px; box-sizing: border-box; background: #ffffff; font-family: Inter, system-ui, sans-serif; color: #141414; position: relative; overflow: hidden">
<!-- Header -->
<div style="display: flex; align-items: center; justify-content: space-between; padding: 56px 16px 0">
<a href="Showtimes.dc.html" aria-label="Back to showtimes" style="width: 40px; height: 40px; border-radius: 50%; background: #f3f3f3; display: flex; align-items: center; justify-content: center"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#141414" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 5l-7 7 7 7"></path></svg></a>
<div style="display: flex; flex-direction: column; align-items: center">
<h1 style="margin: 0; font-size: 15px; font-weight: 700">Iron Petal – The Movie</h1>
<span style="font-size: 11px; color: #5c5c5c; margin-top: 2px">Cineverse, Skyline Mall, Borivali</span>
</div>
<span style="min-width: 40px; height: 26px; border-radius: 999px; background: #fff3d6; color: #8a5a00; font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center; padding: 0 8px; box-sizing: border-box">{{countLabel}}</span>
</div>
<!-- Date + showtimes -->
<div style="padding: 18px 16px 0">
<div style="font-size: 12px; font-weight: 600; color: #3d3d3d">Mon, 16 Nov · 1h 42m · Japanese</div>
<div style="display: flex; gap: 8px; margin-top: 10px">
<button style="height: 32px; padding: 0 11px; border-radius: 9px; border: 1px solid #e0e0e0; background: #ffffff; color: #141414; font-size: 12px; font-weight: 600">4:25 PM</button>
<button style="height: 32px; padding: 0 11px; border-radius: 9px; border: 1px solid #e0e0e0; background: #ffffff; color: #141414; font-size: 12px; font-weight: 600">4:45 PM</button>
<button aria-pressed="true" style="height: 32px; padding: 0 11px; border-radius: 9px; border: 0; background: #141414; color: #ffffff; font-size: 12px; font-weight: 600">7:35 PM <span style="color: #f0a52b; font-size: 10px">LUX</span></button>
<button style="height: 32px; padding: 0 11px; border-radius: 9px; border: 1px solid #e0e0e0; background: #ffffff; color: #141414; font-size: 12px; font-weight: 600">7:45 PM</button>
<button style="height: 32px; padding: 0 11px; border-radius: 9px; border: 1px solid #e0e0e0; background: #ffffff; color: #141414; font-size: 12px; font-weight: 600">10:50 PM</button>
</div>
</div>
<!-- Screen arc -->
<div style="position: relative; margin: 20px 28px 0; height: 40px">
<svg width="334" height="26" viewBox="0 0 334 26" aria-hidden="true" style="display: block"><path d="M4 22 Q167 -8 330 22" fill="none" stroke="{{accent}}" stroke-width="4" stroke-linecap="round"></path><path d="M4 24 Q167 -2 330 24" fill="none" stroke="{{accent}}" stroke-opacity="0.15" stroke-width="10" stroke-linecap="round"></path></svg>
<div style="text-align: center; font-size: 11px; color: #6b6b6b; margin-top: 2px">Screen this way</div>
</div>
<!-- Seat map -->
<div role="group" aria-label="Seat map" style="padding: 8px 20px 0; display: flex; flex-direction: column; gap: 6px">
<div style="display: flex; justify-content: space-between; font-size: 10px; font-weight: 700; letter-spacing: 1px; color: #6b6b6b"><span>CLASSIC</span><span>₹250</span></div>
<sc-for list="{{classicRows}}" as="row" hint-placeholder-count="6">
<div style="display: grid; grid-template-columns: 16px repeat(2, minmax(0, 1fr)) 10px repeat(8, minmax(0, 1fr)) 10px repeat(2, minmax(0, 1fr)) 16px; gap: 4px; align-items: center">
<sc-for list="{{row.cells}}" as="cell" hint-placeholder-count="17">
<sc-if value="{{cell.isLabel}}" hint-placeholder-val="{{false}}"><span style="font-size: 10px; font-weight: 600; color: #6b6b6b; text-align: center">{{cell.text}}</span></sc-if>
<sc-if value="{{cell.isAisle}}" hint-placeholder-val="{{false}}"><span></span></sc-if>
<sc-if value="{{cell.isSeat}}" hint-placeholder-val="{{true}}"><button aria-label="{{cell.aria}}" aria-pressed="{{cell.pressed}}" disabled="{{cell.taken}}" onClick="{{cell.pick}}" style="{{cell.style}}"></button></sc-if>
</sc-for>
</div>
</sc-for>
<div style="display: flex; justify-content: space-between; font-size: 10px; font-weight: 700; letter-spacing: 1px; color: #8a5a00; margin-top: 8px"><span>PREMIUM RECLINER</span><span>₹420</span></div>
<sc-for list="{{premiumRows}}" as="row" hint-placeholder-count="2">
<div style="display: grid; grid-template-columns: 16px repeat(2, minmax(0, 1fr)) 10px repeat(8, minmax(0, 1fr)) 10px repeat(2, minmax(0, 1fr)) 16px; gap: 4px; align-items: center">
<sc-for list="{{row.cells}}" as="cell" hint-placeholder-count="17">
<sc-if value="{{cell.isLabel}}" hint-placeholder-val="{{false}}"><span style="font-size: 10px; font-weight: 600; color: #6b6b6b; text-align: center">{{cell.text}}</span></sc-if>
<sc-if value="{{cell.isAisle}}" hint-placeholder-val="{{false}}"><span></span></sc-if>
<sc-if value="{{cell.isSeat}}" hint-placeholder-val="{{true}}"><button aria-label="{{cell.aria}}" aria-pressed="{{cell.pressed}}" disabled="{{cell.taken}}" onClick="{{cell.pick}}" style="{{cell.style}}"></button></sc-if>
</sc-for>
</div>
</sc-for>
</div>
<!-- Legend -->
<div style="display: flex; gap: 14px; justify-content: center; padding: 16px 16px 0; font-size: 11px; color: #4d4d4d">
<span style="display: flex; align-items: center; gap: 5px"><span style="width: 14px; height: 12px; border-radius: 4px 4px 3px 3px; border: 1.5px solid #9e9e9e; box-sizing: border-box"></span>Classic</span>
<span style="display: flex; align-items: center; gap: 5px"><span style="width: 14px; height: 12px; border-radius: 4px 4px 3px 3px; border: 1.5px solid #d08c14; box-sizing: border-box"></span>Premium</span>
<span style="display: flex; align-items: center; gap: 5px"><span style="width: 14px; height: 12px; border-radius: 4px 4px 3px 3px; background: #dcdcdc"></span>Taken</span>
<span style="display: flex; align-items: center; gap: 5px"><span style="width: 14px; height: 12px; border-radius: 4px 4px 3px 3px; background: {{accent}}"></span>Your pick</span>
</div>
<!-- Ticket stub + CTA -->
<div style="position: absolute; left: 0; right: 0; bottom: 0; background: #141414; border-radius: 28px 28px 0 0; padding: 18px 16px 30px; color: #ffffff">
<div style="display: grid; grid-template-columns: minmax(0, 1fr) auto; border: 1px dashed #4a4a4a; border-radius: 16px">
<dl style="margin: 0; padding: 12px 14px; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 14px">
<div><dt style="font-size: 10px; color: #a6a6a6">Seats</dt><dd style="margin: 2px 0 0; font-size: 13px; font-weight: 600">{{seatsText}}</dd></div>
<div><dt style="font-size: 10px; color: #a6a6a6">Show</dt><dd style="margin: 2px 0 0; font-size: 13px; font-weight: 600">Mon · 7:35 PM</dd></div>
</dl>
<div style="border-left: 1px dashed #4a4a4a; padding: 12px 14px; display: flex; flex-direction: column; justify-content: center; align-items: flex-end">
<span style="font-size: 10px; color: #a6a6a6">Total</span>
<span style="font-size: 17px; font-weight: 700">{{totalText}}</span>
</div>
</div>
<sc-if value="{{held}}" hint-placeholder-val="{{false}}">
<p role="status" style="margin: 10px 0 0; font-size: 12px; color: #ffd9c7">Seats held for 10 minutes. Complete payment to confirm your tickets.</p>
</sc-if>
<button disabled="{{noneSelected}}" onClick="{{hold}}" style="{{holdStyle}}">{{holdLabel}}</button>
<div style="display: flex; justify-content: center; margin-top: 8px">
<button onClick="{{clear}}" style="border: 0; background: none; color: #bdbdbd; font-size: 12px; text-decoration: underline; text-underline-offset: 3px; padding: 6px">Clear selection</button>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"accent":{"editor":"color","default":"#CC3A0E","options":["#CC3A0E","#C2185B","#1E5BD8"]},"$preview":{"width":390,"height":844}}'>
class Component extends DCLogic {
constructor(props) {
super(props);
this.state = { picked: ['F6', 'F7'], held: false };
}
toggle(id) {
var picked = this.state.picked.slice();
var i = picked.indexOf(id);
if (i >= 0) { picked.splice(i, 1); }
else if (picked.length < 10) { picked.push(id); }
this.setState({ picked: picked, held: false });
}
renderVals() {
var self = this;
var accent = this.props.accent ?? '#CC3A0E';
var taken = ['A3','A4','B6','B7','B8','C1','D5','D6','E9','E10','F4','G1','G2','H7','H8','H9','C11','F12'];
var picked = this.state.picked;
var base = 'width: 100%; height: 19px; padding: 0; border-radius: 6px 6px 4px 4px; box-sizing: border-box; ';
function buildRow(r, premium) {
var cells = [{ isLabel: true, text: r }];
for (var n = 1; n <= 12; n++) {
var id = r + n;
var isTaken = taken.indexOf(id) >= 0;
var isPicked = picked.indexOf(id) >= 0;
var style;
if (isTaken) style = base + 'border: 0; background: #dcdcdc;';
else if (isPicked) style = base + 'border: 1.5px solid ' + accent + '; background: ' + accent + ';';
else style = base + 'background: #ffffff; border: 1.5px solid ' + (premium ? '#d08c14' : '#9e9e9e') + ';';
cells.push({
isSeat: true,
aria: 'Seat ' + id + (premium ? ', premium' : ', classic') + (isTaken ? ', taken' : ''),
pressed: isPicked ? 'true' : 'false',
taken: isTaken,
style: style,
pick: (function (sid) { return function () { self.toggle(sid); }; })(id)
});
if (n === 2 || n === 10) cells.push({ isAisle: true });
}
cells.push({ isLabel: true, text: r });
return { name: r, cells: cells };
}
var classicRows = ['A','B','C','D','E','F'].map(function (r) { return buildRow(r, false); });
var premiumRows = ['G','H'].map(function (r) { return buildRow(r, true); });
var sorted = picked.slice().sort(function (a, b) {
if (a[0] !== b[0]) return a < b ? -1 : 1;
return parseInt(a.slice(1), 10) - parseInt(b.slice(1), 10);
});
var total = picked.reduce(function (sum, id) { return sum + (id[0] === 'G' || id[0] === 'H' ? 420 : 250); }, 0);
var none = picked.length === 0;
var totalText = '₹' + total.toLocaleString('en-IN');
return {
accent: accent,
classicRows: classicRows,
premiumRows: premiumRows,
seatsText: none ? 'None yet' : sorted.join(', '),
countLabel: picked.length + (picked.length === 1 ? ' Ticket' : ' Tickets'),
totalText: totalText,
noneSelected: none,
held: this.state.held,
holdLabel: none ? 'Select seats' : 'Hold seats · ' + totalText,
holdStyle: 'margin-top: 14px; width: 100%; height: 50px; border: 0; border-radius: 999px; font-size: 15px; font-weight: 700; color: #ffffff; background: ' + (none ? '#4a4a4a' : accent) + ';',
hold: function () { self.setState({ held: true }); },
clear: function () { self.setState({ picked: [], held: false }); }
};
}
}
</script>
</body>
</html>
```SKILL.md
Save as .claude/skills/movie-ticket-booking-app-ui/SKILL.md
--- name: movie-ticket-booking-app-ui description: Build the Movie Ticket Booking mobile app UI in Expo React Native. Use when asked to create a movie ticket booking app UI or a new screen in this visual style. --- # Movie Ticket Booking app UI Build this UI in an Expo SDK 58 app with Expo Router and strict TypeScript. Match the spec below rather than inventing a new look. The finished reference lives at https://developermill.com/app-designs/movie-ticket-booking-app-ui-design ## Visual direction A cinematic black and white base with a single ember accent, tall poster cards and a dark ticket stub that anchors the checkout. Keep these signature details: - Interactive seat map with classic and premium pricing - Live ticket stub with seats, show time and total - Peeking poster carousel with ranked overlays - Date strip, format chips and theatre showtime cards ## Set up ```bash npx expo install expo-image expo-linear-gradient react-native-svg @expo-google-fonts/bebas-neue @expo-google-fonts/inter expo-font ``` ## Tokens | Token | Hex | | --- | --- | | Ember | `#CC3A0E` | | Ink | `#141414` | | Screen black | `#000000` | | Gold rating | `#F0A52B` | | Available | `#23A55A` | | Hairline | `#E0E0E0` | - Typefaces: Inter, Bebas Neue - Common sizes (px): 10, 11, 12, 13, 14, 15, 16, 17 - Common corner radii (px): 3, 4, 8, 9, 14, 999 Define these once and reuse them. Do not add new colours. ## Screens Build one Expo Router route per screen, in this order: 1. **Home and movie chart**: Primary tabs, a peeking poster carousel with chart rank overlays, booking stats and a floating BOOK action in the tab bar. 2. **Movie detail**: A full-bleed poster fades to black behind the title lockup, runtime, languages, synopsis and a single Book Now action. 3. **Booking by theatre**: Format chips and a poster row on black, then a white sheet with a date strip, city filters and showtime chips colour-coded by availability. 4. **Seat selection**: A curved screen, tappable seat grid with aisles and two price tiers, a legend, and a dark ticket stub with a hold action. ## Build rules - Design on a 390 × 844 artboard, then scale to the device width so layouts match on every phone. - Load each custom font weight by its own family name with expo-font. Do not rely on `fontWeight` with custom fonts. - Draw icons, charts and illustrations with react-native-svg rather than image files or chart libraries. - Use `Pressable` with an `accessibilityLabel` for every icon-only control, and keep touch targets at 44 px or larger. - Wire navigation with `router.navigate`, and keep sample data in constants at the top of each file so it is easy to replace. - Use expo-image for photos, with `contentFit` set explicitly. ## Done when - Every screen above exists as a route and they link together. - Colours, type and radii match the tokens exactly. - `npx tsc --noEmit` and `npx expo lint` both pass. - Layouts hold up on small and large phones.
Set up the project
npx create-expo-app@latest movie-ticket-booking-app --template default cd movie-ticket-booking-app rm -rf src npx expo install expo-image expo-linear-gradient react-native-svg @expo-google-fonts/bebas-neue @expo-google-fonts/inter expo-font
src/app/_layout.tsx
import { BebasNeue_400Regular } from '@expo-google-fonts/bebas-neue';
import {
Inter_400Regular,
Inter_600SemiBold,
Inter_700Bold,
Inter_800ExtraBold,
} from '@expo-google-fonts/inter';
import { useFonts } from 'expo-font';
import { Stack } from 'expo-router';
export default function RootLayout() {
const [fontsLoaded] = useFonts({
BebasNeue_400Regular,
Inter_400Regular,
Inter_600SemiBold,
Inter_700Bold,
Inter_800ExtraBold,
});
if (!fontsLoaded) return null;
return <Stack screenOptions={{ headerShown: false }} />;
}src/app/detail.tsx
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg';
import { Screen } from '@/components/screen';
import { asset } from '@/lib/assets';
const accent = '#CC3A0E';
export default function DetailScreen() {
return (
<Screen background="#000000" statusBar="light">
<View style={{ flex: 1 }}>
<View
style={{
position: 'absolute',
left: 0,
top: 0,
width: 390,
height: 590,
backgroundColor: '#2a0105',
}}>
<Image
source={{ uri: asset('18e23cdfc9.svg') }}
contentFit="cover"
contentPosition="top center"
style={{ position: 'absolute', left: 0, top: 0, width: 390, height: 590 }}
/>
<View style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 300 }}>
<LinearGradient
colors={['rgba(0,0,0,0)', 'rgba(0,0,0,0.6)', '#000000']}
locations={[0, 0.45, 1]}
start={{ x: 0.5, y: 0 }}
end={{ x: 0.5, y: 1 }}
style={StyleSheet.absoluteFill}
/>
</View>
</View>
<View
style={{
position: 'absolute',
left: 16,
right: 16,
top: 56,
height: 44,
borderRadius: 999,
backgroundColor: 'rgba(40,0,4,0.55)',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 0,
paddingHorizontal: 6,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
accessibilityLabel="Back"
onPress={() => router.navigate('/')}
style={{
width: 36,
height: 36,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M15 5l-7 7 7 7" />
</Svg>
</Pressable>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_600SemiBold',
fontSize: 13,
color: '#ffffff',
}}>
Iron Petal – The Movie: Akane Arc
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Share"
style={{
width: 36,
height: 36,
borderWidth: 0,
padding: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="19"
height="19"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M14 4h6v6M20 4l-9 9" />
<Path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5" />
</Svg>
</Pressable>
</View>
<Pressable
accessibilityRole="button"
style={{
position: 'absolute',
left: '50%',
top: 300,
transform: [{ translateX: '-50%' }],
alignItems: 'center',
gap: 7,
borderWidth: 0,
borderRadius: 999,
backgroundColor: 'rgba(30,30,30,0.6)',
paddingVertical: 8,
paddingLeft: 8,
paddingRight: 14,
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
strokeWidth="1.6">
<Circle cx="12" cy="12" r="10" />
<Path d="M10 8.5l5 3.5-5 3.5z" fill="#ffffff" />
</Svg>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 13,
color: '#ffffff',
flexShrink: 1,
}}>
Trailer (2)
</Text>
</Pressable>
<View style={{ position: 'absolute', left: 0, right: 0, top: 448, alignItems: 'center' }}>
<Text
style={{
margin: 0,
fontFamily: 'BebasNeue_400Regular',
fontSize: 64,
color: '#ffffff',
letterSpacing: 2,
lineHeight: 57.6,
}}>
IRON PETAL
</Text>
<Text
style={{
marginTop: 6,
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#ffc9c9',
letterSpacing: 3.5,
}}>
THE MOVIE · AKANE ARC
</Text>
<Text
style={{
marginTop: 10,
fontFamily: 'Inter_600SemiBold',
fontSize: 14,
color: '#ffffff',
}}>
Japanese, Hindi
</Text>
</View>
<View style={{ position: 'absolute', left: 20, right: 20, top: 612 }}>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 13, color: '#e6e6e6' }}>
Streaming Now
</Text>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/showtimes')}
style={{
marginTop: 14,
height: 48,
borderRadius: 999,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 10px 24px rgba(204,58,14,0.35)',
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 15,
color: '#ffffff',
flexShrink: 1,
}}>
Book Now
</Text>
</Pressable>
<Text
style={{
marginTop: 16,
marginBottom: 0,
marginHorizontal: 0,
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#e6e6e6',
}}>
1h 42m • Action, Adventure, Anime, Fantasy • A • 13 Nov, 2026
</Text>
<Text
style={{
marginTop: 6,
marginBottom: 0,
marginHorizontal: 0,
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#9a9a9a',
}}>
{'Japanese • Languages: '}
<Text style={{ color: '#ff8a65' }}>Audio(2)</Text>
{', '}
<Text style={{ color: '#ff8a65' }}>Subtitles(1)</Text>
</Text>
<Text
style={{
marginTop: 10,
marginBottom: 0,
marginHorizontal: 0,
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#d9d9d9',
lineHeight: 18,
}}>
{
'Akane, a sword-for-hire with a blade that blooms, is drawn into a turf war when an old ally resurfaces with a debt she can’t … '
}
Read more…
</Text>
</View>
<View
style={{
position: 'absolute',
bottom: 8,
left: '50%',
transform: [{ translateX: '-50%' }],
width: 134,
height: 5,
borderRadius: 3,
backgroundColor: '#ffffff',
}}
/>
</View>
</Screen>
);
}src/app/index.tsx
import { Image } from 'expo-image';
import { router } from 'expo-router';
import { Pressable, Text, View } from 'react-native';
import Svg, { Circle, Path, Rect } from 'react-native-svg';
import { Screen } from '@/components/screen';
import { asset } from '@/lib/assets';
const accent = '#CC3A0E';
export default function MainScreen() {
return (
<Screen background="#ffffff" statusBar="dark">
<View style={{ flex: 1 }}>
<View
style={{
alignItems: 'center',
justifyContent: 'space-between',
paddingTop: 58,
paddingBottom: 14,
paddingHorizontal: 20,
flexDirection: 'row',
}}>
<Svg
width="36"
height="32"
viewBox="0 0 36 32"
accessibilityRole="image"
accessibilityLabel="Reelo logo">
<Path d="M18 2 L34 28 Q35 30 33 30 L3 30 Q1 30 2 28 Z" fill={accent} />
<Path d="M18 13 L25 25 L11 25 Z" fill="#ffffff" />
</Svg>
<View style={{ gap: 18, alignItems: 'center', flexShrink: 1, flexDirection: 'row' }}>
<Pressable
accessibilityRole="button"
accessibilityLabel="My tickets"
style={{
borderWidth: 0,
padding: 0,
width: 28,
height: 28,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M4 6h16v4a2 2 0 0 0 0 4v4H4v-4a2 2 0 0 0 0-4z" />
<Path d="M12 6v12" strokeDasharray="2 2" />
</Svg>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityLabel="Notifications"
style={{
borderWidth: 0,
padding: 0,
width: 28,
height: 28,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="1.8"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M6 16V11a6 6 0 0 1 12 0v5l1.5 2h-15z" />
<Path d="M10 20a2 2 0 0 0 4 0" />
</Svg>
</Pressable>
<Pressable
accessibilityRole="button"
accessibilityLabel="Search"
style={{
borderWidth: 0,
padding: 0,
width: 28,
height: 28,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="1.8"
strokeLinecap="round">
<Circle cx="11" cy="11" r="7" />
<Path d="M16.5 16.5L21 21" />
</Svg>
</Pressable>
</View>
</View>
<View
style={{
alignItems: 'center',
gap: 22,
paddingVertical: 0,
paddingHorizontal: 20,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="button"
style={{
alignItems: 'center',
gap: 7,
backgroundColor: '#141414',
borderWidth: 0,
borderRadius: 999,
paddingVertical: 9,
paddingHorizontal: 16,
flexShrink: 1,
flexDirection: 'row',
}}>
<Svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
strokeWidth="2"
strokeLinejoin="round">
<Rect x="3" y="5" width="18" height="15" rx="2" />
<Path d="M3 10h18M8 5l2 5M14 5l2 5" />
</Svg>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 15,
color: '#ffffff',
flexShrink: 1,
}}>
Movies
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 0,
padding: 0,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 14,
color: '#5c5c5c',
textAlign: 'center',
}}>
Events / Benefits
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 0,
padding: 0,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 14,
color: '#5c5c5c',
textAlign: 'center',
}}>
Activities
</Text>
</Pressable>
</View>
<View
style={{
alignItems: 'center',
gap: 18,
paddingTop: 18,
paddingBottom: 0,
paddingHorizontal: 20,
flexDirection: 'row',
}}>
<Text
style={{
paddingBottom: 6,
borderBottomWidth: 2,
borderBottomColor: '#141414',
flexShrink: 1,
fontFamily: 'Inter_600SemiBold',
fontSize: 13,
color: '#141414',
}}>
Movie Chart
</Text>
<Text
style={{
paddingBottom: 8,
flexShrink: 1,
fontFamily: 'Inter_400Regular',
fontSize: 13,
color: '#6b6b6b',
}}>
Now Showing
</Text>
<Text
style={{
paddingBottom: 8,
flexShrink: 1,
fontFamily: 'Inter_400Regular',
fontSize: 13,
color: '#6b6b6b',
}}>
Coming Soon
</Text>
<View
style={{
paddingBottom: 8,
alignItems: 'center',
gap: 2,
flexShrink: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 13,
color: '#6b6b6b',
flexShrink: 1,
}}>
See all
</Text>
<Svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="#6b6b6b"
strokeWidth="2.4"
strokeLinecap="round">
<Path d="M9 6l6 6-6 6" />
</Svg>
</View>
</View>
<View style={{ height: 330, marginTop: 22 }}>
<View
style={{
position: 'absolute',
left: -200,
top: 26,
width: 228,
height: 278,
borderRadius: 26,
overflow: 'hidden',
}}>
<Image
source={{ uri: asset('c27d246b2a.svg') }}
contentFit="cover"
style={{ width: '100%', height: '100%' }}
/>
</View>
<View
style={{
position: 'absolute',
right: -200,
top: 26,
width: 228,
height: 278,
borderRadius: 26,
overflow: 'hidden',
}}>
<Image
source={{ uri: asset('c84f4e1efb.svg') }}
contentFit="cover"
style={{ width: '100%', height: '100%' }}
/>
<Text
style={{
position: 'absolute',
left: 14,
top: 10,
fontFamily: 'BebasNeue_400Regular',
fontSize: 34,
color: '#ffffff',
}}>
2
</Text>
</View>
<View
style={{
position: 'absolute',
left: '50%',
top: 0,
transform: [{ translateX: '-50%' }],
width: 252,
height: 330,
borderRadius: 30,
overflow: 'hidden',
boxShadow: '0 18px 36px rgba(196, 30, 30, 0.28)',
backgroundColor: '#6e0610',
}}>
<Image
source={{ uri: asset('18e23cdfc9.svg') }}
contentFit="cover"
accessibilityLabel="Iron Petal – The Movie: Akane Arc poster"
style={{
position: 'absolute',
bottom: 0,
right: 0,
left: 0,
top: 0,
width: '100%',
height: '100%',
}}
/>
<Text
style={{
position: 'absolute',
left: 18,
top: 10,
fontFamily: 'BebasNeue_400Regular',
fontSize: 44,
color: '#ffffff',
fontStyle: 'italic',
}}>
1
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Add to favourites"
style={{
position: 'absolute',
right: 14,
top: 14,
width: 36,
height: 36,
borderWidth: 0,
padding: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg width="26" height="26" viewBox="0 0 24 24" fill="#ffffff">
<Path d="M12 21s-7.5-4.6-9.5-9.2C1 8.2 3.3 4.5 7 4.5c2.1 0 3.7 1.1 5 2.9 1.3-1.8 2.9-2.9 5-2.9 3.7 0 6 3.7 4.5 7.3C19.5 16.4 12 21 12 21z" />
</Svg>
</Pressable>
</View>
</View>
<View
style={{ alignItems: 'center', paddingTop: 22, paddingBottom: 0, paddingHorizontal: 20 }}>
<View style={{ alignItems: 'center', gap: 8, flexDirection: 'row' }}>
<Text
style={{
margin: 0,
flexShrink: 1,
fontFamily: 'Inter_700Bold',
fontSize: 16,
color: '#141414',
}}>
Iron Petal – The Movie: Akane Arc
</Text>
<Text
style={{
backgroundColor: '#f0a52b',
borderRadius: 5,
paddingVertical: 3,
paddingHorizontal: 5,
flexShrink: 1,
fontFamily: 'Inter_800ExtraBold',
fontSize: 11,
color: '#141414',
}}>
16+
</Text>
</View>
<View style={{ alignItems: 'center', gap: 6, marginTop: 12, flexDirection: 'row' }}>
<Svg width="13" height="13" viewBox="0 0 24 24" fill={accent}>
<Path d="M12 2c1 4 6 6 6 12a6 6 0 0 1-12 0c0-3 2-5 3-6 0 2 1 3 2 3 0-3 1-6 1-9z" />
</Svg>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#3d3d3d',
}}>
Booking Rate: 28.4% • Cumulative: 1.4 million •
</Text>
<Svg width="12" height="12" viewBox="0 0 24 24" fill="#c0392b">
<Path d="M12 21s-7.5-4.6-9.5-9.2C1 8.2 3.3 4.5 7 4.5c2.1 0 3.7 1.1 5 2.9 1.3-1.8 2.9-2.9 5-2.9 3.7 0 6 3.7 4.5 7.3C19.5 16.4 12 21 12 21z" />
</Svg>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#3d3d3d',
}}>
8,123
</Text>
</View>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/detail')}
style={{
marginTop: 22,
backgroundColor: '#141414',
borderRadius: 999,
paddingVertical: 13,
paddingHorizontal: 34,
}}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 15, color: '#ffffff' }}>
Book Now
</Text>
</Pressable>
</View>
<View
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: 92,
backgroundColor: '#ffffff',
boxShadow: '0 -4px 18px rgba(0,0,0,0.06)',
alignItems: 'flex-start',
justifyContent: 'space-around',
paddingTop: 12,
paddingBottom: 0,
paddingHorizontal: 8,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/')}
style={{ alignItems: 'center', gap: 5, width: 56 }}>
<Svg width="22" height="22" viewBox="0 0 24 24" fill="#141414">
<Path d="M3 10.5L12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6h-6v6H4a1 1 0 0 1-1-1z" />
</Svg>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 11, color: '#141414' }}>
Home
</Text>
</Pressable>
<Pressable accessibilityRole="link" style={{ alignItems: 'center', gap: 5, width: 56 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="1.8"
strokeLinejoin="round">
<Path d="M4 5h16v11H9l-5 4z" />
<Path
d="M8 10.5h.01M12 10.5h.01M16 10.5h.01"
strokeWidth="2.6"
strokeLinecap="round"
/>
</Svg>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 11, color: '#141414' }}>
Cine Talk
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
accessibilityLabel="Book tickets"
onPress={() => router.navigate('/showtimes')}
style={{
marginTop: -32,
width: 68,
height: 78,
borderRadius: 24,
backgroundColor: accent,
boxShadow: '0 10px 22px rgba(204,58,14,0.4)',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
}}>
<Svg
width="26"
height="26"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
strokeWidth="1.8"
strokeLinejoin="round">
<Path d="M4 6h16v4a2 2 0 0 0 0 4v4H4v-4a2 2 0 0 0 0-4z" />
<Path d="M9 10h6M9 14h6" />
</Svg>
<Text style={{ fontFamily: 'Inter_700Bold', fontSize: 12, color: '#ffffff' }}>
BOOK
</Text>
</Pressable>
<Pressable accessibilityRole="link" style={{ alignItems: 'center', gap: 5, width: 56 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="1.8"
strokeLinejoin="round">
<Path d="M6 10h12l-1.5 11h-9z" />
<Path d="M7 10a3 3 0 0 1 3-4 3 3 0 0 1 4 0 3 3 0 0 1 3 4" />
</Svg>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 11, color: '#141414' }}>
Store
</Text>
</Pressable>
<Pressable accessibilityRole="link" style={{ alignItems: 'center', gap: 5, width: 56 }}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="1.8">
<Circle cx="7.5" cy="7.5" r="3.5" />
<Circle cx="16.5" cy="7.5" r="3.5" />
<Circle cx="7.5" cy="16.5" r="3.5" />
<Circle cx="16.5" cy="16.5" r="3.5" />
</Svg>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 11, color: '#141414' }}>
More
</Text>
</Pressable>
</View>
<View
style={{
position: 'absolute',
bottom: 8,
left: '50%',
transform: [{ translateX: '-50%' }],
width: 134,
height: 5,
borderRadius: 3,
backgroundColor: '#141414',
}}
/>
</View>
</Screen>
);
}src/app/seats.tsx
import { router } from 'expo-router';
import { useState, Fragment } from 'react';
import { Pressable, Text, View } from 'react-native';
import Svg, { Path } from 'react-native-svg';
import { Screen } from '@/components/screen';
const accent = '#CC3A0E';
const TAKEN = [
'A3',
'A4',
'B6',
'B7',
'B8',
'C1',
'D5',
'D6',
'E9',
'E10',
'F4',
'G1',
'G2',
'H7',
'H8',
'H9',
'C11',
'F12',
];
const CLASSIC_PRICE = 250;
const PREMIUM_PRICE = 420;
const isPremium = (id: string) => id[0] === 'G' || id[0] === 'H';
export default function SeatsScreen() {
const [picked, setPicked] = useState(['F6', 'F7']);
const [held, setHeld] = useState(false);
const toggle = (id: string) => {
setHeld(false);
setPicked((seats) =>
seats.includes(id)
? seats.filter((seat) => seat !== id)
: seats.length < 10
? [...seats, id]
: seats,
);
};
const seat = (id: string, premium: boolean) => {
const isTaken = TAKEN.includes(id);
const isPicked = picked.includes(id);
return {
isSeat: true,
aria: `Seat ${id}, ${premium ? 'premium' : 'classic'}${isTaken ? ', taken' : ''}`,
taken: isTaken,
pick: () => toggle(id),
style: {
flex: 1,
height: 19,
borderTopLeftRadius: 6,
borderTopRightRadius: 6,
borderBottomLeftRadius: 4,
borderBottomRightRadius: 4,
...(isTaken
? { backgroundColor: '#dcdcdc' }
: isPicked
? { backgroundColor: accent, borderWidth: 1.5, borderColor: accent }
: {
backgroundColor: '#ffffff',
borderWidth: 1.5,
borderColor: premium ? '#d08c14' : '#9e9e9e',
}),
},
};
};
const buildRow = (row: string, premium: boolean) => {
const cells: {
isLabel?: boolean;
isAisle?: boolean;
isSeat?: boolean;
text?: string;
aria?: string;
taken?: boolean;
pick?: () => void;
style?: object;
}[] = [{ isLabel: true, text: row }];
for (let n = 1; n <= 12; n++) {
cells.push(seat(`${row}${n}`, premium));
if (n === 2 || n === 10) cells.push({ isAisle: true });
}
cells.push({ isLabel: true, text: row });
return { name: row, cells };
};
const classicRows = ['A', 'B', 'C', 'D', 'E', 'F'].map((row) => buildRow(row, false));
const premiumRows = ['G', 'H'].map((row) => buildRow(row, true));
const sorted = [...picked].sort(
(a, b) =>
a.localeCompare(b[0] === a[0] ? a : b) || parseInt(a.slice(1), 10) - parseInt(b.slice(1), 10),
);
const total = picked.reduce(
(sum, id) => sum + (isPremium(id) ? PREMIUM_PRICE : CLASSIC_PRICE),
0,
);
const none = picked.length === 0;
const totalText = `₹${total.toLocaleString('en-IN')}`;
const seatsText = none ? 'None yet' : sorted.join(', ');
const countLabel = `${picked.length} ${picked.length === 1 ? 'Ticket' : 'Tickets'}`;
const noneSelected = none;
const holdLabel = none ? 'Select seats' : `Hold seats · ${totalText}`;
const holdStyle = {
marginTop: 14,
width: '100%' as const,
height: 50,
borderRadius: 999,
backgroundColor: none ? '#4a4a4a' : accent,
};
const hold = () => setHeld(true);
const clear = () => {
setPicked([]);
setHeld(false);
};
return (
<Screen background="#ffffff" statusBar="dark">
<View style={{ flex: 1 }}>
<View
style={{
alignItems: 'center',
justifyContent: 'space-between',
paddingTop: 56,
paddingBottom: 0,
paddingHorizontal: 16,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="link"
accessibilityLabel="Back to showtimes"
onPress={() => router.navigate('/showtimes')}
style={{
width: 40,
height: 40,
borderRadius: '50%',
backgroundColor: '#f3f3f3',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round">
<Path d="M15 5l-7 7 7 7" />
</Svg>
</Pressable>
<View style={{ alignItems: 'center', flexShrink: 1 }}>
<Text
style={{ margin: 0, fontFamily: 'Inter_700Bold', fontSize: 15, color: '#141414' }}>
Iron Petal – The Movie
</Text>
<Text
style={{
marginTop: 2,
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#5c5c5c',
}}>
Cineverse, Skyline Mall, Borivali
</Text>
</View>
<View
style={{
minWidth: 40,
height: 26,
borderRadius: 999,
backgroundColor: '#fff3d6',
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 0,
paddingHorizontal: 8,
flexShrink: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_700Bold',
fontSize: 11,
color: '#8a5a00',
flexShrink: 1,
}}>
{countLabel}
</Text>
</View>
</View>
<View style={{ paddingTop: 18, paddingBottom: 0, paddingHorizontal: 16 }}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 12, color: '#3d3d3d' }}>
Mon, 16 Nov · 1h 42m · Japanese
</Text>
<View style={{ gap: 8, marginTop: 10, flexDirection: 'row' }}>
<Pressable
accessibilityRole="button"
style={{
height: 32,
paddingVertical: 0,
paddingHorizontal: 11,
borderRadius: 9,
borderWidth: 1,
borderColor: '#e0e0e0',
backgroundColor: '#ffffff',
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#141414',
textAlign: 'center',
}}>
4:25 PM
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
height: 32,
paddingVertical: 0,
paddingHorizontal: 11,
borderRadius: 9,
borderWidth: 1,
borderColor: '#e0e0e0',
backgroundColor: '#ffffff',
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#141414',
textAlign: 'center',
}}>
4:45 PM
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
height: 32,
paddingVertical: 0,
paddingHorizontal: 11,
borderRadius: 9,
borderWidth: 0,
backgroundColor: '#141414',
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#ffffff',
textAlign: 'center',
}}>
{'7:35 PM '}
<Text style={{ fontSize: 10, color: '#f0a52b' }}>LUX</Text>
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
height: 32,
paddingVertical: 0,
paddingHorizontal: 11,
borderRadius: 9,
borderWidth: 1,
borderColor: '#e0e0e0',
backgroundColor: '#ffffff',
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#141414',
textAlign: 'center',
}}>
7:45 PM
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
height: 32,
paddingVertical: 0,
paddingHorizontal: 11,
borderRadius: 9,
borderWidth: 1,
borderColor: '#e0e0e0',
backgroundColor: '#ffffff',
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#141414',
textAlign: 'center',
}}>
10:50 PM
</Text>
</Pressable>
</View>
</View>
<View style={{ marginTop: 20, marginBottom: 0, marginHorizontal: 28, height: 40 }}>
<Svg width="334" height="26" viewBox="0 0 334 26">
<Path
d="M4 22 Q167 -8 330 22"
fill="none"
stroke={accent}
strokeWidth="4"
strokeLinecap="round"
/>
<Path
d="M4 24 Q167 -2 330 24"
fill="none"
stroke={accent}
strokeOpacity="0.15"
strokeWidth="10"
strokeLinecap="round"
/>
</Svg>
<Text
style={{
marginTop: 2,
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#6b6b6b',
textAlign: 'center',
}}>
Screen this way
</Text>
</View>
<View style={{ paddingTop: 8, paddingBottom: 0, paddingHorizontal: 20, gap: 6 }}>
<View style={{ justifyContent: 'space-between', flexDirection: 'row' }}>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_700Bold',
fontSize: 10,
color: '#6b6b6b',
letterSpacing: 1,
}}>
CLASSIC
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_700Bold',
fontSize: 10,
color: '#6b6b6b',
letterSpacing: 1,
}}>
₹250
</Text>
</View>
{classicRows.map((row, index) => (
<View key={index} style={{ gap: 4, alignItems: 'center', flexDirection: 'row' }}>
{row.cells.map((cell, index) => (
<Fragment key={index}>
{cell.isLabel && (
<Text
style={{
width: 16,
fontFamily: 'Inter_600SemiBold',
fontSize: 10,
color: '#6b6b6b',
textAlign: 'center',
flexShrink: 1,
}}>
{cell.text}
</Text>
)}
{cell.isAisle && <View style={{ width: 10 }} />}
{cell.isSeat && (
<Pressable
accessibilityRole="button"
accessibilityLabel={cell.aria}
onPress={cell.pick}
disabled={cell.taken}
style={{
...cell.style,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}
/>
)}
</Fragment>
))}
</View>
))}
<View style={{ justifyContent: 'space-between', marginTop: 8, flexDirection: 'row' }}>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_700Bold',
fontSize: 10,
color: '#8a5a00',
letterSpacing: 1,
}}>
PREMIUM RECLINER
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_700Bold',
fontSize: 10,
color: '#8a5a00',
letterSpacing: 1,
}}>
₹420
</Text>
</View>
{premiumRows.map((row, index) => (
<View key={index} style={{ gap: 4, alignItems: 'center', flexDirection: 'row' }}>
{row.cells.map((cell, index) => (
<Fragment key={index}>
{cell.isLabel && (
<Text
style={{
width: 16,
fontFamily: 'Inter_600SemiBold',
fontSize: 10,
color: '#6b6b6b',
textAlign: 'center',
flexShrink: 1,
}}>
{cell.text}
</Text>
)}
{cell.isAisle && <View style={{ width: 10 }} />}
{cell.isSeat && (
<Pressable
accessibilityRole="button"
accessibilityLabel={cell.aria}
onPress={cell.pick}
disabled={cell.taken}
style={{
...cell.style,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}
/>
)}
</Fragment>
))}
</View>
))}
</View>
<View
style={{
gap: 14,
justifyContent: 'center',
paddingTop: 16,
paddingBottom: 0,
paddingHorizontal: 16,
flexDirection: 'row',
}}>
<View style={{ alignItems: 'center', gap: 5, flexShrink: 1, flexDirection: 'row' }}>
<View
style={{
width: 14,
height: 12,
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
borderBottomRightRadius: 3,
borderBottomLeftRadius: 3,
borderWidth: 1.5,
borderColor: '#9e9e9e',
}}
/>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#4d4d4d',
flexShrink: 1,
}}>
Classic
</Text>
</View>
<View style={{ alignItems: 'center', gap: 5, flexShrink: 1, flexDirection: 'row' }}>
<View
style={{
width: 14,
height: 12,
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
borderBottomRightRadius: 3,
borderBottomLeftRadius: 3,
borderWidth: 1.5,
borderColor: '#d08c14',
}}
/>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#4d4d4d',
flexShrink: 1,
}}>
Premium
</Text>
</View>
<View style={{ alignItems: 'center', gap: 5, flexShrink: 1, flexDirection: 'row' }}>
<View
style={{
width: 14,
height: 12,
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
borderBottomRightRadius: 3,
borderBottomLeftRadius: 3,
backgroundColor: '#dcdcdc',
}}
/>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#4d4d4d',
flexShrink: 1,
}}>
Taken
</Text>
</View>
<View style={{ alignItems: 'center', gap: 5, flexShrink: 1, flexDirection: 'row' }}>
<View
style={{
width: 14,
height: 12,
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
borderBottomRightRadius: 3,
borderBottomLeftRadius: 3,
backgroundColor: accent,
}}
/>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#4d4d4d',
flexShrink: 1,
}}>
Your pick
</Text>
</View>
</View>
<View
style={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
backgroundColor: '#141414',
borderTopLeftRadius: 28,
borderTopRightRadius: 28,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
paddingTop: 18,
paddingBottom: 30,
paddingHorizontal: 16,
}}>
<View
style={{
borderWidth: 1,
borderColor: '#4a4a4a',
borderStyle: 'dashed',
borderRadius: 16,
flexDirection: 'row',
}}>
<View
style={{
flex: 1,
margin: 0,
paddingVertical: 12,
paddingHorizontal: 14,
flexDirection: 'row',
gap: 8,
}}>
<View style={{ flex: 1 }}>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 10, color: '#a6a6a6' }}>
Seats
</Text>
<Text
style={{
marginTop: 2,
marginBottom: 0,
marginHorizontal: 0,
fontFamily: 'Inter_600SemiBold',
fontSize: 13,
color: '#ffffff',
}}>
{seatsText}
</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 10, color: '#a6a6a6' }}>
Show
</Text>
<Text
style={{
marginTop: 2,
marginBottom: 0,
marginHorizontal: 0,
fontFamily: 'Inter_600SemiBold',
fontSize: 13,
color: '#ffffff',
}}>
Mon · 7:35 PM
</Text>
</View>
</View>
<View
style={{
borderLeftWidth: 1,
borderLeftColor: '#4a4a4a',
borderStyle: 'dashed',
paddingVertical: 12,
paddingHorizontal: 14,
justifyContent: 'center',
alignItems: 'flex-end',
flexShrink: 1,
}}>
<Text style={{ fontFamily: 'Inter_400Regular', fontSize: 10, color: '#a6a6a6' }}>
Total
</Text>
<Text style={{ fontFamily: 'Inter_700Bold', fontSize: 17, color: '#ffffff' }}>
{totalText}
</Text>
</View>
</View>
{held && (
<Text
style={{
marginTop: 10,
marginBottom: 0,
marginHorizontal: 0,
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#ffd9c7',
}}>
Seats held for 10 minutes. Complete payment to confirm your tickets.
</Text>
)}
<Pressable
accessibilityRole="button"
onPress={hold}
disabled={noneSelected}
style={{
...holdStyle,
alignSelf: 'flex-start',
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_700Bold',
fontSize: 15,
color: '#ffffff',
textAlign: 'center',
}}>
{holdLabel}
</Text>
</Pressable>
<View style={{ justifyContent: 'center', marginTop: 8, flexDirection: 'row' }}>
<Pressable
accessibilityRole="button"
onPress={clear}
style={{
borderWidth: 0,
padding: 6,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#bdbdbd',
textAlign: 'center',
textDecorationLine: 'underline',
}}>
Clear selection
</Text>
</Pressable>
</View>
</View>
</View>
</Screen>
);
}src/app/showtimes.tsx
import { Image } from 'expo-image';
import { LinearGradient } from 'expo-linear-gradient';
import { router } from 'expo-router';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import Svg, { Circle, Path } from 'react-native-svg';
import { Screen } from '@/components/screen';
import { asset } from '@/lib/assets';
const accent = '#CC3A0E';
export default function ShowtimesScreen() {
return (
<Screen background="#000000" statusBar="light">
<View style={{ flex: 1 }}>
<View
style={{
alignItems: 'center',
justifyContent: 'space-between',
paddingTop: 58,
paddingBottom: 0,
paddingHorizontal: 20,
flexDirection: 'row',
}}>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_400Regular',
fontSize: 13,
color: '#ffffff',
}}>
{'Movies Showing '}
<Text style={{ color: '#8a8a8a' }}>|</Text>{' '}
<Text style={{ color: '#a6a6a6' }}>Booking by Theatre</Text>
</Text>
<Pressable
accessibilityRole="link"
accessibilityLabel="Close"
onPress={() => router.navigate('/detail')}
style={{
width: 28,
height: 28,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#ffffff"
strokeWidth="1.6"
strokeLinecap="round">
<Circle cx="12" cy="12" r="10" />
<Path d="M9 9l6 6M15 9l-6 6" />
</Svg>
</Pressable>
</View>
<View
style={{
gap: 8,
paddingTop: 18,
paddingBottom: 0,
paddingHorizontal: 20,
flexDirection: 'row',
}}>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 0,
borderRadius: 999,
backgroundColor: '#ffffff',
paddingVertical: 7,
paddingHorizontal: 14,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 13,
color: '#000000',
textAlign: 'center',
}}>
All
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 1,
borderColor: '#4a4a4a',
borderRadius: 999,
paddingVertical: 7,
paddingHorizontal: 12,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#e0e0e0',
textAlign: 'center',
}}>
SCREENX
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 1,
borderColor: '#4a4a4a',
borderRadius: 999,
paddingVertical: 7,
paddingHorizontal: 12,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#e0e0e0',
textAlign: 'center',
}}>
4DX
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 1,
borderColor: '#4a4a4a',
borderRadius: 999,
paddingVertical: 7,
paddingHorizontal: 12,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#e0e0e0',
textAlign: 'center',
}}>
IMAX
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 1,
borderColor: '#4a4a4a',
borderRadius: 999,
paddingVertical: 7,
paddingHorizontal: 12,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 12,
color: '#e0e0e0',
textAlign: 'center',
}}>
DOLBY
</Text>
</Pressable>
</View>
<View style={{ height: 170, marginTop: 22 }}>
<View
style={{
position: 'absolute',
left: -2,
top: 12,
width: 84,
height: 146,
borderRadius: 16,
overflow: 'hidden',
}}>
<Image
source={{ uri: asset('c27d246b2a.svg') }}
contentFit="cover"
style={{ width: '100%', height: '100%' }}
/>
</View>
<View
style={{
position: 'absolute',
left: '50%',
top: 0,
transform: [{ translateX: '-50%' }],
width: 120,
height: 170,
borderRadius: 18,
overflow: 'hidden',
backgroundColor: '#6d0610',
}}>
<Image
source={{ uri: asset('18e23cdfc9.svg') }}
contentFit="cover"
accessibilityLabel="Iron Petal poster"
style={{ width: '100%', height: '100%' }}
/>
<Text
style={{
position: 'absolute',
left: 10,
top: 4,
fontFamily: 'BebasNeue_400Regular',
fontSize: 30,
color: '#ffffff',
fontStyle: 'italic',
}}>
1
</Text>
</View>
<View
style={{
position: 'absolute',
right: -2,
top: 12,
width: 84,
height: 146,
borderRadius: 16,
overflow: 'hidden',
}}>
<Image
source={{ uri: asset('c84f4e1efb.svg') }}
contentFit="cover"
style={{ width: '100%', height: '100%' }}
/>
</View>
</View>
<View style={{ alignItems: 'center', paddingTop: 20 }}>
<View style={{ alignItems: 'center', gap: 8, flexDirection: 'row' }}>
<Text
style={{
margin: 0,
flexShrink: 1,
fontFamily: 'Inter_700Bold',
fontSize: 16,
color: '#ffffff',
}}>
Iron Petal – The Movie: Akane Arc
</Text>
<Text
style={{
backgroundColor: '#f0a52b',
borderRadius: 5,
paddingVertical: 3,
paddingHorizontal: 5,
flexShrink: 1,
fontFamily: 'Inter_800ExtraBold',
fontSize: 11,
color: '#141414',
}}>
16+
</Text>
</View>
<Text
style={{
marginTop: 6,
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#b3b3b3',
}}>
1h 42m
</Text>
</View>
<View
style={{
position: 'absolute',
left: 0,
right: 0,
top: 444,
bottom: 0,
backgroundColor: '#ffffff',
borderTopLeftRadius: 28,
borderTopRightRadius: 28,
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0,
paddingTop: 18,
paddingBottom: 0,
paddingHorizontal: 16,
}}>
<View style={{ gap: 8, flexDirection: 'row' }}>
<Pressable
accessibilityRole="button"
style={{
width: 50,
height: 56,
borderWidth: 0,
borderRadius: 14,
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
gap: 2,
}}>
<Text style={{ fontFamily: 'Inter_700Bold', fontSize: 11, color: '#ffffff' }}>
MON
</Text>
<Text style={{ fontFamily: 'Inter_700Bold', fontSize: 17, color: '#ffffff' }}>
16
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
width: 50,
height: 56,
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 14,
backgroundColor: '#ffffff',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
}}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 11, color: '#141414' }}>
TUE
</Text>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 17, color: '#141414' }}>
17
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
width: 50,
height: 56,
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 14,
backgroundColor: '#ffffff',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
}}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 11, color: '#141414' }}>
WED
</Text>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 17, color: '#141414' }}>
18
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
width: 50,
height: 56,
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 14,
backgroundColor: '#ffffff',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
}}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 11, color: '#141414' }}>
THU
</Text>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 17, color: '#141414' }}>
19
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
width: 50,
height: 56,
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 14,
backgroundColor: '#ffffff',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
}}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 11, color: '#141414' }}>
FRI
</Text>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 17, color: '#141414' }}>
20
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
width: 50,
height: 56,
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 14,
backgroundColor: '#ffffff',
alignItems: 'center',
justifyContent: 'center',
gap: 2,
}}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 11, color: '#141414' }}>
SAT
</Text>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 17, color: '#141414' }}>
21
</Text>
</Pressable>
</View>
<View style={{ gap: 8, marginTop: 14, flexDirection: 'row' }}>
<Pressable
accessibilityRole="button"
style={{
alignItems: 'center',
gap: 5,
borderWidth: 0,
borderRadius: 999,
backgroundColor: accent,
paddingVertical: 7,
paddingHorizontal: 13,
flexShrink: 1,
flexDirection: 'row',
}}>
<Svg width="13" height="13" viewBox="0 0 24 24" fill="#ffffff">
<Path d="M12 2a7 7 0 0 0-7 7c0 5 7 13 7 13s7-8 7-13a7 7 0 0 0-7-7zm0 9.5A2.5 2.5 0 1 1 12 6.5a2.5 2.5 0 0 1 0 5z" />
</Svg>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 13,
color: '#ffffff',
flexShrink: 1,
}}>
Mumbai
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 999,
backgroundColor: '#ffffff',
paddingVertical: 7,
paddingHorizontal: 14,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 13,
color: '#3d3d3d',
textAlign: 'center',
}}>
All
</Text>
</Pressable>
<Pressable
accessibilityRole="button"
style={{
borderWidth: 1,
borderColor: '#e0e0e0',
borderRadius: 999,
backgroundColor: '#ffffff',
paddingVertical: 7,
paddingHorizontal: 14,
flexShrink: 1,
alignItems: 'center',
justifyContent: 'center',
}}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 13,
color: '#3d3d3d',
textAlign: 'center',
}}>
Special Theatre
</Text>
</Pressable>
</View>
<View
style={{
marginTop: 14,
borderRadius: 18,
boxShadow: '0 4px 18px rgba(0,0,0,0.08)',
paddingTop: 14,
paddingBottom: 16,
paddingHorizontal: 14,
}}>
<View style={{ alignItems: 'center', gap: 10, flexDirection: 'row' }}>
<View
style={{
width: 34,
height: 34,
borderRadius: 10,
backgroundColor: '#fff3d6',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_800ExtraBold',
fontSize: 11,
color: '#b07400',
flexShrink: 1,
}}>
CV
</Text>
</View>
<View style={{ flex: 1 }}>
<Text style={{ fontFamily: 'Inter_600SemiBold', fontSize: 15, color: '#141414' }}>
Cineverse, Skyline Mall, Borivali
</Text>
<Text
style={{
marginTop: 1,
fontFamily: 'Inter_400Regular',
fontSize: 11,
color: '#6b6b6b',
}}>
Non-cancellable.
</Text>
</View>
<Pressable
accessibilityRole="button"
accessibilityLabel="Favourite this cinema"
style={{
width: 36,
height: 36,
borderWidth: 0,
padding: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="1.8">
<Path d="M12 20s-7-4.3-8.8-8.6C1.9 8 4 4.8 7.2 4.8c2 0 3.5 1 4.8 2.7 1.3-1.7 2.8-2.7 4.8-2.7 3.2 0 5.3 3.2 4 6.6C19 15.7 12 20 12 20z" />
</Svg>
</Pressable>
</View>
<View style={{ marginTop: 14, gap: 8 }}>
<View style={{ flexDirection: 'row', gap: 8 }}>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/seats')}
style={{
height: 30,
borderRadius: 8,
backgroundColor: '#141414',
borderLeftWidth: 5,
borderLeftColor: '#23a55a',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 11,
color: '#ffffff',
flexShrink: 1,
}}>
4:25 PM
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/seats')}
style={{
height: 30,
borderRadius: 8,
backgroundColor: '#141414',
borderLeftWidth: 5,
borderLeftColor: '#23a55a',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 11,
color: '#ffffff',
flexShrink: 1,
}}>
4:45 PM
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/seats')}
style={{
height: 30,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
gap: 3,
flex: 1,
overflow: 'hidden',
flexDirection: 'row',
}}>
<LinearGradient
colors={['#f0a52b', '#f0a52b', '#141414', '#141414']}
locations={[0, 0.5, 0.5, 1]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
style={StyleSheet.absoluteFill}
/>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 11,
color: '#ffffff',
flexShrink: 1,
}}>
7:35 PM
</Text>
<Text
style={{
flexShrink: 1,
fontFamily: 'Inter_600SemiBold',
fontSize: 7,
color: '#ffffff',
}}>
· LUX
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/seats')}
style={{
height: 30,
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
flex: 1,
overflow: 'hidden',
flexDirection: 'row',
}}>
<LinearGradient
colors={['#f0a52b', '#f0a52b', '#141414', '#141414']}
locations={[0, 0.5, 0.5, 1]}
start={{ x: 0, y: 0.5 }}
end={{ x: 1, y: 0.5 }}
style={StyleSheet.absoluteFill}
/>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 11,
color: '#ffffff',
flexShrink: 1,
}}>
7:45 PM
</Text>
</Pressable>
</View>
<View style={{ flexDirection: 'row', gap: 8 }}>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/seats')}
style={{
height: 30,
borderRadius: 8,
backgroundColor: '#141414',
borderLeftWidth: 5,
borderLeftColor: '#23a55a',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 11,
color: '#ffffff',
flexShrink: 1,
}}>
10:50 PM
</Text>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/seats')}
style={{
height: 30,
borderRadius: 8,
backgroundColor: '#141414',
borderLeftWidth: 5,
borderLeftColor: '#23a55a',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 11,
color: '#ffffff',
flexShrink: 1,
}}>
11:00 PM
</Text>
<View
style={{
position: 'absolute',
right: -5,
top: -7,
width: 16,
height: 16,
borderRadius: '50%',
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg width="9" height="9" viewBox="0 0 24 24" fill="#ffffff">
<Path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z" />
</Svg>
</View>
</Pressable>
<Pressable
accessibilityRole="link"
onPress={() => router.navigate('/seats')}
style={{
height: 30,
borderRadius: 8,
backgroundColor: '#141414',
borderLeftWidth: 5,
borderLeftColor: '#23a55a',
alignItems: 'center',
justifyContent: 'center',
flex: 1,
flexDirection: 'row',
}}>
<Text
style={{
fontFamily: 'Inter_600SemiBold',
fontSize: 11,
color: '#ffffff',
flexShrink: 1,
}}>
11:05 PM
</Text>
<View
style={{
position: 'absolute',
right: -5,
top: -7,
width: 16,
height: 16,
borderRadius: '50%',
backgroundColor: accent,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg width="9" height="9" viewBox="0 0 24 24" fill="#ffffff">
<Path d="M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5z" />
</Svg>
</View>
</Pressable>
<View style={{ flex: 1 }} />
</View>
</View>
</View>
<View
style={{
marginTop: 12,
height: 60,
borderRadius: 18,
boxShadow: '0 4px 18px rgba(0,0,0,0.08)',
}}
/>
<View
style={{
position: 'absolute',
left: 12,
right: 12,
bottom: 30,
height: 44,
borderRadius: 12,
backgroundColor: '#ffffff',
boxShadow: '0 -2px 16px rgba(0,0,0,0.1)',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 0,
paddingHorizontal: 12,
flexDirection: 'row',
}}>
<View style={{ alignItems: 'center', gap: 4, flexShrink: 1, flexDirection: 'row' }}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#141414',
flexShrink: 1,
}}>
Make favourite cinemas by tapping the
</Text>
<Svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="#141414"
strokeWidth="2">
<Path d="M12 20s-7-4.3-8.8-8.6C1.9 8 4 4.8 7.2 4.8c2 0 3.5 1 4.8 2.7 1.3-1.7 2.8-2.7 4.8-2.7 3.2 0 5.3 3.2 4 6.6C19 15.7 12 20 12 20z" />
</Svg>
</View>
<View style={{ alignItems: 'center', gap: 6, flexShrink: 1, flexDirection: 'row' }}>
<Text
style={{
fontFamily: 'Inter_400Regular',
fontSize: 12,
color: '#3d3d3d',
flexShrink: 1,
}}>
3/3
</Text>
<Pressable
accessibilityRole="button"
accessibilityLabel="Dismiss tip"
style={{
width: 20,
height: 20,
borderWidth: 0,
borderRadius: '50%',
backgroundColor: '#ececec',
padding: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
}}>
<Svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="#3d3d3d"
strokeWidth="3"
strokeLinecap="round">
<Path d="M6 6l12 12M18 6L6 18" />
</Svg>
</Pressable>
</View>
</View>
</View>
<View
style={{
position: 'absolute',
bottom: 8,
left: '50%',
transform: [{ translateX: '-50%' }],
width: 134,
height: 5,
borderRadius: 3,
backgroundColor: '#141414',
}}
/>
</View>
</Screen>
);
}src/components/screen.tsx
import { StatusBar } from 'expo-status-bar';
import type { ReactNode } from 'react';
import { ScrollView, useWindowDimensions, View } from 'react-native';
// Every screen is drawn on a 390 x 844 artboard, then scaled to the device width.
const ARTBOARD_WIDTH = 390;
const ARTBOARD_HEIGHT = 844;
type Props = {
background: string;
statusBar: 'light' | 'dark';
children: ReactNode;
};
export function Screen({ background, statusBar, children }: Props) {
const { width, height } = useWindowDimensions();
const scale = width / ARTBOARD_WIDTH;
return (
<ScrollView
bounces={false}
showsVerticalScrollIndicator={false}
style={{ flex: 1, backgroundColor: background }}
contentContainerStyle={{ height: Math.max(ARTBOARD_HEIGHT * scale, height) }}>
<StatusBar style={statusBar} />
<View
style={{
width: ARTBOARD_WIDTH,
height: ARTBOARD_HEIGHT,
overflow: 'hidden',
transformOrigin: 'left top',
transform: [{ scale }],
}}>
{children}
</View>
</ScrollView>
);
}src/lib/assets.ts
// Demo images are hosted by DeveloperMill. Point this at your own CDN, or swap to require().
const ASSET_BASE = 'https://developermill.com/app-designs/movie-ticket-booking-app-ui-design/assets';
export const asset = (name: string) => `${ASSET_BASE}/${name}`;About this design
The seat picker is real state, not a static mock. Taken seats are disabled, two price tiers total up as you pick, and the ticket stub updates live, so it is a strong starting point for any cinema, theatre or event booking app.
A cinematic black and white base with a single ember accent, tall poster cards and a dark ticket stub that anchors the checkout.
- Interactive seat map with classic and premium pricing
- Live ticket stub with seats, show time and total
- Peeking poster carousel with ranked overlays
- Date strip, format chips and theatre showtime cards
Colour palette
Ember
#CC3A0E
Ink
#141414
Screen black
#000000
Gold rating
#F0A52B
Available
#23A55A
Hairline
#E0E0E0
How to use this design
- 01
Pick what to copy
Copy the Movie Ticket Booking artifact, skill or code from this page.
- 02
Run it or generate it
Paste the code into a new Expo app and start it, or give the skill to your AI coding agent.
- 03
Make it yours
Replace the demo images and sample content, then connect your own data.
Frequently asked questions
Does the seat picker work, or is it a static mockup?
It works. Tapping a seat toggles it, taken seats are disabled, you can pick up to ten seats, and the total recalculates from the classic and premium prices.
Is the Movie Ticket Booking design free to use?
Yes. The design, skill and Expo code are free and MIT licensed, including for commercial apps. Replace the demo images and sample content with your own before you ship.
What is the difference between the artifact, skill and code?
The artifact is the full design: tokens, screens and source. The skill is a SKILL.md that teaches an AI coding agent to build the UI. The code is the finished Expo app you can paste in and run.
Which Expo version does the code use?
It targets Expo SDK 58 with Expo Router and TypeScript. It uses packages installed through npx expo install, with no custom native code.



