## 15. Autres comportements ### 15.1 Partage de contenu **Décision** : Système de partage complet avec web player #### 15.1.1 Bouton "Partager" **Disponibilité** : Partout dans l'application **Emplacements** : - Player en lecture (bouton dans contrôles) - Page profil créateur (sur chaque contenu) - Liste de recherche (menu contextuel) - Historique personnel **Icône** : ⬆️ (universelle iOS/Android) **Menu options** : - Copier le lien - WhatsApp - Email - SMS - Plus... (sheet natif OS) **Justification** : - Viralité = croissance organique gratuite - Aucune friction, partage universel --- #### 15.1.2 Comportement du lien partagé **Format URL** : `https://roadwave.fr/share/c/[content_id]` **Comportement multi-plateforme** : ``` User clique lien partagé ↓ Page web responsive ↓ ┌─────────────────────────────────┐ │ Si app installée │ │ → Deep link (ouverture directe) │ └─────────────────────────────────┘ ↓ ┌─────────────────────────────────┐ │ Si app non installée │ │ → Web player + CTA téléchargement│ └─────────────────────────────────┘ ``` **Contenu de la page web** : ```html ┌───────────────────────────────────────┐ │ RoadWave │ ├───────────────────────────────────────┤ │ [Image cover 16:9] │ │ │ │ 📻 Titre du contenu │ │ Par @créateur · 12 min · 🎧 2.3K │ │ │ │ 📍 Paris 5e · Ancré │ │ 🏷️ #Voyage #Histoire │ │ │ │ Description : Lorem ipsum... │ │ │ │ [▶️ Écouter maintenant] │ │ (Player HTML5 si contenu public) │ │ │ │ ────────────────────────────────── │ │ │ │ 📱 Télécharger l'app RoadWave │ │ [App Store] [Google Play] │ │ │ │ [Voir le profil de @créateur] │ └───────────────────────────────────────┘ ``` **Métadonnées Open Graph (SEO)** : ```html ``` **Deep linking** : - iOS : Universal Links (configuration `apple-app-site-association`) - Android : App Links (configuration `assetlinks.json`) - URL scheme : `roadwave://content/[content_id]` **Justification** : - Meilleure viralité (partage social optimisé) - SEO (contenus indexés Google) - UX optimale (web + app) - Coût : 0€ (backend simple + CDN existant) --- #### 15.1.3 Contenus Premium partagés **Décision** : Preview 30 secondes + paywall **Comportement** : 1. User clique lien contenu Premium partagé 2. Page web affiche badge "👑 Contenu Premium" 3. Player démarre automatiquement 4. Après **30 secondes exactement** : - Fade out audio (2 secondes) - Overlay apparaît : ``` ┌─────────────────────────────────┐ │ 👑 Contenu réservé Premium │ │ │ │ Profitez de ce contenu complet │ │ et de milliers d'autres │ │ sans publicité │ │ │ │ [Passer Premium - 4.99€/mois] │ │ [Télécharger l'app] │ └─────────────────────────────────┘ ``` 5. Utilisateur peut : - S'abonner Premium (redirection web Mangopay) - Télécharger l'app (redirection stores) - Rejouer les 30 premières secondes (illimité) **Tracking** : - Métriques créateur : "Partages Premium" + "Conversions Premium" - Créateur touche sa part si conversion (70%) **Justification** : - Équilibre viralité / monétisation - 30s = assez pour donner envie, pas assez pour satisfaire - Protège revenus créateurs --- ### 15.2 Profil créateur **Décision** : Profil public complet et transparent #### 15.2.1 Structure de la page profil **URL** : `https://roadwave.fr/@[pseudo]` **Layout** : ``` ┌────────────────────────────────────────┐ │ [Photo profil 120×120] │ │ @pseudo ✓ │ │ [Badge vérifié si applicable] │ │ │ │ Bio : Lorem ipsum dolor sit amet... │ │ (300 caractères max) │ │ │ │ 🎧 1.2K abonnés │ │ 📻 42 contenus │ │ ⏱️ 18h de contenu créé │ │ 🔊 54K écoutes totales │ │ │ │ [S'abonner] [Partager profil] [•••] │ │ │ │ ──────────────────────────────────── │ │ │ │ Contenus ▼ [Plus récents ▼] │ │ │ │ ┌──────────────────────────────────┐ │ │ │ [Cover] Titre contenu 1 │ │ │ │ 12 min · 🎧 2.3K · 📍 Paris │ │ │ │ [▶️] │ │ │ └──────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────┐ │ │ │ [Cover] Titre contenu 2 │ │ │ │ 8 min · 🎧 5.1K · 📍 Lyon │ │ │ │ [▶️] │ │ │ └──────────────────────────────────┘ │ │ │ │ [Charger plus] │ └────────────────────────────────────────┘ ``` **Informations affichées** : | Élément | Visibilité | Détails | |---------|------------|---------| | **Photo + pseudo** | ✅ Public | Identité visuelle | | **Badge vérifié ✓** | ✅ Public (si applicable) | Compte authentique | | **Bio** | ✅ Public | 0-300 caractères, markdown basique (gras, italique, liens) | | **Nombre abonnés** | ✅ Public | Arrondi si >1000 (ex: 1.2K, 54K) | | **Nombre contenus** | ✅ Public | Exact | | **Durée totale créée** | ✅ Public | Arrondi en heures (ex: 18h, 142h) | | **Écoutes totales** | ✅ Public | Arrondi (ex: 54K, 1.2M) | | **Liste abonnés** | ❌ Privé | Protection vie privée (RGPD) | | **Revenus** | ❌ Privé | Confidentialité financière | | **Localisation précise** | ❌ Privé | Sécurité | | **Email** | ❌ Privé | Anti-spam | **Tri des contenus** : | Option | Comportement | |--------|--------------| | **Plus récents** | Date publication DESC (défaut) | | **Plus populaires** | Écoutes complètes × (1 + (date_publication - now) / 90 jours) | | **Plus anciens** | Date publication ASC | | **Par tag** | Filtre multi-sélection tags | **Recherche locale** : - Barre recherche dans profil : "Rechercher dans les contenus de @pseudo" - Recherche full-text sur titres + descriptions **Actions menu [•••]** : - Partager profil - Signaler profil (spam, usurpation) - Bloquer créateur (masque tous ses contenus) --- #### 15.2.2 Statistiques publiques **Décision** : Stats arrondies et motivantes **Affichage public** : | Métrique | Format affichage | Exemple | |----------|------------------|---------| | **Abonnés** | Exact si <1000, arrondi sinon | 342 / 1.2K / 54K / 1.2M | | **Écoutes totales** | Arrondi dès 1000 | 842 / 5.4K / 142K / 2.1M | | **Contenus publiés** | Exact | 42 contenus | | **Durée totale** | Arrondi en heures | 18h / 142h de contenu | **Métriques PRIVÉES (créateur uniquement)** : | Métrique | Disponible dans dashboard créateur | |----------|-------------------------------------| | **Taux complétion moyen** | 78% (écoutes >80% / écoutes totales) | | **Évolution abonnés** | Graphique 30j / 90j / 1 an | | **Écoutes par contenu** | Tableau détaillé | | **Revenus** | Dashboard monétisation dédié | | **Taux conversion Premium** | Partages → conversions | | **Démographie** | Âge / zone géo (agrégée, anonymisée) | **Justification** : - Arrondi = évite comparaisons anxiogènes - Preuve sociale pour nouveaux auditeurs (trust) - Gamification douce (motivation créateurs) - Privacy by design --- #### 15.2.3 Badge vérifié **Décision** : Badge unique ✓ (vérifié officiel) **Critères d'attribution** (au moins UN des critères) : 1. **KYC monétisation validé** : identité vérifiée via Mangopay KYC 2. **Célébrité / Média officiel** : validation manuelle équipe RoadWave 3. **Communauté significative** : ≥10K abonnés + compte actif >6 mois **Affichage** : - Badge bleu **✓** accolé au pseudo (partout : profil, player, recherche) - Tooltip au survol/appui long : "Compte vérifié" **Processus d'obtention** : | Type | Processus | |------|-----------| | **Automatique (KYC)** | Badge attribué dès validation documents Mangopay | | **Manuel (célébrité)** | Formulaire demande → équipe vérifie identité → validation 48-72h | | **Automatique (10K)** | Badge attribué automatiquement à 10K abonnés si compte >6 mois | **Retrait du badge** : - Suspension monétisation → badge retiré temporairement - Strikes multiples → badge retiré définitivement - Usurpation identité détectée → ban + retrait **Justification** : - Combat usurpations d'identité - Trust auditeurs (surtout pour médias/personnalités) - Simplicité (1 seul badge, pas de gamification excessive) - Coût : 0€ (champ boolean `verified` en DB) --- ### 15.3 Recherche **Décision** : Recherche full-text + géo + filtres avancés #### 15.3.1 Recherche par mot-clé **Implémentation** : PostgreSQL full-text search (français) **Configuration technique** : ```sql -- Index full-text optimisé français CREATE INDEX idx_content_search ON contents USING GIN( to_tsvector('french', coalesce(title, '') || ' ' || coalesce(description, '') || ' ' || coalesce(creator_pseudo, '') ) ); -- Recherche avec ranking SELECT c.*, ts_rank( to_tsvector('french', c.title || ' ' || c.description), plainto_tsquery('french', $search_query) ) AS rank FROM contents c WHERE to_tsvector('french', c.title || ' ' || c.description) @@ plainto_tsquery('french', $search_query) ORDER BY rank DESC, listen_count DESC LIMIT 20; ``` **Champs indexés** : - Titre du contenu (poids × 3) - Description (poids × 1) - Pseudo créateur (poids × 2) - Tags (poids × 1.5) **Fonctionnalités** : | Feature | Description | |---------|-------------| | **Stemming français** | "voyages" trouve "voyage", "voyager", etc. | | **Correction auto** | Suggestion si 0 résultat | | **Recherches populaires** | "Essayez plutôt : balade paris, audio-guide louvre" | | **Historique personnel** | 10 dernières recherches sauvegardées | | **Autocomplete** | Suggestions pendant frappe (top 5) | **Coût** : 0€ (PostgreSQL natif) **Migration future** : - Si >100K contenus : Meilisearch (typo-tolerance avancée, ~20-50€/mois) - Si >1M contenus : Elasticsearch cluster **Justification** : - PostgreSQL full-text = performant jusqu'à 500K contenus - Stemming français natif - 0€, aucune dépendance externe --- #### 15.3.2 Recherche géographique **Décision** : Recherche lieu + rayon paramétrable **Interface utilisateur** : ``` ┌─────────────────────────────────────┐ │ 🔍 Recherche contenu... │ ├─────────────────────────────────────┤ │ �� Lieu │ │ [Paris, France ▼] │ │ · Autour de moi (GPS actuel) │ │ · Entrer une adresse/ville │ │ │ │ 📏 Rayon de recherche │ │ [●─────────────────] 50 km │ │ (curseur 5 km → 500 km) │ │ │ │ 🗺️ [Afficher sur carte] │ └─────────────────────────────────────┘ ``` **Géocodage** : | Service | Usage | Coût | |---------|-------|------| | **Nominatim (OSM)** | MVP (API publique) | 0€ (rate limit 1 req/s) | | **Nominatim self-hosted** | Scale (Docker) | 20-50€/mois VPS | | **Mapbox Geocoding** | Fallback premium | 0.50€ / 1000 requêtes | **Processus de recherche géo** : 1. User tape "Louvre" ou "Paris" 2. Autocomplete via Nominatim → liste suggestions 3. User sélectionne → récupération coordonnées (lat, lon) 4. Requête PostGIS : ```sql SELECT c.*, ST_Distance(c.location::geography, ST_Point($lon, $lat)::geography) AS distance FROM contents c WHERE ST_DWithin( c.location::geography, ST_Point($lon, $lat)::geography, $radius_meters ) ORDER BY distance ASC; ``` **Affichage résultats** : - Tri par défaut : distance croissante - Indication distance : "À 2.3 km" / "À 15 km" / "À 142 km" - Option carte : markers cliquables (clustering si >50 résultats) **Coût** : - MVP : 0€ (Nominatim public) - Scale : 20-50€/mois (Nominatim self-hosted Docker) **Justification** : - Essentiel pour tourisme / planification trajet - OpenStreetMap = pas de dépendance Google - PostGIS = performant (index GIST natif) --- #### 15.3.3 Filtres avancés **Décision** : 7 catégories de filtres combinables **Interface filtres** : ``` ┌─────────────────────────────────────┐ │ Filtres [×] │ ├─────────────────────────────────────┤ │ Type de contenu │ │ ☐ Contenu court (<5 min) │ │ ☐ Podcast (>5 min) │ │ ☐ Radio live │ │ ☐ Audio-guide │ │ │ │ Durée │ │ ○ Toutes durées │ │ ○ <5 min │ │ ○ 5-15 min │ │ ○ 15-30 min │ │ ○ >30 min │ │ │ │ Classification âge │ │ ☐ Tout public │ │ ☐ 13+ │ │ ☐ 16+ │ │ ☐ 18+ │ │ │ │ Géo-pertinence │ │ ☐ Ancré (lieu précis) │ │ ☐ Contextuel (zone large) │ │ ☐ Neutre (national) │ │ │ │ Tags (multi-sélection) │ │ ☐ Automobile ☐ Voyage │ │ ☐ Famille ☐ Histoire │ │ ☐ Économie ☐ Sciences │ │ ... (liste complète tags) │ │ │ │ Date de publication │ │ ○ Toutes dates │ │ ○ Dernières 24h │ │ ○ Cette semaine │ │ ○ Ce mois │ │ ○ Cette année │ │ │ │ Abonnement │ │ ○ Tous les contenus │ │ ○ Gratuits uniquement │ │ ○ Premium uniquement 👑 │ │ │ │ ────────────────────────────── │ │ [Réinitialiser] [Appliquer] │ └─────────────────────────────────────┘ ``` **Options de tri** : | Tri | Algorithme | |-----|-----------| | **Pertinence** | Score recherche × (1 + log(listen_count + 1)) | | **Popularité** | Écoutes complètes derniers 30j DESC | | **Récent** | Date publication DESC | | **Proximité** | Distance GPS ASC (si recherche géo active) | | **Durée** | Durée audio ASC ou DESC | **Sauvegarde de recherches** : - Bouton "💾 Sauvegarder cette recherche" - Nom personnalisable : "Podcasts voyage Paris" - Maximum **5 recherches sauvegardées** - Accès rapide : onglet "Recherches sauvegardées" dans page recherche - Notifications optionnelles : "3 nouveaux contenus dans 'Podcasts voyage Paris'" **Performances** : ```sql -- Index composites pour filtres CREATE INDEX idx_content_filters ON contents ( content_type, duration, age_rating, geo_type, published_at ); -- Index GIN pour tags CREATE INDEX idx_content_tags ON contents USING GIN(tags); ``` **Coût** : 0€ (PostgreSQL + index standards) **Justification** : - Filtres essentiels pour découvrabilité - Combinables = puissance maximale - Sauvegarde = gain temps utilisateurs réguliers --- #### 15.3.4 Page de résultats **Décision** : Liste avec previews enrichies **Layout résultats** : ``` ┌─────────────────────────────────────────┐ │ 🔍 "voyage paris" │ │ 42 résultats · Tri : Pertinence ▼ │ │ [Filtres] [Carte] │ ├─────────────────────────────────────────┤ │ ┌─────────────────────────────────────┐ │ │ │ [Cover ] Balade à Paris │ │ │ │ [16:9 ] @paris_stories ✓ │ │ │ │ [Image ] 12 min · 🎧 2.3K │ │ │ │ 📍 Paris 5e · Ancré │ │ │ │ 🏷️ #Voyage #Histoire │ │ │ │ [▶️ Écouter] [⋮] │ │ │ └─────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ [Cover ] Secrets Montmartre │ │ │ │ [16:9 ] @explore_paris │ │ │ │ [Image ] 8 min · 🎧 5.1K │ │ │ │ 📍 Paris 18e · Guide │ │ │ │ 🏷️ #Voyage #Art │ │ │ │ [▶️ Écouter] [⋮] │ │ │ └─────────────────────────────────────┘ │ │ │ │ [Charger plus] (20 suivants) │ └─────────────────────────────────────────┘ ``` **Informations par résultat** : | Élément | Affichage | |---------|-----------| | **Cover image** | 16:9, 120×68 px, lazy loading | | **Titre** | Tronqué 2 lignes max | | **Créateur** | @pseudo + badge ✓ si vérifié, cliquable → profil | | **Durée** | Format : "3 min" / "12 min" / "1h 24 min" | | **Écoutes** | Arrondi : "2.3K" / "54K" / "1.2M" | | **Localisation** | Ville + type géo (Ancré/Contextuel/Neutre) | | **Tags** | Maximum 3 premiers tags | | **Badge Premium** | 👑 si contenu premium | | **Distance** | Si recherche géo : "À 2.3 km" | **Actions contextuelles [⋮]** : - Partager - Ajouter à une playlist (future feature) - Télécharger (offline) - Signaler **Pagination** : - **20 résultats** par page - Infinite scroll (charger automatiquement si scroll >80%) - Bouton "Charger 20 suivants" en bas (fallback si scroll auto désactivé) **Vue carte (alternative)** : - Bouton toggle "Liste / Carte" - Map Leaflet (OpenStreetMap) - Markers cliquables → popup avec preview - Clustering si >50 résultats proches **Coût** : 0€ (Leaflet open source + OSM tiles gratuit) **Justification** : - Équilibre information / compacité - Lazy loading = performances - Infinite scroll = UX moderne --- ## Récapitulatif Section 15 | Point | Décision | Coût | Complexité | |-------|----------|------|------------| | **15.1.1** Bouton partager | Disponible partout (⬆️), menu natif OS | 0€ | Faible | | **15.1.2** Lien partagé | Web player + deep link + Open Graph SEO | 0€ | Moyenne | | **15.1.3** Premium partagé | Preview 30s + paywall overlay | 0€ | Faible | | **15.2.1** Page profil | Profil public complet (stats + bio + contenus + tri) | 0€ | Faible | | **15.2.2** Stats publiques | Arrondies (abonnés, écoutes, durée totale) | 0€ | Faible | | **15.2.3** Badge vérifié | ✓ si KYC/célébrité/>10K abonnés | 0€ | Faible | | **15.3.1** Recherche texte | PostgreSQL full-text french + stemming | 0€ | Moyenne | | **15.3.2** Recherche géo | Lieu + rayon (Nominatim OSM) | 0-50€/mois | Moyenne | | **15.3.3** Filtres | 7 catégories combinables + sauvegarde recherches | 0€ | Moyenne | | **15.3.4** Page résultats | Liste enrichie + vue carte Leaflet + infinite scroll | 0€ | Moyenne | **Coût total MVP : 0-50€/mois** (Nominatim self-hosted optionnel) --- ## Points d'attention pour Gherkin - Tester partage contenu public vs Premium (preview 30s) - Tester deep linking iOS/Android (ouverture app si installée) - Tester Open Graph (aperçu correct sur WhatsApp, Twitter, Facebook) - Tester profil public (stats arrondies, badge vérifié) - Tester recherche full-text français (stemming, accents) - Tester recherche géo + rayon (PostGIS distance) - Tester combinaison filtres multiples (AND logic) - Tester sauvegarde recherches (max 5) - Tester pagination infinite scroll + fallback bouton - Tester vue carte Leaflet (clustering, markers cliquables)