From 58faeffbc25bce5098fe0af38a31dd36fc540f86 Mon Sep 17 00:00:00 2001 From: Jonathan Hvid Date: Mon, 11 May 2026 16:03:35 +0200 Subject: [PATCH] =?UTF-8?q?feat(page):=20/members=20=E2=80=94=20editorial?= =?UTF-8?q?=20masthead?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Header: MEMBERS eyebrow, serif italic 'The council.', subtitle pitched at 'an invited circle of operators' — no number, no seat count anywhere. One row per cab user, ordered by member_number asc. Three-column grid (60px / 1fr / 130px, 18px gap) with 0.5px bottom borders. Avatar uses the deterministic id-pigment palette; the same id always yields the same colour across the portal. Body column: serif italic name; if the row belongs to the signed-in user, a small uppercase tracked 'You' tag in terracotta. One metadata line — title · organisation · Member since {month year} — title is dropped if null. The pull-quote block is only rendered when pull_quote is non-null; its 2px left border is the user's pigment at 40% opacity (50% on the signed-in user's own row). Right column: up to 3 focus-tag pills, vertical right-aligned, tinted in the same pigment family (15% bg / 100% colour). Empty focus_tags renders nothing — no placeholder pills. Signed-in user's row gets a 4% pigment tint and rounded corners; no border between rows in that case. No filters, no search, no sort, no header count, no empty-seat row. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/pages/members.astro | 222 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 src/pages/members.astro diff --git a/src/pages/members.astro b/src/pages/members.astro new file mode 100644 index 0000000..ec949ad --- /dev/null +++ b/src/pages/members.astro @@ -0,0 +1,222 @@ +--- +import AppLayout from '../layouts/AppLayout.astro'; +import Avatar from '../components/Avatar.astro'; +import { getAllCabMembers } from '../lib/db'; +import { pigmentForId, readFocusTags } from '../lib/format'; + +const user = Astro.locals.user; +const members = getAllCabMembers(); + +function memberSinceLabel(member: { cab_joined_date: string | null; created_at: string }): string { + const iso = member.cab_joined_date ?? member.created_at; + const normalised = iso.includes('T') ? iso : iso.replace(' ', 'T') + 'Z'; + return new Intl.DateTimeFormat('en-GB', { + month: 'long', year: 'numeric', timeZone: 'Europe/Copenhagen', + }).format(new Date(normalised)); +} +--- + +
+ +
+

Members

+

The council.

+

+ An invited circle of operators shaping what Project Bifrost becomes. +

+
+ +
+ +
    + {members.map(m => { + const pigment = pigmentForId(m.id); + const tags = readFocusTags(m.focus_tags); + const isMe = m.id === user.id; + const metaParts = [m.title, m.organisation, `Member since ${memberSinceLabel(m)}`].filter(Boolean); + return ( +
  • +
    + +
    + +
    +

    + {m.name} + {isMe && You} +

    + +

    {metaParts.join(' · ')}

    + + {m.pull_quote && ( +
    {m.pull_quote}
    + )} +
    + + {tags.length > 0 && ( +
      + {tags.map(t =>
    • {t}
    • )} +
    + )} +
  • + ); + })} + + {members.length === 0 && ( +
  • The council is still being formed.
  • + )} +
+ +
+
+ +