Files
librodrome/app/composables/useTracking.ts
T
Yvv 8408fd6466
ci/woodpecker/push/woodpecker Pipeline was successful
feat: SEO complet + analytics Umami + og:image § logo
SEO :
- composable useSeoPage() : og:*, Twitter Cards, canonical sur toutes les pages (15 pages)
- app.vue : JSON-LD Organization + Book, og:image global og-default.png
- og-default.png 1200×630 : logo § calligraphique + texte (Pillow)
- nuxt.config.ts : @nuxtjs/sitemap avec 26 URLs statiques

Analytics Umami :
- useTracking() : helpers typés audio/pdf/player/scroll/cta
- useScrollTracking() : scroll depth 25/50/75/100% + liens externes auto
- useAudioPlayer : trackAudioPlay/Progress/Complete
- BookPdfReader : trackPdfOpen/Close avec durée
- BookPlayer : trackPlayerOpen/Chapter/Mode
- docker-compose : variables NUXT_PUBLIC_UMAMI_* passées au container

Images :
- Couv-Economie-du-don.jpg ajoutée dans public/images/
- bookplayer.config.yml + home.yml : références mises à jour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 00:25:28 +02:00

97 lines
3.6 KiB
TypeScript

/**
* Umami analytics wrapper — safe server-side, no-op when not configured.
*
* Events convention:
* audio:play | audio:pause | audio:complete | audio:progress
* pdf:open | pdf:close
* player:open | player:chapter | player:mode
* scroll:depth
* link:external
* cta:click
*/
export function useTracking() {
const runtimeConfig = useRuntimeConfig()
const enabled = !!runtimeConfig.public.umamiWebsiteId
// ── Core ────────────────────────────────────────────────────────────────
function track(event: string, data?: Record<string, unknown>) {
if (!import.meta.client || !enabled) return
const umami = (window as Record<string, unknown>).umami as
| { track: (event: string, data?: unknown) => void }
| undefined
umami?.track(event, data)
}
// ── Audio ────────────────────────────────────────────────────────────────
function trackAudioPlay(songId: string, songTitle: string, context?: string) {
track('audio:play', { song_id: songId, song_title: songTitle, context })
}
function trackAudioPause(songId: string, progressPct: number) {
track('audio:pause', { song_id: songId, progress_pct: progressPct })
}
function trackAudioComplete(songId: string, songTitle: string) {
track('audio:complete', { song_id: songId, song_title: songTitle })
}
/** Fired at 25 / 50 / 75 % milestones */
function trackAudioProgress(songId: string, milestone: 25 | 50 | 75) {
track('audio:progress', { song_id: songId, milestone })
}
// ── PDF ──────────────────────────────────────────────────────────────────
function trackPdfOpen(trigger?: string) {
track('pdf:open', { trigger })
}
function trackPdfClose(pagesVisited: number, durationMs: number) {
track('pdf:close', { pages_visited: pagesVisited, duration_ms: durationMs })
}
// ── BookPlayer ───────────────────────────────────────────────────────────
function trackPlayerOpen(trigger?: string) {
track('player:open', { trigger })
}
function trackPlayerChapter(chapterSlug: string) {
track('player:chapter', { chapter_slug: chapterSlug })
}
function trackPlayerMode(mode: 'guided' | 'scroll') {
track('player:mode', { mode })
}
// ── Navigation & UX ──────────────────────────────────────────────────────
function trackScrollDepth(page: string, depth: 25 | 50 | 75 | 100) {
track('scroll:depth', { page, depth })
}
function trackExternalLink(url: string, label?: string) {
const route = useRoute()
track('link:external', { url, label, from_page: route.path })
}
function trackCta(label: string, target?: string) {
const route = useRoute()
track('cta:click', { label, target, from_page: route.path })
}
return {
track,
enabled,
trackAudioPlay,
trackAudioPause,
trackAudioComplete,
trackAudioProgress,
trackPdfOpen,
trackPdfClose,
trackPlayerOpen,
trackPlayerChapter,
trackPlayerMode,
trackScrollDepth,
trackExternalLink,
trackCta,
}
}