Skip to content

Commit b303385

Browse files
committed
feat(platform/wasm): add uv/pv counter
1 parent 54891e1 commit b303385

File tree

1 file changed

+177
-20
lines changed

1 file changed

+177
-20
lines changed

.github/assets/index.html

Lines changed: 177 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,27 +57,136 @@
5757
opacity: 0.85;
5858
margin: 0;
5959
}
60-
#source-link {
60+
#metrics {
6161
position: fixed;
62-
top: 20px;
63-
right: 20px;
62+
bottom: 24px;
63+
left: 24px;
64+
display: inline-flex;
65+
align-items: center;
66+
justify-content: center;
67+
gap: 0;
68+
padding: 10px;
69+
border-radius: 999px;
70+
border: 1px solid rgba(96, 158, 255, 0.35);
71+
background: linear-gradient(135deg, rgba(18, 30, 58, 0.85), rgba(8, 12, 28, 0.85));
72+
box-shadow: 0 12px 28px rgba(10, 22, 48, 0.45), inset 0 0 12px rgba(64, 124, 255, 0.25);
73+
font-size: 0.78rem;
74+
letter-spacing: 0.14em;
75+
text-transform: uppercase;
76+
backdrop-filter: blur(12px);
77+
z-index: 8;
78+
overflow: hidden;
79+
max-width: 56px;
80+
min-height: 46px;
81+
box-sizing: border-box;
82+
transition: max-width 0.28s ease, padding 0.28s ease, border-color 0.28s ease;
83+
}
84+
#metrics:hover,
85+
#metrics:focus-within {
86+
max-width: 320px;
87+
padding: 8px 14px;
88+
border-color: rgba(126, 188, 255, 0.6);
89+
justify-content: flex-start;
90+
}
91+
#source-link {
6492
display: inline-flex;
65-
color: inherit;
93+
align-items: center;
94+
justify-content: center;
95+
width: 26px;
96+
height: 26px;
97+
border-radius: 50%;
98+
color: #d7e5ff;
6699
text-decoration: none;
100+
opacity: 0.8;
101+
transition: opacity 0.2s ease, transform 0.2s ease;
67102
}
68103
#source-link svg {
69-
width: 24px;
70-
height: 24px;
104+
width: 20px;
105+
height: 20px;
71106
fill: currentColor;
72-
transition: transform 0.2s ease, opacity 0.2s ease;
73107
}
74-
#source-link:hover svg,
75-
#source-link:focus svg {
76-
transform: scale(1.05);
108+
#source-link:hover,
109+
#source-link:focus-visible {
110+
opacity: 1;
111+
transform: translateY(-1px);
112+
}
113+
.metrics-details {
114+
display: inline-flex;
115+
align-items: center;
116+
gap: 12px;
117+
opacity: 0;
118+
transform: translateX(-6px);
119+
pointer-events: none;
120+
max-width: 0;
121+
flex: 0 1 0%;
122+
overflow: hidden;
123+
margin-left: 0;
124+
transition: opacity 0.18s ease, transform 0.2s ease, max-width 0.28s ease, margin-left 0.28s ease;
125+
}
126+
#metrics:hover .metrics-details,
127+
#metrics:focus-within .metrics-details {
128+
opacity: 1;
129+
transform: translateX(0);
130+
pointer-events: auto;
131+
max-width: 260px;
132+
flex-basis: 260px;
133+
margin-left: 12px;
134+
}
135+
#metrics label {
136+
position: relative;
137+
display: inline-flex;
138+
align-items: center;
139+
gap: 6px;
140+
color: #d7e5ff;
141+
font-weight: 600;
142+
cursor: default;
143+
}
144+
#metrics label::before {
145+
content: '';
146+
width: 16px;
147+
height: 16px;
148+
background-repeat: no-repeat;
149+
background-size: contain;
77150
opacity: 0.85;
78151
}
152+
#metrics label::after {
153+
position: absolute;
154+
bottom: 120%;
155+
left: 50%;
156+
padding: 4px 8px;
157+
border-radius: 6px;
158+
background: rgba(12, 20, 40, 0.9);
159+
box-shadow: 0 6px 14px rgba(6, 12, 28, 0.3);
160+
color: rgba(215, 229, 255, 0.85);
161+
font-size: 0.65rem;
162+
letter-spacing: 0.1em;
163+
text-transform: uppercase;
164+
white-space: nowrap;
165+
transform: translate(-50%, 4px);
166+
opacity: 0;
167+
pointer-events: none;
168+
transition: opacity 0.18s ease, transform 0.18s ease;
169+
content: attr(data-hint);
170+
}
171+
#metrics label:hover::after,
172+
#metrics label:focus-visible::after {
173+
opacity: 1;
174+
transform: translate(-50%, 0);
175+
}
176+
#uv_label::before {
177+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23d7e5ff' d='M8 7a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm0 1.5c-2.9 0-5.5 1.31-5.5 3.25V13c0 .55.45 1 1 1h9c.55 0 1-.45 1-1v-1.25C13.5 9.81 10.9 8.5 8 8.5Z'/%3E%3C/svg%3E");
178+
}
179+
#pv_label::before {
180+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='%23d7e5ff' d='M8 3.5c3.04 0 5.64 1.84 6.94 4.5-1.3 2.66-3.9 4.5-6.94 4.5S2.36 10.66 1.06 8C2.36 5.34 4.96 3.5 8 3.5Zm0 2.5a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z'/%3E%3C/svg%3E");
181+
}
182+
#metrics .separator {
183+
color: rgba(215, 229, 255, 0.45);
184+
font-size: 0.7rem;
185+
letter-spacing: normal;
186+
}
79187
</style>
80188
<script src="./coi-serviceworker.min.js"></script>
189+
<script defer src="https://stateflare.yuchanns.xyz/track.js"></script>
81190
</head>
82191
<body>
83192
<div id="overlay">
@@ -88,16 +197,25 @@ <h1 id="overlay-title"></h1>
88197
</div>
89198
</div>
90199
<canvas id="canvas" oncontextmenu="event.preventDefault()"></canvas>
91-
<a
92-
id="source-link"
93-
href="https://github.com/cloudwu/deepfuture"
94-
target="_blank"
95-
rel="noopener noreferrer"
96-
>
97-
<svg viewBox="0 0 16 16" aria-hidden="true">
98-
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8" />
99-
</svg>
100-
</a>
200+
<div id="metrics" aria-live="polite" aria-expanded="false">
201+
<a
202+
id="source-link"
203+
href="https://github.com/cloudwu/deepfuture"
204+
target="_blank"
205+
rel="noopener noreferrer"
206+
aria-label="Source code on GitHub"
207+
>
208+
<svg viewBox="0 0 16 16" aria-hidden="true">
209+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8" />
210+
</svg>
211+
</a>
212+
<div class="metrics-details">
213+
<span class="separator" aria-hidden="true">/</span>
214+
<label id="uv_label" data-hint="UV" aria-label="Unique Visitors" title="UV"></label>
215+
<span class="separator" aria-hidden="true">/</span>
216+
<label id="pv_label" data-hint="PV" aria-label="Page Views" title="PV"></label>
217+
</div>
218+
</div>
101219

