Skip to content

Instantly share code, notes, and snippets.

@ankitshekhawat
Last active March 18, 2026 12:48
Show Gist options
  • Select an option

  • Save ankitshekhawat/9b847ba95b47aa70e5e7d84e7e9ed2ac to your computer and use it in GitHub Desktop.

Select an option

Save ankitshekhawat/9b847ba95b47aa70e5e7d84e7e9ed2ac to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agentic Motion System | HR Character</title>
<style>
:root {
/* Semantic Roles Palette */
--canvas: #faf9f7;
--iris: #6366f1;
--dusk: #8b5cf6;
--tide: #0ea5e9;
--ember: #f97316;
--sage: #22c55e;
--coral: #ef4444;
--slate: #64748b;
/* Dynamic State Variables (Updated via JS) */
--current-core: var(--iris);
--current-shell: rgba(99, 102, 241, 0.15); /* Iris with opacity */
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--canvas);
color: #1f2937;
display: flex;
height: 100vh;
overflow: hidden;
}
/* --- Layout --- */
.stage {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.panel {
width: 400px;
background: #ffffff;
box-shadow: -10px 0 30px rgba(0,0,0,0.03);
display: flex;
flex-direction: column;
padding: 40px;
z-index: 10;
overflow-y: auto;
}
/* --- Typography & UI --- */
h1 { font-size: 1.2rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--slate); margin-bottom: 8px; }
h2 { font-size: 2rem; font-weight: 600; margin-bottom: 12px; color: #111827; transition: color 0.3s ease; }
p.desc { font-size: 1rem; line-height: 1.6; color: #4b5563; margin-bottom: 30px; min-height: 80px; }
.controls-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
button {
appearance: none; border: 1px solid #e5e7eb; background: #fff;
padding: 12px 16px; border-radius: 8px; font-size: 0.9rem; font-weight: 500;
color: #374151; cursor: pointer; text-align: left;
transition: all 0.2s ease;
}
button:hover { background: #f3f4f6; border-color: #d1d5db; }
button.active { background: #111827; color: #fff; border-color: #111827; }
/* --- The Character SVG & Motion Base --- */
.character-container {
width: 240px;
height: 240px;
position: relative;
/* Spring ease for state transitions */
transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.char-svg {
width: 100%;
height: 100%;
overflow: visible;
}
.outer-shell {
fill: var(--current-shell);
transform-origin: 50% 50%;
transition: fill 0.6s ease, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.inner-core {
fill: var(--current-core);
transform-origin: 50% 50%;
transition: fill 0.6s ease, transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* =========================================
STATE ANIMATIONS & BEHAVIORS
========================================= */
/* 1. Awaiting (Idle) */
.state-awaiting .outer-shell { animation: breathe 4s ease-in-out infinite; }
.state-awaiting .inner-core { animation: float 6s ease-in-out infinite; }
/* 2. Listening */
.state-listening .outer-shell { transform: scale(1.05); }
.state-listening .inner-core { animation: listenPulse 0.8s ease-in-out infinite alternate; }
/* 3. Thinking */
.state-thinking .outer-shell { animation: spinWobble 4s linear infinite; }
.state-thinking .inner-core { animation: deepPulse 2s ease-in-out infinite; }
/* 4. Searching */
.state-searching .outer-shell { transform: rotate(12deg) scale(0.95); }
.state-searching .inner-core { animation: scanEye 1.2s cubic-bezier(0.65, 0, 0.35, 1) infinite; }
/* 5. Running Tools */
.state-running-tools .outer-shell { transform: scale(0.95); }
.state-running-tools .inner-core { animation: mechanicalSpin 1s cubic-bezier(0.8, 0, 0.2, 1) infinite; }
/* 6. Responding */
.state-responding .outer-shell { animation: flowSquash 2s ease-in-out infinite; }
.state-responding .inner-core { animation: typeCascade 1.2s cubic-bezier(0.2, 0.8, 0.2, 1) infinite; }
/* 7. Speaking */
.state-speaking .outer-shell { animation: speakBounce 0.4s ease-in-out infinite alternate; }
.state-speaking .inner-core { animation: waveForm 0.3s ease-in-out infinite alternate; }
/* 8. Awaiting Confirmation */
.state-awaiting-confirmation .inner-core { animation: heartbeat 2s ease-in-out infinite; }
/* 9. Needs Handoff */
.state-needs-handoff .outer-shell { transform: scale(1.05); }
.state-needs-handoff .inner-core { animation: splitLink 1.5s ease-in-out infinite alternate; }
/* 10. Sending */
.state-sending .outer-shell { animation: gatherMomentum 1.5s cubic-bezier(0.2, 0.8, 0.2, 1) infinite; }
.state-sending .inner-core { animation: shootOut 1.5s cubic-bezier(0.5, 0, 0.2, 1) infinite; }
/* 11. Success */
.state-success .character-container { animation: popUp 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; }
.state-success .inner-core { transform: scale(1.3); }
/* 12. Error */
.state-error .character-container { animation: headShake 0.5s ease-in-out forwards; }
.state-error .outer-shell { transform: scale(0.9); }
.state-error .inner-core { transform: scale(0.5); }
/* 13. Done */
.state-done .character-container { transform: scale(0.8); opacity: 0.4; filter: grayscale(50%); }
/* --- Keyframes --- */
@keyframes breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.03); } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
@keyframes listenPulse { 0% { transform: scale(0.9); } 100% { transform: scale(1.25); } }
@keyframes spinWobble { 0% { transform: rotate(0deg) scale(1); } 50% { transform: rotate(180deg) scale(1.05) skewX(3deg); } 100% { transform: rotate(360deg) scale(1); } }
@keyframes deepPulse { 0%, 100% { transform: scale(0.8); opacity: 0.8;} 50% { transform: scale(1.1); opacity: 1;} }
@keyframes scanEye { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-12px) scale(0.9); } 75% { transform: translateX(12px) scale(0.9); } }
@keyframes mechanicalSpin { 0% { transform: rotate(0deg) scaleX(0.5); } 100% { transform: rotate(180deg) scaleX(0.5); } }
@keyframes flowSquash { 0%, 100% { transform: scaleY(1) scaleX(1); } 50% { transform: scaleY(1.05) scaleX(0.95); } }
@keyframes typeCascade { 0% { transform: scale(0.8); } 40% { transform: scale(1.2); } 100% { transform: scale(0.8); } }
@keyframes speakBounce { 0% { transform: translateY(0); } 100% { transform: translateY(-4px) scale(1.02); } }
@keyframes waveForm { 0% { transform: scaleY(0.6) scaleX(1.1); } 100% { transform: scaleY(1.4) scaleX(0.9); } }
@keyframes heartbeat { 0%, 100% { transform: scale(1); } 15% { transform: scale(1.25); } 30% { transform: scale(1); } 45% { transform: scale(1.25); } }
@keyframes splitLink { 0% { transform: scaleX(1); } 100% { transform: scaleX(1.5) scaleY(0.7); opacity: 0.7;} }
@keyframes gatherMomentum { 0%, 100% { transform: scale(1) translateY(0); } 30% { transform: scaleY(0.8) scaleX(1.1) translateY(10px); } 60% { transform: scaleY(1.1) scaleX(0.9) translateY(-5px); } }
@keyframes shootOut { 0% { transform: translate(0,0) scale(1); opacity: 1; } 40% { transform: translate(40px, -40px) scale(0.2); opacity: 0; } 45% { transform: translate(-30px, 30px) scale(0); opacity: 0; } 100% { transform: translate(0,0) scale(1); opacity: 1; } }
@keyframes popUp { 0% { transform: translateY(0) scale(1); } 40% { transform: translateY(-20px) scale(1.05); } 100% { transform: translateY(0) scale(1); } }
@keyframes headShake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-10px) rotate(-3deg); } 75% { transform: translateX(10px) rotate(3deg); } }
</style>
</head>
<body>
<div class="stage">
<div id="agent-wrapper" class="character-container state-awaiting">
<svg class="char-svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<path class="outer-shell"
d="M50,15 C22,15 12,28 12,50 C12,75 25,88 50,88 C65,88 75,85 85,93 C83,78 88,68 88,50 C88,25 75,15 50,15 Z" />
<circle class="inner-core" cx="50" cy="50" r="16" />
</svg>
</div>
</div>
<div class="panel">
<h1>Motion System</h1>
<h2 id="state-title">Awaiting</h2>
<p class="desc" id="state-desc">The character breathes slowly. The core floats gently. Signals a calm, attentive readiness to assist.</p>
<div class="controls-grid" id="controls">
</div>
</div>
<script>
const states = [
{ id: 'awaiting', name: 'Awaiting / Idle', core: 'var(--iris)', shell: 'rgba(99, 102, 241, 0.15)', desc: 'The character breathes slowly. The core floats gently. Signals a calm, attentive readiness to assist.' },
{ id: 'listening', name: 'Listening', core: 'var(--tide)', shell: 'rgba(14, 165, 233, 0.15)', desc: 'Outer shell leans in slightly. The inner core acts as a rapid, soft input meter, showing active receptiveness to voice or text.' },
{ id: 'thinking', name: 'Thinking', core: 'var(--dusk)', shell: 'rgba(139, 92, 246, 0.15)', desc: 'Organic "lava-lamp" rotation on the shell. The core shrinks and pulses deeply. Signals uninterrupted cognitive processing.' },
{ id: 'searching', name: 'Searching', core: 'var(--iris)', shell: 'rgba(99, 102, 241, 0.15)', desc: 'Shell tilts forward. Core sweeps left and right rapidly, mimicking the saccadic eye movements of reading or scanning databases.' },
{ id: 'running-tools', name: 'Running Tools', core: 'var(--tide)', shell: 'rgba(14, 165, 233, 0.15)', desc: 'Shell stops breathing, focusing inward. Core flattens and rotates sharply, resembling a mechanical gear executing a backend task.' },
{ id: 'responding', name: 'Responding', core: 'var(--dusk)', shell: 'rgba(139, 92, 246, 0.15)', desc: 'Shell squashes vertically. Core cascades outward with asymmetrical easing, mimicking the natural rhythm of generating text.' },
{ id: 'speaking', name: 'Speaking', core: 'var(--ember)', shell: 'rgba(249, 115, 22, 0.15)', desc: 'Warm color shift. Core alters Y-axis scale rapidly like a voice waveform, while the shell gently bounces to the speech cadence.' },
{ id: 'awaiting-confirmation', name: 'Awaiting Confirm', core: 'var(--ember)', shell: 'rgba(249, 115, 22, 0.15)', desc: 'System pauses. The core executes a distinct, rhythmic "heartbeat" double-pulse to signal that user intervention is required.' },
{ id: 'needs-handoff', name: 'Needs Handoff', core: 'var(--slate)', shell: 'rgba(100, 116, 139, 0.15)', desc: 'Shifts to neutral corporate colors. Core stretches horizontally, visually simulating a structural link or bridge to a human agent.' },
{ id: 'sending', name: 'Sending', core: 'var(--iris)', shell: 'rgba(99, 102, 241, 0.15)', desc: 'Shell gathers momentum (squash), then the core shoots upward and diagonally off-screen, symbolizing dispatching a payload.' },
{ id: 'success', name: 'Success', core: 'var(--sage)', shell: 'rgba(34, 197, 94, 0.15)', desc: 'A crisp, spring-physics upward bounce. Green colorway and an expanded core signal positive completion and delight.' },
{ id: 'error', name: 'Error', core: 'var(--coral)', shell: 'rgba(239, 68, 68, 0.15)', desc: 'A gentle, apologetic lateral head-shake. The core shrinks defensively. Signals a halt or misunderstanding without aggressive red.' },
{ id: 'done', name: 'Done', core: 'var(--slate)', shell: 'rgba(100, 116, 139, 0.10)', desc: 'Session complete. Character scales down, loses opacity, and stops all looping animations, returning to a dormant resting state.' }
];
const wrapper = document.getElementById('agent-wrapper');
const titleEl = document.getElementById('state-title');
const descEl = document.getElementById('state-desc');
const controls = document.getElementById('controls');
states.forEach((state, index) => {
const btn = document.createElement('button');
btn.innerText = state.name;
if (index === 0) btn.classList.add('active');
btn.onclick = () => {
// Update Active Button
document.querySelectorAll('button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Clean previous state classes
wrapper.className = 'character-container';
// Trigger reflow to restart CSS animations
void wrapper.offsetWidth;
// Apply new state
wrapper.classList.add(`state-${state.id}`);
// Update Colors via CSS Variables
document.documentElement.style.setProperty('--current-core', state.core);
document.documentElement.style.setProperty('--current-shell', state.shell);
// Update Text
titleEl.innerText = state.name;
titleEl.style.color = state.core === 'var(--canvas)' ? '#111827' : state.core;
descEl.innerText = state.desc;
};
controls.appendChild(btn);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment