Add personality system with emote variations and animations
- Rotating optimal faces with paired animations (breathing, floating, bouncing, swaying) - Occasional idle expressions (winks/blinks) with 15% chance - Recovery celebration emote with bounce animation - Connection lost state with searching animation - Face rotation every 5 minutes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ A lightweight event broker that manages priority-based system status.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import random
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -16,6 +17,22 @@ ROOT_DIR = Path(__file__).parent
|
|||||||
# Configuration
|
# Configuration
|
||||||
STATUS_FILE = Path(__file__).parent / "status.json"
|
STATUS_FILE = Path(__file__).parent / "status.json"
|
||||||
DEFAULT_NOTIFY_TTL = 10 # Default TTL for Priority 3 (Notify) events
|
DEFAULT_NOTIFY_TTL = 10 # Default TTL for Priority 3 (Notify) events
|
||||||
|
CELEBRATION_DURATION = 5 # Seconds to show celebration after recovery
|
||||||
|
|
||||||
|
# Emote variations with paired animations
|
||||||
|
OPTIMAL_EMOTES = [
|
||||||
|
("( ^_^)", "breathing"), # calm, content
|
||||||
|
("( ᵔᴥᵔ)", "floating"), # dreamy
|
||||||
|
("(◕‿◕)", "bouncing"), # cheerful
|
||||||
|
("( ・ω・)", "swaying"), # curious
|
||||||
|
("( ˘▽˘)", "breathing"), # cozy
|
||||||
|
]
|
||||||
|
IDLE_EMOTES = [
|
||||||
|
("( -_^)", "blink"), # wink
|
||||||
|
("( ^_~)", "blink"), # wink
|
||||||
|
("( ᵕ.ᵕ)", "blink"), # blink
|
||||||
|
]
|
||||||
|
CELEBRATION_EMOTE = ("\\(^o^)/", "celebrating")
|
||||||
|
|
||||||
# Priority definitions
|
# Priority definitions
|
||||||
PRIORITY_CONFIG = {
|
PRIORITY_CONFIG = {
|
||||||
@@ -29,9 +46,20 @@ PRIORITY_CONFIG = {
|
|||||||
events_lock = threading.Lock()
|
events_lock = threading.Lock()
|
||||||
active_events = {} # id -> {priority, message, timestamp, ttl}
|
active_events = {} # id -> {priority, message, timestamp, ttl}
|
||||||
|
|
||||||
|
# State tracking for personality
|
||||||
|
previous_priority = 4
|
||||||
|
celebrating_until = 0
|
||||||
|
last_emote_change = 0
|
||||||
|
current_optimal_emote = OPTIMAL_EMOTES[0][0]
|
||||||
|
current_optimal_animation = OPTIMAL_EMOTES[0][1]
|
||||||
|
|
||||||
|
|
||||||
def get_current_state():
|
def get_current_state():
|
||||||
"""Determine current state based on active events."""
|
"""Determine current state based on active events."""
|
||||||
|
global previous_priority, celebrating_until, last_emote_change, current_optimal_emote, current_optimal_animation
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
with events_lock:
|
with events_lock:
|
||||||
if not active_events:
|
if not active_events:
|
||||||
priority = 4
|
priority = 4
|
||||||
@@ -45,11 +73,38 @@ def get_current_state():
|
|||||||
]
|
]
|
||||||
|
|
||||||
config = PRIORITY_CONFIG[priority]
|
config = PRIORITY_CONFIG[priority]
|
||||||
|
emote = config["emote"]
|
||||||
|
animation = config["animation"]
|
||||||
|
color = config["color"]
|
||||||
|
|
||||||
|
# Check for recovery (was bad, now optimal)
|
||||||
|
if priority == 4 and previous_priority < 4:
|
||||||
|
celebrating_until = now + CELEBRATION_DURATION
|
||||||
|
|
||||||
|
previous_priority = priority
|
||||||
|
|
||||||
|
# Handle optimal state personality
|
||||||
|
if priority == 4:
|
||||||
|
if now < celebrating_until:
|
||||||
|
# Celebration mode
|
||||||
|
emote, animation = CELEBRATION_EMOTE
|
||||||
|
else:
|
||||||
|
# Rotate optimal emotes every 5 minutes, occasional idle expression
|
||||||
|
if now - last_emote_change > 300:
|
||||||
|
last_emote_change = now
|
||||||
|
# 15% chance of an idle expression (wink/blink)
|
||||||
|
if random.random() < 0.15:
|
||||||
|
current_optimal_emote, current_optimal_animation = random.choice(IDLE_EMOTES)
|
||||||
|
else:
|
||||||
|
current_optimal_emote, current_optimal_animation = random.choice(OPTIMAL_EMOTES)
|
||||||
|
emote = current_optimal_emote
|
||||||
|
animation = current_optimal_animation
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"current_state": config["name"].lower(),
|
"current_state": config["name"].lower(),
|
||||||
"active_emote": config["emote"],
|
"active_emote": emote,
|
||||||
"color": config["color"],
|
"color": color,
|
||||||
"animation": config["animation"],
|
"animation": animation,
|
||||||
"message": config["name"] if priority == 4 else f"{config['name']} state active",
|
"message": config["name"] if priority == 4 else f"{config['name']} state active",
|
||||||
"active_events": sorted(events_list, key=lambda x: x["priority"]),
|
"active_events": sorted(events_list, key=lambda x: x["priority"]),
|
||||||
"last_updated": datetime.now().isoformat(timespec="seconds"),
|
"last_updated": datetime.now().isoformat(timespec="seconds"),
|
||||||
|
|||||||
103
index.html
103
index.html
@@ -76,6 +76,102 @@
|
|||||||
transform: scale(1.08);
|
transform: scale(1.08);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Celebrating animation - bounce and wiggle */
|
||||||
|
.celebrating {
|
||||||
|
animation: celebrate 0.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes celebrate {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0) rotate(0deg);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateY(-10px) rotate(-5deg);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateY(-10px) rotate(5deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Floating animation - gentle drift */
|
||||||
|
.floating {
|
||||||
|
animation: float 4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bouncing animation - playful hop */
|
||||||
|
.bouncing {
|
||||||
|
animation: bounce 1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-12px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Swaying animation - curious side-to-side */
|
||||||
|
.swaying {
|
||||||
|
animation: sway 3s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sway {
|
||||||
|
0%, 100% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: rotate(-3deg);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: rotate(3deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blink animation - quick fade for winks */
|
||||||
|
.blink {
|
||||||
|
animation: blink 0.3s ease-in-out 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Searching animation - looking around for connection */
|
||||||
|
.searching {
|
||||||
|
animation: search 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes search {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(-10px);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateX(10px);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -94,7 +190,12 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
updateDisplay(data);
|
updateDisplay(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
messageEl.textContent = 'Connection lost...';
|
// Connection lost state
|
||||||
|
emoteEl.textContent = '( ?.?)';
|
||||||
|
emoteEl.style.color = '#888888';
|
||||||
|
emoteEl.className = 'searching';
|
||||||
|
messageEl.style.color = '#888888';
|
||||||
|
messageEl.textContent = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user