@@ -334,7 +383,7 @@ function ContextualNavbar({
>
) : (
<>
- {renderButtons()}
+ {isFamily && !editModeActive ? renderFamilyButtons() : renderButtons()}
{authenticated && (
diff --git a/frontend/src/hooks/useTreeAccess.js b/frontend/src/hooks/useTreeAccess.js
index b734384..2db2f13 100644
--- a/frontend/src/hooks/useTreeAccess.js
+++ b/frontend/src/hooks/useTreeAccess.js
@@ -1,4 +1,4 @@
-import { useCallback, useMemo, useState } from 'react'
+import { useCallback, useMemo, useRef, useState } from 'react'
import { API_BASE_URL } from '../api/client'
import { fetchTreeGraph, unlockTreeAccess } from '../api/treeApi'
import { loadGraphData, persons as graphPersons, resetToDemoData } from '../data/mockData'
@@ -48,6 +48,11 @@ export function useTreeAccess() {
const [selectedMedia, setSelectedMedia] = useState(null)
const [wizardState, setWizardState] = useState({ visible: false, loading: false, error: '' })
const [graphRevision, setGraphRevision] = useState(0)
+ // Change à chaque chargement des données, même sans nouveau placement : l'arbre y charge les images manquantes
+ const [assetRevision, setAssetRevision] = useState(0)
+ // Horodatage des portraits stable pendant la session : un rechargement ne force pas à tout retélécharger.
+ // Il ne change que lorsqu'un portrait change (envoi d'une nouvelle photo).
+ const avatarCacheBustRef = useRef(Date.now())
const canEditCurrentTree = useMemo(() => {
const role = normalizeRole(treeContext.role)
@@ -63,8 +68,10 @@ export function useTreeAccess() {
loadGraphData(graphPayload.graph, {
authToken: token,
apiBaseUrl: API_BASE_URL,
- avatarCacheBust: Date.now(),
+ avatarCacheBust: options.avatarCacheBust ?? avatarCacheBustRef.current,
})
+ if (options.avatarCacheBust) avatarCacheBustRef.current = options.avatarCacheBust
+ setAssetRevision((current) => current + 1)
if (options.relayout !== false) {
setGraphRevision((current) => current + 1)
}
@@ -72,7 +79,9 @@ export function useTreeAccess() {
const rootPerson = preferredRootPersonId
? graphPersons.find((person) => String(person.id) === String(preferredRootPersonId)) || null
: null
- setSelectedPerson(rootPerson)
+ // Par le lien de partage, la famille arrive sur l'arbre et sa carte d'accueil, pas sur une fiche ouverte
+ const openedByShareLink = (options.accessMode ?? '') === 'share'
+ setSelectedPerson(openedByShareLink ? null : rootPerson)
setSelectedMedia(null)
setTreeContext((current) => ({
@@ -188,6 +197,7 @@ export function useTreeAccess() {
return {
treeContext, setTreeContext,
bootState, setBootState,
+ assetRevision,
gateLoading, gateError, setGateError,
selectedPerson, setSelectedPerson,
selectedMedia, setSelectedMedia,
diff --git a/frontend/src/utils/familyWelcome.js b/frontend/src/utils/familyWelcome.js
new file mode 100644
index 0000000..1662437
--- /dev/null
+++ b/frontend/src/utils/familyWelcome.js
@@ -0,0 +1,22 @@
+// Carte d'accueil de la famille : montrée une seule fois par arbre et par navigateur
+function welcomeSeenKey(treeId) {
+ return `family_welcome_seen_${treeId}`
+}
+
+export function hasSeenFamilyWelcome(treeId) {
+ if (!treeId) return true
+ try {
+ return localStorage.getItem(welcomeSeenKey(treeId)) === '1'
+ } catch {
+ return false
+ }
+}
+
+export function markFamilyWelcomeSeen(treeId) {
+ if (!treeId) return
+ try {
+ localStorage.setItem(welcomeSeenKey(treeId), '1')
+ } catch {
+ // Ignore localStorage failures
+ }
+}
diff --git a/landing/public/aide/accueil.webp b/landing/public/aide/accueil.webp
new file mode 100644
index 0000000..059ae2b
Binary files /dev/null and b/landing/public/aide/accueil.webp differ
diff --git a/landing/public/aide/arbre.webp b/landing/public/aide/arbre.webp
new file mode 100644
index 0000000..af42a07
Binary files /dev/null and b/landing/public/aide/arbre.webp differ
diff --git a/landing/public/aide/envoi.webp b/landing/public/aide/envoi.webp
new file mode 100644
index 0000000..98d98aa
Binary files /dev/null and b/landing/public/aide/envoi.webp differ
diff --git a/landing/public/aide/fiche.webp b/landing/public/aide/fiche.webp
new file mode 100644
index 0000000..f1d025a
Binary files /dev/null and b/landing/public/aide/fiche.webp differ
diff --git a/landing/public/aide/index.html b/landing/public/aide/index.html
new file mode 100644
index 0000000..6b8d65c
--- /dev/null
+++ b/landing/public/aide/index.html
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
Vous avez reçu le lien de l'arbre de votre famille, le mode d'emploi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mode d'emploi
+ Vous avez reçu le lien de l'arbre de votre famille
+ Quelqu'un de votre famille a fait un arbre sur Parenthèse et vous invite à le regarder et à le compléter. Il n'y a rien à installer et pas de compte à créer. Voici comment faire, pas à pas, sur téléphone comme sur ordinateur.
+
+ 1. Ouvrir l'arbre
+ Touchez le lien que vous avez reçu par mail, par SMS ou sur WhatsApp. Une page s'ouvre avec le nom de l'arbre.
+ Tapez le mot de passe donné avec le lien, en respectant les majuscules, puis touchez « Ouvrir l'arbre ». Gardez le message qui contient le lien et le mot de passe, il vous servira pour revenir.
+
+

+
+
+ 2. Regarder
+ La première fois, un petit mot d'accueil s'affiche. Touchez « Regarder l'arbre » pour le fermer.
+ Chaque portrait est une personne de la famille. Pour vous déplacer, faites glisser l'arbre avec le doigt. Pour agrandir, écartez deux doigts sur l'écran, et rapprochez-les pour voir plus large. La loupe, en haut à droite, retrouve quelqu'un par son prénom.
+ Touchez un portrait pour ouvrir sa fiche, avec ses photos, ses souvenirs et ses dates. La croix, en haut de la fiche, la referme.
+
+

+

+
+
+ 3. Ajouter un souvenir
+ Un souvenir, c'est une photo, une vidéo, un enregistrement de voix, une phrase qu'elle ou il disait souvent, ou un document comme une lettre ou un faire-part.
+
+ - Touchez « Ajouter un souvenir », en bas de l'écran.
+ - Tapez les premières lettres du prénom de la personne, puis touchez son portrait.
+ - Choisissez ce que vous ajoutez, par exemple Photo, puis touchez « Choisir un fichier ». Votre téléphone vous montre vos photos, touchez celle que vous voulez.
+ - Si vous le souhaitez, écrivez une légende (l'année, le lieu, qui est sur la photo). Écrivez aussi votre prénom, pour que la famille sache qui l'a partagée.
+ - Touchez « Ajouter ce souvenir ». Un message vous confirme que c'est envoyé.
+
+
+

+

+
+
+
Vous pouvez aussi partir de la fiche. Touchez le portrait de la personne, puis « Ajouter un souvenir », sous ses photos.
+
+
+ 4. Ajouter une personne qui manque
+
+ - Touchez « Ajouter une personne », en bas de l'écran.
+ - Écrivez son prénom et son nom, et ses dates si vous les connaissez.
+ - Dites à qui elle est liée. Choisissez Parent, Enfant, Conjoint·e ou Frère·Sœur, puis tapez le prénom de la personne déjà dans l'arbre et touchez-la dans la liste.
+ - Touchez « Ajouter cette personne ». Une fenêtre s'ouvre avec votre prénom et une case pour un mot, si vous voulez expliquer. Touchez « Envoyer mes contributions ».
+
+
+

+

+
+
+ 5. Et ensuite
+ La personne qui gère l'arbre reçoit ce que vous avez envoyé, le relit, puis l'ajoute. Vos photos, vos souvenirs et les personnes que vous avez ajoutées apparaissent alors pour toute la famille. Vous n'avez rien d'autre à faire.
+ Si vous voyez une date fausse ou un prénom mal écrit, le plus simple est de le dire à la personne qui vous a envoyé le lien.
+
+

+
+
+ Questions fréquentes
+
+
+ J'ai perdu le mot de passe
+ Demandez-le à la personne qui vous a envoyé le lien. Elle le retrouve dans l'arbre, dans le panneau de partage.
+
+
+ Le mot de passe est refusé
+ Vérifiez les majuscules et qu'il n'y a pas d'espace en trop avant ou après. S'il est toujours refusé, il a peut-être été changé. Demandez le nouveau à la personne qui gère l'arbre.
+
+
+ Faut-il créer un compte ?
+ Non. Le lien et le mot de passe suffisent pour regarder l'arbre et y ajouter des souvenirs. Un compte sert seulement à retrouver l'arbre sans retaper le mot de passe.
+
+
+ Est-ce que ça marche sur ordinateur ?
+ Oui. Les mêmes boutons se trouvent en bas à droite de l'écran. Pour agrandir l'arbre, tournez la molette de la souris, et cliquez sur un portrait pour ouvrir sa fiche.
+
+
+ Ma photo ne passe pas
+ Une photo peut peser jusqu'à 5 Mo et une vidéo jusqu'à 20 Mo. Si elle est refusée, envoyez-la en taille réduite, ou demandez à quelqu'un de la famille de l'ajouter pour vous.
+
+
+ Qui peut voir l'arbre ?
+ Seules les personnes qui ont le lien et le mot de passe. L'arbre n'apparaît pas dans les moteurs de recherche. Le détail est sur la page données et vie privée.
+
+
+
+
+ Faire l'arbre de votre famille
+ C'est gratuit, et la famille le complète par un lien, comme vous venez de le faire.
+ Créer mon arbre
+
+
+
+
+
+
diff --git a/landing/public/aide/merci.webp b/landing/public/aide/merci.webp
new file mode 100644
index 0000000..cb37384
Binary files /dev/null and b/landing/public/aide/merci.webp differ
diff --git a/landing/public/aide/ouvrir.webp b/landing/public/aide/ouvrir.webp
new file mode 100644
index 0000000..d5eebc0
Binary files /dev/null and b/landing/public/aide/ouvrir.webp differ
diff --git a/landing/public/aide/personne.webp b/landing/public/aide/personne.webp
new file mode 100644
index 0000000..acc6028
Binary files /dev/null and b/landing/public/aide/personne.webp differ
diff --git a/landing/public/aide/pour-qui.webp b/landing/public/aide/pour-qui.webp
new file mode 100644
index 0000000..c193bf9
Binary files /dev/null and b/landing/public/aide/pour-qui.webp differ
diff --git a/landing/public/aide/souvenir.webp b/landing/public/aide/souvenir.webp
new file mode 100644
index 0000000..a51f566
Binary files /dev/null and b/landing/public/aide/souvenir.webp differ
diff --git a/landing/public/guides.css b/landing/public/guides.css
index 1179316..1144567 100644
--- a/landing/public/guides.css
+++ b/landing/public/guides.css
@@ -50,6 +50,11 @@ strong { font-weight: 600; }
.note { padding: 18px 22px; border-left: 3px solid var(--a500); background: var(--a50); border-radius: 0 14px 14px 0; margin: 24px 0; }
.note p:last-child { margin-bottom: 0; }
+/* Captures d'écran de téléphone (mode d'emploi) */
+.shots { display: flex; flex-wrap: wrap; gap: 20px; margin: 20px 0 28px; }
+.shots figure { margin: 0; flex: 0 1 250px; }
+.shots img { display: block; width: 100%; height: auto; border-radius: 26px; border: 1px solid var(--trait); box-shadow: 0 10px 30px rgba(42, 38, 34, .08); }
+
table { width: 100%; border-collapse: collapse; margin: 16px 0 28px; font-size: 15px; line-height: 1.5; }
th, td { text-align: left; vertical-align: top; padding: 12px 10px; border-bottom: 1px solid var(--trait); }
th { font: 500 12px/1.3 var(--sans); letter-spacing: 0.08em; text-transform: uppercase; color: var(--doux); border-bottom-color: var(--a300); }
@@ -76,6 +81,7 @@ footer a { color: inherit; text-decoration: none; }
footer a:hover { color: var(--encre); }
@media (max-width: 600px) {
+ .shots figure { flex-basis: calc(50% - 10px); }
body { font-size: 16px; }
main { padding-top: 40px; }
.lead { font-size: 18px; }
diff --git a/landing/public/sitemap.xml b/landing/public/sitemap.xml
index d84b518..df2b647 100644
--- a/landing/public/sitemap.xml
+++ b/landing/public/sitemap.xml
@@ -48,4 +48,10 @@
yearly
0.3
+
+ https://parenthese.io/aide/
+ 2026-09-25
+ monthly
+ 0.6
+