102220
<script>
103221
(function () {
@@ -106,6 +224,45 @@ <h1 id="overlay-title"></h1>
106224
const introEl = document.getElementById('overlay-intro');
107225
const statusEl = document.getElementById('overlay-status');
108226
const sourceLinkEl = document.getElementById('source-link');
227+
const uvLabel = document.getElementById('uv_label');
228+
const pvLabel = document.getElementById('pv_label');
229+
const metrics = document.getElementById('metrics');
230+
231+
function attachLabelSanitizer(label, prefix) {
232+
if (!label) return;
233+
const config = { childList: true, characterData: true, subtree: true };
234+
const regex = new RegExp('^\\s*' + prefix + '\\s*[::\\-–]?\\s*', 'i');
235+
236+
const apply = () => {
237+
const raw = label.textContent || '';
238+
if (!raw) return;
239+
const cleaned = raw.replace(regex, '').trim();
240+
if (cleaned !== raw) {
241+
observer.disconnect();
242+
label.textContent = cleaned;
243+
observer.observe(label, config);
244+
}
245+
};
246+
247+
let observer = new MutationObserver(apply);
248+
observer.observe(label, config);
249+
apply();
250+
}
251+
252+
attachLabelSanitizer(uvLabel, 'uv');
253+
attachLabelSanitizer(pvLabel, 'pv');
254+
255+
if (metrics) {
256+
const setExpanded = (expanded) => metrics.setAttribute('aria-expanded', expanded ? 'true' : 'false');
257+
metrics.addEventListener('mouseenter', () => setExpanded(true));
258+
metrics.addEventListener('mouseleave', () => setExpanded(false));
259+
metrics.addEventListener('focusin', () => setExpanded(true));
260+
metrics.addEventListener('focusout', () => {
261+
if (!metrics.contains(document.activeElement)) {
262+
setExpanded(false);
263+
}
264+
});
265+
}
109266

110267
const locale = (navigator.language || navigator.userLanguage || 'en').toLowerCase();
111268
const isChinese = locale.startsWith('zh');

0 commit comments

Comments
 (0)