/* eslint-disable */
const { useState: enS, useMemo: enM, useRef: enR, useEffect: enE } = React;
// Hand-placed positions on a 1000x600 canvas (no physics; deterministic)
const NODES = {
p_mert: { x: 360, y: 230, color: "var(--ember)" },
p_leyla: { x: 580, y: 150, color: "var(--plum)" },
p_arda: { x: 760, y: 360, color: "var(--moss)" },
p_aysel: { x: 200, y: 410, color: "var(--gold)" },
p_kaan: { x: 470, y: 470, color: "var(--sky)" },
p_irem: { x: 240, y: 130, color: "var(--ruby)" },
p_renee: { x: 800, y: 130, color: "var(--ink-2)" },
p_dmitri: { x: 870, y: 240, color: "var(--ink-3)" },
};
const EDGES = [
{ a:"p_mert", b:"p_leyla", type:"ex-lover", strength:0.9, since:"8 yr", note:"Üniversite. Ayrıldıktan sonra bir kez konuştular." },
{ a:"p_mert", b:"p_aysel", type:"mentor", strength:0.7, since:"life", note:"Aysel hanım Mert'in lise edebiyat öğretmeniydi." },
{ a:"p_mert", b:"p_irem", type:"client", strength:0.5, since:"6 mo", note:"Mert haftada bir görüşmeye gidiyor; kabullenmiyor." },
{ a:"p_leyla", b:"p_arda", type:"sibling", strength:0.95, since:"birth", note:"Anne aynı, baba farklı. Yıllarca konuşmadılar." },
{ a:"p_leyla", b:"p_kaan", type:"source", strength:0.6, since:"2 yr", note:"Kaan, Leyla'nın gizli kaynağı; mahalleyi anlatıyor." },
{ a:"p_arda", b:"p_renee", type:"acquaintance",strength:0.3, since:"3 yr", note:"Lyon'da bir konferansta tanıştılar." },
{ a:"p_renee", b:"p_dmitri", type:"rival", strength:0.55, since:"15 yr", note:"Bir aşçılık yarışmasında jüri ikilemi." },
{ a:"p_aysel", b:"p_kaan", type:"family", strength:0.85, since:"life", note:"Aysel hanım Kaan'ın anneannesi." },
{ a:"p_irem", b:"p_leyla", type:"friend", strength:0.7, since:"4 yr", note:"Yoga sınıfında tanışmışlar." },
{ a:"p_mert", b:"p_dmitri", type:"correspondent",strength:0.4, since:"2 yr", note:"Mektupla satranç oynuyorlar." },
];
const REL_COLOR = {
"ex-lover": "var(--ruby)",
"mentor": "var(--gold)",
"client": "var(--plum)",
"sibling": "var(--ember)",
"source": "var(--sky)",
"acquaintance": "var(--ink-3)",
"rival": "var(--ruby)",
"family": "var(--moss)",
"friend": "var(--moss)",
"correspondent": "var(--ink-2)",
};
const REL_DASH = {
"ex-lover": "4 4",
"acquaintance": "2 6",
"correspondent": "1 4",
"rival": "8 4",
};
const PageEnsemble = ({ go }) => {
const personas = D.personas;
const byId = enM(()=>Object.fromEntries(personas.map(p=>[p.id,p])), [personas]);
const [selected, setSelected] = enS("p_mert");
const [edgeHover, setEdgeHover] = enS(null);
const [filter, setFilter] = enS(new Set());
const [scenarioOpen, setScenarioOpen] = enS(false);
const [castIds, setCastIds] = enS(["p_mert","p_leyla"]);
const [scenarioSetting, setScenarioSetting] = enS("Beyoğlu, sahafın arka odası, 22:00.");
const openScenario = (ids, setting) => {
if (ids) setCastIds(ids);
if (setting) setScenarioSetting(setting);
setScenarioOpen(true);
};
const toggleCast = (id) => {
setCastIds(prev => prev.includes(id) ? prev.filter(x=>x!==id) : [...prev, id]);
};
const visibleEdges = enM(()=>(
filter.size === 0 ? EDGES : EDGES.filter(e => filter.has(e.type))
), [filter]);
const sel = byId[selected];
const selEdges = EDGES.filter(e => e.a === selected || e.b === selected);
const toggleFilter = (t) => {
const n = new Set(filter);
if (n.has(t)) n.delete(t); else n.add(t);
setFilter(n);
};
const allTypes = [...new Set(EDGES.map(e=>e.type))];
return (
Studio · Ensemble
The cast, in relation. 10 ties across 8 personas — three of them, complicated.
Drag a thread to ground a multi-persona scenario. Conflicts surface when two personas you're casting share a wound.
Export graph ↓
setScenarioOpen(true)}>+ New scenario
{/* Filter bar */}
Filter ties:
{allTypes.map(t => {
const active = filter.size===0 || filter.has(t);
return (
toggleFilter(t)} className="tag" style={{
cursor:"pointer", opacity: active?1:0.4, fontSize:11,
borderColor: REL_COLOR[t], color: REL_COLOR[t], background: `color-mix(in oklch, ${REL_COLOR[t]} 10%, transparent)`
}}>
{t}
);
})}
{visibleEdges.length}/{EDGES.length} edges
{filter.size>0 && setFilter(new Set())} className="muted" style={{fontSize:11, cursor:"pointer", textDecoration:"underline"}}>clear }
{/* Graph canvas */}
Ensemble graph · workspace
Click a node to inspect. Hover an edge for context.
{["all","clusters","arcs"].map((m,i)=>(
{m}
))}
{/* Edges */}
{visibleEdges.map((e,i)=>{
const a = NODES[e.a], b = NODES[e.b];
const mx = (a.x+b.x)/2, my=(a.y+b.y)/2;
const isHover = edgeHover === i;
const isSel = e.a===selected || e.b===selected;
const c = REL_COLOR[e.type] || "var(--ink-3)";
return (
setEdgeHover(i)}
onMouseLeave={()=>setEdgeHover(null)}
style={{cursor:"pointer"}}>
{(isHover||isSel) && (
{e.type}
)}
);
})}
{/* Nodes */}
{personas.map(p => {
const n = NODES[p.id]; if (!n) return null;
const isSel = selected === p.id;
const initials = p.name.split(" ").map(w=>w[0]).slice(0,2).join("");
return (
setSelected(p.id)}>
{isSel && }
{initials}
{p.name.split(" ")[0]}
{p.archetype.replace("The ","")}
);
})}
{/* Legend */}
thicker = stronger tie
distant / dormant
focused persona
{/* Right panel — selected persona */}
{sel && (
portrait
focused
{sel.name}
{sel.tagline}
{sel.archetype}
{sel.lang}
{selEdges.length} relationships
{selEdges.map((e,i) => {
const otherId = e.a===selected ? e.b : e.a;
const o = byId[otherId];
const c = REL_COLOR[e.type];
return (
setSelected(otherId)} style={{
padding:"10px 12px", border:"1px solid var(--line)", borderRadius:"var(--r-md)",
background:"var(--bg-2)", cursor:"pointer",
borderLeft: `3px solid ${c}`
}}>
{e.note}
since {e.since}
strength {Math.round(e.strength*100)}
);
})}
+ Add relationship
)}
{/* Scenario suggestions */}
Suggested scenes
{[
{ids:["p_mert","p_leyla"], who:"Mert × Leyla", note:"Ex-lovers run into each other at a book fair.", ten:"high", set:"Kadıköy kitap fuarı, kapanışa yarım saat."},
{ids:["p_aysel","p_kaan"], who:"Aysel × Kaan", note:"Grandmother gives the taxi-driver grandson advice on a long ride.", ten:"warm", set:"İzmir → Eskişehir, gece otoyolu, 03:10."},
{ids:["p_leyla","p_arda"], who:"Leyla × Arda", note:"Estranged siblings forced to settle a parent's estate.", ten:"high", set:"Bir noter ofisi, Ankara, sabah."},
{ids:["p_mert","p_dmitri","p_renee"], who:"Mert × Dmitri × Renée", note:"Three strangers locked into a small dinner party.", ten:"awkward", set:"Lyon, 8 kişilik masada üç sandalye boş."},
].map((s,i)=>(
openScenario(s.ids, s.set)} style={{padding:"10px 12px", border:"1px solid var(--line)", borderRadius:"var(--r-md)", background:"var(--bg-2)", cursor:"pointer"}}>
{s.note}
))}
{/* Stats row */}
Personas in graph
8
+1 this week
Relationships
10
3 strong, 4 weak
Tightest cluster
Mert · Leyla · Arda
density 0.83
{/* Scenario modal */}
{scenarioOpen && (
setScenarioOpen(false)}>
e.stopPropagation()} style={{width:620}}>
New ensemble scenario
setScenarioOpen(false)} style={{cursor:"pointer", color:"var(--ink-3)", fontSize:18}}>×
Pick 2–4 personas and a setting. Persona will run a multi-agent rehearsal and surface conflicts before you commit.
Cast · {castIds.length} selected
{personas.map(p => {
const on = castIds.includes(p.id);
return (
toggleCast(p.id)} className={"tag "+(on?"ember":"")} style={{cursor:"pointer", fontSize:11, opacity: on?1:0.55}}>
{p.name}
);
})}
Setting
setScenarioSetting(e.target.value)}/>
setScenarioOpen(false)}>Cancel
{ setScenarioOpen(false); go && go("chat", byId[castIds[0]]); }}>Run scenario →
)}
);
};
window.PageEnsemble = PageEnsemble;