feat(遊戲化): 新增大廳、附身、時間流、後日談進度等遊戲化系統功能

新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、
時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙
測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 09:10:26 +08:00
co-authored by Claude Sonnet 5
parent 127a59dafd
commit 1bea06e3cd
195 changed files with 17219 additions and 738 deletions
+42
View File
@@ -0,0 +1,42 @@
import { useEffect, useRef } from "react";
import { Animated, Easing, type StyleProp, type ViewStyle } from "react-native";
import { useReduceMotion } from "../lib/reduce-motion";
export interface HeartbeatDotProps {
size?: number;
color?: string;
style?: StyleProp<ViewStyle>;
}
// Q-4:未讀/推播提示紅點=小心跳,對應 apps/web 的 `.heartbeat-dot`(1.2s 循環的 opacity+scale 脈動)。
export function HeartbeatDot({ size = 10, color = "#FF7E9D", style }: HeartbeatDotProps) {
const reduceMotion = useReduceMotion();
const t = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (reduceMotion) {
t.setValue(0);
return;
}
const loop = Animated.loop(
Animated.sequence([
Animated.timing(t, { toValue: 1, duration: 600, easing: Easing.inOut(Easing.ease), useNativeDriver: true }),
Animated.timing(t, { toValue: 0, duration: 600, easing: Easing.inOut(Easing.ease), useNativeDriver: true }),
]),
);
loop.start();
return () => loop.stop();
}, [reduceMotion, t]);
const scale = t.interpolate({ inputRange: [0, 1], outputRange: [1, 1.3] });
const opacity = t.interpolate({ inputRange: [0, 1], outputRange: [1, 0.7] });
return (
<Animated.View
style={[
{ width: size, height: size, borderRadius: size / 2, backgroundColor: color, opacity, transform: [{ scale }] },
style,
]}
/>
);
}