restyling andalytics

This commit is contained in:
legop3
2026-06-19 03:00:42 -04:00
parent c7f5cb68f4
commit db8dc04b8c
8 changed files with 56 additions and 37 deletions
+11 -1
View File
@@ -63,6 +63,7 @@ export function trackAnalyticsEvent(name, payload = {}) {
if (!eventName) return;
const adapter = getAdapter();
if (!adapter || typeof adapter.track !== 'function') return;
const cleanPayload = normalizePayload(payload);
try {
/*
@@ -70,7 +71,16 @@ export function trackAnalyticsEvent(name, payload = {}) {
broken or blocked third-party script from interfering with rover driving,
scanner input, chat, or any other live-control UI.
*/
adapter.track(eventName, normalizePayload(payload));
/*
Umami accepts an event name without a data object. Avoid forwarding `{}` so
events that only need counting do not create noisy empty-property requests
in the dashboard or the network payload.
*/
if (Object.keys(cleanPayload).length) {
adapter.track(eventName, cleanPayload);
} else {
adapter.track(eventName);
}
} catch (error) {
console.warn('Analytics event failed', error);
}
@@ -47,10 +47,11 @@ function getGameTheme(themeColor) {
selectedButtonStyle: {
borderColor: rgba(rgb, 0.86),
borderLeftColor: rgba(rgb, 1),
backgroundColor: rgba(rgb, 0.16),
backgroundColor: rgba(rgb, 0.14),
},
titleBoxStyle: {
borderLeftColor: rgba(rgb, 0.88),
backgroundColor: rgba(rgb, 0.06),
},
boxStyle: {
borderColor: rgba(rgb, 0.62),
@@ -88,7 +89,7 @@ function GameChoice({ game, disabled, onVote }) {
type="button"
disabled={disabled || game.active}
onClick={() => onVote(game.id)}
className="button-dark flex min-h-[4.25rem] flex-col items-start justify-between border-l-4 px-1.5 py-1 text-left disabled:opacity-70"
className="button-dark flex min-h-[3.5rem] flex-col items-start justify-between border-l-4 border-neutral-600 bg-neutral-800 px-1 py-0.75 text-left hover:bg-neutral-700 disabled:opacity-70"
style={style}
>
<span className="flex w-full items-start justify-between gap-1">
@@ -106,9 +107,9 @@ function GameChoice({ game, disabled, onVote }) {
function StatGrid({ stats, theme }) {
if (!Array.isArray(stats) || !stats.length) return null;
return (
<div className="grid gap-1 sm:grid-cols-3">
<div className="grid gap-0.5 sm:grid-cols-3">
{stats.slice(0, 6).map((stat) => (
<div key={stat.label} className="border border-neutral-700 px-1.5 py-1" style={theme.boxStyle}>
<div key={stat.label} className="surface border border-neutral-700 px-1 py-0.75" style={theme.boxStyle}>
<p className="truncate text-[0.68rem] text-neutral-400">{stat.label}</p>
<p className="truncate text-sm font-semibold text-neutral-100">{stat.value}</p>
</div>
@@ -127,9 +128,9 @@ function ParticipantsBlock({ participants, theme }) {
.filter(Boolean);
return (
<div className="min-w-0 border border-neutral-700 px-2 py-1.5" style={theme.boxStyle}>
<div className="surface min-w-0 border border-neutral-700 px-1 py-0.75" style={theme.boxStyle}>
<p className="text-sm font-semibold text-neutral-400">Participants</p>
<p className="font-mono text-2xl font-semibold leading-tight text-neutral-50">
<p className="font-mono text-xl font-semibold leading-tight text-neutral-50">
{knownParticipants.length}
</p>
<p className="truncate text-sm leading-tight text-neutral-300">
@@ -160,15 +161,15 @@ function Leaderboard({ players, ownPlayer }) {
const hasOwnPoints = ownPlayer && Number.isFinite(ownPlayer.totalPoints);
return (
<div className="space-y-1">
<div className="grid gap-1 sm:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
<div className="border border-neutral-700 px-1.5 py-1">
<div className="grid gap-0.5 sm:grid-cols-[minmax(0,0.8fr)_minmax(0,1.2fr)]">
<div className="surface border border-neutral-700 px-1 py-0.75">
<p className="text-xs text-neutral-400">Your points</p>
<p className="text-lg font-semibold leading-tight text-neutral-50">
{hasOwnPoints ? ownPlayer.totalPoints : 0}
</p>
{ownPlayer?.rank ? <p className="text-[0.7rem] text-neutral-400">Rank {ownPlayer.rank}</p> : null}
</div>
<div className="border border-neutral-700 px-1.5 py-1">
<div className="surface border border-neutral-700 px-1 py-0.75">
<p className="mb-0.5 text-xs font-semibold text-neutral-300">Leaderboard</p>
{Array.isArray(players) && players.length ? (
<div className="space-y-0.5">
@@ -284,8 +285,8 @@ export default function BarcodeGamesPanel() {
};
return (
<CardFrame title="Barcode games" bodyClassName="space-y-2 p-1.5 text-sm">
<div className="grid gap-1 sm:grid-cols-2">
<CardFrame title="Barcode games" bodyClassName="space-y-1 p-0.5 text-sm">
<div className="grid gap-0.5 sm:grid-cols-2">
{games.map((game) => (
<GameChoice
key={game.id}
@@ -296,25 +297,25 @@ export default function BarcodeGamesPanel() {
))}
</div>
<section className="space-y-2 border-t border-neutral-700 pt-2">
<div className="grid items-start gap-2 lg:grid-cols-[minmax(0,1fr)_15rem_15rem]">
<div className="min-w-0 border-l-4 border-neutral-700 pl-2" style={activeTheme.titleBoxStyle}>
<p className="break-words text-2xl font-bold leading-tight text-neutral-50" style={activeTheme.textStyle}>
<section className="space-y-1 border-t border-neutral-700 pt-1">
<div className="grid items-stretch gap-0.5 lg:grid-cols-[minmax(0,1fr)_9rem_10rem]">
<div className="surface min-w-0 border-l-4 border-neutral-700 px-1 py-0.75" style={activeTheme.titleBoxStyle}>
<p className="break-words text-xl font-bold leading-tight text-neutral-50" style={activeTheme.textStyle}>
{display.title || activeGame?.title || 'No game active'}
</p>
<p className="mt-0.5 break-words text-lg font-semibold leading-tight text-neutral-200">
<p className="mt-0.5 break-words text-sm font-semibold leading-tight text-neutral-200">
{display.primary || 'Vote for a game to start'}
</p>
{display.secondary ? (
<p className="mt-1 break-words text-base leading-snug text-neutral-300">{display.secondary}</p>
<p className="mt-0.5 break-words text-xs leading-snug text-neutral-300">{display.secondary}</p>
) : null}
</div>
<div className="border border-neutral-700 px-2 py-1.5 text-left lg:text-right" style={activeTheme.boxStyle}>
<p className="text-sm font-semibold text-neutral-400">{display.timer?.label || 'Time'}</p>
<p className="font-mono text-2xl font-semibold leading-tight text-neutral-50">
<div className="surface border border-neutral-700 px-1 py-0.75 text-left lg:text-right" style={activeTheme.boxStyle}>
<p className="text-xs font-semibold text-neutral-400">{display.timer?.label || 'Time'}</p>
<p className="font-mono text-xl font-semibold leading-tight text-neutral-50">
{timerText || '--'}
</p>
<p className="text-sm leading-tight text-neutral-500">
<p className="text-xs leading-tight text-neutral-500">
{timerText ? 'active timer' : 'no timer'}
</p>
</div>
@@ -325,9 +326,13 @@ export default function BarcodeGamesPanel() {
<Leaderboard players={state.leaderboard} ownPlayer={state.ownPlayer} />
<div className="grid gap-2 border-t border-neutral-700 pt-1.5 sm:grid-cols-2">
<CounterList title="Most scanned objects" entries={state.counters?.objects} />
<CounterList title="Most scanned rovers" entries={state.counters?.rovers} />
<div className="grid gap-0.5 border-t border-neutral-700 pt-1 sm:grid-cols-2">
<div className="surface px-1 py-0.75">
<CounterList title="Most scanned objects" entries={state.counters?.objects} />
</div>
<div className="surface px-1 py-0.75">
<CounterList title="Most scanned rovers" entries={state.counters?.rovers} />
</div>
</div>
</CardFrame>
);
@@ -42,7 +42,7 @@ export default function SocketConnectionPill() {
setLastReason('');
show();
scheduleFade();
trackAnalyticsEvent('socket_connect');
trackAnalyticsEvent('socket_connect', { connected: true });
};
const onDisconnect = (reason) => {
@@ -70,7 +70,11 @@ export default function SocketConnectionPill() {
const onReconnectAttempt = () => {
setConnected(false);
show();
trackAnalyticsEventThrottled('socket_reconnect_attempt', {}, { key: 'socket_reconnect_attempt', throttleMs: 30 * 1000 });
trackAnalyticsEventThrottled(
'socket_reconnect_attempt',
{ connected: false },
{ key: 'socket_reconnect_attempt', throttleMs: 30 * 1000 },
);
};
socket.on('connect', onConnect);
+1 -1
View File
@@ -40,7 +40,7 @@
window.roverAnalytics = {
track: function (name, data) {
enqueue('track', [name, data || {}]);
enqueue('track', typeof data === 'undefined' ? [name] : [name, data]);
},
identify: function (data) {
enqueue('identify', [data || {}]);