Match detail pages: band photo, contact pills, phone, pronunciation playback

This commit is contained in:
Ian Gulliver
2026-08-15 21:35:15 -07:00
parent 6c0b1d34c5
commit 877c989539
3 changed files with 104 additions and 30 deletions
+52 -13
View File
@@ -22,7 +22,9 @@ const icons = {
'chevron-right': '<svg viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></svg>',
heart: '<svg viewBox="0 0 24 24"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>',
mail: '<svg viewBox="0 0 24 24"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>',
copy: '<svg viewBox="0 0 24 24"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>',
message: '<svg viewBox="0 0 24 24"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>',
phone: '<svg viewBox="0 0 24 24"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>',
};
const navSections = [
@@ -362,16 +364,22 @@ function breadcrumbs(parts) {
return top;
}
function iconButton(name, href) {
const node = el(href ? 'a' : 'button', 'icon-button');
if (href) {
node.href = href;
function iconButton(name, label, action) {
const node = el(typeof action === 'string' ? 'a' : 'button', 'icon-button');
if (typeof action === 'string') {
node.href = action;
node.target = '_blank';
} else if (action) {
node.addEventListener('click', action);
}
node.append(svg(name));
node.append(svg(name), el('span', '', label));
return node;
}
function copyButton(text) {
return iconButton('copy', 'Copy', () => navigator.clipboard.writeText(text));
}
function contactRow(value, buttons) {
const row = el('div', 'contact-row');
row.append(value);
@@ -420,6 +428,12 @@ function familyBand(p, family) {
const grid = el('div', 'band-grid container');
const left = el('div', 'band-left');
left.append(el('h2', '', `${p.fullName}'s Family`));
if (family.photoUrl && !p.isStudent) {
const img = el('img', 'band-photo');
img.src = thumbUrl(family.photoUrl);
img.alt = '';
left.append(img);
}
if (family.photoCaption) {
left.append(el('div', 'band-caption', family.photoCaption));
}
@@ -433,14 +447,16 @@ function familyBand(p, family) {
if (p.isStudent && kid.email === p.email) {
continue;
}
right.append(memberRow(kid, gradeChain(kid).toUpperCase(), kid.email));
const label = kid.pronouns ? `${gradeChain(kid)} (${kid.pronouns})` : gradeChain(kid);
right.append(memberRow(kid, label.toUpperCase(), kid.email));
}
}
const adults = (family.adultEmails || []).map(e => byEmail[e]).filter(Boolean).filter(a => a.email !== p.email);
if (adults.length) {
right.append(el('div', 'member-header', 'Adults'));
for (const adult of adults) {
right.append(memberRow(adult, adult.pronouns ? `(${adult.pronouns.toUpperCase()})` : '', adult.email));
const label = [adult.phone, adult.pronouns ? `(${adult.pronouns.toUpperCase()})` : ''].filter(Boolean).join(' ');
right.append(memberRow(adult, label, adult.email));
}
}
const see = el('a', 'see-family');
@@ -489,9 +505,16 @@ function renderPersonDetail(email) {
right.append(el('div', 'detail-sub', chain));
}
}
if (p.phone) {
right.append(contactRow(el('div', 'contact-value', p.phone), [
copyButton(p.phone),
iconButton('message', 'Text', 'sms:' + p.phone),
iconButton('phone', 'Call', 'tel:' + p.phone),
]));
}
right.append(contactRow(el('div', 'contact-value', p.email), [
iconButton('message'),
iconButton('mail', 'mailto:' + p.email),
copyButton(p.email),
iconButton('mail', 'Email', 'mailto:' + p.email),
]));
const family = state.model.families[p.familyKey];
if (family && family.address) {
@@ -499,10 +522,18 @@ function renderPersonDetail(email) {
block.append(el('div', 'field-label', 'Address'));
block.append(el('div', 'contact-value', family.address));
right.append(contactRow(block, [
iconButton('message'),
iconButton('map', 'https://maps.google.com/?q=' + encodeURIComponent(family.address)),
copyButton(family.address),
iconButton('map', 'Map', 'https://maps.google.com/?q=' + encodeURIComponent(family.address)),
]));
}
if (p.pronunciationUrl) {
right.append(el('div', 'pronounce-label', 'How do I pronounce this?'));
const audio = el('audio', 'pronounce-player');
audio.controls = true;
audio.preload = 'metadata';
audio.src = p.pronunciationUrl;
right.append(audio);
}
grid.append(right);
content.append(grid);
@@ -561,10 +592,18 @@ function renderFamilyDetail(key) {
}
if (family.address) {
right.append(contactRow(el('div', 'contact-value', family.address), [
iconButton('message'),
iconButton('map', 'https://maps.google.com/?q=' + encodeURIComponent(family.address)),
copyButton(family.address),
iconButton('map', 'Map', 'https://maps.google.com/?q=' + encodeURIComponent(family.address)),
]));
}
if (family.pronunciationUrl) {
right.append(el('div', 'pronounce-label', 'How do I pronounce this?'));
const audio = el('audio', 'pronounce-player');
audio.controls = true;
audio.preload = 'metadata';
audio.src = family.pronunciationUrl;
right.append(audio);
}
grid.append(right);
content.append(grid);
main.append(content);
+50 -15
View File
@@ -477,7 +477,7 @@ a.family-card {
.detail-grid {
display: grid;
grid-template-columns: 302px minmax(0, 1fr);
grid-template-columns: minmax(0, 31fr) minmax(0, 69fr);
gap: 28px;
}
@@ -523,15 +523,19 @@ a.family-card {
}
.icon-button {
width: 40px;
height: 40px;
border-radius: 50%;
border: 0;
background: #f1f3f4;
color: #3c4650;
display: flex;
align-items: center;
justify-content: center;
gap: 9px;
height: 44px;
padding: 0 20px;
border-radius: 22px;
border: 0;
background: #f1f3f4;
color: #1f2933;
font: inherit;
font-size: 14px;
font-weight: 600;
text-decoration: none;
cursor: pointer;
}
@@ -543,6 +547,32 @@ a.family-card {
stroke-width: 1.5;
stroke-linecap: round;
stroke-linejoin: round;
flex-shrink: 0;
}
@media (max-width: 1399px) {
.icon-button {
width: 40px;
height: 40px;
padding: 0;
justify-content: center;
border-radius: 50%;
}
.icon-button span {
display: none;
}
}
.pronounce-label {
text-align: center;
color: var(--muted);
font-size: 14px;
margin: 34px 0 12px;
}
.pronounce-player {
width: 100%;
display: block;
}
.about-header {
@@ -564,8 +594,8 @@ a.family-card {
.band-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 44px;
grid-template-columns: minmax(0, 31fr) minmax(0, 69fr);
gap: 28px;
padding-top: 36px;
padding-bottom: 40px;
}
@@ -577,13 +607,18 @@ a.family-card {
text-align: center;
}
.band-photo {
width: 100%;
border-radius: 8px;
display: block;
}
.band-caption {
color: rgba(255, 255, 255, 0.72);
font-size: 13px;
line-height: 1.5;
text-align: center;
max-width: 320px;
margin: 0 auto;
margin-top: 20px;
}
.band-title {
@@ -610,9 +645,9 @@ a.family-card {
}
.member-thumb {
width: 40px;
height: 40px;
border-radius: 6px;
width: 50px;
height: 50px;
border-radius: 8px;
object-fit: cover;
flex-shrink: 0;
}