2026-01-28 23:00:58 -05:00

543 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCP Chat Simulation</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--bg-primary: #0f0f1a;
--bg-secondary: #1a1a2e;
--bg-tertiary: #252542;
--border: #2a2a4a;
--input-bg: #12121f;
--text: #e2e2f0;
--text-muted: #8888aa;
--purple: #6366f1;
--purple-end: #8b5cf6;
--stripe: #635BFF;
}
body {
font-family: 'Inter', -apple-system, sans-serif;
background: var(--bg-primary);
overflow: hidden;
}
/* Container - the viewport window */
.simulation-container {
width: 100vw;
height: 100vh;
overflow: hidden;
position: relative;
background:
radial-gradient(ellipse at 30% 20%, rgba(99, 91, 255, 0.1) 0%, transparent 50%),
radial-gradient(ellipse at 70% 80%, rgba(118, 75, 162, 0.1) 0%, transparent 50%),
var(--bg-primary);
}
/* The large canvas that camera moves across */
.canvas {
position: absolute;
width: 2400px;
height: 1600px;
transform-origin: top left;
/* Camera controlled by JS */
}
/* Chat window on the canvas */
.chat-window {
position: absolute;
left: 300px;
top: 200px;
width: 1600px;
height: 1100px;
background: var(--bg-secondary);
border-radius: 20px;
box-shadow: 0 25px 80px rgba(0,0,0,0.5);
border: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
}
.titlebar {
height: 56px;
background: var(--bg-tertiary);
display: flex;
align-items: center;
padding: 0 20px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.traffic-lights { display: flex; gap: 8px; }
.light { width: 12px; height: 12px; border-radius: 50%; }
.light.red { background: #ff5f57; }
.light.yellow { background: #ffbd2e; }
.light.green { background: #27ca40; }
.title { flex: 1; text-align: center; color: var(--text-muted); font-size: 14px; }
.messages {
flex: 1;
padding: 32px;
display: flex;
flex-direction: column;
gap: 24px;
overflow: hidden;
}
.message {
max-width: 70%;
padding: 18px 24px;
border-radius: 20px;
font-size: 18px;
line-height: 1.5;
opacity: 0;
transform: translateY(20px);
}
.message.visible {
opacity: 1;
transform: translateY(0);
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.message.user {
align-self: flex-end;
background: linear-gradient(135deg, var(--purple), var(--purple-end));
color: var(--text);
border-bottom-right-radius: 6px;
}
.message.ai {
align-self: flex-start;
background: var(--border);
color: var(--text);
border-bottom-left-radius: 6px;
}
.cursor {
display: inline-block;
width: 3px;
height: 1.1em;
background: var(--purple);
margin-left: 2px;
vertical-align: text-bottom;
animation: blink 0.8s infinite;
}
@keyframes blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
.typing-indicator {
display: flex;
gap: 5px;
padding: 18px 24px;
background: var(--border);
border-radius: 20px;
border-bottom-left-radius: 6px;
align-self: flex-start;
opacity: 0;
}
.typing-indicator.visible { opacity: 1; }
.typing-dot {
width: 10px;
height: 10px;
background: var(--purple);
border-radius: 50%;
animation: bounce 1.4s infinite ease-in-out;
}
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
@keyframes bounce {
0%, 80%, 100% { transform: translateY(0); }
40% { transform: translateY(-8px); }
}
.spinner {
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid var(--border);
border-top-color: var(--stripe);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-right: 12px;
vertical-align: middle;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Embed */
.embed-wrapper {
margin-top: 16px;
opacity: 0;
transform: scale(0.6) translateY(20px);
transform-origin: top left;
transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.embed-wrapper.visible {
opacity: 1;
transform: scale(1) translateY(0);
}
.stripe-embed {
background: #fff;
border-radius: 14px;
overflow: hidden;
width: 520px;
box-shadow: 0 8px 40px rgba(99, 91, 255, 0.2);
}
.embed-header {
padding: 16px 20px;
border-bottom: 1px solid #E3E8EE;
display: flex;
align-items: center;
gap: 12px;
}
.stripe-logo {
background: var(--stripe);
color: white;
font-weight: 700;
font-size: 12px;
padding: 6px 12px;
border-radius: 5px;
}
.embed-title { font-size: 15px; font-weight: 600; color: #1A1F36; }
.embed-subtitle { font-size: 11px; color: #697386; }
.embed-stats {
display: flex;
gap: 28px;
padding: 14px 20px;
background: #F6F9FC;
border-bottom: 1px solid #E3E8EE;
}
.stat-value { font-size: 20px; font-weight: 600; color: #30B566; }
.stat-label { font-size: 10px; color: #697386; text-transform: uppercase; letter-spacing: 0.5px; }
.embed-row {
display: flex;
align-items: center;
padding: 12px 20px;
border-bottom: 1px solid #E3E8EE;
opacity: 0;
transform: translateX(-15px);
transition: all 0.4s ease;
}
.embed-row.visible {
opacity: 1;
transform: translateX(0);
}
.embed-row:last-child { border-bottom: none; }
.row-email { flex: 1; font-size: 13px; color: #1A1F36; font-weight: 500; }
.row-amount { font-size: 14px; font-weight: 600; color: #1A1F36; margin-right: 16px; }
.row-status {
padding: 4px 10px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.row-status.pending { background: #FFF8E6; color: #9C6F19; }
.row-status.success { background: #D7F7E0; color: #0E6245; }
.input-area {
padding: 20px 28px;
border-top: 1px solid var(--border);
flex-shrink: 0;
}
.input-field {
display: flex;
gap: 14px;
align-items: center;
background: var(--input-bg);
border: 2px solid var(--border);
border-radius: 14px;
padding: 14px 18px;
transition: border-color 0.2s;
}
.input-field.active { border-color: #4a4a7a; }
.input-text {
flex: 1;
font-size: 17px;
color: var(--text-muted);
}
.input-text.typing { color: var(--text); }
.send-btn {
width: 46px;
height: 46px;
background: linear-gradient(135deg, var(--purple), var(--purple-end));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.send-btn svg { width: 22px; height: 22px; }
/* Replay button */
.replay-btn {
position: fixed;
bottom: 24px;
right: 24px;
padding: 12px 24px;
background: var(--purple);
color: white;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
z-index: 100;
}
.replay-btn.visible { opacity: 1; }
.replay-btn:hover { background: var(--purple-end); }
</style>
</head>
<body>
<div class="simulation-container">
<div class="canvas" id="canvas">
<div class="chat-window">
<div class="titlebar">
<div class="traffic-lights">
<div class="light red"></div>
<div class="light yellow"></div>
<div class="light green"></div>
</div>
<div class="title">AI Assistant</div>
<div style="width: 52px;"></div>
</div>
<div class="messages" id="messages">
<div class="message user" id="userMessage"></div>
<div class="typing-indicator" id="aiTyping">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
<div class="message ai" id="aiMessage">
<span id="aiText"></span>
<div class="embed-wrapper" id="embedWrapper">
<div class="stripe-embed">
<div class="embed-header">
<div class="stripe-logo">stripe</div>
<div>
<div class="embed-title">Payment Recovery</div>
<div class="embed-subtitle">3 opportunities • $1,247 recoverable</div>
</div>
</div>
<div class="embed-stats">
<div>
<div class="stat-value">$1,247</div>
<div class="stat-label">Recoverable</div>
</div>
<div>
<div class="stat-value" style="color: #1A1F36;">87%</div>
<div class="stat-label">Success Rate</div>
</div>
</div>
<div class="embed-row" id="row1">
<div class="row-email">john@example.com</div>
<div class="row-amount">$449.00</div>
<div class="row-status pending">Ready to retry</div>
</div>
<div class="embed-row" id="row2">
<div class="row-email">sarah@company.co</div>
<div class="row-amount">$299.00</div>
<div class="row-status success">Card updated</div>
</div>
<div class="embed-row" id="row3">
<div class="row-email">mike@startup.io</div>
<div class="row-amount">$499.00</div>
<div class="row-status pending">Ready to retry</div>
</div>
</div>
</div>
</div>
</div>
<div class="input-area">
<div class="input-field" id="inputField">
<div class="input-text" id="inputText">Type a message...</div>
<div class="send-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
<path d="M22 2L11 13M22 2L15 22L11 13L2 9L22 2Z"/>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
<button class="replay-btn" id="replayBtn" onclick="startAnimation()">↻ Replay</button>
<script>
const USER_QUESTION = "What patterns do you see in my transaction data?";
const AI_RESPONSE = "Analyzing your Stripe data...";
const canvas = document.getElementById('canvas');
const inputField = document.getElementById('inputField');
const inputText = document.getElementById('inputText');
const userMessage = document.getElementById('userMessage');
const aiTyping = document.getElementById('aiTyping');
const aiMessage = document.getElementById('aiMessage');
const aiText = document.getElementById('aiText');
const embedWrapper = document.getElementById('embedWrapper');
const replayBtn = document.getElementById('replayBtn');
let animationId = null;
// Camera state
function setCamera(zoom, x, y, duration = 0.8) {
canvas.style.transition = `transform ${duration}s cubic-bezier(0.16, 1, 0.3, 1)`;
canvas.style.transform = `scale(${zoom}) translate(${-x}px, ${-y}px)`;
}
// Type text with animation
function typeText(element, text, callback, charDelay = 40) {
let i = 0;
element.innerHTML = '';
function type() {
if (i < text.length) {
element.innerHTML = text.slice(0, i + 1) + '<span class="cursor"></span>';
i++;
setTimeout(type, charDelay);
} else {
element.innerHTML = text;
if (callback) callback();
}
}
type();
}
// Main animation sequence
function startAnimation() {
// Reset
replayBtn.classList.remove('visible');
userMessage.classList.remove('visible');
userMessage.innerHTML = '';
aiTyping.classList.remove('visible');
aiMessage.classList.remove('visible');
aiText.innerHTML = '';
embedWrapper.classList.remove('visible');
document.getElementById('row1').classList.remove('visible');
document.getElementById('row2').classList.remove('visible');
document.getElementById('row3').classList.remove('visible');
inputField.classList.remove('active');
inputText.classList.remove('typing');
inputText.innerHTML = 'Type a message...';
// Start: zoom into input
setCamera(1.8, 280, 650, 0);
setTimeout(() => {
// User starts typing
inputField.classList.add('active');
inputText.classList.add('typing');
let charIndex = 0;
const typeInterval = setInterval(() => {
if (charIndex <= USER_QUESTION.length) {
inputText.innerHTML = USER_QUESTION.slice(0, charIndex) + '<span class="cursor"></span>';
// Pan camera to follow text
const followX = Math.min(charIndex * 6, 150);
setCamera(1.8, 280 + followX, 650, 0.15);
charIndex++;
} else {
clearInterval(typeInterval);
// Message sent
setTimeout(() => {
inputText.innerHTML = 'Type a message...';
inputText.classList.remove('typing');
inputField.classList.remove('active');
userMessage.innerHTML = USER_QUESTION;
userMessage.classList.add('visible');
// Zoom out to show message
setCamera(1.0, 200, 180, 0.8);
// AI thinking
setTimeout(() => {
aiTyping.classList.add('visible');
// AI responds
setTimeout(() => {
aiTyping.classList.remove('visible');
aiMessage.classList.add('visible');
// Zoom in on AI
setCamera(1.3, 100, 280, 0.6);
typeText(aiText, AI_RESPONSE, () => {
// Processing
setTimeout(() => {
aiText.innerHTML = '<span class="spinner"></span>Analyzing your payment data...';
// Show embed
setTimeout(() => {
setCamera(0.95, 180, 250, 0.7);
embedWrapper.classList.add('visible');
// Stagger rows
setTimeout(() => document.getElementById('row1').classList.add('visible'), 300);
setTimeout(() => document.getElementById('row2').classList.add('visible'), 450);
setTimeout(() => document.getElementById('row3').classList.add('visible'), 600);
// Show replay
setTimeout(() => {
replayBtn.classList.add('visible');
}, 1500);
}, 800);
}, 500);
}, 30);
}, 1200);
}, 800);
}, 300);
}
}, 40);
}, 500);
}
// Auto-start
setTimeout(startAnimation, 500);
</script>
</body>
</html>