expand more about me section

This commit is contained in:
zoop 2026-09-09 15:21:35 -04:00
parent f6daa5b349
commit 276a266977
No known key found for this signature in database
GPG Key ID: 15C0D336C9E08F3F
6 changed files with 364 additions and 304 deletions

View File

@ -20,15 +20,7 @@ const zone = fmt({ timeZoneName: "short" }).find((p) => p.type === "timeZoneName
aria-label={`zach's local time — ${hour}:${minute} ${mer} ${zone}`}
>
<Icon class="clock__ico" name="ph:clock" aria-hidden="true" />
<span class="clock__time" id="clock-time">
<span class="clock__grp">
{[...hour].map((d) => <span class="clock__digit">{d}</span>)}
</span>
<span class="clock__sep">:</span>
<span class="clock__grp">
{[...minute].map((d) => <span class="clock__digit">{d}</span>)}
</span>
</span>
<span class="clock__time" id="clock-time">{hour}:{minute}</span>
<span class="clock__mer" id="clock-mer">{mer}</span>
<span class="clock__zone" id="clock-zone">{zone}</span>
</div>
@ -36,82 +28,33 @@ const zone = fmt({ timeZoneName: "short" }).find((p) => p.type === "timeZoneName
<style>
.clock {
display: inline-flex;
align-items: center;
gap: 0.6ch;
padding: 0.42rem 0.8rem;
align-items: baseline;
gap: 0.55ch;
padding: 0.36rem 0.7rem;
background: var(--paper-2);
border: 1px solid var(--line);
border-radius: 8px;
font-family: var(--mono);
font-size: 0.95rem;
letter-spacing: 0.06em;
font-size: 0.82rem;
letter-spacing: 0.04em;
font-variant-numeric: tabular-nums;
}
.clock__ico {
width: 1em;
height: 1em;
width: 0.95em;
height: 0.95em;
flex: none;
color: var(--faint);
margin-right: 0.1ch;
align-self: center;
}
.clock__time {
display: inline-flex;
align-items: center;
gap: 0.3ch;
}
.clock__grp {
display: inline-flex;
gap: 0.18ch;
}
.clock__digit {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 1.05em;
padding: 0.14em 0.1em 0.16em;
background: linear-gradient(#201e1b, #191613);
border: 1px solid #2c2926;
border-radius: 3px;
font-weight: 600;
color: var(--accent);
text-shadow: 0 0 6px rgba(255, 171, 94, 0.32);
}
.clock__digit::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 50%;
height: 1px;
background: rgba(0, 0, 0, 0.55);
transform: translateY(-0.5px);
}
.clock__sep {
color: var(--accent);
font-weight: 600;
text-shadow: 0 0 6px rgba(255, 171, 94, 0.32);
animation: clock-blink 1s steps(1, end) infinite;
}
@keyframes clock-blink {
50% {
opacity: 0.25;
}
color: var(--ink);
}
.clock__mer,
.clock__zone {
font-size: 0.72em;
letter-spacing: 0.14em;
font-size: 0.82em;
letter-spacing: 0.1em;
color: var(--faint);
}
.clock__mer {
margin-left: 0.1ch;
}
@media (prefers-reduced-motion: reduce) {
.clock__sep {
animation: none;
}
}
</style>
<script>
@ -128,17 +71,13 @@ const zone = fmt({ timeZoneName: "short" }).find((p) => p.type === "timeZoneName
hour12: true,
});
const zoneFmt = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short" });
const tiles = (s: string) =>
`<span class="clock__grp">${[...s]
.map((d) => `<span class="clock__digit">${d}</span>`)
.join("")}</span>`;
const tick = () => {
const d = new Date();
const p = timeFmt.formatToParts(d);
const h = p.find((x) => x.type === "hour")?.value ?? "--";
const m = p.find((x) => x.type === "minute")?.value ?? "--";
const mer = (p.find((x) => x.type === "dayPeriod")?.value ?? "").toUpperCase();
timeEl.innerHTML = `${tiles(h)}<span class="clock__sep">:</span>${tiles(m)}`;
timeEl.textContent = `${h}:${m}`;
if (merEl) merEl.textContent = mer;
const z = zoneFmt.formatToParts(d).find((x) => x.type === "timeZoneName")?.value;
if (zoneEl && z) zoneEl.textContent = z;

View File

@ -1,57 +1,15 @@
---
import { Icon } from "astro-icon/components";
const clip = (s: string, n = 22): string =>
s && s.length > n ? s.slice(0, n - 1).trimEnd() + "…" : (s ?? "");
let np: any = null;
let top: any[] = [];
let configured = false;
try {
const r = await fetch(new URL("/api/spotify.json", Astro.url), {
headers: { accept: "application/json" },
});
const d: any = await r.json();
configured = !!d?.configured;
np = d?.nowPlaying ?? null;
top = Array.isArray(d?.top) ? d.top : [];
} catch {
}
const hasData = configured && (!!np || top.length > 0);
let mLabel = "now playing";
let mSub = "";
let mArt = "";
let mLive = false;
let mPct: number | null = null;
if (np?.isPlaying) {
mLive = true;
mLabel = clip(np.title);
mSub = np.artist ?? "";
mArt = np.art ?? "";
if (np.progressMs != null && np.durationMs)
mPct = Math.min(100, (np.progressMs / np.durationMs) * 100);
} else if (np?.playedAt) {
mLabel = "last played";
mSub = `${clip(np.title, 26)} · ${np.artist}`;
mArt = np.art ?? "";
} else if (top.length) {
mLabel = "top tracks";
mSub = "last 4 weeks";
mArt = top[0]?.art ?? "";
}
const mTitleAttr = np ? `${np.title} — ${np.artist}` : undefined;
---
<div class="music" id="music">
<button
class:list={["music__trigger", { "music__trigger--live": mLive }]}
class="music__trigger"
type="button"
data-music-open
aria-haspopup="dialog"
data-umami-event="music-open"
title={mTitleAttr}
hidden={!hasData}
hidden
>
<img
class="music__art"
@ -60,18 +18,15 @@ const mTitleAttr = np ? `${np.title} — ${np.artist}` : undefined;
width="42"
height="42"
decoding="async"
src={mArt || undefined}
hidden={!mArt}
hidden
/>
<span class="music__eq" aria-hidden="true"><i></i><i></i><i></i><i></i></span>
<span class="music__main">
<span class="music__label" id="music-label">{mLabel}</span>
<span class="music__sub" id="music-sub" aria-hidden="true">{mSub}</span>
<span class="music__label" id="music-label">now playing</span>
<span class="music__sub" id="music-sub" aria-hidden="true"></span>
</span>
<span class="music__arw" aria-hidden="true">&#8594;</span>
<span class="music__bar" id="music-bar" aria-hidden="true" hidden={mPct == null}>
<i style={mPct == null ? undefined : `--p:${mPct}%`}></i>
</span>
<span class="music__bar" id="music-bar" aria-hidden="true" hidden><i></i></span>
</button>
<dialog class="music-wall" data-music-dialog aria-label="music">

View File

@ -1,47 +1,5 @@
---
interface RBook {
title: string;
author: string;
cover: string | null;
url: string;
progress: number | null;
}
let current: RBook[] = [];
let want: RBook[] = [];
let recent: RBook[] = [];
try {
const r = await fetch(new URL("/api/reading.json", Astro.url), {
headers: { accept: "application/json" },
});
const d: any = await r.json();
if (d?.configured) {
current = Array.isArray(d.current) ? d.current : [];
want = Array.isArray(d.want) ? d.want : [];
recent = Array.isArray(d.recent) ? d.recent : [];
}
} catch {
}
const hasData = current.length > 0 || want.length > 0 || recent.length > 0;
const lead: RBook | null = current[0] ?? recent[0] ?? want[0] ?? null;
let rLabel = "reading";
let rSub = "";
if (lead) {
if (current.length) {
rLabel = lead.title;
rSub = lead.author;
} else if (recent.length) {
rLabel = "last read";
rSub = `${lead.title} · ${lead.author}`;
} else {
rLabel = "want to read";
rSub = `${lead.title} · ${lead.author}`;
}
}
const rCover = lead?.cover ?? "";
const rPct = current.length && lead?.progress != null ? lead.progress : null;
---
<div class="reading" id="books">
@ -51,7 +9,7 @@ const rPct = current.length && lead?.progress != null ? lead.progress : null;
data-reading-open
aria-haspopup="dialog"
data-umami-event="reading-open"
hidden={!hasData}
hidden
>
<img
class="reading__cover"
@ -60,23 +18,15 @@ const rPct = current.length && lead?.progress != null ? lead.progress : null;
width="28"
height="40"
decoding="async"
src={rCover || undefined}
hidden={!rCover}
hidden
/>
<span class="reading__ico" aria-hidden="true" hidden={!!rCover}>&#9634;</span>
<span class="reading__ico" aria-hidden="true">&#9634;</span>
<span class="reading__main">
<span class="reading__label" id="reading-label">{rLabel}</span>
<span class="reading__sub" id="reading-sub" aria-hidden="true">{rSub}</span>
<span class="reading__label" id="reading-label">reading</span>
<span class="reading__sub" id="reading-sub" aria-hidden="true"></span>
</span>
<span class="reading__arw" aria-hidden="true">&#8594;</span>
<span
class="reading__bar"
id="reading-bar"
aria-hidden="true"
hidden={rPct == null}
>
<i style={rPct == null ? undefined : `--p:${rPct}%`}></i>
</span>
<span class="reading__bar" id="reading-bar" aria-hidden="true" hidden><i></i></span>
</button>
<dialog class="reading-wall" data-reading-dialog aria-label="reading">

View File

@ -1,135 +1,288 @@
---
import { Icon } from "astro-icon/components";
const fmt = (n: number) => new Intl.NumberFormat("en-US").format(n);
let pv = 0;
let vis = 0;
let ok = false;
try {
const r = await fetch(new URL("/api/stats.json", Astro.url), {
headers: { accept: "application/json" },
});
const d: any = await r.json();
if (d?.configured && (d.pageviews || d.visitors)) {
pv = Math.max(0, Math.round(Number(d.pageviews) || 0));
vis = Math.max(0, Math.round(Number(d.visitors) || 0));
ok = true;
}
} catch {
}
const width = Math.max(6, String(pv).length);
const digits = ok ? String(pv).padStart(width, "0") : "";
let lead = true;
const cells = [...digits].map((c, i) => {
if (c !== "0" || i === digits.length - 1) lead = false;
return { c, lead };
});
---
<div class="stats" id="stats" hidden={!ok}>
<div class="stats" id="stats">
<button
class="stats__trigger"
type="button"
data-stats-open
aria-haspopup="dialog"
data-umami-event="stats-open"
hidden
>
<Icon class="stats__ico" name="ph:eye" aria-hidden="true" />
<span class="stats__reel" id="stats-reel">
{cells.map((x) => <span class:list={["stats__d", { "stats__d--lead": x.lead }]}>{x.c}</span>)}
</span>
<span class="stats__reel" id="stats-reel"></span>
<span class="stats__unit">views</span>
<span class="stats__sub" id="stats-sub" aria-hidden="true">{ok ? `${fmt(vis)} visitors` : ""}</span>
<span class="stats__arw" aria-hidden="true">&#8594;</span>
</button>
<dialog class="stats-wall" data-stats-dialog aria-label="analytics">
<div class="stats-wall__bar">
<span class="stats-wall__title">analytics</span>
<button
class="stats-wall__close"
type="button"
data-stats-close
aria-label="close"
autofocus>&#10005;</button>
</div>
<style>
.stats {
position: relative;
<div class="stats-wall__body">
<div class="stat-bigs">
<div class="stat-big">
<span class="stat-big__n" id="sb-pv">&mdash;</span>
<span class="stat-big__k">pageviews</span>
</div>
<div class="stat-big">
<span class="stat-big__n" id="sb-vis">&mdash;</span>
<span class="stat-big__k">visitors</span>
</div>
<div class="stat-big">
<span class="stat-big__n" id="sb-visits">&mdash;</span>
<span class="stat-big__k">visits</span>
</div>
</div>
<div class="stat-block" id="sb-pages-block" hidden>
<h3 class="label">top pages</h3>
<ul class="stat-list" id="sb-pages"></ul>
</div>
<div class="stat-block" id="sb-refs-block" hidden>
<h3 class="label">top referrers</h3>
<ul class="stat-list" id="sb-refs"></ul>
</div>
<p class="stats-wall__foot">
all&#8209;time &middot; via <a
href="https://umami.is"
target="_blank"
rel="noopener">umami &#8599;</a>
</p>
</div>
</dialog>
</div>
<style is:global>
.stats__trigger {
display: inline-flex;
align-items: center;
gap: 0.6ch;
padding: 0.3rem 0.62rem;
padding: 0.32rem 0.64rem;
background: var(--paper-2);
border: 1px solid var(--line);
border-radius: 7px;
overflow: hidden;
border-radius: 8px;
cursor: pointer;
font-family: var(--mono);
font-size: 0.95rem;
letter-spacing: 0.06em;
color: var(--muted);
font-variant-numeric: tabular-nums;
transition:
padding 0.22s ease,
border-color 0.18s ease;
border-color 0.18s ease,
color 0.18s ease;
}
.stats:hover,
.stats:focus-within {
padding-bottom: 1rem;
.stats__trigger:hover,
.stats__trigger:focus-visible {
border-color: var(--faint);
color: var(--ink);
}
.stats__trigger:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 3px;
}
.stats__ico {
width: 0.95em;
height: 0.95em;
width: 1em;
height: 1em;
flex: none;
color: var(--faint);
}
.stats__reel {
display: inline-flex;
gap: 0.16ch;
font-variant-numeric: tabular-nums;
}
.stats__d {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 0.92em;
padding: 0.09em 0.06em 0.11em;
background: linear-gradient(#201e1b, #191613);
border: 1px solid #2c2926;
border-radius: 2px;
font-size: 0.82rem;
font-weight: 600;
color: var(--accent);
text-shadow: 0 0 4px rgba(255, 171, 94, 0.3);
}
.stats__d::after {
content: "";
position: absolute;
left: 0;
right: 0;
top: 50%;
height: 1px;
background: rgba(0, 0, 0, 0.55);
transform: translateY(-0.5px);
}
.stats__d--lead {
color: var(--faint);
text-shadow: none;
color: var(--ink);
}
.stats__unit {
font-size: 0.66rem;
letter-spacing: 0.04em;
font-size: 0.62em;
letter-spacing: 0.1em;
color: var(--faint);
}
.stats__sub {
position: absolute;
left: 0.62rem;
right: 0.62rem;
bottom: 0.22rem;
font-size: 0.62rem;
letter-spacing: 0.03em;
.stats__arw {
color: var(--faint);
white-space: nowrap;
font-size: 0.8em;
transition: transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
.stats__trigger:hover .stats__arw {
transform: translateX(0.25em);
}
.stats-wall {
margin: auto;
width: min(24rem, calc(100vw - 1.5rem));
max-height: min(82vh, 40rem);
padding: 0;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--paper-2);
color: var(--ink);
box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.75);
overflow: hidden;
}
.stats-wall[open] {
animation: stats-in 0.28s cubic-bezier(0.17, 0.89, 0.32, 1.28);
}
@keyframes stats-in {
from {
opacity: 0;
transform: translateY(8px) scale(0.96);
}
}
.stats-wall::backdrop {
background: rgba(6, 5, 4, 0.68);
backdrop-filter: blur(3px);
}
.stats-wall__bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.6rem 0.85rem;
border-bottom: 1px solid var(--line);
}
.stats-wall__title {
font-family: var(--mono);
font-size: 0.72rem;
letter-spacing: 0.1em;
color: var(--faint);
}
.stats-wall__close {
background: none;
border: 0;
color: var(--muted);
cursor: pointer;
font-size: 0.85rem;
line-height: 1;
padding: 0.3rem;
}
.stats-wall__close:hover {
color: var(--accent);
}
.stats-wall__body {
padding: 1rem;
overflow-y: auto;
max-height: calc(min(82vh, 40rem) - 2.6rem);
}
.stat-bigs {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
}
.stat-big {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding: 0.7rem 0.6rem;
border: 1px solid var(--line);
border-radius: 6px;
background: #151412;
text-align: center;
}
.stat-big__n {
font-family: var(--mono);
font-size: 1.1rem;
font-weight: 600;
color: var(--ink);
font-variant-numeric: tabular-nums;
}
.stat-big__k {
font-family: var(--mono);
font-size: 0.58rem;
letter-spacing: 0.12em;
color: var(--faint);
}
.stat-block {
margin-top: 1.4rem;
}
.stat-block .label {
margin-bottom: 0.6rem;
}
.stat-list {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.stat-list li {
position: relative;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1ch;
padding: 0.34rem 0.5rem;
border-radius: 4px;
overflow: hidden;
font-family: var(--mono);
font-size: 0.74rem;
}
.stat-list li::before {
content: "";
position: absolute;
inset: 0 auto 0 0;
width: var(--w, 0%);
background: rgba(255, 171, 94, 0.1);
border-right: 1px solid rgba(255, 171, 94, 0.25);
}
.stat-list__k {
position: relative;
overflow: hidden;
text-overflow: ellipsis;
opacity: 0;
transform: translateY(3px);
pointer-events: none;
transition:
opacity 0.18s ease,
transform 0.2s ease;
white-space: nowrap;
color: var(--ink);
}
.stats:hover .stats__sub,
.stats:focus-within .stats__sub {
opacity: 1;
transform: none;
.stat-list__v {
position: relative;
flex: none;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.stats-wall__foot {
margin: 1.5rem 0 0;
font-family: var(--mono);
font-size: 0.66rem;
letter-spacing: 0.03em;
color: var(--faint);
}
.stats-wall__foot a {
color: var(--faint);
text-decoration: none;
}
.stats-wall__foot a:hover {
color: var(--accent);
}
</style>
<script>
import { hashDialog } from "../scripts/hash-dialog.ts";
const fmt = (n: number) => new Intl.NumberFormat("en-US").format(n);
const $ = (id: string) => document.getElementById(id);
const esc = (s: string) =>
s.replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]!);
function rows(el: HTMLElement, items: { k: string; v: number }[]): void {
const max = items.reduce((m, i) => Math.max(m, i.v), 0) || 1;
el.innerHTML = items
.map(
(i) =>
`<li style="--w:${Math.round((i.v / max) * 100)}%"><span class="stat-list__k">${esc(
i.k,
)}</span><span class="stat-list__v">${fmt(i.v)}</span></li>`,
)
.join("");
}
async function load(): Promise<void> {
let d: any;
@ -143,29 +296,71 @@ const cells = [...digits].map((c, i) => {
(!d || !d.configured || (!d.pageviews && !d.visitors)) &&
location.search.includes("mockstats")
)
d = { configured: true, pageviews: 128473, visitors: 41902 };
d = {
configured: true,
pageviews: 128473,
visitors: 41902,
visits: 52310,
topPages: [
{ path: "/", count: 90112 },
{ path: "/projects", count: 21044 },
{ path: "/notes", count: 9010 },
],
topReferrers: [
{ ref: "github.com", count: 4102 },
{ ref: "news.ycombinator.com", count: 880 },
],
};
if (!d || !d.configured || (!d.pageviews && !d.visitors)) return;
const block = document.getElementById("stats");
const reel = document.getElementById("stats-reel");
const sub = document.getElementById("stats-sub");
if (!block || !reel) return;
const trigger = document.querySelector<HTMLButtonElement>(".stats__trigger");
const reel = $("stats-reel");
if (!trigger || !reel) return;
const pv = Math.max(0, Math.round(Number(d.pageviews) || 0));
const vis = Math.max(0, Math.round(Number(d.visitors) || 0));
const width = Math.max(6, String(pv).length);
const digits = String(pv).padStart(width, "0");
let lead = true;
reel.innerHTML = [...digits]
.map((c, i) => {
if (c !== "0" || i === digits.length - 1) lead = false;
return `<span class="stats__d${lead ? " stats__d--lead" : ""}">${c}</span>`;
})
.join("");
reel.textContent = fmt(pv);
trigger.hidden = false;
if (sub) sub.textContent = `${fmt(vis)} visitors`;
block.hidden = false;
const pvEl = $("sb-pv");
const visEl = $("sb-vis");
const visitsEl = $("sb-visits");
if (pvEl) pvEl.textContent = fmt(pv);
if (visEl) visEl.textContent = fmt(Math.max(0, Math.round(Number(d.visitors) || 0)));
if (visitsEl) visitsEl.textContent = fmt(Math.max(0, Math.round(Number(d.visits) || 0)));
const pages = Array.isArray(d.topPages) ? d.topPages : [];
const refs = Array.isArray(d.topReferrers) ? d.topReferrers : [];
if (pages.length) {
rows(
$("sb-pages")!,
pages.map((p: any) => ({ k: p.path, v: Number(p.count) || 0 })),
);
$("sb-pages-block")!.hidden = false;
}
if (refs.length) {
rows(
$("sb-refs")!,
refs.map((r: any) => ({ k: r.ref, v: Number(r.count) || 0 })),
);
$("sb-refs-block")!.hidden = false;
}
}
const dialog = document.querySelector<HTMLDialogElement>("[data-stats-dialog]");
const open = document.querySelector<HTMLButtonElement>("[data-stats-open]");
const close = document.querySelector<HTMLButtonElement>("[data-stats-close]");
const openWall = hashDialog({
name: "stats",
dialog,
canOpen: () => !!open && !open.hidden,
});
open?.addEventListener("click", () => (openWall ? openWall() : dialog?.showModal()));
close?.addEventListener("click", () => dialog?.close());
dialog?.addEventListener("click", (e) => {
if (e.target === dialog) dialog.close();
});
load();
</script>

View File

@ -110,7 +110,7 @@ export const GET: APIRoute = async () => {
const env = getEnv();
const headers = {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=30, s-maxage=60, stale-while-revalidate=300",
"Cache-Control": "public, max-age=120, s-maxage=180, stale-while-revalidate=600",
};
try {

View File

@ -32,7 +32,14 @@ export const GET: APIRoute = async () => {
headers: { "content-type": "application/json", "cache-control": "public, max-age=300" },
});
const hidden = { configured: false, pageviews: 0, visitors: 0 };
const hidden = {
configured: false,
pageviews: 0,
visitors: 0,
visits: 0,
topPages: [] as { path: string; count: number }[],
topReferrers: [] as { ref: string; count: number }[],
};
if (!(shareId || (id && key))) return json(hidden);
const headers: Record<string, string> = { accept: "application/json" };
@ -56,15 +63,29 @@ export const GET: APIRoute = async () => {
if (!websiteId) return json({ ...hidden, configured: true });
const url = `${base}/websites/${websiteId}/stats?startAt=0&endAt=${Date.now()}`;
const res = await fetch(url, { headers });
const data: any = await res.json();
const w = `${base}/websites/${websiteId}`;
const range = `startAt=0&endAt=${Date.now()}`;
const get = (path: string) => fetch(`${w}/${path}`, { headers }).then((r) => r.json());
const pageviews = num(data?.pageviews);
const visitors = num(data?.visitors);
const [data, pages, refs] = await Promise.all([
get(`stats?${range}`),
get(`metrics?${range}&type=path&limit=6`).catch(() => []),
get(`metrics?${range}&type=referrer&limit=6`).catch(() => []),
]);
const pageviews = num((data as any)?.pageviews);
const visitors = num((data as any)?.visitors);
const visits = num((data as any)?.visits);
if (!pageviews && !visitors) return json({ ...hidden, configured: true });
return json({ configured: true, pageviews, visitors });
const topPages = (Array.isArray(pages) ? pages : [])
.map((m: any) => ({ path: String(m?.x ?? ""), count: num(m?.y) }))
.filter((p) => p.path);
const topReferrers = (Array.isArray(refs) ? refs : [])
.map((m: any) => ({ ref: String(m?.x ?? ""), count: num(m?.y) }))
.filter((r) => r.ref);
return json({ configured: true, pageviews, visitors, visits, topPages, topReferrers });
} catch {
return json({ ...hidden, configured: true });
}