mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
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:
680
docs/generate-pptx.cjs
Normal file
680
docs/generate-pptx.cjs
Normal file
@@ -0,0 +1,680 @@
|
||||
/**
|
||||
* Helix Engage — Weekly Update (Mar 18–25, 2026)
|
||||
* Light Mode PowerPoint Generator via PptxGenJS
|
||||
*/
|
||||
const PptxGenJS = require("pptxgenjs");
|
||||
|
||||
// ── Design Tokens (Light Mode) ─────────────────────────────────────────
|
||||
const C = {
|
||||
bg: "FFFFFF",
|
||||
bgSubtle: "F8FAFC",
|
||||
bgCard: "F1F5F9",
|
||||
bgCardAlt: "E2E8F0",
|
||||
text: "1E293B",
|
||||
textSec: "475569",
|
||||
textMuted: "94A3B8",
|
||||
accent1: "0EA5E9", // Sky blue (telephony)
|
||||
accent2: "8B5CF6", // Violet (server/backend)
|
||||
accent3: "10B981", // Emerald (UX)
|
||||
accent4: "F59E0B", // Amber (features)
|
||||
accent5: "EF4444", // Rose (ops)
|
||||
accent6: "6366F1", // Indigo (timeline)
|
||||
white: "FFFFFF",
|
||||
border: "CBD5E1",
|
||||
};
|
||||
|
||||
const FONT = {
|
||||
heading: "Arial",
|
||||
body: "Arial",
|
||||
};
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
function addSlideNumber(slide, num, total) {
|
||||
slide.addText(`${num} / ${total}`, {
|
||||
x: 8.8, y: 5.2, w: 1.2, h: 0.3,
|
||||
fontSize: 8, color: C.textMuted,
|
||||
fontFace: FONT.body,
|
||||
align: "right",
|
||||
});
|
||||
}
|
||||
|
||||
function addAccentBar(slide, color) {
|
||||
slide.addShape("rect", {
|
||||
x: 0, y: 0, w: 10, h: 0.06,
|
||||
fill: { color },
|
||||
});
|
||||
}
|
||||
|
||||
function addLabel(slide, text, color, x, y) {
|
||||
slide.addShape("roundRect", {
|
||||
x, y, w: text.length * 0.09 + 0.4, h: 0.3,
|
||||
fill: { color, transparency: 88 },
|
||||
rectRadius: 0.15,
|
||||
});
|
||||
slide.addText(text.toUpperCase(), {
|
||||
x, y, w: text.length * 0.09 + 0.4, h: 0.3,
|
||||
fontSize: 7, fontFace: FONT.heading, bold: true,
|
||||
color, align: "center", valign: "middle",
|
||||
letterSpacing: 2,
|
||||
});
|
||||
}
|
||||
|
||||
function addCard(slide, opts) {
|
||||
const { x, y, w, h, title, titleColor, items, badge } = opts;
|
||||
// Card background
|
||||
slide.addShape("roundRect", {
|
||||
x, y, w, h,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: C.border, width: 0.5 },
|
||||
rectRadius: 0.1,
|
||||
});
|
||||
// Title
|
||||
const titleText = badge
|
||||
? [{ text: title + " ", options: { bold: true, color: titleColor, fontSize: 11 } },
|
||||
{ text: badge, options: { bold: true, color: C.white, fontSize: 7, highlight: titleColor } }]
|
||||
: title;
|
||||
slide.addText(titleText, {
|
||||
x: x + 0.2, y: y + 0.08, w: w - 0.4, h: 0.35,
|
||||
fontSize: 11, fontFace: FONT.heading, bold: true,
|
||||
color: titleColor,
|
||||
});
|
||||
// Items as bullet list
|
||||
if (items && items.length > 0) {
|
||||
slide.addText(
|
||||
items.map(item => ({
|
||||
text: item,
|
||||
options: {
|
||||
fontSize: 8.5, fontFace: FONT.body, color: C.textSec,
|
||||
bullet: { type: "bullet", style: "arabicPeriod" },
|
||||
paraSpaceAfter: 2,
|
||||
breakLine: true,
|
||||
},
|
||||
})),
|
||||
{
|
||||
x: x + 0.2, y: y + 0.4, w: w - 0.4, h: h - 0.5,
|
||||
valign: "top",
|
||||
bullet: { type: "bullet" },
|
||||
lineSpacingMultiple: 1.1,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Build Presentation ──────────────────────────────────────────────────
|
||||
async function build() {
|
||||
const pptx = new PptxGenJS();
|
||||
pptx.layout = "LAYOUT_16x9";
|
||||
pptx.author = "Satya Suman Sari";
|
||||
pptx.company = "FortyTwo Platform";
|
||||
pptx.title = "Helix Engage — Weekly Update (Mar 18–25, 2026)";
|
||||
pptx.subject = "Engineering Progress Report";
|
||||
|
||||
const TOTAL = 9;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 1 — Title
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
|
||||
// Accent bar top
|
||||
addAccentBar(slide, C.accent1);
|
||||
|
||||
// Decorative side stripe
|
||||
slide.addShape("rect", {
|
||||
x: 0, y: 0, w: 0.12, h: 5.63,
|
||||
fill: { color: C.accent1 },
|
||||
});
|
||||
|
||||
// Label
|
||||
addLabel(slide, "Weekly Engineering Update", C.accent1, 3.0, 1.2);
|
||||
|
||||
// Title
|
||||
slide.addText("Helix Engage", {
|
||||
x: 1.0, y: 1.8, w: 8, h: 1.2,
|
||||
fontSize: 44, fontFace: FONT.heading, bold: true,
|
||||
color: C.accent1, align: "center",
|
||||
});
|
||||
|
||||
// Subtitle
|
||||
slide.addText("Contact Center CRM · Real-time Telephony · AI Copilot", {
|
||||
x: 1.5, y: 2.9, w: 7, h: 0.5,
|
||||
fontSize: 14, fontFace: FONT.body,
|
||||
color: C.textSec, align: "center",
|
||||
});
|
||||
|
||||
// Date
|
||||
slide.addText("March 18 – 25, 2026", {
|
||||
x: 3, y: 3.6, w: 4, h: 0.4,
|
||||
fontSize: 12, fontFace: FONT.heading, bold: true,
|
||||
color: C.textMuted, align: "center",
|
||||
letterSpacing: 3,
|
||||
});
|
||||
|
||||
// Bottom decoration
|
||||
slide.addShape("rect", {
|
||||
x: 3.5, y: 4.2, w: 3, h: 0.04,
|
||||
fill: { color: C.accent2 },
|
||||
});
|
||||
|
||||
// Author
|
||||
slide.addText("Satya Suman Sari · FortyTwo Platform", {
|
||||
x: 2, y: 4.5, w: 6, h: 0.35,
|
||||
fontSize: 9, fontFace: FONT.body,
|
||||
color: C.textMuted, align: "center",
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 1, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 2 — At a Glance
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent2);
|
||||
|
||||
addLabel(slide, "At a Glance", C.accent2, 0.5, 0.3);
|
||||
|
||||
slide.addText("Week in Numbers", {
|
||||
x: 0.5, y: 0.65, w: 5, h: 0.5,
|
||||
fontSize: 24, fontFace: FONT.heading, bold: true,
|
||||
color: C.text,
|
||||
});
|
||||
|
||||
// Stat cards
|
||||
const stats = [
|
||||
{ value: "78", label: "Total Commits", color: C.accent1 },
|
||||
{ value: "3", label: "Repositories", color: C.accent2 },
|
||||
{ value: "8", label: "Days Active", color: C.accent3 },
|
||||
{ value: "50", label: "Frontend Commits", color: C.accent4 },
|
||||
];
|
||||
|
||||
stats.forEach((s, i) => {
|
||||
const x = 0.5 + i * 2.35;
|
||||
// Card bg
|
||||
slide.addShape("roundRect", {
|
||||
x, y: 1.3, w: 2.1, h: 1.7,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: C.border, width: 0.5 },
|
||||
rectRadius: 0.12,
|
||||
});
|
||||
// Accent top line
|
||||
slide.addShape("rect", {
|
||||
x: x + 0.2, y: 1.35, w: 1.7, h: 0.035,
|
||||
fill: { color: s.color },
|
||||
});
|
||||
// Number
|
||||
slide.addText(s.value, {
|
||||
x, y: 1.5, w: 2.1, h: 0.9,
|
||||
fontSize: 36, fontFace: FONT.heading, bold: true,
|
||||
color: s.color, align: "center", valign: "middle",
|
||||
});
|
||||
// Label
|
||||
slide.addText(s.label, {
|
||||
x, y: 2.4, w: 2.1, h: 0.4,
|
||||
fontSize: 9, fontFace: FONT.body,
|
||||
color: C.textSec, align: "center",
|
||||
});
|
||||
});
|
||||
|
||||
// Repo breakdown pills
|
||||
const repos = [
|
||||
{ name: "helix-engage", count: "50", clr: C.accent1 },
|
||||
{ name: "helix-engage-server", count: "27", clr: C.accent2 },
|
||||
{ name: "FortyTwoApps/SDK", count: "1", clr: C.accent3 },
|
||||
];
|
||||
repos.forEach((r, i) => {
|
||||
const x = 1.5 + i * 2.8;
|
||||
slide.addShape("roundRect", {
|
||||
x, y: 3.4, w: 2.5, h: 0.4,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: r.clr, width: 1 },
|
||||
rectRadius: 0.2,
|
||||
});
|
||||
slide.addText(`${r.name} ${r.count}`, {
|
||||
x, y: 3.4, w: 2.5, h: 0.4,
|
||||
fontSize: 9, fontFace: FONT.heading, bold: true,
|
||||
color: r.clr, align: "center", valign: "middle",
|
||||
});
|
||||
});
|
||||
|
||||
// Summary text
|
||||
slide.addText("3 repos · 7 working days · 78 commits shipped to production", {
|
||||
x: 1, y: 4.2, w: 8, h: 0.35,
|
||||
fontSize: 10, fontFace: FONT.body, italic: true,
|
||||
color: C.textMuted, align: "center",
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 2, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 3 — Telephony & SIP
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent1);
|
||||
|
||||
addLabel(slide, "Core Infrastructure", C.accent1, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "☎ ", options: { fontSize: 22 } },
|
||||
{ text: "Telephony & SIP Overhaul", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Outbound Calling", titleColor: C.accent1, badge: "Frontend",
|
||||
items: [
|
||||
"Direct SIP call from browser — no Kookoo bridge",
|
||||
"Immediate call card UI with auto-answer SIP bridge",
|
||||
"End Call label fix, force active state after auto-answer",
|
||||
"Reset outboundPending on call end",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Ozonetel Integration", titleColor: C.accent2, badge: "Server",
|
||||
items: [
|
||||
"Ozonetel V3 dial endpoint + webhook handler",
|
||||
"Set Disposition API for ACW release",
|
||||
"Force Ready endpoint for agent state mgmt",
|
||||
"Token: 10-min cache, 401 invalidation, refresh on login",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "SIP & Agent State", titleColor: C.accent1, badge: "Frontend",
|
||||
items: [
|
||||
"SIP driven by Agent entity with token refresh",
|
||||
"Centralised outbound dial into useSip().dialOutbound()",
|
||||
"UCID tracking from SIP headers for disposition",
|
||||
"Network indicator for connection health",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Multi-Agent & Sessions", titleColor: C.accent2, badge: "Server",
|
||||
items: [
|
||||
"Multi-agent SIP with Redis session lockout",
|
||||
"Strict duplicate login — one device per agent",
|
||||
"Session lock stores IP + timestamp for debugging",
|
||||
"SSE agent state broadcast for supervisor view",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 3, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 4 — Call Desk & Agent UX
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent3);
|
||||
|
||||
addLabel(slide, "User Experience", C.accent3, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "🖥 ", options: { fontSize: 22 } },
|
||||
{ text: "Call Desk & Agent UX", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 3.05, h: 2.6,
|
||||
title: "Call Desk Redesign", titleColor: C.accent3,
|
||||
items: [
|
||||
"2-panel layout with collapsible sidebar & inline AI",
|
||||
"Collapsible context panel, worklist/calls tabs",
|
||||
"Pinned header & chat input, numpad dialler",
|
||||
"Ringtone support for incoming calls",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 3.55, y: 1.35, w: 3.05, h: 2.6,
|
||||
title: "Post-Call Workflow", titleColor: C.accent3,
|
||||
items: [
|
||||
"Disposition → appointment booking → follow-up",
|
||||
"Disposition returns straight to worklist",
|
||||
"Send disposition to sidecar with UCID for ACW",
|
||||
"Enquiry in post-call, appointment skip button",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 6.8, y: 1.35, w: 2.9, h: 2.6,
|
||||
title: "UI Polish", titleColor: C.accent3,
|
||||
items: [
|
||||
"FontAwesome Pro Duotone icon migration",
|
||||
"Tooltips, sticky headers, roles, search",
|
||||
"Fix React error #520 in prod tables",
|
||||
"AI scroll containment, brand tokens refresh",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 4, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 5 — Features Shipped
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent4);
|
||||
|
||||
addLabel(slide, "Features Shipped", C.accent4, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "🚀 ", options: { fontSize: 22 } },
|
||||
{ text: "Major Features", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Supervisor Module", titleColor: C.accent4,
|
||||
items: [
|
||||
"Team performance analytics page",
|
||||
"Live monitor with active calls visibility",
|
||||
"Master data management pages",
|
||||
"Server: team perf + active calls endpoints",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Missed Call Queue (Phase 2)", titleColor: C.accent4,
|
||||
items: [
|
||||
"Missed call queue ingestion & worklist",
|
||||
"Auto-assignment engine for agents",
|
||||
"Login redesign with role-based routing",
|
||||
"Lead lookup for missed callers",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Agent Features (Phase 1)", titleColor: C.accent4,
|
||||
items: [
|
||||
"Agent status toggle (Ready / Not Ready / Break)",
|
||||
"Global search across patients, leads, calls",
|
||||
"Enquiry form for new patient intake",
|
||||
"My Performance page + logout modal",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Recording Analysis", titleColor: C.accent4,
|
||||
items: [
|
||||
"Deepgram diarization + AI insights",
|
||||
"Redis caching layer for analysis results",
|
||||
"Full-stack: frontend player + server module",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 5, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 6 — Backend & Data
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent2);
|
||||
|
||||
addLabel(slide, "Backend & Data", C.accent2, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "⚙ ", options: { fontSize: 22 } },
|
||||
{ text: "Backend & Data Layer", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Platform Data Wiring", titleColor: C.accent2,
|
||||
items: [
|
||||
"Migrated frontend to Jotai + Vercel AI SDK",
|
||||
"Corrected all 7 GraphQL queries (fields, LINKS/PHONES)",
|
||||
"Webhook handler for Ozonetel call records",
|
||||
"Complete seeder: 5 doctors, appointments linked",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Server Endpoints", titleColor: C.accent2,
|
||||
items: [
|
||||
"Call control, recording, CDR, missed calls, live assist",
|
||||
"Agent summary, AHT, performance aggregation",
|
||||
"Token refresh endpoint for auto-renewal",
|
||||
"Search module with full-text capabilities",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Data Pages Built", titleColor: C.accent2,
|
||||
items: [
|
||||
"Worklist table, call history, patients, dashboard",
|
||||
"Reports, team dashboard, campaigns, settings",
|
||||
"Agent detail page, campaign edit slideout",
|
||||
"Appointments page with data refresh on login",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "SDK App", titleColor: C.accent3, badge: "FortyTwoApps",
|
||||
items: [
|
||||
"Helix Engage SDK app entity definitions",
|
||||
"Call center CRM object model for platform",
|
||||
"Foundation for platform-native data integration",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 6, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 7 — Deployment & Ops
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent5);
|
||||
|
||||
addLabel(slide, "Operations", C.accent5, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "🛠 ", options: { fontSize: 22 } },
|
||||
{ text: "Deployment & DevOps", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 3.05, h: 2.2,
|
||||
title: "Deployment", titleColor: C.accent5,
|
||||
items: [
|
||||
"Deployed to Hostinger VPS with Docker",
|
||||
"Switched to global_healthx Ozonetel account",
|
||||
"Dockerfile for server-side containerization",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 3.55, y: 1.35, w: 3.05, h: 2.2,
|
||||
title: "AI & Testing", titleColor: C.accent5,
|
||||
items: [
|
||||
"Migrated AI to Vercel AI SDK + OpenAI provider",
|
||||
"AI flow test script — validates full pipeline",
|
||||
"Live call assist integration",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 6.8, y: 1.35, w: 2.9, h: 2.2,
|
||||
title: "Documentation", titleColor: C.accent5,
|
||||
items: [
|
||||
"Team onboarding README with arch guide",
|
||||
"Supervisor module spec + plan",
|
||||
"Multi-agent spec + plan",
|
||||
"Next session plans in commits",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 7, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 8 — Timeline
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent6);
|
||||
|
||||
addLabel(slide, "Day by Day", C.accent6, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "📅 ", options: { fontSize: 22 } },
|
||||
{ text: "Development Timeline", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
const timeline = [
|
||||
{ date: "MAR 18 (Tue)", title: "Foundation Day", desc: "Call desk redesign, Jotai + AI SDK migration, seeder, AI flow test, VPS deploy" },
|
||||
{ date: "MAR 19 (Wed)", title: "Data Layer Sprint", desc: "All data pages, post-call workflow, GraphQL fixes, Kookoo IVR, outbound UI" },
|
||||
{ date: "MAR 20 (Thu)", title: "Telephony Breakthrough", desc: "Direct SIP replacing Kookoo, UCID tracking, Force Ready, Set Disposition" },
|
||||
{ date: "MAR 21 (Fri)", title: "Agent Experience", desc: "Phase 1: status toggle, search, enquiry form, My Performance, FA icons, AHT" },
|
||||
{ date: "MAR 23 (Sun)", title: "Scale & Reliability", desc: "Phase 2: missed call queue, auto-assign, Redis lockout, Patient 360, SDK defs" },
|
||||
{ date: "MAR 24 (Mon)", title: "Supervisor Module", desc: "Team perf, live monitor, master data, SSE, UUID fix, maintenance, QA sweep" },
|
||||
{ date: "MAR 25 (Tue)", title: "Intelligence Layer", desc: "Deepgram diarization, AI insights, SIP via Agent entity, token refresh, network" },
|
||||
];
|
||||
|
||||
// Vertical line
|
||||
slide.addShape("rect", {
|
||||
x: 1.4, y: 1.3, w: 0.025, h: 4.0,
|
||||
fill: { color: C.accent6, transparency: 60 },
|
||||
});
|
||||
|
||||
timeline.forEach((entry, i) => {
|
||||
const y = 1.3 + i * 0.56;
|
||||
|
||||
// Dot
|
||||
slide.addShape("ellipse", {
|
||||
x: 1.32, y: y + 0.08, w: 0.18, h: 0.18,
|
||||
fill: { color: C.accent6 },
|
||||
line: { color: C.bg, width: 2 },
|
||||
});
|
||||
|
||||
// Date
|
||||
slide.addText(entry.date, {
|
||||
x: 1.7, y: y, w: 1.6, h: 0.22,
|
||||
fontSize: 7, fontFace: FONT.heading, bold: true,
|
||||
color: C.accent6,
|
||||
});
|
||||
|
||||
// Title
|
||||
slide.addText(entry.title, {
|
||||
x: 3.3, y: y, w: 2.0, h: 0.22,
|
||||
fontSize: 9, fontFace: FONT.heading, bold: true,
|
||||
color: C.text,
|
||||
});
|
||||
|
||||
// Description
|
||||
slide.addText(entry.desc, {
|
||||
x: 5.3, y: y, w: 4.2, h: 0.45,
|
||||
fontSize: 8, fontFace: FONT.body,
|
||||
color: C.textSec,
|
||||
valign: "top",
|
||||
});
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 8, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 9 — Closing
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent3);
|
||||
|
||||
// Big headline
|
||||
slide.addText("78 commits. 8 days. Ship mode.", {
|
||||
x: 0.5, y: 1.4, w: 9, h: 0.8,
|
||||
fontSize: 32, fontFace: FONT.heading, bold: true,
|
||||
color: C.accent3, align: "center",
|
||||
});
|
||||
|
||||
// Ship emoji
|
||||
slide.addText("🚢", {
|
||||
x: 4.2, y: 2.3, w: 1.6, h: 0.6,
|
||||
fontSize: 28, align: "center",
|
||||
});
|
||||
|
||||
// Description
|
||||
slide.addText(
|
||||
"From browser-native SIP calling to AI-powered recording analysis — Helix Engage is becoming a production contact center platform.",
|
||||
{
|
||||
x: 1.5, y: 3.0, w: 7, h: 0.6,
|
||||
fontSize: 11, fontFace: FONT.body,
|
||||
color: C.textSec, align: "center",
|
||||
lineSpacingMultiple: 1.3,
|
||||
}
|
||||
);
|
||||
|
||||
// Achievement pills
|
||||
const achievements = [
|
||||
{ text: "SIP Calling ✓", color: C.accent1 },
|
||||
{ text: "Multi-Agent ✓", color: C.accent2 },
|
||||
{ text: "Supervisor ✓", color: C.accent3 },
|
||||
{ text: "AI Copilot ✓", color: C.accent4 },
|
||||
{ text: "Recording Analysis ✓", color: C.accent5 },
|
||||
];
|
||||
|
||||
achievements.forEach((a, i) => {
|
||||
const x = 0.8 + i * 1.8;
|
||||
slide.addShape("roundRect", {
|
||||
x, y: 3.9, w: 1.6, h: 0.35,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: a.color, width: 1 },
|
||||
rectRadius: 0.17,
|
||||
});
|
||||
slide.addText(a.text, {
|
||||
x, y: 3.9, w: 1.6, h: 0.35,
|
||||
fontSize: 8, fontFace: FONT.heading, bold: true,
|
||||
color: a.color, align: "center", valign: "middle",
|
||||
});
|
||||
});
|
||||
|
||||
// Author
|
||||
slide.addText("Satya Suman Sari · FortyTwo Platform", {
|
||||
x: 2, y: 4.7, w: 6, h: 0.3,
|
||||
fontSize: 9, fontFace: FONT.body,
|
||||
color: C.textMuted, align: "center",
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 9, TOTAL);
|
||||
}
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────────────
|
||||
const outPath = "weekly-update-mar18-25.pptx";
|
||||
await pptx.writeFile({ fileName: outPath });
|
||||
console.log(`✅ Presentation saved: ${outPath}`);
|
||||
}
|
||||
|
||||
build().catch(err => {
|
||||
console.error("❌ Failed:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
680
docs/generate-pptx.js
Normal file
680
docs/generate-pptx.js
Normal file
@@ -0,0 +1,680 @@
|
||||
/**
|
||||
* Helix Engage — Weekly Update (Mar 18–25, 2026)
|
||||
* Light Mode PowerPoint Generator via PptxGenJS
|
||||
*/
|
||||
const PptxGenJS = require("pptxgenjs");
|
||||
|
||||
// ── Design Tokens (Light Mode) ─────────────────────────────────────────
|
||||
const C = {
|
||||
bg: "FFFFFF",
|
||||
bgSubtle: "F8FAFC",
|
||||
bgCard: "F1F5F9",
|
||||
bgCardAlt: "E2E8F0",
|
||||
text: "1E293B",
|
||||
textSec: "475569",
|
||||
textMuted: "94A3B8",
|
||||
accent1: "0EA5E9", // Sky blue (telephony)
|
||||
accent2: "8B5CF6", // Violet (server/backend)
|
||||
accent3: "10B981", // Emerald (UX)
|
||||
accent4: "F59E0B", // Amber (features)
|
||||
accent5: "EF4444", // Rose (ops)
|
||||
accent6: "6366F1", // Indigo (timeline)
|
||||
white: "FFFFFF",
|
||||
border: "CBD5E1",
|
||||
};
|
||||
|
||||
const FONT = {
|
||||
heading: "Arial",
|
||||
body: "Arial",
|
||||
};
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
function addSlideNumber(slide, num, total) {
|
||||
slide.addText(`${num} / ${total}`, {
|
||||
x: 8.8, y: 5.2, w: 1.2, h: 0.3,
|
||||
fontSize: 8, color: C.textMuted,
|
||||
fontFace: FONT.body,
|
||||
align: "right",
|
||||
});
|
||||
}
|
||||
|
||||
function addAccentBar(slide, color) {
|
||||
slide.addShape("rect", {
|
||||
x: 0, y: 0, w: 10, h: 0.06,
|
||||
fill: { color },
|
||||
});
|
||||
}
|
||||
|
||||
function addLabel(slide, text, color, x, y) {
|
||||
slide.addShape("roundRect", {
|
||||
x, y, w: text.length * 0.09 + 0.4, h: 0.3,
|
||||
fill: { color, transparency: 88 },
|
||||
rectRadius: 0.15,
|
||||
});
|
||||
slide.addText(text.toUpperCase(), {
|
||||
x, y, w: text.length * 0.09 + 0.4, h: 0.3,
|
||||
fontSize: 7, fontFace: FONT.heading, bold: true,
|
||||
color, align: "center", valign: "middle",
|
||||
letterSpacing: 2,
|
||||
});
|
||||
}
|
||||
|
||||
function addCard(slide, opts) {
|
||||
const { x, y, w, h, title, titleColor, items, badge } = opts;
|
||||
// Card background
|
||||
slide.addShape("roundRect", {
|
||||
x, y, w, h,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: C.border, width: 0.5 },
|
||||
rectRadius: 0.1,
|
||||
});
|
||||
// Title
|
||||
const titleText = badge
|
||||
? [{ text: title + " ", options: { bold: true, color: titleColor, fontSize: 11 } },
|
||||
{ text: badge, options: { bold: true, color: C.white, fontSize: 7, highlight: titleColor } }]
|
||||
: title;
|
||||
slide.addText(titleText, {
|
||||
x: x + 0.2, y: y + 0.08, w: w - 0.4, h: 0.35,
|
||||
fontSize: 11, fontFace: FONT.heading, bold: true,
|
||||
color: titleColor,
|
||||
});
|
||||
// Items as bullet list
|
||||
if (items && items.length > 0) {
|
||||
slide.addText(
|
||||
items.map(item => ({
|
||||
text: item,
|
||||
options: {
|
||||
fontSize: 8.5, fontFace: FONT.body, color: C.textSec,
|
||||
bullet: { type: "bullet", style: "arabicPeriod" },
|
||||
paraSpaceAfter: 2,
|
||||
breakLine: true,
|
||||
},
|
||||
})),
|
||||
{
|
||||
x: x + 0.2, y: y + 0.4, w: w - 0.4, h: h - 0.5,
|
||||
valign: "top",
|
||||
bullet: { type: "bullet" },
|
||||
lineSpacingMultiple: 1.1,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Build Presentation ──────────────────────────────────────────────────
|
||||
async function build() {
|
||||
const pptx = new PptxGenJS();
|
||||
pptx.layout = "LAYOUT_16x9";
|
||||
pptx.author = "Satya Suman Sari";
|
||||
pptx.company = "FortyTwo Platform";
|
||||
pptx.title = "Helix Engage — Weekly Update (Mar 18–25, 2026)";
|
||||
pptx.subject = "Engineering Progress Report";
|
||||
|
||||
const TOTAL = 9;
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 1 — Title
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
|
||||
// Accent bar top
|
||||
addAccentBar(slide, C.accent1);
|
||||
|
||||
// Decorative side stripe
|
||||
slide.addShape("rect", {
|
||||
x: 0, y: 0, w: 0.12, h: 5.63,
|
||||
fill: { color: C.accent1 },
|
||||
});
|
||||
|
||||
// Label
|
||||
addLabel(slide, "Weekly Engineering Update", C.accent1, 3.0, 1.2);
|
||||
|
||||
// Title
|
||||
slide.addText("Helix Engage", {
|
||||
x: 1.0, y: 1.8, w: 8, h: 1.2,
|
||||
fontSize: 44, fontFace: FONT.heading, bold: true,
|
||||
color: C.accent1, align: "center",
|
||||
});
|
||||
|
||||
// Subtitle
|
||||
slide.addText("Contact Center CRM · Real-time Telephony · AI Copilot", {
|
||||
x: 1.5, y: 2.9, w: 7, h: 0.5,
|
||||
fontSize: 14, fontFace: FONT.body,
|
||||
color: C.textSec, align: "center",
|
||||
});
|
||||
|
||||
// Date
|
||||
slide.addText("March 18 – 25, 2026", {
|
||||
x: 3, y: 3.6, w: 4, h: 0.4,
|
||||
fontSize: 12, fontFace: FONT.heading, bold: true,
|
||||
color: C.textMuted, align: "center",
|
||||
letterSpacing: 3,
|
||||
});
|
||||
|
||||
// Bottom decoration
|
||||
slide.addShape("rect", {
|
||||
x: 3.5, y: 4.2, w: 3, h: 0.04,
|
||||
fill: { color: C.accent2 },
|
||||
});
|
||||
|
||||
// Author
|
||||
slide.addText("Satya Suman Sari · FortyTwo Platform", {
|
||||
x: 2, y: 4.5, w: 6, h: 0.35,
|
||||
fontSize: 9, fontFace: FONT.body,
|
||||
color: C.textMuted, align: "center",
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 1, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 2 — At a Glance
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent2);
|
||||
|
||||
addLabel(slide, "At a Glance", C.accent2, 0.5, 0.3);
|
||||
|
||||
slide.addText("Week in Numbers", {
|
||||
x: 0.5, y: 0.65, w: 5, h: 0.5,
|
||||
fontSize: 24, fontFace: FONT.heading, bold: true,
|
||||
color: C.text,
|
||||
});
|
||||
|
||||
// Stat cards
|
||||
const stats = [
|
||||
{ value: "78", label: "Total Commits", color: C.accent1 },
|
||||
{ value: "3", label: "Repositories", color: C.accent2 },
|
||||
{ value: "8", label: "Days Active", color: C.accent3 },
|
||||
{ value: "50", label: "Frontend Commits", color: C.accent4 },
|
||||
];
|
||||
|
||||
stats.forEach((s, i) => {
|
||||
const x = 0.5 + i * 2.35;
|
||||
// Card bg
|
||||
slide.addShape("roundRect", {
|
||||
x, y: 1.3, w: 2.1, h: 1.7,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: C.border, width: 0.5 },
|
||||
rectRadius: 0.12,
|
||||
});
|
||||
// Accent top line
|
||||
slide.addShape("rect", {
|
||||
x: x + 0.2, y: 1.35, w: 1.7, h: 0.035,
|
||||
fill: { color: s.color },
|
||||
});
|
||||
// Number
|
||||
slide.addText(s.value, {
|
||||
x, y: 1.5, w: 2.1, h: 0.9,
|
||||
fontSize: 36, fontFace: FONT.heading, bold: true,
|
||||
color: s.color, align: "center", valign: "middle",
|
||||
});
|
||||
// Label
|
||||
slide.addText(s.label, {
|
||||
x, y: 2.4, w: 2.1, h: 0.4,
|
||||
fontSize: 9, fontFace: FONT.body,
|
||||
color: C.textSec, align: "center",
|
||||
});
|
||||
});
|
||||
|
||||
// Repo breakdown pills
|
||||
const repos = [
|
||||
{ name: "helix-engage", count: "50", clr: C.accent1 },
|
||||
{ name: "helix-engage-server", count: "27", clr: C.accent2 },
|
||||
{ name: "FortyTwoApps/SDK", count: "1", clr: C.accent3 },
|
||||
];
|
||||
repos.forEach((r, i) => {
|
||||
const x = 1.5 + i * 2.8;
|
||||
slide.addShape("roundRect", {
|
||||
x, y: 3.4, w: 2.5, h: 0.4,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: r.clr, width: 1 },
|
||||
rectRadius: 0.2,
|
||||
});
|
||||
slide.addText(`${r.name} ${r.count}`, {
|
||||
x, y: 3.4, w: 2.5, h: 0.4,
|
||||
fontSize: 9, fontFace: FONT.heading, bold: true,
|
||||
color: r.clr, align: "center", valign: "middle",
|
||||
});
|
||||
});
|
||||
|
||||
// Summary text
|
||||
slide.addText("3 repos · 7 working days · 78 commits shipped to production", {
|
||||
x: 1, y: 4.2, w: 8, h: 0.35,
|
||||
fontSize: 10, fontFace: FONT.body, italic: true,
|
||||
color: C.textMuted, align: "center",
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 2, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 3 — Telephony & SIP
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent1);
|
||||
|
||||
addLabel(slide, "Core Infrastructure", C.accent1, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "☎ ", options: { fontSize: 22 } },
|
||||
{ text: "Telephony & SIP Overhaul", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Outbound Calling", titleColor: C.accent1, badge: "Frontend",
|
||||
items: [
|
||||
"Direct SIP call from browser — no Kookoo bridge",
|
||||
"Immediate call card UI with auto-answer SIP bridge",
|
||||
"End Call label fix, force active state after auto-answer",
|
||||
"Reset outboundPending on call end",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Ozonetel Integration", titleColor: C.accent2, badge: "Server",
|
||||
items: [
|
||||
"Ozonetel V3 dial endpoint + webhook handler",
|
||||
"Set Disposition API for ACW release",
|
||||
"Force Ready endpoint for agent state mgmt",
|
||||
"Token: 10-min cache, 401 invalidation, refresh on login",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "SIP & Agent State", titleColor: C.accent1, badge: "Frontend",
|
||||
items: [
|
||||
"SIP driven by Agent entity with token refresh",
|
||||
"Centralised outbound dial into useSip().dialOutbound()",
|
||||
"UCID tracking from SIP headers for disposition",
|
||||
"Network indicator for connection health",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Multi-Agent & Sessions", titleColor: C.accent2, badge: "Server",
|
||||
items: [
|
||||
"Multi-agent SIP with Redis session lockout",
|
||||
"Strict duplicate login — one device per agent",
|
||||
"Session lock stores IP + timestamp for debugging",
|
||||
"SSE agent state broadcast for supervisor view",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 3, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 4 — Call Desk & Agent UX
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent3);
|
||||
|
||||
addLabel(slide, "User Experience", C.accent3, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "🖥 ", options: { fontSize: 22 } },
|
||||
{ text: "Call Desk & Agent UX", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 3.05, h: 2.6,
|
||||
title: "Call Desk Redesign", titleColor: C.accent3,
|
||||
items: [
|
||||
"2-panel layout with collapsible sidebar & inline AI",
|
||||
"Collapsible context panel, worklist/calls tabs",
|
||||
"Pinned header & chat input, numpad dialler",
|
||||
"Ringtone support for incoming calls",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 3.55, y: 1.35, w: 3.05, h: 2.6,
|
||||
title: "Post-Call Workflow", titleColor: C.accent3,
|
||||
items: [
|
||||
"Disposition → appointment booking → follow-up",
|
||||
"Disposition returns straight to worklist",
|
||||
"Send disposition to sidecar with UCID for ACW",
|
||||
"Enquiry in post-call, appointment skip button",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 6.8, y: 1.35, w: 2.9, h: 2.6,
|
||||
title: "UI Polish", titleColor: C.accent3,
|
||||
items: [
|
||||
"FontAwesome Pro Duotone icon migration",
|
||||
"Tooltips, sticky headers, roles, search",
|
||||
"Fix React error #520 in prod tables",
|
||||
"AI scroll containment, brand tokens refresh",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 4, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 5 — Features Shipped
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent4);
|
||||
|
||||
addLabel(slide, "Features Shipped", C.accent4, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "🚀 ", options: { fontSize: 22 } },
|
||||
{ text: "Major Features", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Supervisor Module", titleColor: C.accent4,
|
||||
items: [
|
||||
"Team performance analytics page",
|
||||
"Live monitor with active calls visibility",
|
||||
"Master data management pages",
|
||||
"Server: team perf + active calls endpoints",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Missed Call Queue (Phase 2)", titleColor: C.accent4,
|
||||
items: [
|
||||
"Missed call queue ingestion & worklist",
|
||||
"Auto-assignment engine for agents",
|
||||
"Login redesign with role-based routing",
|
||||
"Lead lookup for missed callers",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Agent Features (Phase 1)", titleColor: C.accent4,
|
||||
items: [
|
||||
"Agent status toggle (Ready / Not Ready / Break)",
|
||||
"Global search across patients, leads, calls",
|
||||
"Enquiry form for new patient intake",
|
||||
"My Performance page + logout modal",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Recording Analysis", titleColor: C.accent4,
|
||||
items: [
|
||||
"Deepgram diarization + AI insights",
|
||||
"Redis caching layer for analysis results",
|
||||
"Full-stack: frontend player + server module",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 5, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 6 — Backend & Data
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent2);
|
||||
|
||||
addLabel(slide, "Backend & Data", C.accent2, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "⚙ ", options: { fontSize: 22 } },
|
||||
{ text: "Backend & Data Layer", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Platform Data Wiring", titleColor: C.accent2,
|
||||
items: [
|
||||
"Migrated frontend to Jotai + Vercel AI SDK",
|
||||
"Corrected all 7 GraphQL queries (fields, LINKS/PHONES)",
|
||||
"Webhook handler for Ozonetel call records",
|
||||
"Complete seeder: 5 doctors, appointments linked",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
|
||||
title: "Server Endpoints", titleColor: C.accent2,
|
||||
items: [
|
||||
"Call control, recording, CDR, missed calls, live assist",
|
||||
"Agent summary, AHT, performance aggregation",
|
||||
"Token refresh endpoint for auto-renewal",
|
||||
"Search module with full-text capabilities",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "Data Pages Built", titleColor: C.accent2,
|
||||
items: [
|
||||
"Worklist table, call history, patients, dashboard",
|
||||
"Reports, team dashboard, campaigns, settings",
|
||||
"Agent detail page, campaign edit slideout",
|
||||
"Appointments page with data refresh on login",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
|
||||
title: "SDK App", titleColor: C.accent3, badge: "FortyTwoApps",
|
||||
items: [
|
||||
"Helix Engage SDK app entity definitions",
|
||||
"Call center CRM object model for platform",
|
||||
"Foundation for platform-native data integration",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 6, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 7 — Deployment & Ops
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent5);
|
||||
|
||||
addLabel(slide, "Operations", C.accent5, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "🛠 ", options: { fontSize: 22 } },
|
||||
{ text: "Deployment & DevOps", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
addCard(slide, {
|
||||
x: 0.3, y: 1.35, w: 3.05, h: 2.2,
|
||||
title: "Deployment", titleColor: C.accent5,
|
||||
items: [
|
||||
"Deployed to Hostinger VPS with Docker",
|
||||
"Switched to global_healthx Ozonetel account",
|
||||
"Dockerfile for server-side containerization",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 3.55, y: 1.35, w: 3.05, h: 2.2,
|
||||
title: "AI & Testing", titleColor: C.accent5,
|
||||
items: [
|
||||
"Migrated AI to Vercel AI SDK + OpenAI provider",
|
||||
"AI flow test script — validates full pipeline",
|
||||
"Live call assist integration",
|
||||
],
|
||||
});
|
||||
|
||||
addCard(slide, {
|
||||
x: 6.8, y: 1.35, w: 2.9, h: 2.2,
|
||||
title: "Documentation", titleColor: C.accent5,
|
||||
items: [
|
||||
"Team onboarding README with arch guide",
|
||||
"Supervisor module spec + plan",
|
||||
"Multi-agent spec + plan",
|
||||
"Next session plans in commits",
|
||||
],
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 7, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 8 — Timeline
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent6);
|
||||
|
||||
addLabel(slide, "Day by Day", C.accent6, 0.5, 0.3);
|
||||
|
||||
slide.addText([
|
||||
{ text: "📅 ", options: { fontSize: 22 } },
|
||||
{ text: "Development Timeline", options: { fontSize: 22, bold: true, color: C.text } },
|
||||
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
|
||||
|
||||
const timeline = [
|
||||
{ date: "MAR 18 (Tue)", title: "Foundation Day", desc: "Call desk redesign, Jotai + AI SDK migration, seeder, AI flow test, VPS deploy" },
|
||||
{ date: "MAR 19 (Wed)", title: "Data Layer Sprint", desc: "All data pages, post-call workflow, GraphQL fixes, Kookoo IVR, outbound UI" },
|
||||
{ date: "MAR 20 (Thu)", title: "Telephony Breakthrough", desc: "Direct SIP replacing Kookoo, UCID tracking, Force Ready, Set Disposition" },
|
||||
{ date: "MAR 21 (Fri)", title: "Agent Experience", desc: "Phase 1: status toggle, search, enquiry form, My Performance, FA icons, AHT" },
|
||||
{ date: "MAR 23 (Sun)", title: "Scale & Reliability", desc: "Phase 2: missed call queue, auto-assign, Redis lockout, Patient 360, SDK defs" },
|
||||
{ date: "MAR 24 (Mon)", title: "Supervisor Module", desc: "Team perf, live monitor, master data, SSE, UUID fix, maintenance, QA sweep" },
|
||||
{ date: "MAR 25 (Tue)", title: "Intelligence Layer", desc: "Deepgram diarization, AI insights, SIP via Agent entity, token refresh, network" },
|
||||
];
|
||||
|
||||
// Vertical line
|
||||
slide.addShape("rect", {
|
||||
x: 1.4, y: 1.3, w: 0.025, h: 4.0,
|
||||
fill: { color: C.accent6, transparency: 60 },
|
||||
});
|
||||
|
||||
timeline.forEach((entry, i) => {
|
||||
const y = 1.3 + i * 0.56;
|
||||
|
||||
// Dot
|
||||
slide.addShape("ellipse", {
|
||||
x: 1.32, y: y + 0.08, w: 0.18, h: 0.18,
|
||||
fill: { color: C.accent6 },
|
||||
line: { color: C.bg, width: 2 },
|
||||
});
|
||||
|
||||
// Date
|
||||
slide.addText(entry.date, {
|
||||
x: 1.7, y: y, w: 1.6, h: 0.22,
|
||||
fontSize: 7, fontFace: FONT.heading, bold: true,
|
||||
color: C.accent6,
|
||||
});
|
||||
|
||||
// Title
|
||||
slide.addText(entry.title, {
|
||||
x: 3.3, y: y, w: 2.0, h: 0.22,
|
||||
fontSize: 9, fontFace: FONT.heading, bold: true,
|
||||
color: C.text,
|
||||
});
|
||||
|
||||
// Description
|
||||
slide.addText(entry.desc, {
|
||||
x: 5.3, y: y, w: 4.2, h: 0.45,
|
||||
fontSize: 8, fontFace: FONT.body,
|
||||
color: C.textSec,
|
||||
valign: "top",
|
||||
});
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 8, TOTAL);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// SLIDE 9 — Closing
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
{
|
||||
const slide = pptx.addSlide();
|
||||
slide.background = { color: C.bg };
|
||||
addAccentBar(slide, C.accent3);
|
||||
|
||||
// Big headline
|
||||
slide.addText("78 commits. 8 days. Ship mode.", {
|
||||
x: 0.5, y: 1.4, w: 9, h: 0.8,
|
||||
fontSize: 32, fontFace: FONT.heading, bold: true,
|
||||
color: C.accent3, align: "center",
|
||||
});
|
||||
|
||||
// Ship emoji
|
||||
slide.addText("🚢", {
|
||||
x: 4.2, y: 2.3, w: 1.6, h: 0.6,
|
||||
fontSize: 28, align: "center",
|
||||
});
|
||||
|
||||
// Description
|
||||
slide.addText(
|
||||
"From browser-native SIP calling to AI-powered recording analysis — Helix Engage is becoming a production contact center platform.",
|
||||
{
|
||||
x: 1.5, y: 3.0, w: 7, h: 0.6,
|
||||
fontSize: 11, fontFace: FONT.body,
|
||||
color: C.textSec, align: "center",
|
||||
lineSpacingMultiple: 1.3,
|
||||
}
|
||||
);
|
||||
|
||||
// Achievement pills
|
||||
const achievements = [
|
||||
{ text: "SIP Calling ✓", color: C.accent1 },
|
||||
{ text: "Multi-Agent ✓", color: C.accent2 },
|
||||
{ text: "Supervisor ✓", color: C.accent3 },
|
||||
{ text: "AI Copilot ✓", color: C.accent4 },
|
||||
{ text: "Recording Analysis ✓", color: C.accent5 },
|
||||
];
|
||||
|
||||
achievements.forEach((a, i) => {
|
||||
const x = 0.8 + i * 1.8;
|
||||
slide.addShape("roundRect", {
|
||||
x, y: 3.9, w: 1.6, h: 0.35,
|
||||
fill: { color: C.bgCard },
|
||||
line: { color: a.color, width: 1 },
|
||||
rectRadius: 0.17,
|
||||
});
|
||||
slide.addText(a.text, {
|
||||
x, y: 3.9, w: 1.6, h: 0.35,
|
||||
fontSize: 8, fontFace: FONT.heading, bold: true,
|
||||
color: a.color, align: "center", valign: "middle",
|
||||
});
|
||||
});
|
||||
|
||||
// Author
|
||||
slide.addText("Satya Suman Sari · FortyTwo Platform", {
|
||||
x: 2, y: 4.7, w: 6, h: 0.3,
|
||||
fontSize: 9, fontFace: FONT.body,
|
||||
color: C.textMuted, align: "center",
|
||||
});
|
||||
|
||||
addSlideNumber(slide, 9, TOTAL);
|
||||
}
|
||||
|
||||
// ── Save ──────────────────────────────────────────────────────────────
|
||||
const outPath = "weekly-update-mar18-25.pptx";
|
||||
await pptx.writeFile({ fileName: outPath });
|
||||
console.log(`✅ Presentation saved: ${outPath}`);
|
||||
}
|
||||
|
||||
build().catch(err => {
|
||||
console.error("❌ Failed:", err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
886
docs/weekly-update-mar18-25.html
Normal file
886
docs/weekly-update-mar18-25.html
Normal 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 18–25, 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. 🚢</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>
|
||||
BIN
docs/weekly-update-mar18-25.pptx
Normal file
BIN
docs/weekly-update-mar18-25.pptx
Normal file
Binary file not shown.
Reference in New Issue
Block a user