147 lines
4.7 KiB
CSS
147 lines
4.7 KiB
CSS
|
|
/*
|
||
|
|
* 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; } }
|