}, { name: "Neo", role: "Innovation Coach", avatar: "💡", accent: "#0ea5e9", status: "online", preview: "Let's explore your ideas together.", description: "Neo helps you think through an innovation scenario and reflect on your own reasoning as you go.", embed: { type: "demo" } }, { name: "Iris", role: "Survey Guide", avatar: "📋", accent: "#10b981", status: "away", preview: "I'll walk you through the questionnaire.", description: "Iris answers questions about the study and helps you complete the survey step by step.", embed: { type: "demo" } // Widget-snippet example: // embed: { type:"widget", code:'' } } ]; /* ================================================================== 3) APP LOGIC —— no need to edit below this line. ================================================================== */ const state = { selectedId: null, inCall: false, seconds: 0, timerHandle: null }; const $ = (sel, root=document) => root.querySelector(sel); // header branding $("#brandName").textContent = CONFIG.appName; $("#brandTag").textContent = CONFIG.appTagline; $("#brandLogo").textContent = CONFIG.logoLetter || "•"; document.title = CONFIG.appName; if (!CONFIG.askParticipantId) $("#pidWrap").style.display = "none"; $("#pidHint").textContent = CONFIG.askParticipantId ? (CONFIG.participantHint || "") : ""; AGENTS.forEach((a, i) => { a._id = "agent-" + i; }); /* ---- avatar rendering ---- */ function avatarInner(a){ const v = a.avatar || ""; if (/^https?:\/\/|^data:/.test(v)) return ``; if (v) return v; // emoji or initials return a.name.trim().charAt(0).toUpperCase(); // fallback: first letter } function avatarEl(a, cls=""){ return `
${avatarInner(a)}
`; } /* ---- contact list ---- */ function renderContacts(filter=""){ const list = $("#contacts"); const q = filter.trim().toLowerCase(); const items = AGENTS.filter(a => !q || a.name.toLowerCase().includes(q) || (a.role||"").toLowerCase().includes(q) ); list.innerHTML = items.map(a => `
  • ${avatarEl(a)}
    ${a.name} ${a.role||''}
    ${a.preview || a.description || ''}
  • `).join(""); if (!items.length){ list.innerHTML = `
  • No assistants match “${filter}”.
  • `; } list.querySelectorAll(".contact").forEach(el => el.addEventListener("click", () => selectAgent(el.dataset.id)) ); } function agentById(id){ return AGENTS.find(a => a._id === id); } function participantId(){ return $("#pidInput") ? $("#pidInput").value.trim() : ""; } /* ---- selection & views ---- */ function selectAgent(id){ if (state.inCall) endCall(true); state.selectedId = id; document.body.classList.add("stage-open"); renderContacts($("#searchInput").value); renderIntro(agentById(id)); } function renderEmpty(){ document.body.classList.remove("stage-open"); $("#stage").innerHTML = `

    Pick an assistant to begin

    Choose a contact on the left to open its conversation space and start talking.

    `; } function renderIntro(a){ $("#stage").innerHTML = `
    ${avatarEl(a)}

    ${a.name}

    ${a.role||''}

    ${a.description || ''}

    `; $("#startBtn").addEventListener("click", () => startCall(a)); $("#backBtn").addEventListener("click", () => { state.selectedId=null; renderContacts(); renderEmpty(); }); } /* ---- call flow ---- */ function startCall(a){ // brief "connecting…" ring animation, then load the embed $("#stage").innerHTML = `
    ${avatarInner(a)}

    ${a.name}

    Connecting…

    `; setTimeout(() => mountCall(a), 1200); } function mountCall(a){ state.inCall = true; state.seconds = 0; $("#stage").innerHTML = `
    ${avatarEl(a)}
    ${a.name}
    Connected · 00:00
    `; // load the actual embedded agent const frame = $("#embedFrame"); const em = a.embed || { type:"demo" }; if (em.type === "iframe"){ let url = em.url || ""; const pid = participantId(); if (CONFIG.askParticipantId && pid){ url += (url.includes("?") ? "&" : "?") + CONFIG.participantParam + "=" + encodeURIComponent(pid); } frame.removeAttribute("srcdoc"); frame.src = url; } else if (em.type === "widget"){ frame.removeAttribute("src"); frame.srcdoc = wrapWidget(em.code || "", a); } else { frame.removeAttribute("src"); frame.srcdoc = demoEmbed(a); } $("#endBtn").addEventListener("click", () => endCall()); $("#backBtn").addEventListener("click", () => endCall()); startTimer(); } function endCall(silent=false){ stopTimer(); state.inCall = false; const frame = $("#embedFrame"); if (frame){ frame.src = "about:blank"; frame.removeAttribute("srcdoc"); } if (!silent){ const a = agentById(state.selectedId); if (a) renderIntro(a); else renderEmpty(); } } /* ---- timer ---- */ function startTimer(){ stopTimer(); state.timerHandle = setInterval(() => { state.seconds++; const m = String(Math.floor(state.seconds/60)).padStart(2,"0"); const s = String(state.seconds%60).padStart(2,"0"); const t = $("#timer"); if (t) t.textContent = `${m}:${s}`; }, 1000); } function stopTimer(){ if (state.timerHandle){ clearInterval(state.timerHandle); state.timerHandle=null; } } /* ---- widget wrapper (runs an embed snippet isolated in the frame) ---- */ function wrapWidget(code, a){ return '' + '' + '' + '' + code + ''; } /* ---- demo placeholder embed (replace agents with real embeds for production) ---- */ function demoEmbed(a){ const accent = a.accent || "#4f46e5"; return '' + '' + '
    ' + '
    ◈ Demo preview — this panel is where '+a.name+' will be embedded. Replace with your embed code.
    ' + '
    ' + '
    Hi! I\'m '+a.name+', your '+(a.role||'assistant')+'. This is a sample of the conversation space.
    ' + '
    How does this work?
    ' + '
    Your real AI agent gets embedded right here. Paste its iframe URL or widget snippet into the AGENTS list to go live.
    ' + '
    ' + '
    ' + '
    Demo mode · no live AI connected
    ' + '
    '; } /* ---- search ---- */ $("#searchInput").addEventListener("input", e => renderContacts(e.target.value)); /* ---- init ---- */ renderContacts(); renderEmpty();