Skip to content

Commit fc20ea5

Browse files
committed
feat(easter-eggs): name the sender on the animation overlay
1 parent c66d459 commit fc20ea5

42 files changed

Lines changed: 124 additions & 55 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/fluux/src/components/ChatView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export function ChatView({ onBack, onSwitchToMessages, onSearchInConversation, o
232232
if (!id) return
233233
const egg = easterEggMentionStore.getState().mentions.get(id)
234234
if (egg && !egg.played) {
235-
chatStore.getState().triggerAnimation(id, egg.animation)
235+
chatStore.getState().triggerAnimation(id, egg.animation, egg.senderName)
236236
easterEggMentionStore.getState().markPlayed(id)
237237
}
238238
}, [activeConversation?.id])
@@ -538,7 +538,7 @@ export function ChatView({ onBack, onSwitchToMessages, onSearchInConversation, o
538538

539539
{/* Reaction mention pills — pinned above the composer */}
540540
<ReactionMentions conversationId={activeConversation.id} onSee={(id) => chatStore.getState().setTargetMessageId(id)} />
541-
<EasterEggMentions conversationId={activeConversation.id} onReplay={(animation) => chatStore.getState().triggerAnimation(activeConversation.id, animation)} />
541+
<EasterEggMentions conversationId={activeConversation.id} onReplay={(animation, senderName) => chatStore.getState().triggerAnimation(activeConversation.id, animation, senderName)} />
542542

543543
{/* Input */}
544544
<MessageInput
@@ -588,7 +588,7 @@ export function ChatView({ onBack, onSwitchToMessages, onSearchInConversation, o
588588

589589
{/* Easter egg animation */}
590590
{activeAnimation?.conversationId === activeConversation.id && (
591-
<EasterEggAnimation animation={activeAnimation.animation} onComplete={clearAnimation} />
591+
<EasterEggAnimation animation={activeAnimation.animation} onComplete={clearAnimation} senderName={activeAnimation.senderName} />
592592
)}
593593
</div>
594594
)

apps/fluux/src/components/RoomView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ export function RoomView({ onBack, mainContentRef, composerRef, showOccupants =
398398
if (!jid) return
399399
const egg = easterEggMentionStore.getState().mentions.get(jid)
400400
if (egg && !egg.played) {
401-
roomStore.getState().triggerAnimation(jid, egg.animation)
401+
roomStore.getState().triggerAnimation(jid, egg.animation, egg.senderName)
402402
easterEggMentionStore.getState().markPlayed(jid)
403403
}
404404
}, [activeRoom?.jid])
@@ -619,7 +619,7 @@ export function RoomView({ onBack, mainContentRef, composerRef, showOccupants =
619619

620620
{/* Reaction mention pills — pinned above the composer */}
621621
<ReactionMentions conversationId={activeRoom.jid} onSee={(id) => roomStore.getState().setTargetMessageId(id)} />
622-
<EasterEggMentions conversationId={activeRoom.jid} onReplay={(animation) => roomStore.getState().triggerAnimation(activeRoom.jid, animation)} />
622+
<EasterEggMentions conversationId={activeRoom.jid} onReplay={(animation, senderName) => roomStore.getState().triggerAnimation(activeRoom.jid, animation, senderName)} />
623623

624624
{/* Input - show composer if joined, join prompt if not */}
625625
{activeRoom.joined ? (
@@ -704,7 +704,7 @@ export function RoomView({ onBack, mainContentRef, composerRef, showOccupants =
704704

705705
{/* Easter egg animation */}
706706
{activeAnimation?.roomJid === activeRoom.jid && (
707-
<EasterEggAnimation animation={activeAnimation.animation} onComplete={clearAnimation} />
707+
<EasterEggAnimation animation={activeAnimation.animation} onComplete={clearAnimation} senderName={activeAnimation.senderName} />
708708
)}
709709

710710
{/* Nick context menu (right-click / long-press on nick in messages) */}

apps/fluux/src/components/conversation/EasterEggMentions.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe('EasterEggMentions', () => {
1616
const onReplay = vi.fn()
1717
render(<EasterEggMentions conversationId="a@x" onReplay={onReplay} />)
1818
fireEvent.click(screen.getByText('Replay'))
19-
expect(onReplay).toHaveBeenCalledWith('fireworks')
19+
expect(onReplay).toHaveBeenCalledWith('fireworks', 'ava')
2020
fireEvent.click(screen.getByLabelText('Dismiss'))
2121
expect(useEasterEggMentionStore.getState().mentions.has('a@x')).toBe(false)
2222
})

apps/fluux/src/components/conversation/EasterEggMentions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { MentionChip } from './MentionChip'
55
interface EasterEggMentionsProps {
66
conversationId: string
77
/** Replay the stored animation in the active view (chat or room triggerAnimation). */
8-
onReplay: (animation: string) => void
8+
onReplay: (animation: string, senderName: string) => void
99
}
1010

1111
export function EasterEggMentions({ conversationId, onReplay }: EasterEggMentionsProps) {
@@ -20,7 +20,7 @@ export function EasterEggMentions({ conversationId, onReplay }: EasterEggMention
2020
<MentionChip
2121
label={t('easterEgg.mention', { name: egg.senderName })}
2222
actionLabel={t('easterEgg.replay')}
23-
onAction={() => onReplay(egg.animation)}
23+
onAction={() => onReplay(egg.animation, egg.senderName)}
2424
onDismiss={() => dismiss(conversationId)}
2525
/>
2626
</div>

apps/fluux/src/components/easter-eggs/ChristmasAnimation.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import React, { useEffect, useState } from 'react'
2+
import { useTranslation } from 'react-i18next'
23

34
interface ChristmasAnimationProps {
45
onComplete: () => void
56
duration?: number // Duration in ms before auto-dismiss
7+
senderName?: string
68
}
79

810
// Snowflake and decoration types
@@ -58,7 +60,8 @@ function generateParticles(count: number): Particle[] {
5860
return particles
5961
}
6062

61-
export function ChristmasAnimation({ onComplete, duration = 6000 }: ChristmasAnimationProps) {
63+
export function ChristmasAnimation({ onComplete, duration = 6000, senderName }: ChristmasAnimationProps) {
64+
const { t } = useTranslation()
6265
const [particles] = useState(() => generateParticles(50))
6366
const [isVisible, setIsVisible] = useState(true)
6467

@@ -133,10 +136,19 @@ export function ChristmasAnimation({ onComplete, duration = 6000 }: ChristmasAni
133136

134137
{/* Click hint at bottom. Dark pill keeps the white text readable when
135138
the app behind the half-opacity scrim is in light mode. */}
136-
<div className="absolute bottom-8 inset-x-0 text-center">
137-
<span className="rounded-full bg-black/40 px-3 py-1 text-sm text-white/90">
138-
Click anywhere to dismiss
139-
</span>
139+
<div className="absolute bottom-8 inset-x-0 text-center space-y-2">
140+
{senderName && (
141+
<div>
142+
<span className="rounded-full bg-black/40 px-3 py-1 text-sm text-white/90">
143+
{t('easterEgg.sentBy', { name: senderName })}
144+
</span>
145+
</div>
146+
)}
147+
<div>
148+
<span className="rounded-full bg-black/40 px-3 py-1 text-sm text-white/90">
149+
Click anywhere to dismiss
150+
</span>
151+
</div>
140152
</div>
141153
</div>
142154
)

apps/fluux/src/components/easter-eggs/EasterEggAnimation.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,22 @@ import { FireworksAnimation } from './FireworksAnimation'
77
* entry here plus a command in `commands/registry.ts` — the views stay
88
* untouched.
99
*/
10-
const ANIMATIONS: Record<string, ComponentType<{ onComplete: () => void }>> = {
10+
const ANIMATIONS: Record<string, ComponentType<{ onComplete: () => void; senderName?: string }>> = {
1111
christmas: ChristmasAnimation,
1212
fireworks: FireworksAnimation,
1313
}
1414

1515
interface EasterEggAnimationProps {
1616
animation: string
1717
onComplete: () => void
18+
senderName?: string
1819
}
1920

2021
/** Full-screen easter-egg overlay dispatcher. Unknown names (e.g. eggs from newer clients) render nothing. */
21-
export function EasterEggAnimation({ animation, onComplete }: EasterEggAnimationProps) {
22+
export function EasterEggAnimation({ animation, onComplete, senderName }: EasterEggAnimationProps) {
2223
// `animation` is wire-controlled (any contact/occupant can send it): only accept the map's own
2324
// properties, so prototype-chain keys like '__proto__'/'constructor'/'toString' never resolve.
2425
const Overlay = Object.hasOwn(ANIMATIONS, animation) ? ANIMATIONS[animation] : undefined
2526
if (!Overlay) return null
26-
return <Overlay onComplete={onComplete} />
27+
return <Overlay onComplete={onComplete} senderName={senderName} />
2728
}

apps/fluux/src/components/easter-eggs/FireworksAnimation.test.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,14 @@ describe('FireworksAnimation', () => {
4343
expect(screen.getByText('Click anywhere to dismiss')).toBeInTheDocument()
4444
expect(document.querySelector('[aria-hidden="true"]')).not.toBeNull()
4545
})
46+
47+
it('shows a "Sent by" pill when senderName is provided', () => {
48+
render(<FireworksAnimation onComplete={vi.fn()} senderName="Ava" />)
49+
expect(screen.getByText('Sent by Ava')).toBeInTheDocument()
50+
})
51+
52+
it('does not show a "Sent by" pill without senderName', () => {
53+
render(<FireworksAnimation onComplete={vi.fn()} />)
54+
expect(screen.queryByText(/Sent by/)).not.toBeInTheDocument()
55+
})
4656
})

apps/fluux/src/components/easter-eggs/FireworksAnimation.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { useCallback, useEffect, useRef, useState } from 'react'
2+
import { useTranslation } from 'react-i18next'
23

34
interface FireworksAnimationProps {
45
onComplete: () => void
56
duration?: number // Duration in ms before auto-dismiss starts
7+
senderName?: string
68
}
79

810
// Soft, slightly desaturated tricolore palette — an understated evening
@@ -115,7 +117,8 @@ function burst(sparks: Spark[], rocket: Rocket): void {
115117
}
116118
}
117119

118-
export function FireworksAnimation({ onComplete, duration = 6000 }: FireworksAnimationProps) {
120+
export function FireworksAnimation({ onComplete, duration = 6000, senderName }: FireworksAnimationProps) {
121+
const { t } = useTranslation()
119122
const canvasRef = useRef<HTMLCanvasElement>(null)
120123
const [isVisible, setIsVisible] = useState(true)
121124
// Read inside the rAF loop to stop launching new shells during fade-out,
@@ -285,10 +288,19 @@ export function FireworksAnimation({ onComplete, duration = 6000 }: FireworksAni
285288

286289
{/* Click hint at bottom. Dark pill keeps the white text readable when
287290
the app behind the half-opacity scrim is in light mode. */}
288-
<div className="absolute bottom-8 inset-x-0 text-center">
289-
<span className="rounded-full bg-black/40 px-3 py-1 text-sm text-white/90">
290-
Click anywhere to dismiss
291-
</span>
291+
<div className="absolute bottom-8 inset-x-0 text-center space-y-2">
292+
{senderName && (
293+
<div>
294+
<span className="rounded-full bg-black/40 px-3 py-1 text-sm text-white/90">
295+
{t('easterEgg.sentBy', { name: senderName })}
296+
</span>
297+
</div>
298+
)}
299+
<div>
300+
<span className="rounded-full bg-black/40 px-3 py-1 text-sm text-white/90">
301+
Click anywhere to dismiss
302+
</span>
303+
</div>
292304
</div>
293305
</div>
294306
)

apps/fluux/src/i18n/locales/ar.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,7 @@
13881388
},
13891389
"easterEgg": {
13901390
"mention": "أرسل لك {{name}} رسمًا متحركًا",
1391-
"replay": "إعادة التشغيل"
1391+
"replay": "إعادة التشغيل",
1392+
"sentBy": "أُرسل من {{name}}"
13921393
}
13931394
}

apps/fluux/src/i18n/locales/be.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,7 @@
13861386
},
13871387
"easterEgg": {
13881388
"mention": "{{name}} даслаў(-ла) вам анімацыю",
1389-
"replay": "Паўтарыць"
1389+
"replay": "Паўтарыць",
1390+
"sentBy": "Даслана ад {{name}}"
13901391
}
13911392
}

0 commit comments

Comments
 (0)