Skip to content

Commit 57b2e43

Browse files
committed
feat(world): 签名时刻可变重奏演出库(宪法P2第一批)
- 三套演出变体随机重奏(宪法第三条§1可变比率,对抗多巴胺适应): star 孤星亮起+秩序波纹(默认)/ trinity 三星连线(知识成网)/ bloom 光尘上浮(秩序开花) - 每次签名事件随机选取变体,reduced-motion 各变体均静帧可用
1 parent 9238745 commit 57b2e43

1 file changed

Lines changed: 94 additions & 17 deletions

File tree

‎client/src/features/retention/components/SignatureMoment.tsx‎

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ const STATIC_MS = 3000;
2626

2727
type Act = 'silence' | 'event' | 'afterglow';
2828

29+
/**
30+
* 可变重奏演出变体(宪法第三条 §1:同一成就不重播同一演出)
31+
* star=孤星亮起(默认);trinity=三星连线(知识成网);bloom=光尘上浮(秩序开花)
32+
*/
33+
type SignatureVisual = 'star' | 'trinity' | 'bloom';
34+
const VISUALS: SignatureVisual[] = ['star', 'trinity', 'bloom'];
35+
2936
export function SignatureMoment() {
3037
const signatureSeq = useWorldEvents((s) => s.signatureSeq);
3138
const signatureConcept = useWorldEvents((s) => s.signatureConcept);
@@ -34,6 +41,7 @@ export function SignatureMoment() {
3441
const [act, setAct] = useState<Act>('silence');
3542
const [concept, setConcept] = useState('');
3643
const [variant, setVariant] = useState<'mastery' | 'genesis'>('mastery');
44+
const [visual, setVisual] = useState<SignatureVisual>('star');
3745
const timers = useRef<Array<ReturnType<typeof setTimeout>>>([]);
3846
const reduced = useMemo(
3947
() => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches,
@@ -57,6 +65,8 @@ export function SignatureMoment() {
5765
clearTimers();
5866
setConcept(signatureConcept);
5967
setVariant(signatureVariant);
68+
// 可变重奏:每次事件随机选取演出变体(可变比率,对抗多巴胺适应)
69+
setVisual(VISUALS[Math.floor(Math.random() * VISUALS.length)]);
6070
setActive(true);
6171

6272
if (reduced) {
@@ -88,6 +98,88 @@ export function SignatureMoment() {
8898
// eslint-disable-next-line react-hooks/exhaustive-deps
8999
}, [active]);
90100

101+
/** 星核(三个变体共用的发光体) / Shared glowing star core */
102+
const starCore = (
103+
<div style={{
104+
position: 'absolute', inset: 44, borderRadius: '50%',
105+
background: 'radial-gradient(circle at 40% 36%, #E0F7FF, #67E8F9 60%, #0891B2)',
106+
boxShadow: '0 0 34px #22D3EE, 0 0 90px rgba(34,211,238,0.4)',
107+
}} />
108+
);
109+
110+
/** 可变重奏演出渲染(痕迹由场景层永久保留,此处是演出) */
111+
const renderVisual = () => {
112+
if (visual === 'trinity') {
113+
// 三星连线:知识结成网
114+
return (
115+
<motion.svg
116+
width={180} height={140} viewBox="0 0 180 140"
117+
initial={{ opacity: 0, scale: 0.9 }}
118+
animate={{ opacity: 1, scale: 1 }}
119+
transition={{ type: 'spring', stiffness: 180, damping: 24 }}
120+
>
121+
{[[90, 24], [34, 108], [146, 108]].map(([x, y], i) => (
122+
<motion.line
123+
key={`l${i}`}
124+
x1={90} y1={24}
125+
x2={x} y2={y}
126+
stroke="#22D3EE" strokeWidth={0.8}
127+
initial={{ opacity: 0 }}
128+
animate={{ opacity: 0.55 }}
129+
transition={{ delay: 0.3 + i * 0.18, duration: 0.5 }}
130+
/>
131+
))}
132+
{[[90, 24, 9], [34, 108, 6], [146, 108, 6]].map(([x, y, r], i) => (
133+
<motion.circle
134+
key={`s${i}`}
135+
cx={x} cy={y} r={r}
136+
fill="#67E8F9"
137+
style={{ filter: 'drop-shadow(0 0 12px #22D3EE)' }}
138+
initial={{ opacity: 0, scale: 0.2 }}
139+
animate={{ opacity: 1, scale: 1 }}
140+
transition={{ delay: i * 0.22, type: 'spring', stiffness: 220, damping: 20 }}
141+
/>
142+
))}
143+
</motion.svg>
144+
);
145+
}
146+
if (visual === 'bloom') {
147+
// 光尘上浮:秩序开花
148+
return (
149+
<div style={{ position: 'relative', width: 120, height: 120 }}>
150+
{!reduced && [0, 1, 2, 3, 4, 5].map((i) => (
151+
<motion.span
152+
key={i}
153+
initial={{ opacity: 0, y: 10, x: (i - 2.5) * 14 }}
154+
animate={{ opacity: [0, 0.9, 0], y: -70 - (i % 3) * 18 }}
155+
transition={{ duration: 2.4, delay: i * 0.3, repeat: Infinity, ease: 'easeOut' }}
156+
style={{
157+
position: 'absolute', left: '50%', bottom: 30, width: 3, height: 3,
158+
borderRadius: '50%', background: '#A5F3FC', boxShadow: '0 0 8px #22D3EE',
159+
}}
160+
/>
161+
))}
162+
{starCore}
163+
</div>
164+
);
165+
}
166+
// 默认:孤星亮起 + 秩序波纹
167+
return (
168+
<div style={{ position: 'relative', width: 120, height: 120 }}>
169+
{!reduced && [0, 0.5, 1].map((d) => (
170+
<motion.span
171+
key={d}
172+
initial={{ scale: 0.2, opacity: 0.8 }}
173+
animate={{ scale: 2.6, opacity: 0 }}
174+
transition={{ duration: 2, delay: d, ease: 'easeOut', repeat: Infinity }}
175+
style={{ position: 'absolute', inset: 0, borderRadius: '50%', border: '1px solid #22D3EE' }}
176+
/>
177+
))}
178+
{starCore}
179+
</div>
180+
);
181+
};
182+
91183
if (!active) return null;
92184

93185
return createPortal(
@@ -127,23 +219,8 @@ export function SignatureMoment() {
127219
transition={{ type: 'spring', stiffness: 200, damping: 26 }}
128220
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 26, textAlign: 'center', padding: '0 32px' }}
129221
>
130-
{/* 亮起的星 + 秩序波纹(痕迹由场景层永久保留,此处是演出) */}
131-
<div style={{ position: 'relative', width: 120, height: 120 }}>
132-
{!reduced && [0, 0.5, 1].map((d) => (
133-
<motion.span
134-
key={d}
135-
initial={{ scale: 0.2, opacity: 0.8 }}
136-
animate={{ scale: 2.6, opacity: 0 }}
137-
transition={{ duration: 2, delay: d, ease: 'easeOut', repeat: Infinity }}
138-
style={{ position: 'absolute', inset: 0, borderRadius: '50%', border: '1px solid #22D3EE' }}
139-
/>
140-
))}
141-
<div style={{
142-
position: 'absolute', inset: 44, borderRadius: '50%',
143-
background: 'radial-gradient(circle at 40% 36%, #E0F7FF, #67E8F9 60%, #0891B2)',
144-
boxShadow: '0 0 34px #22D3EE, 0 0 90px rgba(34,211,238,0.4)',
145-
}} />
146-
</div>
222+
{/* 可变重奏演出区(star/trinity/bloom 随机) */}
223+
{renderVisual()}
147224

148225
<div>
149226
{variant === 'genesis' ? (

0 commit comments

Comments
 (0)