Files
Teamup/client/src/components/agent-face.css
T

147 lines
4.7 KiB
CSS
Raw Normal View History

/*
* The Companion agent face. One expressive face used at every size; the animation is load-bearing —
* it maps to a real AgentRun state (queued/running/held/completed/failed) so a glance reads as live
* status, the same way the seat-state triad reads human/open/AI. All metrics are in `em` and the size
* classes set the root font-size, so the whole face scales from a board chip to the configurator.
*/
.agent-face {
position: relative;
display: inline-block;
width: 6em;
height: 6em;
flex: none;
line-height: 0;
--rc: #64748b; /* state ring colour, overridden per state */
--hue: 245;
}
.agent-face.af-sm { font-size: 3.3px; }
.agent-face.af-md { font-size: 7.3px; }
.agent-face.af-lg { font-size: 14px; }
.agent-face.af-xl { font-size: 20px; }
.af-head {
position: absolute;
inset: 0;
border-radius: 30%;
background: hsl(var(--hue) 62% 62%);
animation: af-breathe 3.4s ease-in-out infinite;
}
.af-ring {
position: absolute;
inset: -0.55em;
border-radius: 32%;
border: 0.18em solid var(--rc);
opacity: 0.85;
transition: border-color 0.35s ease, opacity 0.35s ease;
}
.af-spin {
position: absolute;
inset: -0.55em;
border-radius: 32%;
border: 0.18em solid transparent;
border-top-color: var(--rc);
opacity: 0;
}
.af-eye {
position: absolute;
top: 0.42em;
width: 0.13em;
height: 0.13em;
width: 0.8em;
height: 0.8em;
background: #fff;
border-radius: 50%;
animation: af-blink 4s infinite;
}
.af-eye-l { left: 0.27em; }
.af-eye-r { right: 0.27em; }
.af-mouth {
position: absolute;
bottom: 0.24em;
left: 50%;
transform: translateX(-50%);
width: 1.15em;
height: 0.2em;
border-radius: 0.2em;
background: rgba(255, 255, 255, 0.85);
}
.af-dots {
position: absolute;
top: -0.15em;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 0.22em;
opacity: 0;
}
.af-dots i {
width: 0.36em;
height: 0.36em;
border-radius: 50%;
background: #6366f1;
animation: af-bob 0.9s infinite;
}
.af-dots i:nth-child(2) { animation-delay: 0.15s; }
.af-dots i:nth-child(3) { animation-delay: 0.3s; }
/* The mouth and thinking-dots are clutter at chip size — eyes + ring carry the state there. */
.af-sm .af-mouth,
.af-sm .af-dots { display: none; }
/* ---- state: ring colour ---- */
.agent-face[data-state='idle'] { --rc: #64748b; }
.agent-face[data-state='thinking'] { --rc: #6366f1; }
.agent-face[data-state='working'] { --rc: #6366f1; }
.agent-face[data-state='review'] { --rc: #f59e0b; }
.agent-face[data-state='done'] { --rc: #14b8a6; }
.agent-face[data-state='failed'] { --rc: #ef4444; }
/* ---- state: expression ---- */
.agent-face[data-state='thinking'] .af-eye { top: 0.36em; height: 0.5em; border-radius: 40%; }
.agent-face[data-state='thinking'] .af-dots { opacity: 1; }
.agent-face[data-state='thinking'] .af-ring { animation: af-rpulse 1.4s ease-in-out infinite; }
.agent-face[data-state='working'] .af-eye { height: 0.92em; top: 0.4em; }
.agent-face[data-state='working'] .af-mouth { width: 0.6em; }
.agent-face[data-state='working'] .af-spin { opacity: 1; animation: af-spin 1.05s linear infinite; }
.agent-face[data-state='working'] .af-ring { opacity: 0.3; }
.agent-face[data-state='review'] .af-ring { animation: af-rpulse 1s ease-in-out infinite; }
.agent-face[data-state='review'] .af-eye { top: 0.34em; }
.agent-face[data-state='done'] .af-eye {
height: 0.42em;
border-radius: 0 0 0.8em 0.8em;
top: 0.5em;
}
.agent-face[data-state='done'] .af-mouth {
width: 1.4em;
height: 0.62em;
border-radius: 0 0 1.4em 1.4em;
border-bottom: 0.2em solid #fff;
background: transparent;
}
.agent-face[data-state='done'] .af-ring { animation: af-pop 0.5s ease-out; }
.agent-face[data-state='failed'] .af-head { background: hsl(var(--hue) 14% 56%); }
.agent-face[data-state='failed'] .af-eye { height: 0.28em; border-radius: 0.14em; top: 0.56em; background: #e6e0ef; }
.agent-face[data-state='failed'] .af-mouth {
width: 0.85em;
height: 0.55em;
border-radius: 1.4em 1.4em 0 0;
border-top: 0.2em solid #e6e0ef;
background: transparent;
bottom: 0.2em;
}
@media (prefers-reduced-motion: reduce) {
.af-head, .af-ring, .af-spin, .af-eye, .af-dots i { animation: none !important; }
}
@keyframes af-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.045); } }
@keyframes af-blink { 0%, 92%, 100% { transform: scaleY(1); } 96% { transform: scaleY(0.1); } }
@keyframes af-spin { to { transform: rotate(360deg); } }
@keyframes af-rpulse { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.3; } }
@keyframes af-pop { 0% { transform: scale(0.8); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }
@keyframes af-bob { 0%, 100% { transform: translateY(0); opacity: 0.5; } 50% { transform: translateY(-0.3em); opacity: 1; } }