Replaces the CouncilMark slot on /pulse. Dark --ink surface, --ink-text foreground, --ink-muted for labels. Top row: 22px cream circle with the N monogram, COUNCIL · NNN (zero-padded to 3) on the right; defensive fallback COUNCIL · MEMBER when member_number is null. First and last name on separate lines in serif italic, split on the last space so compound surnames hang together. MEMBER SINCE / month-year block sits above the focus-tag pill row. Pills render only when focus_tags is non-empty — no placeholder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
145 lines
3.6 KiB
Text
145 lines
3.6 KiB
Text
---
|
|
import type { UserPublic } from '../lib/db';
|
|
import { readFocusTags } from '../lib/format';
|
|
|
|
interface Props {
|
|
member: UserPublic;
|
|
}
|
|
|
|
const { member } = Astro.props;
|
|
|
|
// Split on the last space — handles compound surnames better than first-space split.
|
|
const parts = member.name.trim().split(/\s+/);
|
|
const firstName = parts.length === 1 ? parts[0] : parts.slice(0, -1).join(' ');
|
|
const lastName = parts.length === 1 ? '' : parts[parts.length - 1];
|
|
|
|
const numberLabel = member.member_number != null
|
|
? `COUNCIL · ${String(member.member_number).padStart(3, '0')}`
|
|
: 'COUNCIL · MEMBER';
|
|
|
|
// Member-since: prefer cab_joined_date, fall back to created_at.
|
|
const sinceISO = member.cab_joined_date ?? member.created_at;
|
|
const since = new Intl.DateTimeFormat('en-GB', {
|
|
month: 'long', year: 'numeric', timeZone: 'Europe/Copenhagen',
|
|
}).format(new Date(sinceISO.replace(' ', 'T') + (sinceISO.includes('T') ? '' : 'Z')));
|
|
|
|
const tags = readFocusTags(member.focus_tags);
|
|
---
|
|
<article class="m-card" aria-label={`${member.name} — membership card`}>
|
|
<header class="m-top">
|
|
<span class="m-monogram" aria-hidden="true">N</span>
|
|
<span class="m-number">{numberLabel}</span>
|
|
</header>
|
|
|
|
<h3 class="m-name">
|
|
<span class="m-given">{firstName}</span>
|
|
{lastName && <span class="m-family">{lastName}</span>}
|
|
</h3>
|
|
|
|
<p class="m-since-block">
|
|
<span class="m-since-label">Member since</span>
|
|
<span class="m-since-value">{since}</span>
|
|
</p>
|
|
|
|
{tags.length > 0 && (
|
|
<ul class="m-tags" aria-label="Focus areas">
|
|
{tags.map(t => <li class="m-tag">{t}</li>)}
|
|
</ul>
|
|
)}
|
|
</article>
|
|
|
|
<style>
|
|
.m-card {
|
|
background: var(--ink);
|
|
color: var(--ink-text);
|
|
border-radius: var(--radius-lg);
|
|
padding: 1.25rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-5);
|
|
min-height: 220px;
|
|
}
|
|
|
|
.m-top {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.m-monogram {
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 50%;
|
|
background: var(--ink-text);
|
|
color: var(--ink);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-family: var(--font-serif);
|
|
font-style: italic;
|
|
font-weight: 700;
|
|
font-size: 13px;
|
|
line-height: 1;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.m-number {
|
|
font-family: var(--font-sans);
|
|
font-size: var(--text-label-sm);
|
|
font-weight: 500;
|
|
letter-spacing: var(--tracking-wider);
|
|
text-transform: uppercase;
|
|
color: var(--ink-muted);
|
|
}
|
|
|
|
.m-name {
|
|
margin: 0;
|
|
font-family: var(--font-serif);
|
|
font-style: italic;
|
|
font-weight: 400;
|
|
font-size: 1.5rem;
|
|
line-height: 1.15;
|
|
color: var(--ink-text);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.m-given, .m-family { display: block; }
|
|
|
|
.m-since-block {
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.m-since-label {
|
|
font-family: var(--font-sans);
|
|
font-size: var(--text-label-sm);
|
|
letter-spacing: var(--tracking-wider);
|
|
text-transform: uppercase;
|
|
color: var(--ink-muted);
|
|
}
|
|
.m-since-value {
|
|
font-family: var(--font-sans);
|
|
font-size: var(--text-body-sm);
|
|
color: var(--ink-text);
|
|
}
|
|
|
|
.m-tags {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: auto 0 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
.m-tag {
|
|
border: 0.5px solid rgba(232, 224, 208, 0.3);
|
|
color: var(--ink-text);
|
|
padding: 3px 8px;
|
|
border-radius: 999px;
|
|
font-family: var(--font-sans);
|
|
font-size: var(--text-label-sm);
|
|
letter-spacing: var(--tracking-wide);
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|