{"release_id":"01a1c502-4ad1-0000-ec62-adc54d3c69aa","chunk_id":"01a1c502-4cda-75e2-b631-5ae5bf0f8f61","file":"188.Ccn1IFOY.mjs","mappings":"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","names":["users","$.state","$.proxy","untrack","fetched","$$props","loadingMore","loadError","ownerNickname","$.derived","ownerAvatarUrl","getAvatarUrl","hasMore","$.get","onMount","trackFollowListView","loadMore","$.set","res","t","body","seen","u","fresh","main","root","$.head","$$anchor","meta","root_1","$0","div","div_1","a","$.child","node","DuelAvatar","$.reset","div_2","$.sibling","a_1","text","p","div_3","a_2","text_2","span","a_3","text_4","span_1","div_4","root_2","p_1","p_2","a_4","root_3","$.template_effect","$.set_text","text_8","$$render","consequent","div_5","$.first_child","fragment","UserListItem","div_6","root_6","button","text_9","$.delegated","consequent_2","p_3","root_7","consequent_3","consequent_1","alternate","$.set_attribute","$1","$2","$4","text_3","$6","text_5"],"sources":["../../../../../../src/routes/u/[nickname]/followers/+page.svelte"],"sourcesContent":["<script lang=\"ts\">\n\timport { t } from '$lib/i18n';\n\timport { onMount, untrack } from 'svelte';\n\timport DuelAvatar from '$lib/components/duel/DuelAvatar.svelte';\n\timport UserListItem from '$lib/components/profile/UserListItem.svelte';\n\timport { getAvatarUrl } from '$lib/avatar';\n\timport { trackFollowListView } from '$lib/analytics';\n\n\tlet { data } = $props();\n\n\ttype Row = (typeof data.users)[number];\n\tlet users = $state<Row[]>(untrack(() => [...data.users]));\n\t// Rows the SERVER has served, including ones the dedupe below dropped. The\n\t// next offset and the \"is there more\" check both key off this rather than\n\t// the rendered list: a dropped duplicate would otherwise pull the offset\n\t// back a row and, because the rendered count can then never reach the\n\t// server's total, leave the button live on an exhausted list.\n\tlet fetched = $state(untrack(() => data.users.length));\n\tlet loadingMore = $state(false);\n\tlet loadError = $state('');\n\n\tconst ownerNickname = $derived(data.owner.nickname ?? '');\n\tconst ownerAvatarUrl = $derived(getAvatarUrl(data.owner.avatarPath));\n\tconst hasMore = $derived(fetched < data.counts.followers);\n\n\tonMount(() => {\n\t\ttrackFollowListView('followers', {\n\t\t\tlistOwner: data.owner.id,\n\t\t\tisOwnList: data.viewer.isOwn\n\t\t});\n\t});\n\n\tasync function loadMore() {\n\t\tif (loadingMore || !hasMore) return;\n\t\tloadingMore = true;\n\t\tloadError = '';\n\t\ttry {\n\t\t\tconst res = await fetch(\n\t\t\t\t`/api/users/${encodeURIComponent(data.owner.id)}/follow-list?type=followers&offset=${fetched}`\n\t\t\t);\n\t\t\tif (!res.ok) {\n\t\t\t\tloadError = t('failed to load more');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst body = (await res.json()) as { users: Row[] };\n\t\t\tfetched += body.users.length;\n\t\t\t// RAT-460 pattern: offset pagination over a list that can gain rows\n\t\t\t// mid-session re-serves a user across page boundaries. Appending blind\n\t\t\t// duplicates the (u.id) key in the keyed each → each_key_duplicate aborts\n\t\t\t// the render and blanks the list. Drop anyone already held before appending.\n\t\t\tconst seen = new Set(users.map((u) => u.id));\n\t\t\tconst fresh = body.users.filter((u) => !seen.has(u.id));\n\t\t\tusers = [...users, ...fresh];\n\t\t} catch {\n\t\t\tloadError = t('network error');\n\t\t} finally {\n\t\t\tloadingMore = false;\n\t\t}\n\t}\n</script>\n\n<svelte:head>\n\t<title>{t('{name} · followers — ratemyd.', { name: ownerNickname })}</title>\n\t<meta name=\"robots\" content=\"noindex\" />\n</svelte:head>\n\n<main class=\"relative min-h-screen overflow-hidden px-5 pt-20 pb-24 md:pt-28\">\n\t<div class=\"pointer-events-none absolute inset-0 bg-[linear-gradient(rgba(255,255,255,.006)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,.006)_1px,transparent_1px)] bg-[size:60px_60px]\"></div>\n\n\t<div class=\"relative z-10 mx-auto max-w-2xl\">\n\t\t<!-- header: small avatar + nickname + tab switcher -->\n\t\t<div class=\"flex items-center gap-3\">\n\t\t\t<a\n\t\t\t\thref=\"/u/{encodeURIComponent(ownerNickname)}\"\n\t\t\t\tclass=\"inline-flex items-center justify-center rounded-full text-muted transition-colors hover:text-text\"\n\t\t\t\taria-label={t('back to profile')}\n\t\t\t>\n\t\t\t\t<DuelAvatar nickname={ownerNickname} avatarUrl={ownerAvatarUrl} size={40} href=\"\" />\n\t\t\t</a>\n\t\t\t<div class=\"min-w-0\">\n\t\t\t\t<a\n\t\t\t\t\thref=\"/u/{encodeURIComponent(ownerNickname)}\"\n\t\t\t\t\tclass=\"font-display text-base font-bold tracking-tight text-text no-underline hover:text-coral\"\n\t\t\t\t>\n\t\t\t\t\t{ownerNickname}\n\t\t\t\t</a>\n\t\t\t\t<p class=\"text-[11px] font-semibold uppercase tracking-[0.22em] text-muted/60\">\n\t\t\t\t\t{data.viewer.isOwn ? t('your network') : t('public profile')}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- tab switcher -->\n\t\t<div class=\"mt-6 flex gap-1 rounded-xl border border-border bg-surface/50 p-1\">\n\t\t\t<a\n\t\t\t\thref=\"/u/{encodeURIComponent(ownerNickname)}/followers\"\n\t\t\t\tclass=\"flex flex-1 items-center justify-center gap-2 rounded-lg bg-bg px-3 py-2 font-display text-xs font-semibold text-text shadow-sm\"\n\t\t\t\taria-current=\"page\"\n\t\t\t>\n\t\t\t\t{t('followers')}\n\t\t\t\t<span class=\"rounded bg-coral/15 px-1.5 py-0.5 text-[10px] font-bold tabular-nums text-coral\">{data.counts.followers}</span>\n\t\t\t</a>\n\t\t\t<a\n\t\t\t\thref=\"/u/{encodeURIComponent(ownerNickname)}/following\"\n\t\t\t\tclass=\"flex flex-1 items-center justify-center gap-2 rounded-lg px-3 py-2 font-display text-xs font-semibold text-muted/60 hover:text-muted\"\n\t\t\t>\n\t\t\t\t{t('following')}\n\t\t\t\t<span class=\"rounded bg-white/5 px-1.5 py-0.5 text-[10px] font-bold tabular-nums\">{data.counts.following}</span>\n\t\t\t</a>\n\t\t</div>\n\n\t\t<!-- list -->\n\t\t{#if users.length === 0}\n\t\t\t<div class=\"mt-12 rounded-2xl border border-border bg-surface/40 p-10 text-center\">\n\t\t\t\t<p class=\"font-display text-lg font-bold tracking-tight\">{t('no followers yet')}</p>\n\t\t\t\t<p class=\"mt-2 text-sm text-muted\">\n\t\t\t\t\t{data.viewer.isOwn\n\t\t\t\t\t\t? t('share your profile and your followers will show up here.')\n\t\t\t\t\t\t: t(\"{name} hasn't picked up any followers yet.\", { name: ownerNickname })}\n\t\t\t\t</p>\n\t\t\t\t{#if data.viewer.isOwn}\n\t\t\t\t\t<a href=\"/l\" class=\"mt-5 inline-block text-sm font-semibold text-coral no-underline hover:text-coral-dark\">\n\t\t\t\t\t\t{t('explore the leaderboard →')}\n\t\t\t\t\t</a>\n\t\t\t\t{/if}\n\t\t\t</div>\n\t\t{:else}\n\t\t\t<div class=\"mt-8 flex flex-col gap-2.5\">\n\t\t\t\t{#each users as u (u.id)}\n\t\t\t\t\t<UserListItem\n\t\t\t\t\t\tid={u.id}\n\t\t\t\t\t\tnickname={u.nickname}\n\t\t\t\t\t\tavatarPath={u.avatarPath}\n\t\t\t\t\t\tbio={u.bio}\n\t\t\t\t\t\tisSelf={u.id === data.viewer.id}\n\t\t\t\t\t\tviewerFollows={u.viewerFollows}\n\t\t\t\t\t\tviewerLoggedIn={data.viewer.isLoggedIn}\n\t\t\t\t\t/>\n\t\t\t\t{/each}\n\t\t\t</div>\n\n\t\t\t{#if hasMore}\n\t\t\t\t<div class=\"mt-6 flex justify-center\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tonclick={loadMore}\n\t\t\t\t\t\tdisabled={loadingMore}\n\t\t\t\t\t\tclass=\"cursor-pointer rounded-lg border border-border bg-surface px-5 py-2.5 text-sm font-semibold text-text transition-colors hover:border-muted disabled:opacity-50\"\n\t\t\t\t\t>\n\t\t\t\t\t\t{loadingMore ? t('loading…') : t('load more')}\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t{/if}\n\n\t\t\t{#if loadError}\n\t\t\t\t<p class=\"mt-3 text-center text-xs text-coral\">{loadError}</p>\n\t\t\t{/if}\n\t\t{/if}\n\t</div>\n</main>\n"],"version":3}