@@ -7,6 +7,7 @@ import { OKLCH_NEUTRAL_FALLBACK, lightenOklch } from '~/utils/colors'
77import { applyBlocklistCorrection } from ' ~/utils/download-anomalies'
88import type { RepoRef } from ' #shared/utils/git-providers'
99import type { VueUiSparklineConfig , VueUiSparklineDatasetItem } from ' vue-data-ui'
10+ import { onKeyDown } from ' @vueuse/core'
1011
1112const props = defineProps <{
1213 packageName: string
@@ -205,6 +206,110 @@ const dataset = computed<VueUiSparklineDatasetItem[]>(() =>
205206
206207const lastDatapoint = computed (() => dataset .value .at (- 1 )?.period ?? ' ' )
207208
209+ const isLoop = shallowRef (false )
210+ const showPulse = shallowRef (true )
211+ const keyboardShortcuts = useKeyboardShortcuts ()
212+
213+ const cheatCode = [
214+ ' arrowup' ,
215+ ' arrowright' ,
216+ ' arrowleft' ,
217+ ' arrowup' ,
218+ ' arrowleft' ,
219+ ' arrowright' ,
220+ ] as const
221+
222+ type CheatKey = (typeof cheatCode )[number ]
223+
224+ const easterEgg = shallowRef <CheatKey []>([])
225+ let resetTimeout: ReturnType <typeof setTimeout > | undefined
226+ const easterEggResetDelay = 1500
227+
228+ function resetEasterEgg() {
229+ easterEgg .value = []
230+ clearTimeout (resetTimeout )
231+ resetTimeout = undefined
232+ }
233+
234+ function pushEasterEggKey(key : CheatKey ) {
235+ clearTimeout (resetTimeout )
236+ resetTimeout = setTimeout (resetEasterEgg , easterEggResetDelay )
237+
238+ const nextIndex = easterEgg .value .length
239+ const expectedKey = cheatCode [nextIndex ]
240+ // Reset if the position is wrong
241+ if (! expectedKey || expectedKey !== key ) {
242+ resetEasterEgg ()
243+ return
244+ }
245+
246+ easterEgg .value .push (key )
247+
248+ // Match! reset & trigger
249+ if (easterEgg .value .length === cheatCode .length ) {
250+ resetEasterEgg ()
251+ layEgg ()
252+ }
253+ }
254+
255+ onKeyDown (
256+ ' ArrowUp' ,
257+ e => {
258+ if (! keyboardShortcuts .value ) return
259+ pushEasterEggKey (' arrowup' )
260+ },
261+ { dedupe: true },
262+ )
263+
264+ onKeyDown (
265+ ' ArrowRight' ,
266+ e => {
267+ if (! keyboardShortcuts .value ) return
268+ pushEasterEggKey (' arrowright' )
269+ },
270+ { dedupe: true },
271+ )
272+
273+ onKeyDown (
274+ ' ArrowLeft' ,
275+ e => {
276+ if (! keyboardShortcuts .value ) return
277+ pushEasterEggKey (' arrowleft' )
278+ },
279+ { dedupe: true },
280+ )
281+
282+ onBeforeUnmount (() => {
283+ resetEasterEgg ()
284+ clearTimeout (eggPulseTimeout )
285+ eggPulseTimeout = undefined
286+ })
287+
288+ const eggPulse = ref (false )
289+
290+ let eggPulseTimeout: ReturnType <typeof setTimeout > | undefined
291+
292+ function playEggPulse() {
293+ eggPulse .value = false
294+ void document .documentElement .offsetHeight
295+ eggPulse .value = true
296+
297+ clearTimeout (eggPulseTimeout )
298+
299+ eggPulseTimeout = setTimeout (() => {
300+ eggPulse .value = false
301+ }, 900 )
302+ }
303+
304+ function layEgg() {
305+ showPulse .value = false
306+ nextTick (() => {
307+ showPulse .value = true
308+ isLoop .value = ! isLoop .value
309+ playEggPulse ()
310+ })
311+ }
312+
208313const config = computed <VueUiSparklineConfig >(() => {
209314 return {
210315 theme: ' dark' ,
@@ -248,8 +353,8 @@ const config = computed<VueUiSparklineConfig>(() => {
248353 line: {
249354 color: colors .value .borderHover ,
250355 pulse: {
251- show: true , // the pulse will not show if prefers-reduced-motion (enforced by vue-data-ui)
252- loop: false ,
356+ show: showPulse . value , // the pulse will not show if prefers-reduced-motion (enforced by vue-data-ui)
357+ loop: isLoop . value ,
253358 radius: 1.5 ,
254359 color: pulseColor .value ! ,
255360 easing: ' ease-in-out' ,
@@ -306,7 +411,10 @@ const config = computed<VueUiSparklineConfig>(() => {
306411 <span v-else-if =" isLoadingWeeklyDownloads" class =" min-w-6 min-h-6 -m-1 p-1" />
307412 </template >
308413
309- <div class =" w-full overflow-hidden h-[76px]" >
414+ <div
415+ class =" w-full overflow-hidden h-[76px] egg-pulse-target"
416+ :class =" { 'egg-pulse': eggPulse }"
417+ >
310418 <template v-if =" isLoadingWeeklyDownloads || hasWeeklyDownloads " >
311419 <ClientOnly >
312420 <VueUiSparkline class =" w-full max-w-xs" :dataset :config >
@@ -402,4 +510,48 @@ const config = computed<VueUiSparklineConfig>(() => {
402510 Geist Mono,
403511 monospace !important ;
404512}
513+
514+ .egg-pulse-target {
515+ transform-origin : center ;
516+ will-change : transform;
517+ }
518+
519+ .egg-pulse {
520+ animation : egg-heartbeat 900ms ease-in-out 0ms 1 ;
521+ }
522+
523+ /* 3 heart pulses */
524+ @keyframes egg-heartbeat {
525+ 0% {
526+ transform : scale (1 );
527+ }
528+ 10% {
529+ transform : scale (1.1 );
530+ }
531+ 20% {
532+ transform : scale (1 );
533+ }
534+ 35% {
535+ transform : scale (1.03 );
536+ }
537+ 45% {
538+ transform : scale (1 );
539+ }
540+ 60% {
541+ transform : scale (1.01 );
542+ }
543+ 70% {
544+ transform : scale (1 );
545+ }
546+ 100% {
547+ transform : scale (1 );
548+ }
549+ }
550+
551+ @media (prefers-reduced-motion: reduce) {
552+ .egg-pulse {
553+ animation : none !important ;
554+ transform : none !important ;
555+ }
556+ }
405557 </style >
0 commit comments