Skip to content

Commit 0d48c21

Browse files
committed
chore: update
1 parent 1066b18 commit 0d48c21

4 files changed

Lines changed: 262 additions & 97 deletions

File tree

app/components/SiteFooter.vue

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const commitUrl = computed(() =>
1515
const { stats } = useSiteStats()
1616
const totalViews = computed(() => stats.value.totalViews?.toLocaleString('en-US') ?? '')
1717
const onlineVisitors = computed(() => stats.value.onlineVisitors?.toLocaleString('en-US') ?? '')
18+
const isPresenceConnected = computed(() => stats.value.onlineVisitors !== null)
1819
1920
const socialLinks = [
2021
{ label: 'GitHub', href: 'https://github.com/zyyv', external: true },
@@ -44,7 +45,10 @@ function backToTop() {
4445
<dd>{{ totalViews }}</dd>
4546
</div>
4647
<div>
47-
<dt><span class="site-footer__live-dot" aria-hidden="true" /> Online</dt>
48+
<dt class="site-footer__online-label">
49+
<StatusDot :active="isPresenceConnected" pulse />
50+
Online
51+
</dt>
4852
<dd>{{ onlineVisitors }}</dd>
4953
</div>
5054
</dl>
@@ -171,15 +175,10 @@ function backToTop() {
171175
color: color-mix(in srgb, currentColor 48%, transparent);
172176
}
173177
174-
.site-footer__live-dot {
175-
display: inline-block;
176-
width: 0.38rem;
177-
height: 0.38rem;
178-
margin-right: 0.18rem;
179-
border-radius: 50%;
180-
background: #4f9a68;
181-
box-shadow: 0 0 0 0.18rem color-mix(in srgb, #4f9a68 18%, transparent);
182-
vertical-align: 0.04rem;
178+
.site-footer__online-label {
179+
display: inline-flex;
180+
align-items: center;
181+
gap: 0.35rem;
183182
}
184183
185184
.site-footer__links a {

app/components/StatusDot.vue

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<script setup lang="ts">
2+
interface Props {
3+
active?: boolean
4+
color?: string
5+
pulse?: boolean
6+
}
7+
8+
const props = withDefaults(defineProps<Props>(), {
9+
active: true,
10+
color: '#4f9a68',
11+
pulse: false,
12+
})
13+
14+
const dotStyle = computed(() => ({ '--status-dot-color': props.color }))
15+
</script>
16+
17+
<template>
18+
<span
19+
class="status-dot"
20+
:class="{
21+
'status-dot--active': active,
22+
'status-dot--pulse': active && pulse,
23+
}"
24+
:style="dotStyle"
25+
aria-hidden="true"
26+
/>
27+
</template>
28+
29+
<style scoped>
30+
.status-dot {
31+
position: relative;
32+
display: inline-block;
33+
width: 0.4rem;
34+
height: 0.4rem;
35+
flex: 0 0 auto;
36+
border-radius: 50%;
37+
background: color-mix(in srgb, currentColor 28%, transparent);
38+
}
39+
40+
.status-dot--active {
41+
background: var(--status-dot-color);
42+
box-shadow: 0 0 0 0.15rem color-mix(in srgb, var(--status-dot-color) 16%, transparent);
43+
}
44+
45+
.status-dot--pulse::after {
46+
position: absolute;
47+
inset: -0.16rem;
48+
border: 1px solid color-mix(in srgb, var(--status-dot-color) 58%, transparent);
49+
border-radius: inherit;
50+
content: '';
51+
opacity: 0;
52+
transform: scale(0.72);
53+
animation: status-dot-pulse 2.2s cubic-bezier(0.16, 1, 0.3, 1) infinite;
54+
pointer-events: none;
55+
}
56+
57+
@keyframes status-dot-pulse {
58+
0% {
59+
opacity: 0;
60+
transform: scale(0.72);
61+
}
62+
63+
14% {
64+
opacity: 0.62;
65+
}
66+
67+
72%,
68+
100% {
69+
opacity: 0;
70+
transform: scale(1.55);
71+
}
72+
}
73+
74+
@media (prefers-reduced-motion: reduce) {
75+
.status-dot--pulse::after {
76+
opacity: 0.38;
77+
transform: scale(1);
78+
animation: none;
79+
}
80+
}
81+
</style>

0 commit comments

Comments
 (0)