/* T-AV — Showcase, What We Do, Service Detail, About, Contact, Careers, 404
   All non-home pages live here. */

const SHOWCASE_CATEGORIES = ['ALL', 'CONCERTS', 'LAUNCH EVENTS', 'SPORTS', 'GOVERNMENT', 'PRIVATE', 'CORPORATE', 'EXHIBITIONS', 'CULTURAL EVENTS', 'LIGHTING', 'GEAR', 'DESIGNS'];

/* Showcase items. Each entry carries either an `img` (full URL or
   path) or a fallback `svg` (legacy placeholder kind). When `img`
   is present the masonry tile and lightbox render an <img>; the
   SVG fallback stays for any entry that's still a placeholder.

   ⚠️ DO NOT EDIT THE ARRAY BELOW BY HAND.
   Edit assets/showcase/showcase.csv in Numbers / Sheets / Excel,
   then run: python3 scripts/csv-to-jsx.py
   The script regenerates everything between the sentinel comments.
   See assets/showcase/README.md for the workflow. */
/* === SHOWCASE_ITEMS · auto-generated by scripts/csv-to-jsx.py === */
const SHOWCASE_ITEMS = [
{ id: 1, title: 'Club Night', category: 'PRIVATE', venue: 'Nightclub · UAE', year: '2017', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-001-YItx6x9MwlkKV6c9IWcfkOQzbiY7Ms.avif', span: 'tall' },
{ id: 2, title: 'Eid Concert', category: 'CONCERTS', venue: 'Thafra · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-002-tWY5R9dq2sQY9wFLHio7zpZa6BWXsC.jpg', span: 'wide' },
{ id: 3, title: 'Performance Night', category: 'CONCERTS', venue: 'Stage · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-003-ti2XhWrDz1dsBzi5f1koMzm2o03srD.jpg' },
{ id: 4, title: 'Lighting Design', category: 'DESIGNS', venue: 'KSA', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-004-3pbzKHYWxoeQktiZ8ct1eru2nNbe4w.avif' },
{ id: 5, title: 'Four Digico Q338 on site', category: 'GEAR', venue: 'Abu Dhabi - UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-005-X1usZJQaQ71RO1Sazq6T2pjsoKLhgX.avif' },
{ id: 6, title: 'Red Bull Sound Clash', category: 'CONCERTS', venue: 'Arena · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-006-6Qhdsqg05jRTlPRWUTrA4MMEArSkXo.avif' },
{ id: 7, title: 'Technical AV Design', category: 'DESIGNS', venue: 'Jordan', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-007-u1TaGpBGEB2pQfScOqxZpZASTMljIB.avif', span: 'wide' },
{ id: 8, title: 'Eid Concerts', category: 'CONCERTS', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-008-VVYFSBdRHG7Xzh4IiFkwXPTTriemvQ.avif' },
{ id: 10, title: 'Technical AV Design', category: 'DESIGNS', venue: 'Jordan', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-010-O9IAftGRnJMepmbbmvBVZknU3WBXAl.avif' },
{ id: 11, title: 'Charity Dinner Concert', category: 'CONCERTS', venue: 'Abu Dhabi - UAE', year: '2017', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-011-Je7cpwecPL98qPYgkfwEn2jI2NPXip.avif' },
{ id: 13, title: 'Red Bull Sound Clash', category: 'CONCERTS', venue: 'Cairo', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-013-q5byaAbNct0KyDlb1uNQ4AKqwFo8tq.avif', span: 'wide' },
{ id: 15, title: 'BMW M5 Launch', category: 'LAUNCH EVENTS', venue: 'Autdrome · UAE', year: '2014', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-015-q5uHA0Hln51OkdAJPEkHCxM1IKHmCF.avif' },
{ id: 17, title: 'She’s Mercedes', category: 'LAUNCH EVENTS', venue: 'St Regis · UAE', year: '2015', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-017-gjpIm9kSXNfAsQVf80PUY2ekSmAeqz.avif' },
{ id: 18, title: 'Lamborghini Launch', category: 'LAUNCH EVENTS', venue: 'Showroom · UAE', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-018-spVDWhcq1FJ7YTq7kgc2lnmUEZkX3G.avif' },
{ id: 19, title: 'Lamborghini Launch', category: 'LAUNCH EVENTS', venue: 'Showroom · UAE', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-019-WJAi77i7jDhKhQ3NfbPBh0rM6XRPVo.jpg' },
{ id: 20, title: 'Nile Property Exhibition', category: 'EXHIBITIONS', venue: 'Exhibition Hall · UAE', year: '2021', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-020-AxAIoa32YSkb2udpwWcGwmaWVPfNOZ.avif' },
{ id: 21, title: 'Toyota Launch', category: 'LAUNCH EVENTS', venue: 'Showroom · UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-021-j3Uzd6QnekvFL0h1f0Lhnqiw8YMKlz.avif', span: 'wide' },
{ id: 22, title: 'H&M Fashion Event', category: 'LAUNCH EVENTS', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-022-77pgvUcyqWIiVzYcwXnewu1uJTqBf5.avif' },
{ id: 23, title: 'Abu Dhabi Ports Launch', category: 'GOVERNMENT', venue: 'Abu Dhabi', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-023-Q1EqwT4gMoEuLfH0gaZaq6JWifOOBq.avif' },
{ id: 24, title: 'Boxing in the Arena', category: 'SPORTS', venue: 'Arena · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-024-qKNCWHlGlNb1mzYnkSbS5dA3vC2tkk.avif', span: 'wide' },
{ id: 25, title: 'Football Awards', category: 'SPORTS', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-025-PKLUYttd9XuUsEcoTSHtvM79sLaJe8.avif' },
{ id: 26, title: 'Football Awards', category: 'SPORTS', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-026-ccG27h9eRI7vAFwkGOgql7b9yV95mi.avif' },
{ id: 27, title: 'Surf Abu Dhabi Pro', category: 'SPORTS', venue: 'Abu Dhabi', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-027-7iDKs8WLGauNt4I6X52xTdmkfUFHCx.avif', hero: true },
{ id: 28, title: 'FIFA CWC · Press', category: 'SPORTS', venue: 'UAE', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-028-pzM0cK6CUNWSKo5OhQXf34Pyoydq28.avif', span: 'tall' },
{ id: 29, title: 'FIFA CWC · TV Studio', category: 'SPORTS', venue: 'UAE', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-029-LX9eOdONJLaDpE1alDZbfFi63KjFkE.avif' },
{ id: 30, title: 'FIFA CWC', category: 'SPORTS', venue: 'UAE', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-030-ijwKLDCbBRuQsFHqLgEmHpMfxof9yz.avif' },
{ id: 31, title: 'AFC Asian Cup', category: 'SPORTS', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-031-NxNhOMmEDxqYJHFmaBCs8Ng7c022uX.avif' },
{ id: 32, title: 'AFC TV Lighting', category: 'SPORTS', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-032-0mnKFpUowB5dQL82K3Bd0Wxk7eIOnO.jpg' },
{ id: 33, title: 'Desert Force MMA', category: 'SPORTS', venue: 'UAE', year: '2016', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-033-Ct6WNiGL9sMFjKHLZhxx1WEtBJI6FJ.avif', span: 'wide' },
{ id: 34, title: 'FIFA CWC Stadium · Gates', category: 'SPORTS', venue: 'Lusail', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-034-pcGBUWZzknDm6YUq1kZ2Y0UBGDdYUO.avif' },
{ id: 35, title: 'Horse Pegging Championship', category: 'SPORTS', venue: 'UAE', year: '2018', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-035-eahyFCYddxgZGInVBicMoX34bJdZ8u.avif' },
{ id: 37, title: 'Wings for Life Charity', category: 'SPORTS', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-037-a0SUd54e1VK83PXXkQE6FPSSFZYGpp.avif' },
{ id: 38, title: 'Fight Night · OSN', category: 'SPORTS', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-038-YbnR49yvMB7l5FJ2lf1mqh3wkVTdDE.avif' },
{ id: 39, title: 'Sports Awards', category: 'SPORTS', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-039-9fBQDQNL8QcPYF9kLMvSzNUGzHfXKr.avif' },
{ id: 40, title: 'Game Expo', category: 'SPORTS', venue: 'Arena · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-040-RZOxscq9mfgRX6FjcHn4mMmaYZYr9Q.webp', span: 'tall' },
{ id: 43, title: 'Government Lighting', category: 'GOVERNMENT', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-043-5t16mwMEgZP6XkP6Klhoj2jetsK1P3.avif' },
{ id: 44, title: 'UAE National Day', category: 'GOVERNMENT', venue: 'UAE', year: '2020', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-044-Efaa8xaEl5qR9RpPFO184WNBRih3B0.avif', span: 'wide' },
{ id: 45, title: 'Building Projection', category: 'PRIVATE', venue: 'Sharjah · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-045-5CkJd6BERZj3L8XAagJmcir7w9YsvS.avif' },
{ id: 46, title: 'Hologram', category: 'PRIVATE', venue: 'Private · UAE', year: '2016', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-046-aDSQcweotKIw6XWYc9VDtLtAcw7DNq.avif' },
{ id: 47, title: 'Home Video Mapping', category: 'PRIVATE', venue: 'Private · UAE', year: '2017', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-047-YjNkXjy34UXcsPuOq9aUehRmrwzx8D.avif', span: 'tall' },
{ id: 48, title: 'Tory Burch', category: 'CORPORATE', venue: 'Private · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-048-cS8PYneT8Lh7NPSadolofdPjOtNzHZ.avif' },
{ id: 49, title: 'Beam Lights', category: 'GEAR', venue: 'UAE', year: '', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-049-SAZTC9X2q48AFraXMPCESViozf1vMC.avif' },
{ id: 50, title: 'F1 Structures', category: 'SPORTS', venue: 'Yas Island · UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-050-KWBwt9XGSX5oeq544mptgeb5nmYole.jpg' },
{ id: 51, title: 'Wedding Video Mapping', category: 'PRIVATE', venue: 'Private · UAE', year: '2015', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-051-YztVCCUAsRr9jgqkgXFgkJRwugaBXp.avif', hero: true },
{ id: 52, title: 'Virtual Wedding', category: 'GOVERNMENT', venue: 'Private · UAE', year: '2020', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-052-3QG8xypoQZuwszdpxXfv25zxxbJHG0.avif' },
{ id: 53, title: 'Private Party', category: 'PRIVATE', venue: 'Private · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-053-3NivRaxULyNaqq7QEmUR2Q7Yww5GCR.avif', span: 'wide' },
{ id: 54, title: 'Nickelodeon', category: 'CULTURAL EVENTS', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-054-Ambh5iTMMNBqo54jYlCkiuG9d8Ecy7.avif' },
{ id: 55, title: 'H&M Home', category: 'PRIVATE', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-055-UTnZ4DOHKlMzcOnVZV7cBw6zemzq0N.avif' },
{ id: 56, title: 'X Yoga', category: 'SPORTS', venue: 'Dubai', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-056-a82347jNt9FoHKi8qEtjz1A1HM1nJr.avif' },
{ id: 57, title: 'X Yoga', category: 'PRIVATE', venue: 'Dubai', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-057-9dscFAlYGG4MkA2nNQI0VkUkp8g25e.avif' },
{ id: 58, title: 'X Yoga', category: 'PRIVATE', venue: 'Dubai', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-058-fV0nbdIKCLhtqJGs9fVdLhq7RbQKL5.avif' },
{ id: 59, title: 'X Yoga', category: 'PRIVATE', venue: 'Dubai', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-059-M08gR2M3zsCgDmj8Lss7S8yimHf5FL.avif', span: 'wide' },
{ id: 60, title: 'Skylanders Live', category: 'PRIVATE', venue: 'MAF · UAE', year: '2014', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-060-xg2pZ9e24MXsqreQGkgtys2y1Cu94V.avif' },
{ id: 61, title: 'Nile Property Exhibition', category: 'EXHIBITIONS', venue: 'Exhibition · KSA', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-061-yGFxzol6xj8j1cv56aesRixjfhk4Zd.avif' },
{ id: 62, title: 'SEF', category: 'CORPORATE', venue: 'Sharjah', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-062-SwXyKUmEwTjgmtb6SI513OVSAxNZGJ.avif', span: 'tall' },
{ id: 63, title: 'Pfizer Conference', category: 'CORPORATE', venue: 'Conference · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-063-6eBWe0wMY1LT5DQXjnPlXoXNDSHm00.avif' },
{ id: 64, title: 'BSH Conference', category: 'CORPORATE', venue: 'Conference · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-064-UmPI2ZCxIEZ0qYPSE6t09fB1ZP9NTU.avif', span: 'wide' },
{ id: 65, title: 'ARES Conference', category: 'CORPORATE', venue: 'Conference · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-065-UDSUxI1jHvsnjz2RcLDvSNUag9D2zd.avif' },
{ id: 66, title: 'Conference Stage', category: 'CORPORATE', venue: 'Conference · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-066-7eeB40ZOmajG78IiBkkkQf6qw8WEmd.avif' },
{ id: 67, title: 'Night Showcase', category: 'CORPORATE', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-067-lPi7cBpzASiMIQGL3hMCkchQCq0BoB.webp' },
{ id: 68, title: 'Eid Concerts', category: 'CONCERTS', venue: 'Al-Ain UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-068-Bz0R30Zy636Bg8OGVufDmdugLBWC7j.avif' },
{ id: 69, title: 'World Cities Cultural Summit', category: 'GOVERNMENT', venue: 'Dubai', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-069-36lXaTNzkM3uV3p7sCWAlUdFw6Eiyh.avif', span: 'wide' },
{ id: 70, title: 'SAWE Event', category: 'CORPORATE', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-070-qPwuEiKFSQa1dEvPzd11u5i3iucBlD.avif' },
{ id: 71, title: 'Imeta Conference', category: 'CORPORATE', venue: 'Conference · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-071-psZLOyKk02kVlXamG0cK1vTZRTxCHp.avif' },
{ id: 72, title: 'Imeta Conference', category: 'CORPORATE', venue: 'Conference · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-072-Mu4WtwiDvchNWOwULGVTgOMp3AubtR.avif' },
{ id: 73, title: 'Kaspersky Awards', category: 'CORPORATE', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-073-PxUNhm6h1DihGLuvjdlA6hdIcL6w7f.avif' },
{ id: 74, title: 'Nestle Corporate Event', category: 'CORPORATE', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-074-oIEzQy82oYCjqv1eMVtrBtavizcEcq.avif' },
{ id: 76, title: 'Dubai Opera Hologram', category: 'CULTURAL EVENTS', venue: 'Dubai Opera', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-076-NMFgZCPxQd9Ick1MnP1jraKz1wJz3F.avif', span: 'wide' },
{ id: 77, title: 'Dubai Opera Hologram', category: 'CULTURAL EVENTS', venue: 'Dubai Opera', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-077-lv56zHDJqmr00Yr3NHxsIcgIOVIeeW.avif' },
{ id: 78, title: 'Hologram', category: 'GEAR', venue: 'AIN University · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-078-KtID1CSQNHHySsKVRQoXovmleM6ucN.avif' },
{ id: 79, title: 'National Day Hologram', category: 'CULTURAL EVENTS', venue: 'Al-Ain · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-079-LsWsLckBo8TkJL9Lfd2VQsP2aogIzd.jpg' },
{ id: 80, title: 'AIN University National Day', category: 'CULTURAL EVENTS', venue: 'Al-Ain · UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-080-ZFYKUGVZbMkSAPrJYjEBj1bC0PNF7J.avif' },
{ id: 81, title: 'Souq Jubail Mapping', category: 'LIGHTING', venue: 'Souq Jubail · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-081-BdKhxqB34ctYUdu8ENjgAdk1A3WecW.avif' },
{ id: 82, title: 'Club Night', category: 'PRIVATE', venue: 'UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-082-ZGgCSmBBLvEkcRy0kAPPustPj8tadl.avif' },
{ id: 83, title: 'National Day', category: 'GOVERNMENT', venue: 'Abu Dhabi· UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-083-HniNB40ztC2eEzdEK33fncm5roCJRM.avif', span: 'tall' },
{ id: 84, title: 'National Day', category: 'LIGHTING', venue: 'Meydan · UAE', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-084-tG39rLeIKgC4Zy8NTTVxwHiMPnLziR.avif' },
{ id: 85, title: 'L-Acoustics K2', category: 'GEAR', venue: 'Warehouse · Dubai', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-085-zivy9Jbwow83Lss8gGL9wTDJR3DB7v.avif' },
{ id: 86, title: 'Lighting Prep · Warehouse', category: 'GEAR', venue: 'Warehouse · Dubai', year: '2019', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-086-rtJ2lo4nEaR6T3Jgg0sUwl9oStAE6Z.avif', span: 'wide' },
{ id: 87, title: 'Design Concepts', category: 'DESIGNS', venue: 'Jordan', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-087-rPNAYEv3hla7b7R9Gq8bF2yDhDtJbf.webp' },
{ id: 88, title: 'Interactive Installation', category: 'GEAR', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-088-0RRKBhS5fyBYfzr1SQOMT8s82oqpcf.avif' },
{ id: 89, title: 'Broadcast Studio · Covid', category: 'PRIVATE', venue: 'UAE', year: '2020', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-089-WRlwASEBLCs2L3bjCLIzjc5lwfZt3u.avif' },
{ id: 90, title: 'Solar Lights', category: 'GEAR', venue: 'UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-090-PMTrrxQE1b2FRozChhxhah2Wdmf7qM.avif' },
{ id: 91, title: 'Architectural Lighting', category: 'CULTURAL EVENTS', venue: 'UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-091-ZDn4lcHr6iUnMFW6m1pk862KcLg3GL.avif' },
{ id: 92, title: 'HM Art Gallery', category: 'LAUNCH EVENTS', venue: 'Serkal  · UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-092-B3jbDjpKeJ63ccp5QCC4ZUCOh3jaNn.avif' },
{ id: 93, title: 'Fitness District', category: 'DESIGNS', venue: 'Abu Dhabi - UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-093-3eZr5QlIjFJD4jjMOJ8EkfHQmg7nBx.avif', span: 'tall' },
{ id: 94, title: 'World Cities Cultural Summit', category: 'GOVERNMENT', venue: 'Dubai - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-094-o6zHX3I3XqaaA24hOcFKTu02nFv7KF.avif' },
{ id: 96, title: 'Umm Kalthoum Hologram', category: 'CONCERTS', venue: 'Dubai Opera - UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_5432-3QobIz2B5WujNMBq87w9cPiNDcJVRC.webp', hero: true },
{ id: 97, title: 'Gov Games', category: 'GOVERNMENT', venue: 'Dubai - UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_4611-uNhG83d5tNQn0hnBNy4aQ4Nc823EYz.webp' },
{ id: 98, title: 'Gov Games', category: 'SPORTS', venue: 'Dubai - UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_4626-xBzgDh5W84Hcxt0oOtIf5w65a1PJdh.webp' },
{ id: 99, title: 'LED Cube Room', category: 'GEAR', venue: 'Dubai - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_7021-lQvtLqjMsQ61NqweQxCrHiZTu9pG4I.webp' },
{ id: 100, title: 'Marmot Film Festival', category: 'CULTURAL EVENTS', venue: 'Dubai - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_6946-7MAIMRRgRdeVrGFrsWaTxRARDw4Rkh.webp' },
{ id: 101, title: 'SEF', category: 'CULTURAL EVENTS', venue: 'Sharjah - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_7097-CY1VPJLsgCfFMa5qXISB3a78JRf3NA.webp' },
{ id: 102, title: 'ADQ Festival', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_7237-sw7alzyL3C9NhMvI5Zoy6Av5Ecyv01.webp' },
{ id: 103, title: 'KFC Hologram', category: 'CORPORATE', venue: 'Dubai - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_7970-9qsAroggJltUV4SeyHqGRuDrBeVwHi.webp' },
{ id: 104, title: 'ACS Gala Dinner', category: 'PRIVATE', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/54523348945_abc9ff66a3_k-5XzBvtFaZtmFLd0Rpf7Ee1lHkYB3sP.webp' },
{ id: 105, title: 'F1 stage', category: 'CONCERTS', venue: 'Abu Dhabi - UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_6519-7EgRQMXksydx7grmYT6qYXq4525U7b.webp' },
{ id: 106, title: 'F1 stage', category: 'SPORTS', venue: 'Abu Dhabi - UAE', year: '2024', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_6509-YdbwTIn6Hu9Cpon8A4e6cMAV1Iaw4F.webp' },
{ id: 107, title: 'CRM Stage', category: 'CORPORATE', venue: 'Dubai- UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/BF405F72-4ADD-40FE-9AFC-2A743C4EE529-KNPjQ9FWUAajj0M1hxcJbNEETjV0f0.webp' },
{ id: 108, title: 'CRM Stage', category: 'CORPORATE', venue: 'Dubai - UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/5DB79149-8510-48C6-8E93-A3699CC4B731-DwMXlQe6bWHd4ZLz4Mt7PQrH3QRyMI.webp' },
{ id: 109, title: 'Blippi', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_1745-ODyjdZ7zZ82lgHiTW82ml2eYJqjvQW.webp' },
{ id: 110, title: 'Maui Thai', category: 'SPORTS', venue: 'Abu Dhabi - UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_1610-o7Ig8LrouJUe1TuCY91es4daG1NC6Q.webp' },
{ id: 111, title: 'DAZ Festival', category: 'CULTURAL EVENTS', venue: 'Al-Ain- UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_1678-PMnfOJvuGublCpMwfrUKTZekYN8kT8.webp' },
{ id: 112, title: 'SEI', category: 'SPORTS', venue: 'Abu Dhabi - UAE', year: '2023', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_1990-QqI4m63OHOlrvuXX3uiqCf9a83Tfm2.webp' },
{ id: 113, title: 'MOTN', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_1025-X5ahpql3n6pFd3kFOJYuYKsmJdbML0.webp' },
{ id: 114, title: 'MOTN', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/9AF1C194-5478-4435-B58E-B2AB55357290-b3jAtVJpWRXyeTaPxhKJJjpe130q1z.webp' },
{ id: 115, title: 'Floor Mapping', category: 'GEAR', venue: 'Abu Dhabi - UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/4B3F0FF9-4AC1-41EC-96AD-37A82AE7F237-bCtIrs0ZUk5cu5bTfxOHog8nFJtiLQ.webp' },
{ id: 116, title: 'MOTN', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0902-QRUaYC0VoJd4a6jATyr8bocaM99Fe2.webp' },
{ id: 117, title: 'our "T" Truss', category: 'GEAR', venue: 'Abu Dhabi - UAE', year: '2026', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_1007-kDpCBmvJs5dg1cOkpGv84gGqY8OrTY.webp' },
{ id: 118, title: 'Al Hosn Festival', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2026', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0838-L29F7X9FhdogK2e1ui8zcXLzB1VW2b.webp' },
{ id: 119, title: 'GoTF', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/a40e85c6-6c24-4112-8be9-71700b663ed2-NGb25ZKitiuFgwDQkCKnpPbz42PBbH.webp', hero: true },
{ id: 120, title: 'GoTF', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/b65ccde5-09a1-4a0c-9638-e8db1829c96b-4AZ1XwgdcRqRZHAMj5JBYZWI5K8a2d.webp' },
{ id: 121, title: 'GoTF', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0556-KI2PHEfiKvpgsvIQ2vWDHuHCZk0rGg.webp' },
{ id: 122, title: 'GoTF', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/716ac269-848b-4a5b-8a54-0ccf97ce9680-FOG2SAWaAcqaLM43jtqcY2ik0Md37Z.webp' },
{ id: 123, title: 'GoTF', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/1D96DCF1-985C-4923-832F-3C3964CCBA59-WAdsn4IVtlQAhzGSGHfRsL4XjMwDbI.webp' },
{ id: 124, title: 'Twelve GMA3 on site', category: 'GEAR', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0453-bUHy4mRgtufrDSFGrN1TunguyyEOSO.webp' },
{ id: 125, title: 'F1', category: 'SPORTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0342-QFGVFMXcpahml1ZX7uKuJnNFZ3hj1q.webp' },
{ id: 126, title: 'Calvin Harris @ F1', category: 'CONCERTS', venue: 'Abu Dhabi - UAE', year: '2025', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0161-qrM6yUgppwoyJOW4sATl93npr7uZIx.webp' },
{ id: 127, title: 'Al Hosn Festival', category: 'CONCERTS', venue: 'Abu Dhabi - UAE', year: '2026', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0832-luEhOq8h5KeWcZqtRYgXl9RGUAu9ud.webp' },
{ id: 128, title: 'Al Hosn Festival', category: 'CONCERTS', venue: 'Abu Dhabi - UAE', year: '2026', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0805-D9iKKxigS7muQ910NfPOvSPjaj9kLA.webp' },
{ id: 129, title: 'MOTN Floor Interactive Mapping', category: 'GEAR', venue: 'Abu Dhabi - UAE', year: '2022', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_0940-WyF2tIT5Lo0TVVrou9oQ4SNatCoB6h.webp' }];
/* === /SHOWCASE_ITEMS === */

/* Expose SHOWCASE_ITEMS on window so other split <script> files
   (Home.jsx, the BeatShowcaseTeaser specifically) can pull a random
   sample without a build step. Lives outside the auto-generated
   sentinels so csv-to-jsx.py won't strip it on the next sync. */
window.SHOWCASE_ITEMS = SHOWCASE_ITEMS;

/* IG_POOL — photos the owner flagged in the CMS as eligible for the
   Instagram-style strips on Beat 10 and the Showcase page. Each page
   shuffles this pool independently and takes 6. Generated from the
   `ig` column in showcase.csv by scripts/csv-to-jsx.py. */
/* === IG_POOL · auto-generated by scripts/csv-to-jsx.py === */
const IG_POOL = [
{ id: 7, title: 'Technical AV Design', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-007-u1TaGpBGEB2pQfScOqxZpZASTMljIB.avif', category: 'DESIGNS', venue: 'Jordan' },
{ id: 10, title: 'Technical AV Design', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-010-O9IAftGRnJMepmbbmvBVZknU3WBXAl.avif', category: 'DESIGNS', venue: 'Jordan' },
{ id: 17, title: 'She’s Mercedes', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-017-gjpIm9kSXNfAsQVf80PUY2ekSmAeqz.avif', category: 'LAUNCH EVENTS', venue: 'St Regis · UAE' },
{ id: 23, title: 'Abu Dhabi Ports Launch', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-023-Q1EqwT4gMoEuLfH0gaZaq6JWifOOBq.avif', category: 'GOVERNMENT', venue: 'Abu Dhabi' },
{ id: 24, title: 'Boxing in the Arena', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-024-qKNCWHlGlNb1mzYnkSbS5dA3vC2tkk.avif', category: 'SPORTS', venue: 'Arena · UAE' },
{ id: 27, title: 'Surf Abu Dhabi Pro', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-027-7iDKs8WLGauNt4I6X52xTdmkfUFHCx.avif', category: 'SPORTS', venue: 'Abu Dhabi' },
{ id: 51, title: 'Wedding Video Mapping', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/img-051-YztVCCUAsRr9jgqkgXFgkJRwugaBXp.avif', category: 'PRIVATE', venue: 'Private · UAE' },
{ id: 96, title: 'Umm Kalthoum Hologram', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/IMG_5432-3QobIz2B5WujNMBq87w9cPiNDcJVRC.webp', category: 'CONCERTS', venue: 'Dubai Opera - UAE' },
{ id: 119, title: 'GoTF', img: 'https://75s4lm2fpemfzgyl.public.blob.vercel-storage.com/showcase/a40e85c6-6c24-4112-8be9-71700b663ed2-NGb25ZKitiuFgwDQkCKnpPbz42PBbH.webp', category: 'CULTURAL EVENTS', venue: 'Abu Dhabi - UAE' }];
/* === /IG_POOL === */

window.IG_POOL = IG_POOL;

/* Categories that actually have items in the current SHOWCASE_ITEMS.
   The filter chip row only renders these (plus ALL), so empty
   categories don't show. Recomputed every time the CSV is synced. */
const PRESENT_CATEGORIES = new Set(SHOWCASE_ITEMS.map((i) => i.category));
const VISIBLE_CATEGORIES = SHOWCASE_CATEGORIES.filter((c) => c === 'ALL' || PRESENT_CATEGORIES.has(c));

/* Fisher-Yates shuffle. Returns a new array; does not mutate input.
   Used in two places (ShowcasePage + Home's BeatShowcaseTeaser) so
   defined here at module scope. */
function shuffleArray(arr) {
  const a = arr.slice();
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    const tmp = a[i]; a[i] = a[j]; a[j] = tmp;
  }
  return a;
}
window.shuffleArray = shuffleArray;


function ShowcaseSVG({ kind }) {
  const map = {
    concert: TeaserConcert, launch: TeaserLaunch, festival: TeaserFestival,
    sports: TeaserSports, gov: TeaserGov, private: TeaserPrivate,
    'gear-audio': GearAudio, 'gear-light': GearLight, 'gear-video': GearVideo,
    design: TeaserDesign
  };
  const Comp = map[kind] || TeaserConcert;
  return <Comp />;
}

function GearAudio() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      {Array.from({ length: 6 }, (_, i) =>
      <g key={i}>
          <path d={`M 80 ${30 + i * 14} L 120 ${28 + i * 14} L 122 ${42 + i * 14} L 78 ${44 + i * 14} Z`}
        fill="none" stroke="#3E8C84" strokeWidth="1.2" />
          <circle cx="92" cy={36 + i * 14} r="2.2" fill="none" stroke="#3E8C84" strokeWidth="0.6" />
          <circle cx="108" cy={36 + i * 14} r="2.2" fill="none" stroke="#3E8C84" strokeWidth="0.6" />
        </g>
      )}
      <line x1="100" y1="6" x2="100" y2="30" stroke="#3d3a39" strokeDasharray="2 3" />
    </svg>);

}
function GearLight() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      <rect x="20" y="50" width="160" height="60" rx="2" fill="none" stroke="#3E8C84" strokeWidth="1.2" />
      {Array.from({ length: 16 }, (_, i) =>
      <rect key={i} x={28 + i * 9} y="58" width="6" height="14" fill="#3E8C84" opacity="0.3" />
      )}
      {Array.from({ length: 4 }, (_, i) =>
      <rect key={i} x={30 + i * 38} y="78" width="34" height="22" rx="1" fill="none" stroke="#3E8C84" strokeOpacity="0.6" />
      )}
    </svg>);

}
function GearVideo() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      <rect x="40" y="40" width="120" height="70" fill="#050507" stroke="#3E8C84" strokeWidth="1.4" />
      {Array.from({ length: 60 }, (_, i) =>
      <rect key={i} x={44 + i % 12 * 10} y={44 + Math.floor(i / 12) * 12} width="3" height="3"
      fill="#3E8C84" opacity={Math.random() * 0.7} />
      )}
    </svg>);

}
function TeaserDesign() {
  return (
    <svg className="teaser-card__svg" viewBox="0 0 200 150" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
      <rect width="200" height="150" fill="#0a0a0a" />
      {Array.from({ length: 12 }, (_, i) =>
      <line key={i} x1={i * 17} y1="0" x2={i * 17} y2="150" stroke="#3d3a39" strokeOpacity="0.3" />
      )}
      {Array.from({ length: 9 }, (_, i) =>
      <line key={i} x1="0" y1={i * 17} x2="200" y2={i * 17} stroke="#3d3a39" strokeOpacity="0.3" />
      )}
      <rect x="65" y="55" width="70" height="35" fill="none" stroke="#3E8C84" strokeWidth="1.6" />
      <text x="100" y="76" textAnchor="middle" fontFamily="ui-monospace, monospace" fontSize="9" fill="#3E8C84" letterSpacing="2">STAGE</text>
      <line x1="50" y1="105" x2="150" y2="105" stroke="#6DACB5" strokeOpacity="0.4" strokeDasharray="2 3" />
    </svg>);

}

/* ===== SHOWCASE PAGE ===== */

/* Fallback captions for ShowcaseIgStrip — hoisted to module scope to
   avoid per-render allocation. Svg components are resolved lazily at
   render time (via window.Teaser*) because Pages.jsx loads before
   Home.jsx which defines those globals. */
const SHOWCASE_IG_FALLBACK_CAPS = [
  'CONCERT · DUBAI',
  'LAUNCH · ABU DHABI',
  'GOVERNMENT',
  'FESTIVAL · SHARJAH',
  'PRIVATE · UAE',
  'SPORTS · DUBAI',
];

function ShowcaseIgStrip() {
  // Independent shuffle from Home's Beat 10 strip — each component
  // computes its own random 6 on mount. Pool sourced from window.IG_POOL.
  const tiles = React.useMemo(() => {
    const pool = window.IG_POOL || [];
    const shuffle = window.shuffleArray;
    const shuffled = (shuffle ? shuffle(pool) : pool.slice()).slice(0, 6);
    if (shuffled.length >= 6) return shuffled;
    const svgs = [
      window.TeaserConcert, window.TeaserLaunch, window.TeaserGov,
      window.TeaserFestival, window.TeaserPrivate, window.TeaserSports,
    ];
    const fallback = SHOWCASE_IG_FALLBACK_CAPS.map((cap, i) => ({ cap, Svg: svgs[i] }));
    const padded = [...shuffled];
    for (let i = 0; padded.length < 6 && i < fallback.length; i++) {
      padded.push(fallback[i]);
    }
    return padded;
  }, []);
  return (
    <div className="ig-strip">
      {tiles.map((t, i) => {
        if (t.img) {
          const cap = `${t.category} · ${t.venue}`.toUpperCase();
          return (
            <a key={`p-${t.id}`} className="ig-tile" href="https://www.instagram.com/av_t_av/" target="_blank" rel="noopener noreferrer">
              <img src={t.img} alt="" loading="lazy" decoding="async" />
              <div className="ig-tile__caption">{cap}</div>
            </a>);
        }
        const SvgComp = t.Svg;
        return (
          <a key={`s-${i}`} className="ig-tile" href="https://www.instagram.com/av_t_av/" target="_blank" rel="noopener noreferrer">
            {SvgComp && <SvgComp />}
            <div className="ig-tile__caption">{t.cap}</div>
          </a>);
      })}
    </div>);
}

function ShowcasePage({ navigate }) {
  const PAGE_SIZE = 20;
  const [filter, setFilter] = useState('ALL');
  const [shown, setShown] = useState(PAGE_SIZE);
  const [lightbox, setLightbox] = useState(null);
  /* Lightbox exit-fade: when closing, we keep the element mounted with
     .is-closing for 150ms so the CSS opacity transition can run, then
     null the index to unmount. Enter is handled by @starting-style.
     (Motion audit 2026-05-25 finding #1 — lightbox enter/exit motion.) */
  const [lightboxClosing, setLightboxClosing] = useState(false);
  const closeLightbox = useCallback(() => {
    setLightboxClosing(true);
    setTimeout(() => {
      setLightbox(null);
      setLightboxClosing(false);
    }, 150);
  }, []);
  // Shuffle once per mount so each visit to /showcase shows photos in
  // a fresh random order. The shuffled array is stable across filter
  // chip clicks (filter operates on this array, doesn't re-shuffle),
  // so a user clicking CONCERTS then PRIVATE sees consistent ordering
  // within the visit. Navigating away and back triggers remount + new
  // shuffle.
  const shuffled = React.useMemo(() => shuffleArray(SHOWCASE_ITEMS), []);
  const filtered = filter === 'ALL' ? shuffled : shuffled.filter((i) => i.category === filter);
  const items = filtered.slice(0, shown);
  const hasMore = filtered.length > shown;
  const remaining = filtered.length - shown;

  // Reset the visible count whenever the filter changes — otherwise a
  // user who scrolled far through ALL would see an empty page when they
  // switch to a smaller category.
  useEffect(() => { setShown(PAGE_SIZE); }, [filter]);

  useEffect(() => {
    const onKey = (e) => {
      if (lightbox === null) return;
      if (e.key === 'Escape') closeLightbox();
      if (e.key === 'ArrowRight') setLightbox((l) => Math.min(items.length - 1, l + 1));
      if (e.key === 'ArrowLeft') setLightbox((l) => Math.max(0, l - 1));
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [lightbox, items.length, closeLightbox]);

  // Body scroll-lock when the lightbox is open. Prevents the page
  // underneath from scrolling on touch / wheel, which would also be
  // confusing because the lightbox is a portal at body level.
  useEffect(() => {
    if (lightbox === null) return;
    const prev = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
    return () => { document.body.style.overflow = prev; };
  }, [lightbox]);

  return (
    <div className="page">
      <section className="container-wide" style={{ padding: '64px 40px 32px' }}>
        <div className="overline">01 · PORTFOLIO</div>
        <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
          Fifteen years <span className="accent">on the deck.</span>
        </h1>
        <p className="section__sub" style={{ maxWidth: 640 }}>
          Over fifteen years on the deck. F1 Abu Dhabi grandstands, FIFA Club World Cup feeds at Lusail, the Umm Kalthoum hologram at Dubai Opera, UAE National Day cultural stages, Calvin Harris at the F1 main stage. Concerts, launches, sports, government, cultural and private — every show a different floor plan, the same engineering rule.
        </p>
      </section>

      <section className="container-wide">
        <div className="showcase-types">
          <h2 className="divider-row">EVENT TYPES · WHAT WE SHIP</h2>
          <p><strong>Corporate conferences and product launches.</strong> Pfizer, BSH, ARES, Kaspersky, Nestle, Imeta, KFC, BMW, Lamborghini, Toyota, She's Mercedes, H&amp;M, Tory Burch — annual conferences, executive summits, brand activations and showroom launches across the UAE.</p>
          <p><strong>Music festivals and concerts.</strong> Eid concerts, Red Bull Sound Clash, Calvin Harris at F1, Umm Kalthoum hologram at Dubai Opera, Al Hosn Festival, ADQ Festival — outdoor stages from 200 to 50,000 capacity.</p>
          <p><strong>Sports and broadcast.</strong> FIFA Club World Cup, AFC Asian Cup, F1 Abu Dhabi, Surf Abu Dhabi Pro, Wings for Life, Desert Force MMA — IMAG, broadcast feeds, stage rigs and field-of-play lighting.</p>
          <p><strong>Government and cultural.</strong> UAE National Day, World Cities Cultural Summit, Gov Games, Diplocon Abu Dhabi, Sharjah Entrepreneurship Festival.</p>
          <p><strong>Private and weddings.</strong> Wedding video mapping, gala dinners, private parties, residential projection across the UAE.</p>
        </div>
      </section>

      {/* Filter chips — only categories that have items render
          (plus ALL). VISIBLE_CATEGORIES is derived from
          SHOWCASE_ITEMS so it reflects the current CSV sync. */}
      <section className="container-wide" style={{ paddingBottom: 24 }}>
        <div className="showcase-filters">
          {VISIBLE_CATEGORIES.map((cat) =>
          <button key={cat}
          className={"chip-filter" + (filter === cat ? " is-active" : "")}
          onClick={() => setFilter(cat)}>
            {cat}</button>
          )}
        </div>
        <div className="showcase-meta">
          <span>Showing {items.length} of {filtered.length}</span>
          <span>·</span>
          <span>{filter === 'ALL' ? 'All categories' : filter}</span>
        </div>
      </section>

      {/* Masonry — renders <img> when item.img is set, falls back to
          the legacy ShowcaseSVG placeholder otherwise. object-fit: cover
          fills the tile without distorting; lazy-loading + async decode
          keeps the rest of the page snappy while photos stream in.
          Paginated via load-more so visitors aren't hit with all 95
          images upfront. */}
      <section className="container-wide" style={{ paddingBottom: 64 }}>
        <div className="masonry">
          {items.map((item, i) =>
          <button key={item.id}
          className={"masonry-card" + (item.span ? " span-" + item.span : "")}
          onClick={() => setLightbox(i)}>
              {item.img
              ? <img className="masonry-card__img" src={item.img} alt={item.title} loading="lazy" decoding="async" />
              : <ShowcaseSVG kind={item.svg} />}
              <div className="masonry-card__overlay">
                <div className="masonry-card__cat">{item.category}</div>
                <div className="masonry-card__title">{item.title}</div>
                <div className="masonry-card__meta">{item.venue} · {item.year}</div>
              </div>
            </button>
          )}
        </div>
        {hasMore && (
          <div className="showcase-loadmore">
            <button
              type="button"
              className="btn"
              onClick={() => setShown((s) => s + PAGE_SIZE)}>
              Load more ({remaining} remaining)
              <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" aria-hidden="true">
                <path d="M 8 3 L 8 13 M 4 9 L 8 13 L 12 9" strokeLinecap="square" strokeLinejoin="miter" />
              </svg>
            </button>
          </div>
        )}
      </section>

      {/* Instagram strip — same six teaser tiles used on the home beat
          arc, here as a "From the feed" sidebar to the masonry. Linking
          out to the public profile until Phase C wires the real
          Instagram Graph API feed. */}
      <section className="section" style={{ paddingTop: 32, paddingBottom: 96 }}>
        <div className="container-wide">
          <h2 className="divider-row" style={{ marginBottom: 24 }}>FROM THE FEED · @AV_T_AV</h2>
          <div className="beat__inner" style={{ display: 'grid', gridTemplateColumns: '1fr', gap: 24 }}>
            <div className="beat__text" style={{ maxWidth: 760 }}>
              <h3 className="beat__title">Off-the-record frames, <span className="accent">straight from the floor.</span></h3>
            </div>
            <ShowcaseIgStrip />
            <div style={{ marginTop: 12 }}>
              <a className="btn-link" href="https://www.instagram.com/av_t_av/" target="_blank" rel="noopener noreferrer">
                Open Instagram <ArrowRight />
              </a>
            </div>
          </div>
        </div>
      </section>

      {/* Lightbox — portaled to document.body so it escapes
          .page (which has a permanent transform: translateY(0)
          for the page-transition animation). Without the portal,
          .page becomes the containing block for the lightbox's
          position: fixed, so the modal sits at the top of the
          document instead of the viewport, forcing the visitor
          to scroll back up to see it. */}
      {lightbox !== null && ReactDOM.createPortal(
      <div className={"lightbox" + (lightboxClosing ? " is-closing" : "")} onClick={closeLightbox}>
          <button className="lightbox__close" onClick={(e) => {e.stopPropagation();closeLightbox();}} aria-label="Close">×</button>
          <button className="lightbox__nav lightbox__prev"
        onClick={(e) => {e.stopPropagation();setLightbox((l) => Math.max(0, l - 1));}}
        disabled={lightbox === 0}><ArrowLeft size={20} /></button>
          <div className="lightbox__inner" onClick={(e) => e.stopPropagation()}>
            <div className="lightbox__media">
              {items[lightbox].img
              ? <img className="lightbox__img" src={items[lightbox].img} alt={items[lightbox].title} />
              : <ShowcaseSVG kind={items[lightbox].svg} />}
            </div>
            <div className="lightbox__info">
              <div className="overline-mono">{items[lightbox].category}</div>
              <h3 className="lightbox__title">{items[lightbox].title}</h3>
              <div className="lightbox__caption">{items[lightbox].venue} · {items[lightbox].year}</div>
              <div className="lightbox__count">{lightbox + 1} / {items.length}</div>
            </div>
          </div>
          <button className="lightbox__nav lightbox__next"
        onClick={(e) => {e.stopPropagation();setLightbox((l) => Math.min(items.length - 1, l + 1));}}
        disabled={lightbox === items.length - 1}><ArrowRight size={20} /></button>
        </div>,
      document.body
      )}
    </div>);

}

/* ===== WHAT WE DO HUB ===== */
const SERVICES = [
{ slug: 'creative-design', name: 'Creative Design', tagline: 'CONCEPT · LAYOUT · DIRECTION', desc: 'Show concepts, stage layouts and visual direction that read from every seat and every camera.' },
{ slug: 'rigging', name: 'Rigging & Structures', tagline: 'TRUSSES · GROUND SUPPORT · LOAD PLANS', desc: 'Trusses, ground support and load plans engineered for the venue, the kit, and the safety officer.' },
{ slug: 'audio', name: 'Audio', tagline: 'FOH · MONITORS · COMMS', desc: 'Front-of-house, monitor world, IEMs and comms, tuned for the room before doors open.' },
{ slug: 'lighting', name: 'Lighting', tagline: 'MOVING HEADS · WASH · PROGRAMMING', desc: 'Moving heads, wash, beam, and pixel; programmed to the show, not improvised on the night.' },
{ slug: 'video', name: 'Video', tagline: 'LED · IMAG · PROJECTION', desc: 'LED walls, IMAG, projection and switching; pixel-mapped, color-graded.' }];


function WhatWeDoPage({ navigate }) {
  const B = window.BeatVisuals;
  const visualMap = {
    'creative-design': B.BeatFloorPlan,
    'rigging': B.BeatTrusses,
    'audio': B.BeatAudio,
    'lighting': B.BeatLights,
    'video': B.BeatVideo
  };
  return (
    <div className="page">
      <section className="container-wide" style={{ padding: '64px 40px 48px' }}>
        <div className="overline">CAPABILITIES</div>
        <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
          Five disciplines, <span className="accent">one production.</span>
        </h1>
        <p className="section__sub">
          We design, build and run live production from the floor plan up. Every layer talks to the next, programmed to the same clock, signed off before doors open.
        </p>
      </section>

      {/* Stacked alternating rows. Each service is a full-width row with
          the discipline blueprint (existing BeatVisuals) on one side and
          the heading + description on the other. Image alternates left
          and right down the page, echoing the home story-arc rhythm.
          BeatVisuals SVGs are honest stand-ins; real photography lands
          in Phase C. */}
      <section className="container-wide" style={{ paddingBottom: 64 }}>
        <div className="service-stack">
          {SERVICES.map((s, i) => {
            const Visual = visualMap[s.slug];
            const reverse = i % 2 === 1;
            return (
              <a key={s.slug}
                className={"service-row" + (reverse ? " reverse" : "")}
                href={"#/what-we-do/" + s.slug}
                onClick={(e) => {e.preventDefault();navigate('what-we-do/' + s.slug);}}>
                <div className="service-row__visual">
                  {Visual && <Visual progress={0.7} />}
                </div>
                <div className="service-row__body">
                  <div className="service-row__head">
                    <span className="service-row__num">{String(i + 1).padStart(2, '0')}</span>
                    <span className="service-row__tagline">{s.tagline}</span>
                  </div>
                  <h2 className="service-row__name">{s.name}</h2>
                  <p className="service-row__desc">{s.desc}</p>
                  <div className="service-row__more">Open the spec sheet <ArrowRight /></div>
                </div>
              </a>
            );
          })}
        </div>
      </section>

      {/* Process strip — `.section` for vertical rhythm, `.container-wide`
          nested inside (NOT co-classed — co-classing lets `.section`'s
          `padding: … 0` zero the side gutters). */}
      <section className="section" style={{ paddingTop: 32 }}>
        <div className="container-wide">
          <h2 className="divider-row" style={{ marginBottom: 32 }}>HOW WE WORK · 04 STAGES</h2>
          <div className="process-grid">
            {[
            { n: '01', t: 'Brief', d: 'Venue, scope, dates, budget. Tight discovery, fast back.' },
            { n: '02', t: 'Design', d: 'Floor plan, rig plot, signal paths. Vectorworks + Depence.' },
            { n: '03', t: 'Build', d: 'Pre-rigged in the warehouse. Verified before it leaves.' },
            { n: '04', t: 'Show', d: 'On-site crew. Cues fire on the same clock. Strike on schedule.' }].
            map((p) =>
            <div key={p.n} className="process-cell">
                <div className="process-cell__num">{p.n}</div>
                <div className="process-cell__title">{p.t}</div>
                <p className="process-cell__desc">{p.d}</p>
              </div>
            )}
          </div>
        </div>
      </section>

      <ContactStrip navigate={navigate} />
    </div>);

}

/* ===== SERVICE DETAIL PAGE ===== */
function ServicePage({ slug, navigate }) {
  /* Inject FAQPage JSON-LD for this service; remove on unmount / slug change.
     Seen by crawlers that execute JS. The plain-text llms-full.txt copy covers
     crawlers that don't. */
  React.useEffect(() => {
    const faq = SERVICE_FAQ[slug];
    if (!faq || !faq.length) return;
    const ld = {
      "@context": "https://schema.org",
      "@type": "FAQPage",
      "mainEntity": faq.map((item) => ({
        "@type": "Question",
        "name": item.q,
        "acceptedAnswer": { "@type": "Answer", "text": item.a }
      }))
    };
    const el = document.createElement('script');
    el.type = 'application/ld+json';
    el.id = 'faq-schema';
    el.textContent = JSON.stringify(ld);
    document.head.appendChild(el);
    return () => { const old = document.getElementById('faq-schema'); if (old) old.remove(); };
  }, [slug]);

  const idx = SERVICES.findIndex((s) => s.slug === slug);
  const service = SERVICES[idx];
  if (!service) return <NotFoundPage navigate={navigate} />;
  const next = SERVICES[(idx + 1) % SERVICES.length];

  const detail = SERVICE_DETAIL[slug];

  const approachHeading = {
    'audio': 'How we approach event sound system hire in Dubai',
    'lighting': 'How we approach stage lighting design and hire across the UAE',
    'video': 'How we approach LED screen and video hire for live events',
    'rigging': 'How we approach stage rigging and structures',
    'creative-design': 'How we approach show design and creative direction',
  }[slug] || 'HOW WE APPROACH IT';

  const gearHeading = {
    'audio': 'Audio gear from our Dubai warehouse',
    'lighting': 'Lighting gear from our Dubai warehouse',
    'video': 'Video gear from our Dubai warehouse',
    'rigging': 'Rigging and structures from our Dubai warehouse',
    'creative-design': 'Creative design tools and resources',
  }[slug] || 'SELECTED GEAR · ON THE TRUCK';

  const faqHeading = {
    'audio': 'Common questions · event sound in Dubai',
    'lighting': 'Common questions · stage lighting in the UAE',
    'video': 'Common questions · LED & video in Dubai',
    'rigging': 'Common questions · rigging & structures',
    'creative-design': 'Common questions · show design',
  }[slug] || 'COMMON QUESTIONS';

  const B = window.BeatVisuals;
  const visualMap = {
    'creative-design': B.BeatFloorPlan,
    'rigging': B.BeatTrusses,
    'audio': B.BeatAudio,
    'lighting': B.BeatLights,
    'video': B.BeatVideo
  };
  const Visual = visualMap[slug];

  return (
    <div className="page">
      <section className="container-wide" style={{ padding: '48px 40px 32px' }}>
        <a className="btn-link" href="#/what-we-do" onClick={(e) => {e.preventDefault();navigate('what-we-do');}}>
          <ArrowLeft /> All capabilities
        </a>
      </section>

      <section className="container-wide" style={{ paddingBottom: 32 }}>
        <div className="overline">{service.tagline}</div>
        <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
          {detail.title} <span className="accent">{detail.titleAccent}</span>
        </h1>
        <p className="section__sub" style={{ maxWidth: 640 }}>{detail.intro}</p>
        {detail.hire && <p className="section__sub" style={{ maxWidth: 640 }}>{detail.hire}</p>}
      </section>

      {/* Big visual */}
      <section className="container-wide" style={{ paddingBottom: 64 }}>
        <div className="service-visual">
          {Visual && <Visual progress={0.7} />}
        </div>
      </section>

      {/* Two column body — `.section` rhythm, `.container-wide` nested
          (NOT co-classed, which would zero the side gutters). */}
      <section className="section" style={{ paddingTop: 0 }}>
        <div className="container-wide">
          <div className="service-body">
            <div className="service-body__copy">
              <h2 className="divider-row" style={{ marginBottom: 24 }}>{approachHeading}</h2>
              {detail.body.map((p, i) => <p key={i} className="service-body__p">{p}</p>)}
            </div>
            <div className="service-body__spec">
              <div className="term">
                <div className="term__head">
                  <span>SPEC SHEET · {service.name.toUpperCase()}</span>
                  <span className="right">v.2026</span>
                </div>
                {detail.spec.map((row, i) =>
                <div key={i} className="spec-row">
                    <span className="spec-row__k">{row[0]}</span>
                    <span className="spec-row__v">{row[1]}</span>
                  </div>
                )}
              </div>
            </div>
          </div>
        </div>
      </section>

      {/* Gear list */}
      <section className="section" style={{ paddingTop: 0 }}>
        <div className="container-wide">
          <h3 className="divider-row" style={{ marginBottom: 24 }}>{gearHeading}</h3>
          <div className="gear-list">
            {detail.gear.map((g, i) =>
            <div key={i} className="gear-cell">
                <div className="gear-cell__cat">{g.cat}</div>
                <div className="gear-cell__name">{g.name}</div>
                <div className="gear-cell__meta">{g.meta}</div>
              </div>
            )}
          </div>
        </div>
      </section>

      {/* FAQ — real-question Q&A; also emitted as FAQPage JSON-LD (see effect) */}
      {SERVICE_FAQ[slug] &&
      <section className="section" style={{ paddingTop: 0 }}>
        <div className="container-wide">
          <h3 className="divider-row" style={{ marginBottom: 24 }}>{faqHeading}</h3>
          <div className="faq-list">
            {SERVICE_FAQ[slug].map((item, i) =>
            <div key={i} className="faq-item">
                <h4 className="faq-item__q">{item.q}</h4>
                <p className="faq-item__a">{item.a}</p>
              </div>
            )}
          </div>
        </div>
      </section>
      }

      {/* Next service */}
      <section className="section" style={{ paddingTop: 32 }}>
        <div className="container-wide">
          <a className="next-service" href={"#/what-we-do/" + next.slug}
          onClick={(e) => {e.preventDefault();navigate('what-we-do/' + next.slug);}}>
            <div>
              <div className="overline-mono" style={{ marginBottom: 8 }}>NEXT · {next.tagline}</div>
              <div className="next-service__name">{next.name}</div>
            </div>
            <ArrowRight size={20} />
          </a>
        </div>
      </section>

      <ContactStrip navigate={navigate} />
    </div>);

}

const SERVICE_DETAIL = {
  'creative-design': {
    title: 'Show concepts that read', titleAccent: 'from every seat.',
    intro: "T-AV's show design and creative direction service in Dubai turns a brief into a buildable production. Stage layouts, sight lines, FOH position — argued, drafted, scaled, signed off before any flight case leaves the warehouse.",
    hire: "T-AV's creative design and show design service in Dubai turns a brief into a buildable production. Stage layouts, sight-line studies, FOH and monitor world position, lighting and video pre-vis — everything in Vectorworks before a flight case leaves the warehouse. We design for clients across the UAE: corporate conferences, festivals, product launches, government and cultural events, private weddings.",
    body: [
    'Creative Design at T-AV is engineering, not mood-boarding. We start with the client\'s brief, venue\u2019s as-built drawings and build up: stage geometry, sight lines, audience density, projection throw, FOH and monitor world, load-in path.',
    'You see one PDF. Behind it sits Vectorworks, Depence, a load plan signed by a structural engineer, and a signal-flow diagram that survives a hand-off to anyone on the crew.',
    'When the show changes (and it will), we update the drawings on the laptop, not at the venue at 02:00.'],

    spec: [
    ['Software', 'Vectorworks 2026 · Production Assist'],
    ['Deliverables', 'Stage plot · rig plot · floor plan · sight-line study'],
    ['Sign-off', 'Structural engineer · venue ops · creative'],
    ['Iteration', 'Many rounds before lock'],
    ['Lead time', '2-7 days from brief']],

    gear: [
    { cat: 'CAD', name: 'Vectorworks 2026', meta: 'Spotlight licence' },
    { cat: 'PRE-VIS', name: 'Depence, L8', meta: 'Latest edition' },
    { cat: 'SURVEY', name: 'Share Slam 20S', meta: 'LIDAR 3d Venue Scanner' },
    { cat: 'SAFETY', name: 'Production Assist', meta: 'Load Calculations' }]

  },
  'rigging': {
    title: 'Trusses dialed in', titleAccent: 'on the load plan.',
    intro: 'T-AV is a stage rigging and structures hire company in Dubai. Goalpost, ground support, mother grid — engineered for the venue, the kit, and the safety officer. Load plans on file, stamped, before the first motor moves.',
    hire: 'T-AV is a stage rigging and trussing rental company in Dubai. We engineer and supply Eurotruss TT roof systems, ground support, mother grids, goalposts and ladder truss for live events across the UAE. Motors (Movecat D8+, ChainMaster) from 250kg to 1000kg SWL. Every rig built to a stamped load plan, signed off by a structural engineer before the first motor moves. IRATA and IOSH-credentialed crew on site.',
    body: [
    'Rigging is the part of the show that doesn\u2019t care about adjectives. Either the load plan stamps clean or it doesn\u2019t. Either the SWL holds or you don\u2019t fly.',
    'We work with structural engineers on every plot. Motor specs, bridle calcs, point loads: documented, signed, on file before load-in. Our riggers carry IRATA and IOSH credentials.',
    'The chain hoist either lifts or it doesn\u2019t. Same answer at 04:00 as at 14:00.'],

    spec: [
    ['Capacity', 'SWL 250–1000 kg per motor'],
    ['Motors', 'Movecat · ChainMaster'],
    ['Truss', 'Eurotruss TT, ST, HD44, HD34'],
    ['Sign-off', 'Structural eng. · venue safety'],
    ['Crew', 'IRATA · IOSH rated']],

    gear: [
    { cat: 'TRUSS', name: 'Eurotruss TT Roof', meta: '20x16m · with IMAG wings' },
    { cat: 'MOTOR', name: 'Movecat D8 plus Hoists', meta: '1000kg · 4m/min' },
    { cat: 'CONTROL', name: 'xScene Motion', meta: '32 channel' },
    { cat: 'SAFETY', name: 'Load cells', meta: '1 ton' }]

  },
  'audio': {
    title: 'Line-array sound,', titleAccent: 'dialed in on-site.',
    intro: 'T-AV deploys event sound system hire across the UAE with line-array systems matched to the venue, time-aligned with measurement, mixed by engineers who know how to make a Dubai ballroom or an outdoor festival sound like a record.',
    hire: 'T-AV is a sound system rental and hire company in Dubai. Our warehouse holds L-Acoustics K2, Kara and Siva line arrays, d&b audiotechnik Y-Series and KSL boxes, DiGiCo Quantum and Q338 consoles, Yamaha CL5, Shure PSM1000 IEM packs, Hollyland H1 comms and the wireless microphone, lapel, headset and IEM inventory to cover a 50-input festival or a four-mic corporate keynote. Engineers come with the gear. We deploy across the UAE — Dubai, Abu Dhabi, Sharjah, Ras Al Khaimah, Al Ain — and into the GCC on request.',
    body: [
    'Audio is the only system the audience grades in real time. If the kick drum hits late, they don\u2019t care that the design was beautiful.',
    'We run prediction in Soundvision before the K2 leaves the warehouse, then verify with Smaart on-site. Time alignment is measured, not eyeballed. Monitor world and intercom are treated with the same care as FOH.',
    'Engineers ride the desk because they want to, not because they have to.'],

    spec: [
    ['Mains', 'L-Acoustics K2 / Kara / Siva'],
    ['Console FOH', 'DiGiCo Q338 · Yamaha CL5'],
    ['Monitors', 'Shure PSM1000 · L-Acoustics Hiq X15'],
    ['Measurement', 'Smaart v9 · Shure workbench 7'],
    ['Comms', 'Hollyland H1 Systems']],

    gear: [
    { cat: 'MAINS', name: 'd&b Audiotechnik Y8', meta: 'Hangs of 16 a-side' },
    { cat: 'SUBS', name: 'd&b Audiotechnik Y Sub', meta: 'Cardioid array' },
    { cat: 'FOH', name: 'DiGiCo Quantum 338', meta: '128-input · 64-bus' },
    { cat: 'IEM', name: 'Shure PSM1000', meta: 'Up to 24 channels' }]

  },
  'lighting': {
    title: 'Light that follows', titleAccent: 'the cue.',
    intro: 'T-AV provides stage lighting hire and design for live events across Dubai and the UAE: moving heads, wash, beam, and pixel, programmed to the show, not improvised on the night. Every cue timecoded against the show flow.',
    hire: 'T-AV is an event and stage lighting rental company in Dubai. Our hire inventory covers moving heads (Ayrton Khamsin, ClayPaky Scenius Unico, Ayrton Diablo), wash and beam fixtures, LED battens, hazers, atmospherics, conventional fixtures, grandMA3 consoles and Depence pre-vis. Designed, programmed and operated by lighting designers and MA3 programmers, not just truck drivers. Stage lighting for concerts and festivals, conference and corporate lighting, wedding and private-event lighting, architectural and cultural-event lighting across the UAE.',
    body: [
    'Lighting at T-AV is programmed in Vectorworks and visualised in Depence before the rig touches the venue. By the time the moving heads land, the cue stack is 80% of the way there.',
    'We pre-vis to a level where the on-site plot run is a refinement, not a rehearsal. Timecode locked to the show, MA3 console synced to the audio playback, redundant data paths to every fixture.',
    'When the artist changes the running order at line check, the show file already accommodates it.'],

    spec: [
    ['Console', 'grandMA3 · Avolites Arena'],
    ['Movers', 'ClayPaky Scenius Unico · Ayrton Diablo'],
    ['Wash', 'Acme Neozone - ClayPaky 300 wash'],
    ['Pre-vis', 'l8 - Depence · MA3D'],
    ['Sync', 'LTC 30fps · MIDI']],

    gear: [
    { cat: 'CONSOLE', name: 'grandMA3 Full Size', meta: '20480 parameters' },
    { cat: 'WASH', name: 'Acme Neozone', meta: '19 × 40W' },
    { cat: 'PROFILE', name: 'Ayrton Khamsin', meta: '750W LED' },
    { cat: 'EFFECT LIGHT', name: 'Colorimagination Crazy Light ', meta: '6 heads' }]

  },
  'video': {
    title: 'Pixels that hit', titleAccent: 'on the frame.',
    intro: "T-AV's LED video wall and event video hire in Dubai covers LED walls, IMAG, projection mapping and switching — pixel-mapped, color-graded, redundant where it counts. Every signal path documented before doors open.",
    hire: 'T-AV is a video and LED screen rental company in Dubai. We hire indoor and outdoor LED walls (Absen PL2 Pro, P2.6 to P3.9), projection up to 30,000 lumens, NovaStar H15 processing, disguise media servers, Resolume and Notch playback and switching on Analog Way Aquilon C4 or Lightware MX8x8HDMI-Pro. Video mapping on buildings up to 30m wide, hologram rigs as deployed for Umm Kalthoum at Dubai Opera. IMAG, broadcast, content playback and live switching from one operator desk.',
    body: [
    'Video is the part of the show that gets recorded. Every editorial decision in front of camera lives forever. Color and exposure are non-negotiable.',
    'NovaStar processing on every wall, redundant feeds on broadcast, color-graded against a calibrated reference monitor. We run pixel maps in Resolume / Notch, switch on the Aquilon C4, and treat IMAG with the same care as the broadcast feed.',
    'If it\u2019s on the screen, it\u2019s where it should be.'],

    spec: [
    ['LED', 'P2.6 / P3.9 · indoor / outdoor'],
    ['Processor', 'NovaStar H15'],
    ['Switcher', 'Analog Way Aquilon C4 · Lightware MX8x8HDMI-Pro'],
    ['Playback', 'Resolume Arena · disguise gx 2c'],
    ['Projection', 'Panasonic PT-RZ31K']],

    gear: [
    { cat: 'LED', name: 'Absen PL2 Pro', meta: '2.9mm · indoor' },
    { cat: 'PROCESSOR', name: 'NovaStar H15', meta: 'Multiple 4K @ 60Hz' },
    { cat: 'PLAYBACK', name: 'disguise gx 2c', meta: '4 SDI outputs' },
    { cat: 'PROJECTION', name: 'Panasonic PT-RQ50K', meta: 'Native 4K' }]

  }
};

/* ===== SERVICE FAQ =====
   Real-question Q&A per discipline, grounded in Dubai/UAE search queries.
   Rendered at the foot of each ServicePage AND emitted as FAQPage JSON-LD.
   Mirror any edits here into llms-full.txt's "Frequently Asked Questions". */
const SERVICE_FAQ = {
  'audio': [
    { q: "How much does it cost to hire a sound system for an event in Dubai?",
      a: "It depends on the room, the headcount and the show — a 200-seat conference and a 5,000-capacity outdoor stage are different jobs. We quote per event once we know the venue, audience size and what's playing, rather than pricing boxes off a shelf. Send the brief and a costed spec comes back, usually inside a day or two." },
    { q: "What size system do I need, and how many speakers?",
      a: "That's ours to spec, not yours to guess. We predict coverage in Soundvision from the room — capacity, indoor or outdoor, ceiling height, speech vs band vs DJ — so it's even from the front row to the back wall. You describe the event; we design the rig." },
    { q: "Do you provide a sound engineer and on-site crew?",
      a: "Yes — engineers come with the gear. We tune the room with Smaart measurement before doors and ride the desk live. We don't drop a stack of speakers and leave you to it." },
    { q: "Can you cover large outdoor events and festivals?",
      a: "Yes. L-Acoustics K2/Kara and d&b line arrays sized for festival stages and big outdoor crowds, with the power and rigging to match — the same engineers, a bigger rig. We deploy across the UAE and into the GCC." },
    { q: "How far ahead should we book?",
      a: "Lock it in as soon as the venue and date are set. The earlier we have the brief, the more we design and the less we improvise on the night." }
  ],
  'lighting': [
    { q: "How much does stage lighting hire cost in Dubai?",
      a: "It scales with the rig and the show — a wedding wash is a different build to a festival main stage. We quote per event from the design, not off a per-fixture price list. Tell us the venue and the look you're after and a costed plot comes back." },
    { q: "Do you design and program the lighting, or just supply fixtures?",
      a: "Both — and the design is the point. Our lighting designers and grandMA3 programmers pre-visualise the whole show in L8 and Depence before the rig lands, so on-site is refinement, not a first rehearsal. You can dry-hire fixtures elsewhere; here the cues come programmed to the show." },
    { q: "What kind of events do you light?",
      a: "Concerts and festivals, conferences and corporate, weddings and private events, architectural and cultural shows across the UAE. Moving heads, wash, beam and pixel — matched to the room, not a one-size rig." },
    { q: "Can the lighting be timed to music or video?",
      a: "Yes. Cues are timecoded against the show and the MA3 console syncs to audio playback, with redundant data paths to every fixture — so when the running order changes at line check, the show file already handles it." },
    { q: "How far in advance do you need the brief?",
      a: "The sooner the better for big builds. Once the venue, date and creative direction are set, we can pre-vis properly instead of plotting live." }
  ],
  'video': [
    { q: "How much does it cost to hire an LED video wall in Dubai?",
      a: "LED is usually priced by the size of the wall, the pixel pitch, and whether it's indoor or outdoor — finer pitch and weatherproof outdoor panels cost more. Rather than quote a rate per square metre blind, we size the wall to your content and viewing distance and quote the whole package — processing, rigging and an operator included." },
    { q: "What size LED wall and which pixel pitch do I need?",
      a: "That depends on how close the audience sits and what's on screen. We spec pitch and size from the venue — a P2.6 indoor wall for a close conference reads differently to a P3.9 outdoor stage screen. We recommend the right one rather than oversell pixels." },
    { q: "Indoor or outdoor — can you do both?",
      a: "Yes. Indoor and outdoor LED, projection up to 30,000 lumens, and projection mapping on structures up to 30 metres wide. We've also run hologram rigs — including for Umm Kalthoum at Dubai Opera." },
    { q: "Do you handle content playback, IMAG and broadcast too?",
      a: "Yes — LED walls, IMAG, content playback, broadcast and live switching from one operator desk, colour-graded against a calibrated reference with redundant feeds where it counts." },
    { q: "How far ahead should we book video?",
      a: "As early as you can — content and screen sizing drive a lot of the design. Once the venue and content are known, we lock the signal paths well before doors." }
  ],
  'rigging': [
    { q: "How much does truss and rigging hire cost in Dubai?",
      a: "It depends on the structure, the load and the venue — a goalpost for a backdrop is a different job to a mother grid over a stage. We quote per event from the load plan, not off a price list, because the engineering decides the kit." },
    { q: "Is your rigging certified and safe?",
      a: "Yes — that's non-negotiable. Every rig is built to a stamped load plan, signed off by a structural engineer before the first motor moves, and our riggers carry IRATA and IOSH credentials. Motor specs, bridle calcs and point loads are documented and on file before load-in." },
    { q: "What weight can your truss and motors handle?",
      a: "Motors run from 250 kg to 1000 kg SWL (Movecat, ChainMaster), on Eurotruss TT, ST, HD44 and HD34 systems. The right combination is engineered to the venue and the kit it's flying — we don't fly anything the plan doesn't stamp clean." },
    { q: "What kind of structures do you build?",
      a: "Ground support, mother grids, goalposts, ladder truss, roof systems, and a large stock of circular truss in a range of sizes up to 30 m, for live events across the UAE — indoor and outdoor, sized to the venue and the rig hanging from it." },
    { q: "How early do you need the plot?",
      a: "The rigging plan sits under everything else, so the sooner we have the design and venue drawings, the sooner we can engineer and certify it ahead of load-in." }
  ],
  'creative-design': [
    { q: "What does show design / creative direction actually include?",
      a: "It turns a brief into a buildable production: stage geometry, sight-line studies, FOH and monitor positions, plus lighting and video pre-vis. You get one clear plan; behind it sits a load plan, a signal-flow diagram and drawings the whole crew can work from." },
    { q: "How much does it cost?",
      a: "It depends on the scale and how many design rounds the show needs. We quote per project from the brief — most clients fold it into the wider production, and it pays for itself by catching problems on the laptop instead of at the venue at 2am." },
    { q: "What do you deliver, and in what software?",
      a: "Stage plot, rig plot, floor plan and sight-line study, drawn in Vectorworks 2026 with Depence pre-vis and LIDAR venue scans where it helps. Everything is signed off by a structural engineer, venue ops and the creative lead before kit leaves the warehouse." },
    { q: "What types of events do you design for?",
      a: "Corporate conferences, festivals, product launches, government and cultural events, and private weddings across the UAE — anywhere the show has to read from every seat and every camera." },
    { q: "How long does it take?",
      a: "Anywhere from 2 days to several months, depending on the scope and the level of detail the show needs. From a clear brief we turn around a first set of drawings, then iterate until it locks — and when the show changes, we update the drawings on the laptop, not at the venue." }
  ]
};

/* ===== ABOUT ===== */
function AboutPage({ navigate }) {
  return (
    <div className="page">
      <section className="container-wide" style={{ padding: '64px 40px 48px' }}>
        <div className="overline">ABOUT</div>
        <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
          We're an engineering team <span className="accent">that ships shows.</span>
        </h1>
        <p className="section__sub">T-AV is the trade name of TriTec AV Events Organizing &amp; Managing LLC, founded in Dubai in 2011 and based at 407C DBC, Arjaan.</p>
        <p className="section__sub">T-AV designs, builds and runs live production for clients who don't get a second take. Dubai-based, on-site across the GCC.</p>
      </section>

      <section className="container-wide" style={{ paddingBottom: 32 }}>
        <div className="about-grid">
          <div>
            <h2 className="about-grid__h">Built for live production.</h2>
            <p className="about-grid__p">We started with a simple rule: design the show on paper first, on-site never. Every plot, every cue, every signal chain is built and verified before the kit leaves the warehouse. By the time our crew is on the venue floor, the show is already running in the room. They're just dialing in the room.</p>
          </div>
          <div>
            <h2 className="about-grid__h">Where you'll find us.</h2>
            <p className="about-grid__p">Dubai-based. On-site across the GCC. We bring our own crew, our own kit, and a bench depth that means a sub-snake going down at 30 minutes to doors is a 30-second problem.</p>
            <p className="about-grid__p">We deploy crew, kit and engineers from our Dubai warehouse to events across the UAE — Abu Dhabi, Sharjah, Ras Al Khaimah, Al Ain, Fujairah, Ajman — and into the wider GCC on request.</p>
          </div>
        </div>
      </section>

      {/* Numbers — `.section` rhythm, `.container-wide` nested (NOT
          co-classed, which would zero the side gutters). */}
      <section className="section">
        <div className="container-wide">
          <h2 className="divider-row" style={{ marginBottom: 32 }}>BY THE NUMBERS</h2>
          <div className="about-stats">
            <div className="about-stat"><div className="about-stat__n">500<span>+</span></div><div className="about-stat__l">Events delivered</div></div>
            <div className="about-stat"><div className="about-stat__n">15<span>+ yrs</span></div><div className="about-stat__l">On-site in the UAE</div></div>
            <div className="about-stat"><div className="about-stat__n">40<span>+</span></div><div className="about-stat__l">Brands trusted</div></div>
            <div className="about-stat"><div className="about-stat__n">100<span> max</span></div><div className="about-stat__l">CREW ON THE SITE</div></div>
            <div className="about-stat"><div className="about-stat__n">12 thousand<span>+</span></div><div className="about-stat__l">Inventory items</div></div>
            <div className="about-stat"><div className="about-stat__n">0</div><div className="about-stat__l">Missed cues</div></div>
          </div>
        </div>
      </section>

      {/* Team / Values */}
      <section className="section">
        <div className="container-wide">
          <h2 className="divider-row" style={{ marginBottom: 32 }}>OPERATING PRINCIPLES</h2>
          <div className="principles">
            {[
            { n: '01', t: 'Design before drive.', d: 'No flight case leaves the warehouse without a sign-off on its purpose.' },
            { n: '02', t: 'Numbers beat adjectives.', d: 'SPL, lumens, pixel pitch, kg per point. Adjectives are for marketing.' },
            { n: '03', t: 'Redundancy where it counts.', d: 'Mains, monitors, comms, broadcast. The audience never sees the backup.' },
            { n: '04', t: 'Strike on schedule.', d: 'The show ends. The cases load. The warehouse opens at 06:00 either way.' }].
            map((p) =>
            <div key={p.n} className="principle">
                <div className="principle__n">{p.n}</div>
                <h3 className="principle__t">{p.t}</h3>
                <p className="principle__d">{p.d}</p>
              </div>
            )}
          </div>
        </div>
      </section>

      {/* Clients — same continuous monochrome marquee used on home.
          PartnerMarquee renders the 12-brand list as white silhouettes
          scrolling horizontally, with hover-pause and a reduced-motion
          fallback to a wrapped static grid. */}
      <section className="section">
        <div className="container-wide">
          <h2 className="divider-row" style={{ marginBottom: 32 }}>HAPPY PARTNERS · 40+ TRUSTED</h2>
          <p className="section__sub">In over 15 years of operation T-AV has delivered shows for Red Bull, F1, FIFA (Club World Cup, Lusail), AFC Asian Cup, Louvre Abu Dhabi, Dubai Expo 2020, Memac Ogilvy, NYU Abu Dhabi, BMW, Lamborghini, Toyota, Mercedes-Benz, Pfizer, Nestle, H&amp;M, Tory Burch, Ethara, Electra, Blink, Stamina, Projex — alongside hundreds of corporate, government, cultural and private clients across the UAE and the GCC.</p>
          <PartnerMarquee />
        </div>
      </section>

      <ContactStrip navigate={navigate} />
    </div>);

}

/* =========================================================
   PartnerLogo — shared monochrome client-logo treatment.
   Used on the home Happy Partners section and on the About
   Happy Partners section so both pages read consistently.

   Until real client logos arrive (Phase C), we render a
   placeholder: a small geometric mark + the brand name in
   the display font as a wordmark. Everything paints in
   currentColor; the parent sets that color (defaults to
   parchment) so the row reads as a single-color logo wall.

   When real assets arrive, pass `src="..."` and the
   placeholder is replaced by an <img> with monochrome
   filter treatment via .partner-logo__img in CSS.
   ========================================================= */
function PartnerLogo({ name, src, variant }) {
  if (src) {
    return (
      <div className="partner-logo" title={name}>
        <img className="partner-logo__img" src={src} alt={name} />
      </div>);

  }
  // Mark glyph variants — cycled by `variant` index so a row
  // of logos has shape variety without breaking the monochrome
  // uniformity. All paint in currentColor.
  const marks = [
  <rect x="0" y="0" width="14" height="14" fill="currentColor" />,
  <circle cx="7" cy="7" r="6" fill="none" stroke="currentColor" strokeWidth="1.6" />,
  <polygon points="7,0 14,14 0,14" fill="currentColor" />,
  <g><line x1="0" y1="4" x2="14" y2="4" stroke="currentColor" strokeWidth="1.6" /><line x1="0" y1="10" x2="14" y2="10" stroke="currentColor" strokeWidth="1.6" /></g>,
  <polygon points="7,0 14,3.5 14,10.5 7,14 0,10.5 0,3.5" fill="none" stroke="currentColor" strokeWidth="1.6" />,
  <g><circle cx="7" cy="7" r="6" fill="none" stroke="currentColor" strokeWidth="1.4" /><circle cx="7" cy="7" r="2" fill="currentColor" /></g>];

  const idx = (typeof variant === 'number' ? variant : 0) % marks.length;
  return (
    <div className="partner-logo" aria-label={name}>
      <svg className="partner-logo__mark" viewBox="0 0 14 14" width="14" height="14" aria-hidden="true">
        {marks[idx]}
      </svg>
      <span className="partner-logo__name">{name}</span>
    </div>);

}
window.PartnerLogo = PartnerLogo;

/* Canonical partner list. Real client brands trusted by T-AV with
   their logo files dropped in /assets/logos/. Both home and about
   render this list via <PartnerMarquee>. Filenames are kept verbatim
   (incl. the trailing space in "Ethara .png"); the browser URL-encodes
   automatically when the path is set via JSX.
   ========================================================= */
const PARTNERS = [
{ name: 'BLINK',                          src: 'assets/logos/Blink.png' },
{ name: 'ELECTRA',                        src: 'assets/logos/Electra.png' },
{ name: 'ETHARA',                         src: 'assets/logos/Ethara .png' },
{ name: 'EXPO 2020',                      src: 'assets/logos/Expo 2020.png' },
{ name: 'F1',                             src: 'assets/logos/F1.png' },
{ name: 'FIFA',                           src: 'assets/logos/FIFA.png' },
{ name: 'LOUVRE ABU DHABI',               src: 'assets/logos/Louvre Abu Dhabi.png' },
{ name: 'MEMAC OGILVY',                   src: 'assets/logos/Memac Ogilvy.png' },
{ name: 'NEW YORK UNIVERSITY ABU DHABI',  src: 'assets/logos/New York University Abu Dhabi.png' },
{ name: 'PROJEX',                         src: 'assets/logos/Projex.png' },
{ name: 'RED BULL',                       src: 'assets/logos/Red Bull.png' },
{ name: 'STAMINA',                        src: 'assets/logos/Stamina.png' }];

window.PARTNERS = PARTNERS;

/* =========================================================
   PartnerMarquee — continuous horizontal logo strip.

   The logo set is duplicated inline so the translateX keyframe
   can loop from 0 → -50% without a visible seam. On hover the
   animation pauses (gives the visitor a chance to read each
   brand). Under prefers-reduced-motion the marquee stops and
   the logos wrap to a static centered grid so visitors with the
   preference still see every brand.

   The PNGs delivered by the user are in their original colors;
   .partner-marquee__img applies `brightness(0) invert(1)` to
   force every logo to a white silhouette so the row reads as
   one monochrome line. Earned-Motion exception: client-logo
   strips are explicitly sanctioned by DESIGN.md.
   ========================================================= */
function PartnerMarquee({ partners }) {
  const list = partners || PARTNERS;
  return (
    <div className="partner-marquee" aria-label="Trusted partners">
      <div className="partner-marquee__track">
        {/* Two passes of the list, side-by-side, so the loop seams
            invisibly when translateX reaches -50%. The second pass
            is aria-hidden so screen readers don't repeat brand names. */}
        {list.map((p, i) =>
        <div key={'a-' + i} className="partner-marquee__item">
            <img className="partner-marquee__img" src={p.src} alt={p.name} loading="lazy" />
          </div>
        )}
        {list.map((p, i) =>
        <div key={'b-' + i} className="partner-marquee__item" aria-hidden="true">
            <img className="partner-marquee__img" src={p.src} alt="" loading="lazy" />
          </div>
        )}
      </div>
    </div>);

}
window.PartnerMarquee = PartnerMarquee;

/* ===== CONTACT ===== */
function ContactPage({ navigate }) {
  const [form, setForm] = useState({ first: '', last: '', company: '', email: '', phone: '', message: '' });
  const [errors, setErrors] = useState({});
  const [submitted, setSubmitted] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState('');
  /* Brief opacity fade on the form before swapping to the success view,
     so the change reads as "received → confirmed" rather than "form gone,
     success appeared." (Motion audit 2026-05-25 — Jakub Opportunity.) */
  const [leaving, setLeaving] = useState(false);

  const onChange = (k) => (e) => {
    let value = e.target.value;
    if (k === 'phone') value = value.replace(/[^0-9+\s()-]/g, '');
    setForm((f) => ({ ...f, [k]: value }));
  };
  const onSubmit = async (e) => {
    e.preventDefault();
    const errs = {};
    if (!form.first) errs.first = 'Required';
    if (!form.email) errs.email = 'Required';
    else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email)) errs.email = 'Invalid email';
    if (!form.message) errs.message = 'Tell us about the show';
    setErrors(errs);
    if (Object.keys(errs).length > 0) return;

    setSubmitting(true);
    setSubmitError('');
    try {
      const r = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...form, hp_company_url: form.hp_company_url || '' }),
      });
      if (!r.ok) {
        const data = await r.json().catch(() => ({}));
        if (r.status === 429) setSubmitError('Too many submissions — please try again in a minute.');
        else if (r.status === 400 && data.fields) {
          setErrors(data.fields);
          setSubmitError('Please check the highlighted fields.');
        } else setSubmitError('Something went wrong — please try again, or email us at hello@t-av.com.');
        return;
      }
      setLeaving(true);
      setTimeout(() => setSubmitted(true), 150);
    } catch {
      setSubmitError('Could not reach our servers. Please try again, or email us at hello@t-av.com.');
    } finally {
      setSubmitting(false);
    }
  };

  if (submitted) {
    return (
      <div className="page">
        <section className="container-wide" style={{ padding: '120px 40px', textAlign: 'center' }}>
          <div className="overline" style={{ justifyContent: 'center' }}>RECEIVED</div>
          <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
            Loud and clear, <span className="accent">{form.first}.</span>
          </h1>
          <p className="section__sub" style={{ maxWidth: 540, margin: '0 auto 32px' }}>We'll come back to you with a quote, usually inside a working day. If it's urgent, call us on +971 585 246 555.</p>
          <button className="btn" onClick={() => navigate('home')}>Back to home <ArrowRight /></button>
        </section>
      </div>);

  }

  return (
    <div className="page">
      <section className="container-wide" style={{ padding: '64px 40px 48px' }}>
        <div className="overline">GET IN TOUCH</div>
        <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
          Tell us about <span className="accent">your show.</span>
        </h1>
        <p className="section__sub">Brief, venue, dates, scope. We come back with a quote, usually inside a working day.</p>
      </section>

      <section className="container-wide" style={{ paddingBottom: 96 }}>
        <div className="contact-layout">
          <form className={"contact-form" + (leaving ? " is-leaving" : "")} onSubmit={onSubmit} noValidate>
            <div className="term__head">
              <span>NEW INQUIRY · /contact@</span>
              <span className="right">SECURE</span>
            </div>
            {/* Honeypot — humans never see this field; bots fill it. */}
            <div className="hp" aria-hidden="true">
              <label>Company URL</label>
              <input
                type="text"
                tabIndex={-1}
                autoComplete="off"
                value={form.hp_company_url || ''}
                onChange={onChange('hp_company_url')}
              />
            </div>
            <div className="contact-form__row">
              <div className={"field" + (errors.first ? ' error' : '')}>
                <label>First name <span className="required-asterisk" aria-hidden="true">*</span></label>
                <input type="text" value={form.first} onChange={onChange('first')} aria-required="true" />
                {errors.first && <span className="field__error">{errors.first}</span>}
              </div>
              <div className="field">
                <label>Last name</label>
                <input type="text" value={form.last} onChange={onChange('last')} />
              </div>
            </div>
            <div className="field">
              <label>Company</label>
              <input type="text" value={form.company} onChange={onChange('company')} />
            </div>
            <div className="contact-form__row">
              <div className={"field" + (errors.email ? ' error' : '')}>
                <label>Email <span className="required-asterisk" aria-hidden="true">*</span></label>
                <input type="email" value={form.email} onChange={onChange('email')} aria-required="true" />
                {errors.email && <span className="field__error">{errors.email}</span>}
              </div>
              <div className="field">
                <label>Contact number</label>
                <input type="tel" inputMode="tel" value={form.phone} onChange={onChange('phone')} />
              </div>
            </div>
            <div className={"field" + (errors.message ? ' error' : '')}>
              <label>Tell us about the show <span className="required-asterisk" aria-hidden="true">*</span></label>
              <textarea value={form.message} onChange={onChange('message')} placeholder="Brief, venue, dates, scope, any references." aria-required="true" />
              {errors.message && <span className="field__error">{errors.message}</span>}
            </div>
            {submitError && (
              <div className="form-error" role="alert">{submitError}</div>
            )}
            <div className="contact-form__actions">
              <span className="text-label text-fg3" style={{ fontSize: 11, letterSpacing: 1 }}>
                By sending, you agree to our <a href="#/privacy" onClick={(e) => { e.preventDefault(); navigate('privacy'); }}>privacy policy</a>. Replies within 1 working day.
              </span>
              <button type="submit" className="btn" disabled={submitting}>
                {submitting ? 'Sending…' : <>Send request <ArrowRight /></>}
              </button>
            </div>
          </form>

          <aside className="contact-side">
            <div className="card elevated">
              <h2 className="contact-side__h">Let's Talk</h2>
              <DirectLine icon="mail" label="EMAIL" value="hello@t-av.com" link="mailto:hello@t-av.com" />
              <DirectLine icon="phone" label="PHONE" value="+971 585 246 555" link="tel:+971585246555" />
              <DirectLine icon="pin" label="ADDRESS" value="407C DBC, Arjaan, Dubai, UAE" link="https://maps.app.goo.gl/WF2tyjH2SR9ez8sR6" />
              <DirectLine icon="clock" label="HOURS" value="Sun–Fri · 09:00–18:00" />
            </div>
            <div className="card" style={{ marginTop: 14 }}>
              <h3 className="contact-side__sub">CAREERS</h3>
              <p className="contact-side__p">Looking for crew, not clients?</p>
              <a className="btn-link" href="#/careers" onClick={(e) => {e.preventDefault();navigate('careers');}}>Apply to join <ArrowRight /></a>
            </div>
          </aside>
        </div>
      </section>
    </div>);

}

function DirectLine({ icon, label, value, link }) {
  const Icon = () => {
    const props = { width: 18, height: 18, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'square', 'aria-hidden': 'true' };
    if (icon === 'mail') return <svg {...props} viewBox="0 0 18 18"><rect x="2" y="4" width="14" height="10" rx="1" /><path d="M 2 5 L 9 10 L 16 5" /></svg>;
    if (icon === 'phone') return <svg {...props} viewBox="0 0 18 18"><path d="M 4 3 H 7 L 8 7 L 6 8 Q 7 12 11 13 L 12 11 L 16 12 V 15 Q 10 16 6 12 Q 2 8 3 3 Z" /></svg>;
    if (icon === 'pin') return <svg {...props} viewBox="0 0 18 18"><path d="M 9 16 Q 4 10 4 7 Q 4 3 9 3 Q 14 3 14 7 Q 14 10 9 16 Z" /><circle cx="9" cy="7" r="2" /></svg>;
    if (icon === 'clock') return <svg {...props} viewBox="0 0 18 18"><circle cx="9" cy="9" r="6" /><path d="M 9 5 V 9 L 12 11" /></svg>;
    return null;
  };
  return (
    <div className="direct-line">
      <div className="direct-line__icon"><Icon /></div>
      <div>
        <div className="direct-line__label">{label}</div>
        {link ? <a className="direct-line__value" href={link}>{value}</a> : <div className="direct-line__value">{value}</div>}
      </div>
    </div>);

}

/* ===== CAREERS ===== */
function CareersPage({ navigate }) {
  const [form, setForm] = useState({ name: '', email: '', phone: '', position: '', message: '' });
  const [cvFile, setCvFile] = useState(null);
  const [cvDragOver, setCvDragOver] = useState(false);
  const [errors, setErrors] = useState({});
  const [submitted, setSubmitted] = useState(false);
  const [submitting, setSubmitting] = useState(false);
  const [submitError, setSubmitError] = useState('');
  /* Form exit-fade — same pattern as ContactPage; see motion audit
     2026-05-25 Jakub Opportunity. */
  const [leaving, setLeaving] = useState(false);

  const onChange = (k) => (e) => {
    let value = e.target.value;
    if (k === 'phone') value = value.replace(/[^0-9+\s()-]/g, '');
    setForm((f) => ({ ...f, [k]: value }));
  };
  const onCvChange = (e) => {
    const f = e.target.files && e.target.files[0];
    setCvFile(f || null);
  };
  const onCvDragOver = (e) => {
    e.preventDefault();
    if (!cvDragOver) setCvDragOver(true);
  };
  const onCvDragLeave = (e) => {
    e.preventDefault();
    if (e.currentTarget.contains(e.relatedTarget)) return;
    setCvDragOver(false);
  };
  const onCvDrop = (e) => {
    e.preventDefault();
    setCvDragOver(false);
    const f = e.dataTransfer.files && e.dataTransfer.files[0];
    if (f) setCvFile(f);
  };
  const onSubmit = async (e) => {
    e.preventDefault();
    const errs = {};
    if (!form.name) errs.name = 'Required';
    if (!form.email) errs.email = 'Required';
    else if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email)) errs.email = 'Invalid';
    if (!form.position) errs.position = 'Tell us what you ride';
    if (!cvFile) errs.cv = 'A CV is required.';
    else if (cvFile.size > 10 * 1024 * 1024) errs.cv = 'CV must be 10 MB or smaller.';
    setErrors(errs);
    if (Object.keys(errs).length > 0) return;

    setSubmitting(true);
    setSubmitError('');
    try {
      const fd = new FormData();
      fd.append('name', form.name);
      fd.append('email', form.email);
      fd.append('phone', form.phone || '');
      fd.append('position', form.position);
      fd.append('message', form.message || '');
      fd.append('hp_company_url', form.hp_company_url || '');
      fd.append('cv', cvFile, cvFile.name);

      const r = await fetch('/api/careers', { method: 'POST', body: fd });
      if (!r.ok) {
        const data = await r.json().catch(() => ({}));
        if (r.status === 429) setSubmitError('Too many submissions — please try again in a minute.');
        else if (r.status === 413) setSubmitError('Your CV is too large. Please attach a file under 10 MB.');
        else if (r.status === 400 && data.fields) {
          setErrors(data.fields);
          setSubmitError('Please check the highlighted fields.');
        } else setSubmitError('Something went wrong — please try again, or email us at careers@t-av.com.');
        return;
      }
      setLeaving(true);
      setTimeout(() => setSubmitted(true), 150);
    } catch {
      setSubmitError('Could not reach our servers. Please try again, or email us at careers@t-av.com.');
    } finally {
      setSubmitting(false);
    }
  };

  if (submitted) {
    return (
      <div className="page">
        <section className="container-wide" style={{ padding: '120px 40px', textAlign: 'center' }}>
          <div className="overline" style={{ justifyContent: 'center' }}>APPLICATION RECEIVED</div>
          <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
            Got it, <span className="accent">{form.name.split(' ')[0]}.</span>
          </h1>
          <p className="section__sub" style={{ maxWidth: 540, margin: '0 auto 32px' }}>We read every application. If your shows match what we're shipping, you'll hear from us inside a week.</p>
          <button className="btn" onClick={() => navigate('home')}>Back to home <ArrowRight /></button>
        </section>
      </div>);

  }

  return (
    <div className="page">
      <section className="container-wide" style={{ padding: '64px 40px 48px' }}>
        <div className="overline">CAREERS</div>
        <h1 className="display" style={{ marginTop: 24, marginBottom: 24 }}>
          Join the <span className="accent">crew.</span>
        </h1>
        <p className="section__sub">You'll get senior shows from week one, kit you've actually heard of, and a warehouse that runs on plans, not panic. We hire engineers, programmers, riggers, operators and crew.</p>
      </section>

      <section className="container-wide" style={{ paddingBottom: 96 }}>
        <div className="contact-layout">
          <form className={"contact-form" + (leaving ? " is-leaving" : "")} onSubmit={onSubmit} noValidate>
            <div className="term__head">
              <span>NEW APPLICATION · /careers@</span>
              <span className="right">CV · REQUIRED</span>
            </div>
            {/* Honeypot — humans never see this field; bots fill it. */}
            <div className="hp" aria-hidden="true">
              <label>Company URL</label>
              <input
                type="text"
                tabIndex={-1}
                autoComplete="off"
                value={form.hp_company_url || ''}
                onChange={onChange('hp_company_url')}
              />
            </div>
            <div className={"field" + (errors.name ? ' error' : '')}>
              <label>Name <span className="required-asterisk" aria-hidden="true">*</span></label>
              <input type="text" value={form.name} onChange={onChange('name')} aria-required="true" />
              {errors.name && <span className="field__error">{errors.name}</span>}
            </div>
            <div className="contact-form__row">
              <div className={"field" + (errors.email ? ' error' : '')}>
                <label>Email <span className="required-asterisk" aria-hidden="true">*</span></label>
                <input type="email" value={form.email} onChange={onChange('email')} aria-required="true" />
                {errors.email && <span className="field__error">{errors.email}</span>}
              </div>
              <div className="field">
                <label>Phone</label>
                <input type="tel" inputMode="tel" value={form.phone} onChange={onChange('phone')} />
              </div>
            </div>
            <div className={"field" + (errors.position ? ' error' : '')}>
              <label>Position you're applying for <span className="required-asterisk" aria-hidden="true">*</span></label>
              <input type="text" value={form.position} onChange={onChange('position')} placeholder="FOH engineer · MA3 programmer · rigger · video op…" aria-required="true" />
              {errors.position && <span className="field__error">{errors.position}</span>}
            </div>
            <div className="field">
              <label>Tell us about yourself</label>
              <textarea value={form.message} onChange={onChange('message')} placeholder="Shows you've shipped, with your role. Console / fixture / rig you're fluent on. How long it takes you to get up on a desk you don't know." />
            </div>
            <div className="field">
              <label>CV (PDF, DOC, DOCX, max 10MB) <span className="required-asterisk" aria-hidden="true">*</span></label>
              <div
                className={"cv-upload" + (cvDragOver ? " cv-upload--drag-over" : "")}
                onDragOver={onCvDragOver}
                onDragLeave={onCvDragLeave}
                onDrop={onCvDrop}>
                <input
                  id="cv-upload"
                  type="file"
                  accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
                  onChange={onCvChange}
                  className="cv-upload__input" />
                <label htmlFor="cv-upload" className="cv-upload__trigger">
                  <span className="cv-upload__icon" aria-hidden="true">
                    <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6">
                      <path d="M 8 11 V 2 M 4 6 L 8 2 L 12 6 M 2 13 H 14" strokeLinecap="square" strokeLinejoin="miter" />
                    </svg>
                  </span>
                  <span className="cv-upload__label">
                    {cvFile ? cvFile.name : (cvDragOver ? 'Drop CV here' : 'Choose a file or drag it here')}
                  </span>
                </label>
                {cvFile && (
                  <button
                    type="button"
                    className="cv-upload__clear"
                    onClick={() => setCvFile(null)}
                    aria-label="Remove attached CV">
                    Remove
                  </button>
                )}
              </div>
              {errors.cv && <span className="field__error">{errors.cv}</span>}
            </div>
            {submitError && (
              <div className="form-error" role="alert">{submitError}</div>
            )}
            <div className="contact-form__actions">
              <span className="text-label text-fg3" style={{ fontSize: 11, letterSpacing: 1 }}>
                By applying, you agree to our <a href="#/privacy" onClick={(e) => { e.preventDefault(); navigate('privacy'); }}>privacy policy</a>. We read every application.
              </span>
              <button type="submit" className="btn" disabled={submitting}>
                {submitting ? 'Sending…' : <>Send application <ArrowRight /></>}
              </button>
            </div>
          </form>

          <aside className="contact-side">
            <div className="card elevated">
              <h2 className="contact-side__h">How we read CVs</h2>
              <ul className="cv-list">
                <li><span className="cv-list__bullet" />Specific shows you've shipped, with role.</li>
                <li><span className="cv-list__bullet" />Console / fixture / rig you're fluent on.</li>
                <li><span className="cv-list__bullet" />How long it takes you to get up on a desk you don't know.</li>
                <li><span className="cv-list__bullet" />Driver's licence, GCC location, valid visa status.</li>
              </ul>
            </div>
            <div className="card" style={{ marginTop: 14 }}>
              <h3 className="contact-side__sub">OPEN ROLES · 04</h3>
              {[
              { r: 'Project Manager',           t: 'Full-time · Abu Dhabi' },
              { r: 'Sound Operator',            t: 'Part-time · UAE' },
              { r: 'System Engineer',           t: 'Part-time · UAE' },
              { r: 'Lighting Operator (GMA3)',  t: 'Part-time · UAE' }].
              map((role) =>
              <div key={role.r} className="role-row">
                  <div className="role-row__t">{role.r}</div>
                  <div className="role-row__m">{role.t}</div>
                </div>
              )}
            </div>
          </aside>
        </div>
      </section>
    </div>);

}

/* ===== 404 ===== */
function NotFoundPage({ navigate }) {
  return (
    <div className="page">
      <section className="container-wide" style={{ padding: '96px 40px' }}>
        <div className="overline-mono" style={{ marginBottom: 16 }}>404 · PAGE NOT FOUND</div>
        <h1 className="display" style={{ marginBottom: 24 }}>Off the <span className="accent">plot.</span></h1>
        <p className="section__sub" style={{ maxWidth: 560 }}>This page isn't on the rig. Try the home page, the showcase or get in touch and we'll point you the right way.</p>
        <div style={{ display: 'flex', gap: 14, marginTop: 32, flexWrap: 'wrap' }}>
          <button className="btn" onClick={() => navigate('home')}>Home <ArrowRight /></button>
          <button className="btn-ghost" onClick={() => navigate('showcase')}>Showcase</button>
          <button className="btn-ghost" onClick={() => navigate('contact')}>Contact</button>
        </div>
      </section>

      {/* Floor plan with red marker */}
      <section className="container-wide" style={{ paddingBottom: 96 }}>
        <div className="beat__visual" style={{ aspectRatio: '16/7', maxWidth: 1100 }}>
          <svg viewBox="0 0 1100 480" style={{ width: '100%', height: '100%' }} preserveAspectRatio="xMidYMid slice" role="img" aria-label="Floor plan diagram: stage at centre, marker showing current position as off-plan">
            <rect width="1100" height="480" fill="#0a0a0a" />
            {Array.from({ length: 35 }, (_, i) =>
            <line key={'v' + i} x1={i * 32} y1={0} x2={i * 32} y2={480} stroke="#3d3a39" strokeOpacity="0.2" strokeWidth="0.5" />
            )}
            {Array.from({ length: 16 }, (_, i) =>
            <line key={'h' + i} x1={0} y1={i * 32} x2={1100} y2={i * 32} stroke="#3d3a39" strokeOpacity="0.2" strokeWidth="0.5" />
            )}
            <rect x="380" y="170" width="340" height="140" fill="none" stroke="#3E8C84" strokeWidth="1.5" />
            <text x="550" y="246" textAnchor="middle" fontFamily="ui-monospace, monospace" fontSize="14" fill="#3E8C84" letterSpacing="2.5">STAGE</text>
            <g>
              <circle cx="900" cy="380" r="14" fill="none" stroke="#fb565b" strokeWidth="1.5" />
              <line x1="892" y1="372" x2="908" y2="388" stroke="#fb565b" strokeWidth="1.5" />
              <line x1="908" y1="372" x2="892" y2="388" stroke="#fb565b" strokeWidth="1.5" />
              <text x="930" y="384" fontFamily="ui-monospace, monospace" fontSize="11" fill="#fb565b" letterSpacing="2">YOU · OFF-PLAN</text>
            </g>
          </svg>
        </div>
      </section>
    </div>);

}

/* ===== Reusable contact strip footer-section ===== */
function ContactStrip({ navigate }) {
  return (
    <section className="container-wide" style={{ paddingTop: 48, paddingBottom: 96 }}>
      <div className="contact-strip">
        <div>
          <div className="overline-mono" style={{ marginBottom: 8 }}>NEXT MOVE</div>
          <h2 className="contact-strip__h">Tell us about the show.</h2>
        </div>
        <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap' }}>
          <button className="btn" onClick={() => navigate('contact')}>Request a quote <ArrowRight /></button>
          <button className="btn-ghost" onClick={() => navigate('showcase')}>See the work</button>
        </div>
      </div>
    </section>);

}

window.ShowcasePage = ShowcasePage;
window.WhatWeDoPage = WhatWeDoPage;
window.ServicePage = ServicePage;
window.ServiceDetailPage = ServicePage;
window.AboutPage = AboutPage;
window.ContactPage = ContactPage;
window.CareersPage = CareersPage;
window.NotFoundPage = NotFoundPage;
window.ContactStrip = ContactStrip;