re-voice/site/red-team-shadow-dossiers/components/LeakViewer.tsx

169 lines
7.1 KiB
TypeScript

import React from 'react';
import { DOSSIERS } from '../constants';
import { resolvePublicUrl } from '../lib/urls';
const formatBytes = (bytes?: number) => {
if (!bytes) return null;
const units = ['B', 'KB', 'MB', 'GB'];
let b = bytes;
let i = 0;
while (b >= 1024 && i < units.length - 1) {
b /= 1024;
i += 1;
}
return `${b.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
};
const statusLabel = (status: string) => {
switch (status) {
case 'live':
return 'LIVE';
case 'scheduled':
return 'SCHEDULED';
case 'draft':
default:
return 'DRAFT';
}
};
export const LeakViewer: React.FC = () => {
return (
<section id="dossiers" className="py-16 md:py-20 px-6">
<div className="max-w-6xl mx-auto">
<div className="mb-10">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Dossiers</div>
<h2 className="text-3xl md:text-4xl font-extrabold tracking-tight text-slate-900">Current publications</h2>
</div>
<div className="space-y-8">
{DOSSIERS.map((d) => {
const isLive = d.status === 'live';
const pdfUrl = resolvePublicUrl(d.pdfPath);
const evidenceUrl = resolvePublicUrl(d.evidencePath);
return (
<article key={d.id} className="rounded-2xl border hairline bg-white shadow-sm overflow-hidden">
<div className="grid md:grid-cols-12">
<div className="md:col-span-4 p-6 md:p-8 bg-paper">
<div className="rounded-xl border hairline bg-white overflow-hidden">
<img
src={d.previewImageUrl}
alt={`Cover for Shadow Dossier: ${d.title}`}
className="w-full h-auto block"
loading="lazy"
/>
</div>
</div>
<div className="md:col-span-8 p-6 md:p-8">
<div className="flex flex-wrap items-center gap-2 mb-3">
<span className="mono text-[11px] text-slate-500">{d.leakDate}</span>
<span className="text-slate-300"></span>
<span className="inline-flex items-center rounded-full border hairline px-2.5 py-1 mono text-[11px] text-slate-600">
{d.classification}
</span>
<span className="inline-flex items-center rounded-full border hairline px-2.5 py-1 mono text-[11px] text-slate-600">
{statusLabel(d.status)}
</span>
<span className="text-slate-300"></span>
<span className="mono text-[11px] text-slate-500">{d.id}</span>
</div>
<h3 className="text-2xl md:text-3xl font-extrabold tracking-tight text-slate-900 mb-3">
{d.title}
</h3>
<p className="text-base md:text-lg text-slate-700 leading-relaxed mb-6">{d.summary}</p>
<div className="grid md:grid-cols-2 gap-6">
<div className="rounded-xl border hairline bg-paper p-4">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Verification</div>
{d.sha256 && isLive ? (
<div className="space-y-1">
<div className="mono text-[11px] text-slate-500">SHA-256</div>
<div className="mono text-[12px] text-slate-700 break-all">{d.sha256}</div>
{d.bytes ? (
<div className="mono text-[11px] text-slate-500">Size: {formatBytes(d.bytes)}</div>
) : null}
</div>
) : (
<div className="text-sm text-slate-600 leading-relaxed">
Integrity details will publish with the drop. Until then, link verification remains staged.
</div>
)}
{evidenceUrl && isLive ? (
<div className="mt-3">
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={evidenceUrl}
target="_blank"
rel="noopener noreferrer"
>
Verify (evidence index)
</a>
</div>
) : null}
</div>
<div className="rounded-xl border hairline bg-paper p-4">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Links</div>
<div className="flex flex-wrap gap-3 items-center">
{pdfUrl && isLive ? (
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={pdfUrl}
target="_blank"
rel="noopener noreferrer"
>
Download PDF
</a>
) : (
<span className="text-sm font-semibold text-slate-400 cursor-not-allowed" title="Not published yet">
PDF not published
</span>
)}
{d.gumroadUrl ? (
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={d.gumroadUrl}
target="_blank"
rel="noopener noreferrer"
>
Classified edition
</a>
) : null}
{d.contactEmail ? (
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={`mailto:${d.contactEmail}?subject=${encodeURIComponent(`Shadow Dossier: ${d.title}`)}`}
>
Contact
</a>
) : null}
</div>
{!isLive ? (
<div className="mt-3 text-sm text-slate-600 leading-relaxed">
This dossier is staged. Links will activate when the drop is published.
</div>
) : null}
</div>
</div>
</div>
</div>
</article>
);
})}
</div>
</div>
</section>
);
};