feat(blotter): replace board stats with changelog-backed blotter

Add static blotter snapshot, route /blotter, compact board blotter (Hide/Show All).
Include yarn blotter tooling and AGENTS rules so every changelog version has a
release line. 4chan-style table UI, subplebbit→board and plebchan→5chan in messages.
This commit is contained in:
plebeius
2026-02-22 19:37:49 +08:00
parent a13c72f888
commit 0cb7b1a970
54 changed files with 1180 additions and 430 deletions
+72
View File
@@ -0,0 +1,72 @@
.wrapper {
width: 100%;
}
.content {
margin: auto;
text-align: center;
padding: 0 10px;
}
.content h1 {
font-size: 28px;
font-weight: 700;
text-align: center;
margin: 20px 0 0;
font-family: 'Helvetica Neue', arial, sans-serif;
letter-spacing: -2px;
}
.table {
width: 80%;
max-width: 1000px;
margin: 25px auto;
border-collapse: collapse;
border: 1px solid #606060;
background-color: #fff;
color: #000;
}
.table th {
padding: 5px;
font-weight: 700;
background-color: #ffccaa;
vertical-align: middle;
}
.table .dateHeader {
text-align: center;
white-space: nowrap;
width: 90px;
}
.table .messageHeader {
text-align: left;
border-left: 1px solid #606060;
}
.table .dateCell {
white-space: nowrap;
width: 90px;
text-align: center;
}
.table .messageCell {
text-align: left;
border-left: 1px solid #606060;
}
.table td {
padding: 5px;
vertical-align: top;
}
.table tbody tr {
border-bottom: 1px solid #606060;
}
@media (max-width: 640px) {
.table {
width: calc(100% - 20px);
}
}
+48
View File
@@ -0,0 +1,48 @@
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import blotterData from '../../data/5chan-blotter.json';
import BlotterMessage from '../../components/blotter-message';
import { formatBlotterDate, isBlotterEntry, sortBlotterEntries } from '../../lib/utils/blotter-utils';
import { Footer } from '../home';
import styles from './blotter.module.css';
const Blotter = () => {
const { t } = useTranslation();
const entries = (blotterData as { entries?: unknown[] }).entries ?? [];
const validEntries = entries.filter(isBlotterEntry);
const sorted = sortBlotterEntries(validEntries);
useEffect(() => {
window.scrollTo(0, 0);
document.title = 'Blotter - 5chan';
}, []);
return (
<div className={styles.wrapper}>
<div className={styles.content}>
<h1>5chan Blotter</h1>
<table className={styles.table}>
<thead>
<tr>
<th className={styles.dateHeader}>{t('date')}</th>
<th className={styles.messageHeader}>{t('message')}</th>
</tr>
</thead>
<tbody>
{sorted.map((entry) => (
<tr key={entry.id}>
<td className={styles.dateCell}>{formatBlotterDate(entry.timestamp)}</td>
<td className={styles.messageCell}>
<BlotterMessage entry={entry} />
</td>
</tr>
))}
</tbody>
</table>
<Footer />
</div>
</div>
);
};
export default Blotter;
+1
View File
@@ -0,0 +1 @@
export { default } from './blotter';