feat: unified context panel, remove tabs, context-aware layout

- Merged AI Assistant + Lead 360 tabs into single context-aware panel
- Context section shows: lead profile, AI insight (live from event bus), appointments, activity
- "On call with" banner only shows during active calls (isInCall prop)
- AI Chat always available at bottom of panel
- Phase 1 only — AI Chat panel needs full redesign with Vercel AI SDK tool calling (Phase 2)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 09:51:49 +05:30
parent e6b2208077
commit 48ed300094
7 changed files with 2424 additions and 195 deletions

View File

@@ -0,0 +1,886 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Helix Engage — Weekly Update (Mar 1825, 2026)</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
/* ===========================================
CSS CUSTOM PROPERTIES (DARK EXECUTIVE THEME)
=========================================== */
:root {
--bg-primary: #0b0e17;
--bg-secondary: #111827;
--bg-card: rgba(255,255,255,0.04);
--bg-card-hover: rgba(255,255,255,0.07);
--text-primary: #f0f2f5;
--text-secondary: #8892a4;
--text-muted: #4b5563;
--accent-cyan: #22d3ee;
--accent-violet: #a78bfa;
--accent-emerald: #34d399;
--accent-amber: #fbbf24;
--accent-rose: #fb7185;
--accent-blue: #60a5fa;
--glow-cyan: rgba(34,211,238,0.15);
--glow-violet: rgba(167,139,250,0.15);
--glow-emerald: rgba(52,211,153,0.15);
--font-display: 'Space Grotesk', sans-serif;
--font-body: 'DM Sans', sans-serif;
--slide-padding: clamp(2rem, 6vw, 5rem);
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--duration: 0.7s;
}
/* ===========================================
BASE RESET
=========================================== */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; scroll-snap-type: y mandatory; }
body {
font-family: var(--font-body);
background: var(--bg-primary);
color: var(--text-primary);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
}
/* ===========================================
SLIDE CONTAINER
=========================================== */
.slide {
min-height: 100vh;
padding: var(--slide-padding);
scroll-snap-align: start;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
overflow: hidden;
}
/* ===========================================
PROGRESS BAR
=========================================== */
.progress-bar {
position: fixed; top: 0; left: 0;
height: 3px; width: 0%;
background: linear-gradient(90deg, var(--accent-cyan), var(--accent-violet));
z-index: 100;
transition: width 0.3s ease;
}
/* ===========================================
NAVIGATION DOTS
=========================================== */
.nav-dots {
position: fixed; right: 1.5rem; top: 50%;
transform: translateY(-50%);
display: flex; flex-direction: column; gap: 10px;
z-index: 100;
}
.nav-dot {
width: 10px; height: 10px;
border-radius: 50%;
background: var(--text-muted);
border: none; cursor: pointer;
transition: all 0.3s ease;
}
.nav-dot.active {
background: var(--accent-cyan);
box-shadow: 0 0 12px var(--glow-cyan);
transform: scale(1.3);
}
/* ===========================================
SLIDE COUNTER
=========================================== */
.slide-counter {
position: fixed; bottom: 1.5rem; right: 2rem;
font-family: var(--font-display);
font-size: 0.85rem;
color: var(--text-muted);
z-index: 100;
letter-spacing: 0.1em;
}
/* ===========================================
REVEAL ANIMATIONS
=========================================== */
.reveal {
opacity: 0;
transform: translateY(35px);
transition: opacity var(--duration) var(--ease-out-expo),
transform var(--duration) var(--ease-out-expo);
}
.slide.visible .reveal { opacity: 1; transform: translateY(0); }
.reveal:nth-child(1) { transition-delay: 0.08s; }
.reveal:nth-child(2) { transition-delay: 0.16s; }
.reveal:nth-child(3) { transition-delay: 0.24s; }
.reveal:nth-child(4) { transition-delay: 0.32s; }
.reveal:nth-child(5) { transition-delay: 0.40s; }
.reveal:nth-child(6) { transition-delay: 0.48s; }
.reveal:nth-child(7) { transition-delay: 0.56s; }
.reveal:nth-child(8) { transition-delay: 0.64s; }
@media (prefers-reduced-motion: reduce) {
.reveal { transition: opacity 0.3s ease; transform: none; }
}
/* ===========================================
TYPOGRAPHY
=========================================== */
h1 { font-family: var(--font-display); font-weight: 700; }
h2 { font-family: var(--font-display); font-weight: 600; font-size: clamp(1.6rem, 4vw, 2.5rem); margin-bottom: 0.5em; }
h3 { font-family: var(--font-display); font-weight: 500; font-size: 1.1rem; }
p, li { line-height: 1.65; }
.label {
display: inline-block;
font-family: var(--font-display);
text-transform: uppercase;
letter-spacing: 0.15em;
font-size: 0.7rem;
font-weight: 600;
padding: 0.3em 0.9em;
border-radius: 100px;
margin-bottom: 1rem;
}
/* ===========================================
TITLE SLIDE
=========================================== */
.title-slide {
text-align: center;
background:
radial-gradient(ellipse at 30% 70%, rgba(34,211,238,0.08) 0%, transparent 50%),
radial-gradient(ellipse at 70% 30%, rgba(167,139,250,0.08) 0%, transparent 50%),
var(--bg-primary);
}
.title-slide h1 {
font-size: clamp(2.5rem, 6vw, 4.5rem);
background: linear-gradient(135deg, var(--accent-cyan) 0%, var(--accent-violet) 50%, var(--accent-emerald) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1.15;
margin-bottom: 0.3em;
}
.title-slide .subtitle {
font-size: clamp(1rem, 2vw, 1.4rem);
color: var(--text-secondary);
margin-bottom: 0.4em;
}
.title-slide .date-range {
font-family: var(--font-display);
color: var(--text-muted);
font-size: 0.9rem;
letter-spacing: 0.08em;
}
/* ===========================================
STAT CARDS
=========================================== */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1.2rem;
margin-top: 1.5rem;
}
.stat-card {
background: var(--bg-card);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 16px;
padding: 1.8rem 1.5rem;
text-align: center;
transition: all 0.4s var(--ease-out-expo);
position: relative;
overflow: hidden;
}
.stat-card::after {
content: '';
position: absolute; top: 0; left: 0; right: 0;
height: 2px;
background: linear-gradient(90deg, transparent, var(--accent-cyan), transparent);
opacity: 0;
transition: opacity 0.4s ease;
}
.stat-card:hover { background: var(--bg-card-hover); transform: translateY(-4px); }
.stat-card:hover::after { opacity: 1; }
.stat-number {
font-family: var(--font-display);
font-size: 3rem;
font-weight: 700;
line-height: 1;
margin-bottom: 0.3em;
}
.stat-number.cyan { color: var(--accent-cyan); }
.stat-number.violet { color: var(--accent-violet); }
.stat-number.emerald { color: var(--accent-emerald); }
.stat-number.amber { color: var(--accent-amber); }
.stat-label { color: var(--text-secondary); font-size: 0.85rem; }
/* ===========================================
CONTENT CARDS
=========================================== */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.2rem;
margin-top: 1.2rem;
}
.content-card {
background: var(--bg-card);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 14px;
padding: 1.5rem;
transition: all 0.3s var(--ease-out-expo);
}
.content-card:hover { background: var(--bg-card-hover); border-color: rgba(255,255,255,0.1); }
.content-card h3 { margin-bottom: 0.6rem; }
.content-card ul {
list-style: none; padding: 0;
}
.content-card li {
position: relative;
padding-left: 1.2em;
margin-bottom: 0.45em;
color: var(--text-secondary);
font-size: 0.9rem;
}
.content-card li::before {
content: '';
position: absolute;
left: 0;
color: var(--accent-cyan);
font-weight: 700;
}
/* ===========================================
TIMELINE
=========================================== */
.timeline {
position: relative;
padding-left: 2rem;
margin-top: 1.5rem;
}
.timeline::before {
content: '';
position: absolute; left: 0; top: 0; bottom: 0;
width: 2px;
background: linear-gradient(to bottom, var(--accent-cyan), var(--accent-violet), var(--accent-emerald));
opacity: 0.4;
}
.tl-item {
position: relative;
margin-bottom: 1.5rem;
padding-left: 1rem;
}
.tl-item::before {
content: '';
position: absolute; left: -2.35rem; top: 0.3em;
width: 10px; height: 10px;
border-radius: 50%;
background: var(--accent-cyan);
border: 2px solid var(--bg-primary);
}
.tl-date {
font-family: var(--font-display);
font-size: 0.75rem;
color: var(--accent-cyan);
letter-spacing: 0.08em;
margin-bottom: 0.15em;
}
.tl-title {
font-weight: 600;
font-size: 1rem;
margin-bottom: 0.15em;
}
.tl-desc {
font-size: 0.85rem;
color: var(--text-secondary);
}
/* ===========================================
REPO BADGE
=========================================== */
.repo-badge {
display: inline-block;
font-family: var(--font-display);
font-size: 0.65rem;
padding: 2px 8px;
border-radius: 4px;
font-weight: 600;
letter-spacing: 0.05em;
margin-left: 0.5em;
vertical-align: middle;
}
.badge-frontend { background: rgba(34,211,238,0.15); color: var(--accent-cyan); }
.badge-server { background: rgba(167,139,250,0.15); color: var(--accent-violet); }
.badge-sdk { background: rgba(52,211,153,0.15); color: var(--accent-emerald); }
/* ===========================================
PILL LIST
=========================================== */
.pill-list {
display: flex; flex-wrap: wrap; gap: 0.5rem;
margin-top: 0.8rem;
}
.pill {
display: inline-block;
font-size: 0.78rem;
padding: 0.3em 0.9em;
border-radius: 100px;
background: var(--bg-card);
border: 1px solid rgba(255,255,255,0.08);
color: var(--text-secondary);
}
/* ===========================================
SECTION HEADER
=========================================== */
.section-header {
display: flex;
align-items: center;
gap: 0.7rem;
margin-bottom: 0.3rem;
}
.section-icon {
width: 36px; height: 36px;
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 1.1rem;
}
/* ===========================================
KEYBOARD HINT
=========================================== */
.keyboard-hint {
position: fixed; bottom: 1.5rem; left: 2rem;
font-size: 0.75rem; color: var(--text-muted);
z-index: 100;
display: flex; align-items: center; gap: 0.5rem;
opacity: 0;
animation: hintFade 0.6s 2s forwards;
}
.key {
display: inline-block;
padding: 2px 8px;
border: 1px solid var(--text-muted);
border-radius: 4px;
font-family: var(--font-display);
font-size: 0.7rem;
}
@keyframes hintFade { to { opacity: 1; } }
/* ===========================================
CLOSING SLIDE
=========================================== */
.closing-slide {
text-align: center;
background:
radial-gradient(ellipse at 50% 50%, rgba(34,211,238,0.06) 0%, transparent 60%),
var(--bg-primary);
}
.closing-slide h2 {
font-size: clamp(1.8rem, 4vw, 3rem);
background: linear-gradient(135deg, var(--accent-emerald), var(--accent-cyan));
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
background-clip: text;
}
/* ===========================================
RESPONSIVE
=========================================== */
@media (max-width: 768px) {
.nav-dots, .keyboard-hint { display: none; }
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.card-grid { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<!-- Progress bar -->
<div class="progress-bar" id="progressBar"></div>
<!-- Navigation dots -->
<nav class="nav-dots" id="navDots"></nav>
<!-- Slide counter -->
<div class="slide-counter" id="slideCounter"></div>
<!-- Keyboard hint -->
<div class="keyboard-hint">
<span class="key"></span><span class="key"></span> or <span class="key">Space</span> to navigate
</div>
<!-- ======================================
SLIDE 1: TITLE
====================================== -->
<section class="slide title-slide">
<div class="reveal">
<span class="label" style="background: var(--glow-cyan); color: var(--accent-cyan);">Weekly Engineering Update</span>
</div>
<h1 class="reveal">Helix Engage</h1>
<p class="subtitle reveal">Contact Center CRM · Real-time Telephony · AI Copilot</p>
<p class="date-range reveal">March 18 25, 2026</p>
</section>
<!-- ======================================
SLIDE 2: AT A GLANCE
====================================== -->
<section class="slide">
<div class="reveal">
<span class="label" style="background: var(--glow-violet); color: var(--accent-violet);">At a Glance</span>
</div>
<h2 class="reveal">Week in Numbers</h2>
<div class="stats-grid">
<div class="stat-card reveal">
<div class="stat-number cyan" data-count="78">0</div>
<div class="stat-label">Total Commits</div>
</div>
<div class="stat-card reveal">
<div class="stat-number violet" data-count="3">0</div>
<div class="stat-label">Repositories</div>
</div>
<div class="stat-card reveal">
<div class="stat-number emerald" data-count="8">0</div>
<div class="stat-label">Days Active</div>
</div>
<div class="stat-card reveal">
<div class="stat-number amber" data-count="50">0</div>
<div class="stat-label">Frontend Commits</div>
</div>
</div>
<div class="pill-list reveal" style="margin-top:1.5rem; justify-content: center;">
<span class="pill" style="border-color: rgba(34,211,238,0.3); color: var(--accent-cyan);">helix-engage <b>50</b></span>
<span class="pill" style="border-color: rgba(167,139,250,0.3); color: var(--accent-violet);">helix-engage-server <b>27</b></span>
<span class="pill" style="border-color: rgba(52,211,153,0.3); color: var(--accent-emerald);">FortyTwoApps/SDK <b>1</b></span>
</div>
</section>
<!-- ======================================
SLIDE 3: TELEPHONY & SIP
====================================== -->
<section class="slide">
<div class="reveal">
<div class="section-header">
<div class="section-icon" style="background: var(--glow-cyan);">📞</div>
<span class="label" style="background: var(--glow-cyan); color: var(--accent-cyan);">Core Infrastructure</span>
</div>
</div>
<h2 class="reveal">Telephony & SIP Overhaul</h2>
<div class="card-grid">
<div class="content-card reveal">
<h3 style="color: var(--accent-cyan);">Outbound Calling <span class="repo-badge badge-frontend">Frontend</span></h3>
<ul>
<li>Direct SIP call from browser — no Kookoo bridge needed</li>
<li>Immediate call card UI with auto-answer SIP bridge</li>
<li>End Call label fix, force active state after auto-answer</li>
<li>Reset outboundPending on call end to prevent inbound poisoning</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-violet);">Ozonetel Integration <span class="repo-badge badge-server">Server</span></h3>
<ul>
<li>Ozonetel V3 dial endpoint + webhook handler for call events</li>
<li>Kookoo IVR outbound bridging (deprecated → direct SIP)</li>
<li>Set Disposition API for ACW release</li>
<li>Force Ready endpoint for agent state management</li>
<li>Token: 10-min cache, 401 invalidation, refresh on login</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-cyan);">SIP & Agent State <span class="repo-badge badge-frontend">Frontend</span></h3>
<ul>
<li>SIP driven by Agent entity with token refresh</li>
<li>Dynamic SIP from agentConfig, logout cleanup, heartbeat</li>
<li>Centralised outbound dial into <code>useSip().dialOutbound()</code></li>
<li>UCID tracking from SIP headers for Ozonetel disposition</li>
<li>Network indicator for connection health</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-violet);">Multi-Agent & Sessions <span class="repo-badge badge-server">Server</span></h3>
<ul>
<li>Multi-agent SIP with Redis session lockout</li>
<li>Strict duplicate login lockout — one device per agent</li>
<li>Session lock stores IP + timestamp for debugging</li>
<li>SSE agent state broadcast for real-time supervisor view</li>
</ul>
</div>
</div>
</section>
<!-- ======================================
SLIDE 4: CALL DESK & UX
====================================== -->
<section class="slide">
<div class="reveal">
<div class="section-header">
<div class="section-icon" style="background: var(--glow-emerald);">🖥️</div>
<span class="label" style="background: var(--glow-emerald); color: var(--accent-emerald);">User Experience</span>
</div>
</div>
<h2 class="reveal">Call Desk & Agent UX</h2>
<div class="card-grid">
<div class="content-card reveal">
<h3 style="color: var(--accent-emerald);">Call Desk Redesign</h3>
<ul>
<li>2-panel layout with collapsible sidebar & inline AI</li>
<li>Collapsible context panel, worklist/calls tabs, phone numbers</li>
<li>Pinned header & chat input, numpad dialler</li>
<li>Ringtone support for incoming calls</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-emerald);">Post-Call Workflow</h3>
<ul>
<li>Disposition → appointment booking → follow-up creation</li>
<li>Disposition returns straight to worklist — no intermediate screens</li>
<li>Send disposition to sidecar with UCID for Ozonetel ACW</li>
<li>Enquiry in post-call, appointment skip button</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-emerald);">UI Polish</h3>
<ul>
<li>FontAwesome Pro Duotone icon migration (all icons)</li>
<li>Tooltips, sticky headers, roles, search, AI prompts</li>
<li>Fix React error #520 (isRowHeader) in production tables</li>
<li>AI scroll containment, brand tokens refresh</li>
</ul>
</div>
</div>
</section>
<!-- ======================================
SLIDE 5: FEATURES SHIPPED
====================================== -->
<section class="slide">
<div class="reveal">
<div class="section-header">
<div class="section-icon" style="background: rgba(251,191,36,0.15);">🚀</div>
<span class="label" style="background: rgba(251,191,36,0.15); color: var(--accent-amber);">Features Shipped</span>
</div>
</div>
<h2 class="reveal">Major Features</h2>
<div class="card-grid">
<div class="content-card reveal">
<h3 style="color: var(--accent-amber);">Supervisor Module</h3>
<ul>
<li>Team performance analytics page</li>
<li>Live monitor with active calls visibility</li>
<li>Master data management pages</li>
<li>Server: team perf + active calls endpoints</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-amber);">Missed Call Queue (Phase 2)</h3>
<ul>
<li>Missed call queue ingestion & worklist</li>
<li>Auto-assignment engine for agents</li>
<li>Login redesign with role-based routing</li>
<li>Lead lookup for missed callers</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-amber);">Agent Features (Phase 1)</h3>
<ul>
<li>Agent status toggle (Ready / Not Ready / Break)</li>
<li>Global search across patients, leads, calls</li>
<li>Enquiry form for new patient intake</li>
<li>My Performance page + logout modal</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-amber);">Recording Analysis</h3>
<ul>
<li>Deepgram diarization + AI insights</li>
<li>Redis caching layer for analysis results</li>
<li>Full-stack: frontend player + server module</li>
</ul>
</div>
</div>
</section>
<!-- ======================================
SLIDE 6: DATA & BACKEND
====================================== -->
<section class="slide">
<div class="reveal">
<div class="section-header">
<div class="section-icon" style="background: var(--glow-violet);">⚙️</div>
<span class="label" style="background: var(--glow-violet); color: var(--accent-violet);">Backend & Data</span>
</div>
</div>
<h2 class="reveal">Backend & Data Layer</h2>
<div class="card-grid">
<div class="content-card reveal">
<h3 style="color: var(--accent-violet);">Platform Data Wiring</h3>
<ul>
<li>Migrated frontend to Jotai + Vercel AI SDK</li>
<li>Corrected all 7 GraphQL queries (field names, LINKS/PHONES)</li>
<li>Webhook handler for Ozonetel call records</li>
<li>Complete seeder: 5 doctors, appointments linked, agent names match</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-violet);">Server Endpoints</h3>
<ul>
<li>Call control, recording, CDR, missed calls, live call assist</li>
<li>Agent summary, AHT, performance aggregation</li>
<li>Token refresh endpoint for auto-renewal</li>
<li>Search module with full-text capabilities</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-violet);">Data Pages Built</h3>
<ul>
<li>Worklist table, call history, patients, dashboard</li>
<li>Reports, team dashboard, campaigns, settings</li>
<li>Agent detail page, campaign edit slideout</li>
<li>Appointments page with data refresh on login</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-emerald);">SDK App <span class="repo-badge badge-sdk">FortyTwoApps</span></h3>
<ul>
<li>Helix Engage SDK app entity definitions</li>
<li>Call center CRM object model for Fortytwo platform</li>
<li>Foundation for platform-native data integration</li>
</ul>
</div>
</div>
</section>
<!-- ======================================
SLIDE 7: DEPLOYMENT & OPS
====================================== -->
<section class="slide">
<div class="reveal">
<div class="section-header">
<div class="section-icon" style="background: rgba(251,113,133,0.15);">🛠️</div>
<span class="label" style="background: rgba(251,113,133,0.15); color: var(--accent-rose);">Operations</span>
</div>
</div>
<h2 class="reveal">Deployment & DevOps</h2>
<div class="card-grid">
<div class="content-card reveal">
<h3 style="color: var(--accent-rose);">Deployment</h3>
<ul>
<li>Deployed to Hostinger VPS with Docker</li>
<li>Switched to global_healthx Ozonetel account</li>
<li>Dockerfile for server-side containerization</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-rose);">AI & Testing</h3>
<ul>
<li>Migrated AI to Vercel AI SDK + OpenAI provider</li>
<li>AI flow test script — validates auth, lead, patient, doctor, appointments</li>
<li>Live call assist integration</li>
</ul>
</div>
<div class="content-card reveal">
<h3 style="color: var(--accent-rose);">Documentation</h3>
<ul>
<li>Team onboarding README with architecture guide</li>
<li>Supervisor module spec + implementation plan</li>
<li>Multi-agent spec + plan</li>
<li>Next session plans documented in commits</li>
</ul>
</div>
</div>
</section>
<!-- ======================================
SLIDE 8: TIMELINE
====================================== -->
<section class="slide">
<div class="reveal">
<div class="section-header">
<div class="section-icon" style="background: var(--glow-cyan);">📅</div>
<span class="label" style="background: var(--glow-cyan); color: var(--accent-cyan);">Day by Day</span>
</div>
</div>
<h2 class="reveal">Development Timeline</h2>
<div class="timeline reveal" style="max-height: 60vh; overflow-y: auto; padding-right: 1rem;">
<div class="tl-item">
<div class="tl-date">MAR 18 (Tue)</div>
<div class="tl-title">Foundation Day</div>
<div class="tl-desc">Call desk redesign, Jotai + Vercel AI SDK migration, seeder with 5 doctors + linked appointments, AI flow test script, deployed to VPS</div>
</div>
<div class="tl-item">
<div class="tl-date">MAR 19 (Wed)</div>
<div class="tl-title">Data Layer Sprint</div>
<div class="tl-desc">All data pages built (worklist, call history, patients, dashboard, reports), post-call workflow (disposition → booking), GraphQL fixes, Kookoo IVR outbound, outbound call UI</div>
</div>
<div class="tl-item">
<div class="tl-date">MAR 20 (Thu)</div>
<div class="tl-title">Telephony Breakthrough</div>
<div class="tl-desc">Direct SIP call from browser replacing Kookoo bridge, UCID tracking, Force Ready, Ozonetel Set Disposition, telephony overhaul</div>
</div>
<div class="tl-item">
<div class="tl-date">MAR 21 (Fri)</div>
<div class="tl-title">Agent Experience</div>
<div class="tl-desc">Phase 1 shipped — agent status toggle, global search, enquiry form, My Performance page, full FontAwesome icon migration, agent summary/AHT endpoints</div>
</div>
<div class="tl-item">
<div class="tl-date">MAR 23 (Sun)</div>
<div class="tl-title">Scale & Reliability</div>
<div class="tl-desc">Phase 2 — missed call queue + auto-assignment, multi-agent SIP with Redis lockout, duplicate login prevention, Patient 360 rewrite, onboarding docs, SDK entity defs</div>
</div>
<div class="tl-item">
<div class="tl-date">MAR 24 (Mon)</div>
<div class="tl-title">Supervisor Module</div>
<div class="tl-desc">Supervisor module with team performance + live monitor + master data, SSE agent state, UUID fix, maintenance module, QA bug sweep, supervisor endpoints</div>
</div>
<div class="tl-item">
<div class="tl-date">MAR 25 (Tue)</div>
<div class="tl-title">Intelligence Layer</div>
<div class="tl-desc">Call recording analysis with Deepgram diarization + AI insights, SIP driven by Agent entity, token refresh, network indicator</div>
</div>
</div>
</section>
<!-- ======================================
SLIDE 9: CLOSING
====================================== -->
<section class="slide closing-slide">
<h2 class="reveal">78 commits. 8 days. Ship mode.&nbsp;🚢</h2>
<p class="reveal" style="color: var(--text-secondary); margin-top: 0.6em; font-size: 1.1rem; max-width: 600px; margin-inline: auto;">
From browser-native SIP calling to AI-powered recording analysis — Helix Engage is becoming a production contact center platform.
</p>
<div class="pill-list reveal" style="justify-content: center; margin-top: 1.5rem;">
<span class="pill" style="border-color: rgba(34,211,238,0.3); color: var(--accent-cyan);">SIP Calling ✓</span>
<span class="pill" style="border-color: rgba(167,139,250,0.3); color: var(--accent-violet);">Multi-Agent ✓</span>
<span class="pill" style="border-color: rgba(52,211,153,0.3); color: var(--accent-emerald);">Supervisor Module ✓</span>
<span class="pill" style="border-color: rgba(251,191,36,0.3); color: var(--accent-amber);">AI Copilot ✓</span>
<span class="pill" style="border-color: rgba(251,113,133,0.3); color: var(--accent-rose);">Recording Analysis ✓</span>
</div>
<p class="reveal" style="color: var(--text-muted); margin-top: 2rem; font-size: 0.8rem;">Satya Suman Sari · FortyTwo Platform</p>
</section>
<!-- ===========================================
SLIDE PRESENTATION CONTROLLER
=========================================== -->
<script>
class SlidePresentation {
constructor() {
this.slides = document.querySelectorAll('.slide');
this.progressBar = document.getElementById('progressBar');
this.navDots = document.getElementById('navDots');
this.slideCounter = document.getElementById('slideCounter');
this.currentSlide = 0;
this.createNavDots();
this.setupObserver();
this.setupKeyboard();
this.setupTouch();
this.animateCounters();
this.updateCounter();
}
/* --- Navigation dots --- */
createNavDots() {
this.slides.forEach((_, i) => {
const dot = document.createElement('button');
dot.classList.add('nav-dot');
dot.setAttribute('aria-label', `Go to slide ${i + 1}`);
dot.addEventListener('click', () => this.goToSlide(i));
this.navDots.appendChild(dot);
});
}
/* --- Intersection Observer for reveal animations --- */
setupObserver() {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
const idx = Array.from(this.slides).indexOf(entry.target);
if (idx !== -1) {
this.currentSlide = idx;
this.updateProgress();
this.updateDots();
this.updateCounter();
if (idx === 1) this.animateCounters();
}
}
});
}, { threshold: 0.45 });
this.slides.forEach(slide => observer.observe(slide));
}
/* --- Keyboard navigation --- */
setupKeyboard() {
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown' || e.key === ' ' || e.key === 'ArrowRight') {
e.preventDefault();
this.goToSlide(Math.min(this.currentSlide + 1, this.slides.length - 1));
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
e.preventDefault();
this.goToSlide(Math.max(this.currentSlide - 1, 0));
}
});
}
/* --- Touch swipe support --- */
setupTouch() {
let startY = 0;
document.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; });
document.addEventListener('touchend', (e) => {
const dy = startY - e.changedTouches[0].clientY;
if (Math.abs(dy) > 50) {
if (dy > 0) this.goToSlide(Math.min(this.currentSlide + 1, this.slides.length - 1));
else this.goToSlide(Math.max(this.currentSlide - 1, 0));
}
});
}
goToSlide(idx) {
this.slides[idx].scrollIntoView({ behavior: 'smooth' });
}
updateProgress() {
const pct = ((this.currentSlide) / (this.slides.length - 1)) * 100;
this.progressBar.style.width = pct + '%';
}
updateDots() {
this.navDots.querySelectorAll('.nav-dot').forEach((dot, i) => {
dot.classList.toggle('active', i === this.currentSlide);
});
}
updateCounter() {
this.slideCounter.textContent = `${this.currentSlide + 1} / ${this.slides.length}`;
}
/* --- Animate counter numbers --- */
animateCounters() {
document.querySelectorAll('[data-count]').forEach(el => {
const target = parseInt(el.dataset.count);
const duration = 1200;
const start = performance.now();
const animate = (now) => {
const elapsed = now - start;
const progress = Math.min(elapsed / duration, 1);
const eased = 1 - Math.pow(1 - progress, 3);
el.textContent = Math.round(eased * target);
if (progress < 1) requestAnimationFrame(animate);
};
requestAnimationFrame(animate);
});
}
}
// Initialize
new SlidePresentation();
</script>
</body>
</html>