Files
homepage_relaunch/src/pages/_InsightsIndex.astro
T
2026-10-04 19:12:42 +02:00

160 lines
7.0 KiB
Plaintext

---
import { feedType, feedTypes, getFeed } from '../data/insights';
import { Picture } from 'astro:assets';
import Base from '../layouts/Base.astro';
import { formatDate, localePath, t, type Lang } from '../i18n/ui';
interface Props { lang: Lang }
const { lang } = Astro.props;
const ui = t(lang);
const feed = await getFeed();
const m = ui.insights.mention;
const f = ui.insights.filter;
/* Only filters that have entries; "All" first. */
const filters = [
{ key: 'all', label: f.all, count: feed.length },
...feedTypes
.map((key) => ({ key, label: f[key], count: feed.filter((item) => feedType(item) === key).length }))
.filter((x) => x.count > 0),
];
---
<Base lang={lang} title={ui.meta.insights.title} description={ui.meta.insights.description}>
<section class="section section--intro">
<div class="container grid">
<h1 class="display index__title">{ui.insights.title}</h1>
<p class="index__lead">{ui.insights.intro}</p>
</div>
</section>
<section class="section--list" aria-label={ui.insights.title}>
<div class="container">
{filters.length > 2 && (
<fieldset class="choice-group filters" data-filters>
<legend class="choice-group__legend">{f.label}</legend>
<div class="choices">
{filters.map((x) => (
<label class="choice">
<input type="radio" name="insights-type" value={x.key} checked={x.key === 'all'} />
<span>{x.label} <span class="choice__n">{x.count}</span></span>
</label>
))}
</div>
</fieldset>
)}
<ul class="posts" role="list" data-feed>
{feed.map((item) => item.kind === 'post' ? (
<li class="post" data-type={feedType(item)}>
<a class="post__link" href={localePath(lang, `/insights/${item.entry.id}`)} lang={item.entry.data.lang !== lang ? item.entry.data.lang : undefined}>
<div class="post__media">
<Picture
src={item.entry.data.cover}
alt={item.entry.data.coverAlt}
formats={['avif']}
fallbackFormat="webp"
widths={[400, 640, 960]}
sizes="(min-width: 48rem) 40vw, 100vw"
loading="lazy"
decoding="async"
class="post__img img-doc"
/>
</div>
<div class="post__text">
<p class="small muted post__meta">
<span>{item.entry.data.category}</span>
<time datetime={item.date.toISOString().slice(0, 10)}>{formatDate(item.date, lang)}</time>
<span>{ui.insights.readingTime(item.entry.data.readingTime)}</span>
</p>
<h2 class="h2">{item.entry.data.title}</h2>
{item.entry.data.subtitle && <p class="muted">{item.entry.data.subtitle}</p>}
{item.entry.data.lang !== lang && <p class="small muted">{ui.insights.inEnglish}</p>}
</div>
</a>
</li>
) : (
<li class="post" data-type={feedType(item)}>
<a class="post__link" href={localePath(lang, `/insights/${item.entry.id}`)} lang={item.entry.data.lang !== lang ? item.entry.data.lang : undefined}>
<div class="post__media mention__panel" aria-hidden="true">
<span class="mention__source">{item.entry.data.source}</span>
<span class="mention__platform">{item.entry.data.platform}</span>
</div>
<div class="post__text">
<p class="small muted post__meta">
<span>{m.category(item.entry.data.platform)}</span>
<time datetime={item.date.toISOString().slice(0, 10)}>{formatDate(item.date, lang)}</time>
<span>{m.source(item.entry.data.source)}</span>
</p>
<h2 class="h2">{item.entry.data.title}</h2>
<p class="muted mention__summary">{item.entry.data.summary}</p>
{item.entry.data.lang !== lang && <p class="small muted">{item.entry.data.lang === 'en' ? m.inEnglish : m.inGerman}</p>}
</div>
</a>
</li>
))}
</ul>
</div>
</section>
</Base>
<script>
/* Filter the list by type. The choice lives in ?type= so a filtered view can be linked;
without script every entry shows. */
const bar = document.querySelector<HTMLElement>('[data-filters]');
const items = document.querySelectorAll<HTMLElement>('[data-feed] > li');
if (bar) {
const inputs = bar.querySelectorAll<HTMLInputElement>('input[name="insights-type"]');
const keys = [...inputs].map((i) => i.value);
const apply = (key: string, push: boolean) => {
if (!keys.includes(key)) key = 'all';
inputs.forEach((i) => { i.checked = i.value === key; });
items.forEach((li) => { li.hidden = key !== 'all' && li.dataset.type !== key; });
if (push) {
const url = new URL(location.href);
if (key === 'all') url.searchParams.delete('type'); else url.searchParams.set('type', key);
history.replaceState(null, '', url);
}
};
bar.addEventListener('change', (e) => apply((e.target as HTMLInputElement).value, true));
apply(new URL(location.href).searchParams.get('type') ?? 'all', false);
}
</script>
<style>
.index__title { grid-column: 1 / -1; }
.index__lead { grid-column: 1 / -1; max-width: var(--measure); color: var(--ink-muted); }
@media (min-width: 48rem) {
.index__title { grid-column: 1 / span 8; }
.index__lead { grid-column: 1 / span 5; }
}
.section--list { padding-bottom: var(--section); }
/* Filter row: the shared segmented choice (base.css), as on the research list. */
.filters { padding-bottom: var(--space-6); }
.post[hidden] { display: none; }
.posts { border-top: var(--hairline) solid var(--line); }
.post { border-bottom: var(--hairline) solid var(--line); }
.post__link {
display: grid;
gap: var(--space-5) var(--gap);
padding-block: var(--space-7);
text-decoration: none;
}
.post__link:hover h2 { text-decoration: underline; text-decoration-thickness: var(--hairline); text-underline-offset: 0.12em; }
.post__img, :global(.post__img) { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; }
.post__text { display: grid; gap: var(--space-4); align-content: start; }
.post__meta { display: flex; flex-wrap: wrap; gap: var(--space-4); }
/* A mention has no cover: a typographic panel names who posted it and where. */
.mention__panel {
display: flex;
flex-direction: column;
justify-content: space-between;
aspect-ratio: 3 / 2;
padding: var(--space-5);
background: var(--paper-deep);
}
.mention__source { font-size: var(--step-title); line-height: var(--leading-title); letter-spacing: var(--track-h2); color: var(--ink-muted); }
.mention__platform { font-size: var(--step-small); line-height: var(--leading-small); color: var(--ink-muted); }
.mention__summary { max-width: var(--measure); }
@media (min-width: 48rem) {
.post__link { grid-template-columns: 5fr 7fr; }
}
</style>