{
  "version": 3,
  "sources": ["../../renderer/src/observability/analytics.js", "../../renderer/src/observability/analytics-entry.js"],
  "sourcesContent": ["const STORAGE_KEY = 'ss_anon_session_id';\n\n/** The anonymous per-tab id every reader signal shares \u2014 analytics and reactions (PCB-34). */\nexport function getOrCreateSessionId() {\n  try {\n    let id = sessionStorage.getItem(STORAGE_KEY);\n    if (!id) {\n      id = crypto.randomUUID();\n      sessionStorage.setItem(STORAGE_KEY, id);\n    }\n    return id;\n  } catch {\n    return `ss-${Date.now()}`;\n  }\n}\n\nfunction getCsrfToken() {\n  const m = document.cookie.match(/csrftoken=([^;]+)/);\n  return m ? decodeURIComponent(m[1].trim()) : '';\n}\n\n/** Do Not Track, or Global Privacy Control. */\nexport function doNotTrackEnabled() {\n  try {\n    const n = typeof navigator !== 'undefined' ? navigator : null;\n    if (!n) return false;\n    if (n.globalPrivacyControl === true) return true;\n    const dnt = n.doNotTrack || (typeof window !== 'undefined' ? window.doNotTrack : null) || n.msDoNotTrack;\n    return dnt === '1' || dnt === 'yes';\n  } catch {\n    return false;\n  }\n}\n\n/** Only the referring host leaves the browser, and never our own. */\nexport function referrerHostFor(referrer, ownHost) {\n  if (!referrer) return '';\n  try {\n    const h = new URL(referrer).hostname || '';\n    return h && h !== ownHost ? h : '';\n  } catch {\n    return '';\n  }\n}\n\n/**\n * @param {{\n *   analyticsEndpoint?: string,\n *   analyticsEnabled?: boolean,\n *   pageType?: string,\n *   storyId?: string,\n *   trackClicks?: boolean,\n *   crossOrigin?: boolean,\n *   coverVariant?: string,\n * }} config\n *   `trackClicks` / `crossOrigin`: PCB-25's component never reports the host page's clicks,\n *   and posts cross-origin as a simple request (no preflight, no CORS).\n *   `coverVariant` is `?c=` as the server read it (PCB-29); it rides on the page view only.\n */\nexport function initAnalytics(config) {\n  const endpoint = (config && config.analyticsEndpoint) || '/api/analytics/events/';\n  const enabled = !config || config.analyticsEnabled !== false;\n  const sessionId = getOrCreateSessionId();\n  const queue = [];\n  let flushTimer = null;\n  const storyId = (config && config.storyId) || '';\n  const pageType = (config && config.pageType) || 'app';\n  const trackClicks = !config || config.trackClicks !== false;\n  const crossOrigin = !!(config && config.crossOrigin);\n  const coverVariant = config && (config.coverVariant === 'a' || config.coverVariant === 'b') ? config.coverVariant : '';\n  const dnt = doNotTrackEnabled();\n  const openedAt = Date.now();\n\n  function flush() {\n    if (!queue.length) return;\n    const batch = { session_id: sessionId, events: queue.splice(0, queue.length) };\n    if (crossOrigin) {\n      /* `text/plain` and no custom header keep this a simple request \u2014 no preflight. */\n      fetch(endpoint, {\n        method: 'POST',\n        mode: 'no-cors',\n        headers: { 'Content-Type': 'text/plain;charset=UTF-8' },\n        credentials: 'omit',\n        body: JSON.stringify(batch),\n        keepalive: true,\n      }).catch(() => {});\n      return;\n    }\n    const headers = { 'Content-Type': 'application/json' };\n    const t = getCsrfToken();\n    if (t) headers['X-CSRFToken'] = t;\n\n    fetch(endpoint, {\n      method: 'POST',\n      headers,\n      credentials: 'same-origin',\n      body: JSON.stringify(batch),\n      keepalive: true,\n    }).catch(() => {});\n  }\n\n  function scheduleFlush() {\n    if (flushTimer) clearTimeout(flushTimer);\n    flushTimer = setTimeout(flush, 4000);\n  }\n\n  function track(eventType, extra = {}) {\n    if (!enabled) return;\n    /* DNT drops their path through the story; the page open has no per-reader dimension. */\n    if (dnt && (eventType === 'scroll_depth' || eventType === 'session_end')) return;\n    const sid = extra.storyId || storyId || null;\n    queue.push({\n      event_type: eventType,\n      path: window.location.pathname,\n      story_id: sid,\n      metadata: {\n        ...(extra.metadata || {}),\n        page_type: pageType,\n      },\n    });\n    if (queue.length >= 12) flush();\n    else scheduleFlush();\n  }\n\n  function trackEvent(eventType, opts = {}) {\n    track(eventType, opts);\n  }\n\n  const referrer = referrerHostFor(\n    typeof document !== 'undefined' ? document.referrer : '',\n    typeof window !== 'undefined' ? window.location.hostname : ''\n  );\n\n  track('page_view', {\n    metadata: {\n      ...(referrer ? { referrer } : {}),\n      ...(storyId && coverVariant ? { cover_variant: coverVariant } : {}),\n    },\n  });\n\n  if (storyId) {\n    track('story_start', { storyId, metadata: referrer ? { referrer } : {} });\n  }\n\n  /* On a story page the renderer owns depth, in scrollports; elsewhere, document-scroll pct. */\n  let onScroll = null;\n  if (!storyId) {\n    const milestones = [25, 50, 75, 100];\n    const hit = new Set();\n    let maxPct = 0;\n    onScroll = () => {\n      const doc = document.documentElement;\n      const scrollable = doc.scrollHeight - window.innerHeight;\n      const pct = scrollable <= 0 ? 100 : Math.round((window.scrollY / scrollable) * 100);\n      if (pct > maxPct) maxPct = pct;\n      for (const m of milestones) {\n        if (maxPct >= m && !hit.has(m)) {\n          hit.add(m);\n          track('scroll_depth', { metadata: { depth_pct: m } });\n        }\n      }\n    };\n    window.addEventListener('scroll', onScroll, { passive: true });\n  }\n\n  const onClick = !trackClicks\n    ? null\n    : (e) => {\n        const t = e.target && e.target.closest ? e.target.closest('a,button') : null;\n        if (t) {\n          track('click', { metadata: { tag: t.tagName, id: t.id || '' } });\n        }\n      };\n  if (onClick) document.addEventListener('click', onClick, true);\n\n  /* `pagehide` is the unload signal mobile browsers reliably fire, and `keepalive` on the flush\n     lets the request outlive the page; `ended` guards the pair, since a desktop fires both. */\n  let ended = false;\n  function endSession() {\n    if (!ended) {\n      ended = true;\n      track('session_end', { metadata: { dwell_ms: Math.max(0, Date.now() - openedAt) } });\n    }\n    flush();\n  }\n  window.addEventListener('pagehide', endSession);\n  window.addEventListener('beforeunload', endSession);\n\n  /** Detach without reporting a session end. */\n  function destroy() {\n    if (flushTimer) clearTimeout(flushTimer);\n    if (onScroll) window.removeEventListener('scroll', onScroll);\n    if (onClick) document.removeEventListener('click', onClick, true);\n    window.removeEventListener('pagehide', endSession);\n    window.removeEventListener('beforeunload', endSession);\n    ended = true;\n  }\n\n  return { trackEvent, flush, destroy };\n}\n", "/** PCB-03 reader counters, with no OTel SDK. Same `window.ScrollingStoriesObservability` as `index.js`. */\nimport { initAnalytics } from './analytics.js';\n\nconst pending = [];\n\nfunction queueEvent(eventType, opts) {\n  if (pending.length < 50) pending.push([eventType, opts]);\n}\n\nfunction init(config) {\n  const { trackEvent, flush, destroy } = initAnalytics(config);\n  window.ScrollingStoriesObservability = { init, trackEvent, flush, destroy };\n  while (pending.length) {\n    const [eventType, opts] = pending.shift();\n    trackEvent(eventType, opts);\n  }\n}\n\nwindow.ScrollingStoriesObservability = { init, trackEvent: queueEvent };\n"],
  "mappings": "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",
  "names": ["STORAGE_KEY", "getOrCreateSessionId", "id", "getCsrfToken", "m", "doNotTrackEnabled", "n", "dnt", "referrerHostFor", "referrer", "ownHost", "h", "initAnalytics", "config", "endpoint", "enabled", "sessionId", "queue", "flushTimer", "storyId", "pageType", "trackClicks", "crossOrigin", "coverVariant", "openedAt", "flush", "batch", "headers", "t", "scheduleFlush", "track", "eventType", "extra", "sid", "trackEvent", "opts", "onScroll", "milestones", "hit", "maxPct", "scrollable", "pct", "onClick", "ended", "endSession", "destroy", "pending", "queueEvent", "eventType", "opts", "init", "config", "trackEvent", "flush", "destroy", "initAnalytics"]
}
