feat(component): RecentlyFromTheCouncil — pulls the most recent contribution

White-card surface. Header row with secondary 'Recently from the council'
eyebrow and a terracotta 'All contributions →' link. Body is one card:
22px id-pigment avatar, full name, relative time, contribution-type pill
(idea→copper, inspiration→ochre, question→indigo), then the body rendered
as a serif italic pull-quote with a 40%-opacity terracotta left border,
clamped to 3 lines via -webkit-line-clamp with markdown stripped before
trimming. Footer shows the reaction count.

Hidden entirely when the contributions table has no visible rows — no
empty-state placeholder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan Hvid 2026-05-11 15:58:29 +02:00
parent 3b602a787b
commit 3e9fadcf79

View file

@ -0,0 +1,123 @@
---
import Avatar from './Avatar.astro';
import { getContributions } from '../lib/db';
import { relativeTime, stripMarkdownLight } from '../lib/format';
const [latest] = getContributions({ sort: 'newest' });
const TYPE_LABEL = { idea: 'Idea', inspiration: 'Inspiration', question: 'Question' } as const;
const TYPE_PIGMENT = {
idea: 'var(--pigment-copper)',
inspiration: 'var(--pigment-ochre)',
question: 'var(--pigment-indigo)',
} as const;
function trimQuote(body: string, max = 220): string {
const stripped = stripMarkdownLight(body);
if (stripped.length <= max) return stripped;
const cut = stripped.slice(0, max);
const last = Math.max(cut.lastIndexOf(' '), cut.lastIndexOf('.'));
return (last > max - 40 ? cut.slice(0, last) : cut).trim() + '…';
}
---
{latest && (
<section class="r-card" aria-label="Recently from the council">
<header class="r-head">
<p class="label-sm r-eyebrow">Recently from the council</p>
<a href="/contribute" class="r-all">All contributions →</a>
</header>
<header class="r-byline">
<Avatar id={latest.user_id} name={latest.author_name} size={22} />
<span class="r-name">{latest.author_name}</span>
<span class="r-time label-sm">{relativeTime(latest.created_at)}</span>
<span class="r-type" style={`--type: ${TYPE_PIGMENT[latest.type]}`}>{TYPE_LABEL[latest.type]}</span>
</header>
<blockquote class="r-quote">{trimQuote(latest.body_md)}</blockquote>
<p class="r-foot label-sm">
{latest.reaction_count === 0
? 'No reactions yet'
: `${latest.reaction_count} reaction${latest.reaction_count === 1 ? '' : 's'} from the council`}
</p>
</section>
)}
<style>
.r-card {
background: var(--surface-card);
border: 0.5px solid var(--surface-card-border);
border-radius: var(--radius-lg);
padding: var(--space-6);
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.r-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-3);
}
.r-eyebrow {
letter-spacing: var(--tracking-wider);
text-transform: uppercase;
color: var(--on-surface-variant);
margin: 0;
}
.r-all {
font-family: var(--font-sans);
font-size: var(--text-label-sm);
letter-spacing: var(--tracking-wide);
text-transform: uppercase;
color: var(--pigment-terracotta);
text-decoration: none;
border-bottom: none;
}
.r-all:hover { opacity: 0.85; border-bottom: none; }
.r-byline {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--text-body-sm);
}
.r-name { font-weight: 600; color: var(--on-surface); }
.r-time { color: var(--on-surface-muted); letter-spacing: var(--tracking-wide); margin-left: auto; }
.r-type {
color: var(--type);
background: color-mix(in oklab, var(--type) 14%, transparent);
padding: 2px 9px;
border-radius: 999px;
font-family: var(--font-sans);
font-size: var(--text-label-sm);
letter-spacing: var(--tracking-wide);
font-weight: 600;
}
.r-quote {
margin: 0;
padding: 0 0 0 var(--space-4);
border-left: 2px solid color-mix(in oklab, var(--pigment-terracotta) 40%, transparent);
font-family: var(--font-serif);
font-style: italic;
font-size: 1.0625rem;
line-height: 1.5;
color: var(--on-surface);
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.r-foot {
color: var(--on-surface-muted);
letter-spacing: var(--tracking-wide);
margin: 0;
}
</style>