// ============================================================ // Tribeca Lisboa — TALENTOS listing + detail (one app, two pages) // Data is derived from existing SPEAKERS + event guests + news // (getTalents in data.jsx). No new data model. // ============================================================ const TL_CATS = [ { key: 'all', en: 'All', pt: 'Todos' }, { key: 'guest', en: 'Confirmed guests', pt: 'Confirmed guests' }, { key: 'director', en: 'Directors', pt: 'Realizadores' }, { key: 'host', en: 'Hosts & podcasts', pt: 'Hosts & podcasts' }, { key: 'orchestra', en: 'Orchestras', pt: 'Orquestras' }, ]; function tlGroup(t) { if (t.news) return 'guest'; const r = (t.roles[0] || {}).role || ''; if (/Orquestra/i.test(r)) return 'orchestra'; if (/Realizador/i.test(r)) return 'director'; return 'host'; } const tlEvTitle = (lang, ev) => tt(lang, ev.titleEn || ev.title, ev.title); function TlCard({ t }) { const lang = useCtx(LangContext); const ev = t.events[0]; return (
{t.name}
{talentCategory(t, lang)}

{t.name}

{ev &&
{tlEvTitle(lang, ev)}{t.events.length > 1 ? ` + ${t.events.length - 1}` : ''}
} {!t.news &&
{isPendingImg(t.img) ? tt(lang, 'Bio and headshot awaiting approval.', 'Bio e headshot a aguardar aprovação.') : tt(lang, 'Bio awaiting approval.', 'Bio a aguardar aprovação.')}
}
); } function TalentsListInner() { const lang = useCtx(LangContext); const [menu, setMenu] = React.useState(false); const [cat, setCat] = React.useState('all'); const all = React.useMemo(() => { const order = { guest: 0, director: 1, host: 2, orchestra: 3 }; return getTalents().filter((t) => t.news).sort((a, b) => order[tlGroup(a)] - order[tlGroup(b)]); }, []); const list = cat === 'all' ? all : all.filter((t) => tlGroup(t) === cat); return (
setMenu(true)} />
Tribeca Festival Lisboa 2026

{tt(lang, 'Talent', 'Talentos')}

{tt(lang, 'Guests, directors, hosts and artists taking part in the festival.', 'Convidados, realizadores, hosts e artistas que participam no festival.')}

{list.map((t) => )}
setMenu(false)} />
); } function TalentDetailInner() { const lang = useCtx(LangContext); const [menu, setMenu] = React.useState(false); const all = React.useMemo(() => getTalents().filter((x) => x.news), []); const t = all.find((x) => x.id === getParam('id')) || all[0]; const n = t.news; const roleFor = (ev) => t.roles.filter((r) => r.eventId === ev.id).map((r) => tt(lang, r.roleEn || r.role, r.role)).join(' · '); return (
setMenu(true)} />
{t.name}
← {tt(lang, 'Talent', 'Talentos')}
{talentCategory(t, lang)}

{t.name}

{t.events.length > 0 &&
{t.events.map((ev) =>
{roleFor(ev)} — {tlEvTitle(lang, ev)}
)}
} {n &&

{tt(lang, n.dekEn, n.dek)}

} {isPendingImg(t.img) &&

{tt(lang, 'Headshot awaiting approval.', 'Headshot a aguardar aprovação.')}

}
{n ? (
{n.body.map((b, i) => b.t === 'h2' ?

{tt(lang, b.xEn, b.x)}

:

{tt(lang, b.xEn, b.x)}

)}
) : (

{isPendingImg(t.img) ? tt(lang, 'Bio and headshot awaiting approval.', 'Bio e headshot a aguardar aprovação.') : tt(lang, 'Bio awaiting approval.', 'Bio a aguardar aprovação.')}

)}
{t.events.length > 0 && (

{tt(lang, 'At the festival', 'No festival')}

{t.events.map((ev) => (
{tlEvTitle(lang, ev)}
{tt(lang, ev.dayDateEn, ev.dayDate)} • {ev.time} • {ev.venue}
{roleFor(ev)}
))}
)} setMenu(false)} />
); } function TalentsRoot() { const [lang] = useLang(); const Page = window.TL_PAGE === 'detail' ? TalentDetailInner : TalentsListInner; return ; } ReactDOM.createRoot(document.getElementById('root')).render();