mirror of
https://github.com/pupperpowell/bibdle.git
synced 2026-10-22 05:54:10 -04:00
83 lines
3.3 KiB
Svelte
83 lines
3.3 KiB
Svelte
<script lang="ts">
|
|
import Container from '$lib/components/Container.svelte';
|
|
import CohortHeatmap from '$lib/components/charts/CohortHeatmap.svelte';
|
|
import MiniLineChart from '$lib/components/charts/MiniLineChart.svelte';
|
|
import BarList from '$lib/components/charts/BarList.svelte';
|
|
import type { SnapshotPayload, StatsSettings } from '$lib/types/stats';
|
|
import { pctOrDash, numOrDash } from '$lib/utils/stats-format';
|
|
|
|
interface Props {
|
|
payload: SnapshotPayload;
|
|
settings: StatsSettings;
|
|
}
|
|
let { payload, settings }: Props = $props();
|
|
|
|
const mix = $derived(payload.retention.segmentMix);
|
|
const colors = ['#60a5fa', '#34d399', '#a78bfa', '#f59e0b', '#fb7185'];
|
|
</script>
|
|
|
|
<section class="flex flex-col gap-3">
|
|
<h2 class="text-lg font-bold text-gray-100">3 · Retention Lab</h2>
|
|
|
|
<div class="grid gap-3 md:grid-cols-2">
|
|
<Container class="p-4 w-full items-start text-left">
|
|
<p class="text-[11px] font-semibold uppercase tracking-wider text-gray-400">
|
|
Overall return rate
|
|
</p>
|
|
<p class="text-2xl font-bold text-gray-100">
|
|
{pctOrDash(payload.kpis.overallReturnRate)}
|
|
</p>
|
|
<p class="text-xs text-gray-400">
|
|
Share of all players who ever played more than once. Target:
|
|
{settings.return7_target}%+ within 7 days.
|
|
</p>
|
|
</Container>
|
|
<Container class="p-4 w-full items-start text-left">
|
|
<p class="text-[11px] font-semibold uppercase tracking-wider text-gray-400">
|
|
Time to second play
|
|
</p>
|
|
<BarList items={payload.retention.timeToSecondPlay.map((b) => ({ label: b.label, value: b.value }))} />
|
|
</Container>
|
|
</div>
|
|
|
|
<Container class="p-4 w-full items-stretch text-left gap-2">
|
|
<p class="font-semibold text-gray-100">Cohort retention triangle</p>
|
|
<p class="text-xs text-gray-400">
|
|
Each row is a starting week; each column is weeks later. Darker = more came back.
|
|
Only resolved weeks are filled.
|
|
</p>
|
|
<CohortHeatmap cohorts={payload.retention.cohorts} weeks={8} />
|
|
</Container>
|
|
|
|
<Container class="p-4 w-full items-stretch text-left gap-2">
|
|
<p class="font-semibold text-gray-100">Retention curves</p>
|
|
<p class="text-xs text-gray-400">
|
|
D1 / D7 / D30 return over time. Latest resolvable cohorts only.
|
|
</p>
|
|
<MiniLineChart
|
|
series={[
|
|
{ name: 'D1', color: '#34d399', points: payload.retention.curves.d1 },
|
|
{ name: 'D7', color: '#60a5fa', points: payload.retention.curves.d7 },
|
|
{ name: 'D30', color: '#a78bfa', points: payload.retention.curves.d30 }
|
|
]}
|
|
ySuffix="%"
|
|
/>
|
|
</Container>
|
|
|
|
<Container class="p-4 w-full items-stretch text-left gap-2">
|
|
<p class="font-semibold text-gray-100">Segment mix over time</p>
|
|
<p class="text-xs text-gray-400">
|
|
Active players split into new, core, returning, at-risk and churned. Total tracked:
|
|
{numOrDash(payload.activity.mau)} recently active.
|
|
</p>
|
|
<MiniLineChart
|
|
series={[
|
|
{ name: 'New', color: colors[0], points: mix.map((m) => ({ date: m.date, value: m.new })) },
|
|
{ name: 'Core', color: colors[1], points: mix.map((m) => ({ date: m.date, value: m.core })) },
|
|
{ name: 'Returning', color: colors[2], points: mix.map((m) => ({ date: m.date, value: m.returning })) },
|
|
{ name: 'At risk', color: colors[3], points: mix.map((m) => ({ date: m.date, value: m.atRisk })) },
|
|
{ name: 'Churned', color: colors[4], points: mix.map((m) => ({ date: m.date, value: m.churned })) }
|
|
]}
|
|
/>
|
|
</Container>
|
|
</section> |