spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1import type { Metadata } from 'next';2import Link from 'next/link';3import { t } from '@/i18n';4import { routes } from '@/lib/site';5import { STORIES, storyChartCount, storyIndicators } from '@/lib/stories';6import { topicById } from '@/lib/topics';7import { PageHeader } from '@/components/explore/page-header';8import { JsonLd } from '@/components/platform/json-ld';9import { jsonLd } from '@/lib/seo';1011export const revalidate = 3600;1213export const metadata: Metadata = {14 title: t('stories.title'),15 description: t('stories.description'),16 alternates: { canonical: routes.stories() },17 openGraph: { title: `${t('stories.title')} — ${t('site.name')}`, description: t('stories.description'), url: routes.stories(), type: 'website' },18};1920/** /stories — editorial index: one rule per story, standfirst, topics, chart count. */21export default function StoriesPage() {22 return (23 <>24 <JsonLd data={jsonLd.breadcrumbs([{ name: t('site.name'), path: '/' }, { name: t('stories.title'), path: routes.stories() }])} />25 <PageHeader title={t('stories.title')} lede={t('stories.sub')} />26 <ol className="divide-y divide-rule border-y border-rule">27 {STORIES.map((s, i) => {28 const topics = s.topics.map((tp) => topicById(tp)?.short ?? tp);29 return (30 <li key={s.slug}>31 <Link href={routes.story(s.slug)} className="group grid gap-x-8 gap-y-2 py-6 md:grid-cols-[4rem_minmax(0,1fr)_14rem] md:py-8">32 <span className="tnum display text-2xl text-ink-3 md:text-3xl">{String(i + 1).padStart(2, '0')}</span>33 <span className="min-w-0">34 <span className="display block text-2xl leading-tight text-ink group-hover:text-accent md:text-3xl">{s.title}</span>35 <span className="mt-2 block max-w-prose text-base text-ink-2">{s.standfirst}</span>36 </span>37 <span className="tnum flex flex-wrap items-start gap-x-3 gap-y-1 text-xs text-ink-3 md:flex-col md:items-end md:text-right">38 <span>{topics.join(' · ')}</span>39 <span>{t('stories.charts', { n: storyChartCount(s) })}</span>40 <span>{t('stories.minutes', { n: Math.max(2, Math.round(storyChartCount(s) * 0.8)) })}</span>41 <span className="hidden md:block">{storyIndicators(s).length === 1 ? t('stories.indicatorsOne') : t('stories.indicatorsN', { n: storyIndicators(s).length })}</span>42 </span>43 </Link>44 </li>45 );46 })}47 </ol>48 <p className="mt-6 max-w-prose text-sm leading-relaxed text-ink-3">{t('stories.methodText')}</p>49 </>50 );51}52