mirror of
https://github.com/IliesAllali/parenthese.git
synced 2026-11-15 08:32:03 +00:00
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:
1 parent
0b429e78a9
commit
2aab008766
3 files changed
+40
-15
No files matched your search
@@ -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; }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
Reference in new issue
Block a user