160 lines
7.0 KiB
Plaintext
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>
|