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:
parent
3b602a787b
commit
3e9fadcf79
1 changed files with 123 additions and 0 deletions
123
src/components/RecentlyFromTheCouncil.astro
Normal file
123
src/components/RecentlyFromTheCouncil.astro
Normal 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>
|
||||||
Loading…
Add table
Reference in a new issue