{"release_id":"01a1c502-4ad1-0000-ec62-adc54d3c69aa","chunk_id":"01a1c502-4c39-7480-b83b-da01dd2db66c","file":"DZC0LZl_.mjs","mappings":"wVAmBA,MAAMA,EAAgB,GAChBC,EAAW,IACXC,EAAW,IAEjB,IAAIC,EAAe,CAAA,EACfC,EAAmD,KACvD,MAAMC,MAAc,IACpB,IAAIC,EAAS,GAEb,SAASC,EAAMC,EAAY,GAAO,CACjC,GAAIL,EAAO,SAAW,EAAG,OACzB,MAAMM,EAAU,KAAK,UAAU,CAAE,OAAQN,EAAO,OAAO,EAAGA,EAAO,MAAM,CAAA,CAAG,EAK1E,GAJIC,IACH,aAAaA,CAAU,EACvBA,EAAa,MAEVI,GAAa,UAAU,WAAY,CACtC,UAAU,WAAW,yBAA0B,IAAI,KAAK,CAACC,CAAO,EAAG,CAAE,KAAM,kBAAA,CAAoB,CAAC,EAChG,MACD,CACK,MAAM,yBAA0B,CACpC,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAA,EAC3B,KAAMA,EACN,UAAW,EAAA,CACX,EAAE,MAAM,IAAM,CAAC,CAAC,CAClB,CAEA,SAASC,EAAKC,EAAQ,CAErBR,EAAO,KAAKQ,CAAE,EACTL,IACJA,EAAS,GAET,SAAS,iBAAiB,mBAAoB,IAAM,CAC/C,SAAS,kBAAoB,UAAUC,EAAM,EAAI,CACtD,CAAC,EACD,OAAO,iBAAiB,WAAY,IAAMA,EAAM,EAAI,CAAC,GAEjDH,IAAYA,EAAa,WAAW,IAAMG,EAAA,EAASL,CAAQ,EACjE,CAGO,SAASU,EAAiBC,EAAiBC,EAAuB,CACxEJ,EAAK,CAAE,QAAAG,EAAS,QAAAC,EAAS,KAAM,QAAS,EACxCP,EAAM,EAAI,EACVQ,EAAoBF,EAASC,CAAO,CACrC,CAKO,SAASE,EAAsBH,EAAiBC,EAAuB,CAE7E,MAAMG,EAAM,GAAGJ,CAAO,IAAIC,CAAO,GAC7BT,EAAQ,IAAIY,CAAG,IACnBZ,EAAQ,IAAIY,CAAG,EACfP,EAAK,CAAE,QAAAG,EAAS,QAAAC,EAAS,KAAM,aAAc,EAC9C,CAGO,SAASI,EACfC,EACAC,EACC,CACD,GAAgB,CAACA,GAAU,OAAO,qBAAyB,UAAoB,CAAA,EAC/E,MAAMH,EAAM,GAAGG,EAAO,OAAO,IAAIA,EAAO,OAAO,GAC/C,GAAIf,EAAQ,IAAIY,CAAG,QAAU,CAAA,EAE7B,IAAII,EAA8C,KAClD,MAAMC,EAAW,IAAI,qBACnBC,GAAY,CACFA,EAAQ,CAAC,EACb,mBAAqBvB,EACrBqB,IACJA,EAAQ,WAAW,IAAM,CACnBhB,EAAQ,IAAIY,CAAG,IACnBZ,EAAQ,IAAIY,CAAG,EACfP,EAAK,CAAE,QAASU,EAAO,QAAS,QAASA,EAAO,QAAS,KAAM,aAAc,GAE9EE,EAAS,WAAA,CACV,EAAGrB,CAAQ,GAEFoB,IACV,aAAaA,CAAK,EAClBA,EAAQ,KAEV,EACA,CAAE,UAAW,CAACrB,CAAa,CAAA,CAAE,EAE9B,OAAAsB,EAAS,QAAQH,CAAI,EAEd,CACN,SAAU,CACLE,gBAAoBA,CAAK,EAC7BC,EAAS,WAAA,CACV,CAAA,CAEF","names":["VISIBLE_RATIO","DWELL_MS","FLUSH_MS","buffer","flushTimer","counted","hooked","flush","useBeacon","payload","push","ev","recordAlbumClick","albumId","surface","trackAlbumCardClick","recordAlbumImpression","key","albumImpression","node","params","dwell","observer","entries"],"sources":["../../../../../../src/lib/album-impressions.ts"],"sourcesContent":["/**\n * RAT-449: client-side album-card funnel tracker.\n *\n * `albumImpression` is a Svelte action for card roots: an impression counts\n * when ≥50% of the card is actually visible (works inside horizontal\n * carousels — the peeking next card never fires) for ≥1s continuously. Each\n * album counts once per page session (module-level dedup), so swiping back\n * and forth doesn't inflate the counter.\n *\n * Events buffer locally and flush in one small POST every few seconds and on\n * page hide (sendBeacon) — scrolling never spams the network.\n */\nimport { browser } from '$app/environment';\nimport { trackAlbumCardClick } from '$lib/analytics';\n\nexport type AlbumSurface = 'feed' | 'browse' | 'r' | 'arena' | 'profile';\n\ntype Ev = { albumId: string; surface: AlbumSurface; kind: 'impression' | 'click' };\n\nconst VISIBLE_RATIO = 0.5;\nconst DWELL_MS = 1000;\nconst FLUSH_MS = 4000;\n\nlet buffer: Ev[] = [];\nlet flushTimer: ReturnType<typeof setTimeout> | null = null;\nconst counted = new Set<string>();\nlet hooked = false;\n\nfunction flush(useBeacon = false) {\n\tif (buffer.length === 0) return;\n\tconst payload = JSON.stringify({ events: buffer.splice(0, buffer.length) });\n\tif (flushTimer) {\n\t\tclearTimeout(flushTimer);\n\t\tflushTimer = null;\n\t}\n\tif (useBeacon && navigator.sendBeacon) {\n\t\tnavigator.sendBeacon('/api/album/impressions', new Blob([payload], { type: 'application/json' }));\n\t\treturn;\n\t}\n\tvoid fetch('/api/album/impressions', {\n\t\tmethod: 'POST',\n\t\theaders: { 'content-type': 'application/json' },\n\t\tbody: payload,\n\t\tkeepalive: true\n\t}).catch(() => {});\n}\n\nfunction push(ev: Ev) {\n\tif (!browser) return;\n\tbuffer.push(ev);\n\tif (!hooked) {\n\t\thooked = true;\n\t\t// Flush whatever's pending when the tab hides / page unloads.\n\t\tdocument.addEventListener('visibilitychange', () => {\n\t\t\tif (document.visibilityState === 'hidden') flush(true);\n\t\t});\n\t\twindow.addEventListener('pagehide', () => flush(true));\n\t}\n\tif (!flushTimer) flushTimer = setTimeout(() => flush(), FLUSH_MS);\n}\n\n/** Card click → immediate flush (navigation follows right away). */\nexport function recordAlbumClick(albumId: string, surface: AlbumSurface) {\n\tpush({ albumId, surface, kind: 'click' });\n\tflush(true);\n\ttrackAlbumCardClick(albumId, surface);\n}\n\n/** Direct impression for surfaces that show a card programmatically rather than\n *  via scroll (e.g. the arena promo ad shown over the deck) — no observer, but\n *  the same once-per-session dedup as the scroll action. */\nexport function recordAlbumImpression(albumId: string, surface: AlbumSurface) {\n\tif (!browser) return;\n\tconst key = `${albumId}|${surface}`;\n\tif (counted.has(key)) return;\n\tcounted.add(key);\n\tpush({ albumId, surface, kind: 'impression' });\n}\n\n/** Svelte action: count a real view of this card. No-op without params. */\nexport function albumImpression(\n\tnode: HTMLElement,\n\tparams: { albumId: string; surface: AlbumSurface } | undefined\n) {\n\tif (!browser || !params || typeof IntersectionObserver === 'undefined') return {};\n\tconst key = `${params.albumId}|${params.surface}`;\n\tif (counted.has(key)) return {};\n\n\tlet dwell: ReturnType<typeof setTimeout> | null = null;\n\tconst observer = new IntersectionObserver(\n\t\t(entries) => {\n\t\t\tconst e = entries[0];\n\t\t\tif (e.intersectionRatio >= VISIBLE_RATIO) {\n\t\t\t\tif (!dwell) {\n\t\t\t\t\tdwell = setTimeout(() => {\n\t\t\t\t\t\tif (!counted.has(key)) {\n\t\t\t\t\t\t\tcounted.add(key);\n\t\t\t\t\t\t\tpush({ albumId: params.albumId, surface: params.surface, kind: 'impression' });\n\t\t\t\t\t\t}\n\t\t\t\t\t\tobserver.disconnect();\n\t\t\t\t\t}, DWELL_MS);\n\t\t\t\t}\n\t\t\t} else if (dwell) {\n\t\t\t\tclearTimeout(dwell);\n\t\t\t\tdwell = null;\n\t\t\t}\n\t\t},\n\t\t{ threshold: [VISIBLE_RATIO] }\n\t);\n\tobserver.observe(node);\n\n\treturn {\n\t\tdestroy() {\n\t\t\tif (dwell) clearTimeout(dwell);\n\t\t\tobserver.disconnect();\n\t\t}\n\t};\n}\n"],"version":3}