Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 155 additions & 3 deletions app/components/Package/WeeklyDownloadStats.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { OKLCH_NEUTRAL_FALLBACK, lightenOklch } from '~/utils/colors'
import { applyBlocklistCorrection } from '~/utils/download-anomalies'
import type { RepoRef } from '#shared/utils/git-providers'
import type { VueUiSparklineConfig, VueUiSparklineDatasetItem } from 'vue-data-ui'
import { onKeyDown } from '@vueuse/core'

const props = defineProps<{
packageName: string
Expand Down Expand Up @@ -205,6 +206,110 @@ const dataset = computed<VueUiSparklineDatasetItem[]>(() =>

const lastDatapoint = computed(() => dataset.value.at(-1)?.period ?? '')

const isLoop = shallowRef(false)
const showPulse = shallowRef(true)
const keyboardShortcuts = useKeyboardShortcuts()

const cheatCode = [
'arrowup',
'arrowright',
'arrowleft',
'arrowup',
'arrowleft',
'arrowright',
] as const

type CheatKey = (typeof cheatCode)[number]

const easterEgg = shallowRef<CheatKey[]>([])
let resetTimeout: ReturnType<typeof setTimeout> | undefined
const easterEggResetDelay = 1500

function resetEasterEgg() {
easterEgg.value = []
clearTimeout(resetTimeout)
resetTimeout = undefined
}

function pushEasterEggKey(key: CheatKey) {
clearTimeout(resetTimeout)
resetTimeout = setTimeout(resetEasterEgg, easterEggResetDelay)

const nextIndex = easterEgg.value.length
const expectedKey = cheatCode[nextIndex]
// Reset if the position is wrong
if (!expectedKey || expectedKey !== key) {
resetEasterEgg()
return
}

easterEgg.value.push(key)

// Match! reset & trigger
if (easterEgg.value.length === cheatCode.length) {
resetEasterEgg()
layEgg()
}
}

onKeyDown(
'ArrowUp',
e => {
if (!keyboardShortcuts.value) return
pushEasterEggKey('arrowup')
},
{ dedupe: true },
)

onKeyDown(
'ArrowRight',
e => {
if (!keyboardShortcuts.value) return
pushEasterEggKey('arrowright')
},
{ dedupe: true },
)

onKeyDown(
'ArrowLeft',
e => {
if (!keyboardShortcuts.value) return
pushEasterEggKey('arrowleft')
},
{ dedupe: true },
)

onBeforeUnmount(() => {
resetEasterEgg()
clearTimeout(eggPulseTimeout)
eggPulseTimeout = undefined
})

const eggPulse = ref(false)

let eggPulseTimeout: ReturnType<typeof setTimeout> | undefined

function playEggPulse() {
eggPulse.value = false
void document.documentElement.offsetHeight
eggPulse.value = true

clearTimeout(eggPulseTimeout)

eggPulseTimeout = setTimeout(() => {
eggPulse.value = false
}, 900)
}

function layEgg() {
showPulse.value = false
nextTick(() => {
showPulse.value = true
isLoop.value = !isLoop.value
playEggPulse()
})
}

const config = computed<VueUiSparklineConfig>(() => {
return {
theme: 'dark',
Expand Down Expand Up @@ -248,8 +353,8 @@ const config = computed<VueUiSparklineConfig>(() => {
line: {
color: colors.value.borderHover,
pulse: {
show: true, // the pulse will not show if prefers-reduced-motion (enforced by vue-data-ui)
loop: false,
show: showPulse.value, // the pulse will not show if prefers-reduced-motion (enforced by vue-data-ui)
loop: isLoop.value,
radius: 1.5,
color: pulseColor.value!,
easing: 'ease-in-out',
Expand Down Expand Up @@ -306,7 +411,10 @@ const config = computed<VueUiSparklineConfig>(() => {
<span v-else-if="isLoadingWeeklyDownloads" class="min-w-6 min-h-6 -m-1 p-1" />
</template>

<div class="w-full overflow-hidden h-[76px]">
<div
class="w-full overflow-hidden h-[76px] egg-pulse-target"
:class="{ 'egg-pulse': eggPulse }"
>
<template v-if="isLoadingWeeklyDownloads || hasWeeklyDownloads">
<ClientOnly>
<VueUiSparkline class="w-full max-w-xs" :dataset :config>
Expand Down Expand Up @@ -402,4 +510,48 @@ const config = computed<VueUiSparklineConfig>(() => {
Geist Mono,
monospace !important;
}

.egg-pulse-target {
transform-origin: center;
will-change: transform;
}

.egg-pulse {
animation: egg-heartbeat 900ms ease-in-out 0ms 1;
}

/* 3 heart pulses */
@keyframes egg-heartbeat {
0% {
transform: scale(1);
}
10% {
transform: scale(1.1);
}
20% {
transform: scale(1);
}
35% {
transform: scale(1.03);
}
45% {
transform: scale(1);
}
60% {
transform: scale(1.01);
}
70% {
transform: scale(1);
}
100% {
transform: scale(1);
}
}

@media (prefers-reduced-motion: reduce) {
.egg-pulse {
animation: none !important;
transform: none !important;
}
}
</style>
Loading