Visages sur la photo : rien par défaut, ronds fins à la demande

- Les ronds des personnes identifiées ne restent plus posés sur les visages. Ils apparaissent au survol de la photo
  (ordinateur), quand on touche la photo (téléphone, un second toucher les cache), ou pour une seule personne quand
  on survole ou focalise son prénom sous la photo. Toujours visibles pendant l'identification.
- Rond plus large (34 px) et plus fin (1,5 px) : il entoure le visage au lieu de le couvrir.
- Sur téléphone, les prénoms ne s'affichent plus en permanence par-dessus la photo.

Planche validée par Patch le 27/10 (option A) : _bmad-output/parenthese/motion/2026-10-27-visages/

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
IliesAllaliandClaude Opus 5.5 committed 2026-10-27 14:26:08 +01:00
1 parent 0b429e78a9
commit 2aab008766
3 files changed
+40 -15

No files matched your search

+15 -8
View File
@@ -3,19 +3,27 @@
.mp-avatar { width: 24px; height: 24px; flex: none; border-radius: 50%; object-fit: cover; }
.mp-avatar--initial { display: grid; place-items: center; background: var(--pz-a100); color: var(--pz-a700); font: 400 12px/1 var(--pz-serif); }
/* Point sur un visage : rond blanc cerclé, le prénom apparaît au survol ou au focus (toujours en identification) */
/* Rond sur un visage : fil fin, assez large pour entourer le visage sans le couvrir. Caché par défaut, la photo d'abord
(Patch, 27/10) : visible au survol de la photo, quand on la touche (.show-names), quand son prénom est survolé
dessous (.is-hot), et toujours en identification. Apparition d'un coup, sans fondu. */
.mp-marker {
position: absolute;
width: 26px;
height: 26px;
margin: -13px 0 0 -13px;
width: 34px;
height: 34px;
margin: -17px 0 0 -17px;
padding: 0;
border: 2px solid #fff;
border: 1.5px solid rgba(255, 255, 255, .9);
border-radius: 50%;
background: transparent;
box-shadow: 0 0 0 1px rgba(42, 38, 34, .3), 0 2px 8px rgba(42, 38, 34, .25);
box-shadow: 0 0 0 1px rgba(42, 38, 34, .18);
cursor: pointer;
visibility: hidden;
}
@media (hover: hover) and (pointer: fine) { .mv-photo-box:hover .mp-marker { visibility: visible; } }
.mv-photo-box.is-tagging .mp-marker,
.mv-photo-box.show-names .mp-marker,
.mp-marker.is-hot { visibility: visible; }
.mp-marker.is-hot { border-color: #fff; box-shadow: 0 0 0 1px rgba(42, 38, 34, .25), 0 0 0 4px rgba(255, 255, 255, .25); }
.mp-marker.is-active { border-color: var(--pz-a500); box-shadow: 0 0 0 3px rgba(210, 105, 74, .35), 0 2px 8px rgba(42, 38, 34, .25); }
.mp-marker:focus-visible { outline: 2px solid var(--pz-a700); outline-offset: 3px; }
.mp-marker-name {
@@ -33,10 +41,9 @@
visibility: hidden;
}
.mp-marker:hover .mp-marker-name,
.mp-marker:focus-visible .mp-marker-name,
.mp-marker.is-hot .mp-marker-name,
.mv-photo-box.is-tagging .mp-marker-name,
.mv-photo-box.show-names .mp-marker-name { visibility: visible; }
@media (hover: none) { .mp-marker-name { visibility: visible; } }
/* Photo cliquable pendant le placement d'un point */
.mv-photo-box.is-placing img { cursor: crosshair; }
+15 -5
View File
@@ -14,9 +14,9 @@ function PersonAvatar({ person }) {
: <span className="mp-avatar mp-avatar--initial" aria-hidden="true">{person?.firstName?.[0] || '?'}</span>
}
// Points posés sur la photo : un rond par visage, le prénom au survol ou au focus. En identification, le point de
// la personne choisie est mis en avant.
export function MediaPeopleMarkers({ people, getPerson, activeId = null, onOpenPerson }) {
// Ronds posés sur les visages, cachés tant qu'on ne les demande pas (survol de la photo, photo touchée, prénom survolé
// dessous : `hotId`). En identification, ils restent visibles et le rond de la personne choisie est mis en avant.
export function MediaPeopleMarkers({ people, getPerson, activeId = null, hotId = null, onOpenPerson }) {
return people.filter((tag) => tag.x != null && tag.y != null).map((tag) => {
const person = getPerson(tag.personId)
if (!person) return null
@@ -25,7 +25,7 @@ export function MediaPeopleMarkers({ people, getPerson, activeId = null, onOpenP
<button
key={tag.personId}
type="button"
className={`mp-marker${activeId === tag.personId ? ' is-active' : ''}`}
className={`mp-marker${activeId === tag.personId ? ' is-active' : ''}${hotId === tag.personId ? ' is-hot' : ''}`}
style={{ left: `${tag.x * 100}%`, top: `${tag.y * 100}%` }}
onClick={(event) => { event.stopPropagation(); onOpenPerson?.(tag.personId) }}
aria-label={t('Voir la fiche de {name}', { name })}
@@ -49,6 +49,7 @@ export function MediaPeopleList({
onAdd,
onRemove,
onOpenPerson,
onHover,
}) {
const [query, setQuery] = useState('')
const taken = useMemo(() => new Set(people.map((tag) => tag.personId)), [people])
@@ -69,7 +70,16 @@ export function MediaPeopleList({
<div className="mp-list" aria-label={t('Sur la photo')}>
<span className="mp-list-label">{t('Sur la photo')}</span>
{known.map(({ person }) => (
<button key={person.id} type="button" className="mp-chip" onClick={() => onOpenPerson?.(person.id)}>
<button
key={person.id}
type="button"
className="mp-chip"
onClick={() => onOpenPerson?.(person.id)}
onPointerEnter={() => onHover?.(person.id)}
onPointerLeave={() => onHover?.(null)}
onFocus={() => onHover?.(person.id)}
onBlur={() => onHover?.(null)}
>
<PersonAvatar person={person} />
<span>{firstNameLabel(person)}</span>
</button>
+10 -2
View File
@@ -42,6 +42,10 @@ const MediaViewer = ({
const [tagging, setTagging] = useState(false)
const [draftPeople, setDraftPeople] = useState([])
const [activeTagId, setActiveTagId] = useState(null)
// Visages identifiés : rien sur la photo par défaut (Patch, 27/10). Toucher la photo montre ou cache les prénoms,
// survoler un prénom sous la photo entoure son visage.
const [showNames, setShowNames] = useState(false)
const [hotPersonId, setHotPersonId] = useState(null)
const [savingPeople, setSavingPeople] = useState(false)
const [peopleError, setPeopleError] = useState('')
const [peopleNotice, setPeopleNotice] = useState('')
@@ -309,12 +313,16 @@ const MediaViewer = ({
// La boîte épouse l'image : les points (x, y en proportion) tombent pile sur les visages
return (
<div className="media-photo-frame">
<div className={`mv-photo-box${tagging ? ' is-tagging' : ''}${activeTagId ? ' is-placing' : ''}`} onClick={placeActiveTag}>
<div
className={`mv-photo-box${tagging ? ' is-tagging' : ''}${activeTagId ? ' is-placing' : ''}${showNames ? ' show-names' : ''}`}
onClick={(event) => (tagging ? placeActiveTag(event) : setShowNames((shown) => !shown))}
>
<img src={mediaUrl} alt={media?.label || t('Photo')} />
<MediaPeopleMarkers
people={shownPeople}
getPerson={getPerson}
activeId={activeTagId}
hotId={tagging ? null : hotPersonId}
onOpenPerson={tagging ? (id) => setActiveTagId(id) : onOpenPerson}
/>
</div>
@@ -646,7 +654,7 @@ const MediaViewer = ({
</div>
) : (
<div className="mv-people">
<MediaPeopleList people={savedPeople} getPerson={getPerson} onOpenPerson={onOpenPerson} />
<MediaPeopleList people={savedPeople} getPerson={getPerson} onOpenPerson={onOpenPerson} onHover={setHotPersonId} />
{canTag && onSavePeople && (
<button type="button" className="pz-btn pz-btn--soft pz-btn--sm mv-people-btn" onClick={startTagging}>
<UserPlus size={15} strokeWidth={2} aria-hidden="true" />