Skip to content

Commit b070c87

Browse files
viki-shclaude
andcommitted
Redesign Explore detail to match CaseDetail page style
- Use CaseDetail section headers (\ TITLE + 3px divider line) - Yellow detail-yellow-box with drop shadows for severity snapshot - Loot-drop accordion (yellow circle + plus icon) for Main Privacy Laws - Remove Enforcement Authority section (redundant with overview) - Replace Fine column with Outcome in top companies table - Yellow header row on table - Bars scaled by percentage of total cases - Bold labels matching site-wide text-sm font-bold pattern Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4d1dd7d commit b070c87

1 file changed

Lines changed: 139 additions & 139 deletions

File tree

src/components/JurisdictionDetail.tsx

Lines changed: 139 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useMemo, useState } from "react";
2+
import { Plus, Minus } from "lucide-react";
23
import { cases, getDisplayCompany, type Jurisdiction } from "@/data/cases";
34
import { JURISDICTION_INFO } from "@/data/jurisdictionInfo";
45
import { JurisdictionLogo } from "./JurisdictionLogos";
@@ -58,10 +59,16 @@ export default function JurisdictionDetail({ jurisdiction }: JurisdictionDetailP
5859
.sort(([, a], [, b]) => b - a)
5960
.slice(0, 5);
6061

62+
// Top companies by fine — include outcome
6163
const topCompanies = [...jCases]
6264
.sort((a, b) => b.fineAmount - a.fineAmount)
6365
.slice(0, 5)
64-
.map((c) => ({ name: getDisplayCompany(c), fine: c.fineAmount, year: c.year }));
66+
.map((c) => ({
67+
name: getDisplayCompany(c),
68+
fine: c.fineAmount,
69+
year: c.year,
70+
outcome: c.outcomeSummary || "—",
71+
}));
6572

6673
return {
6774
totalCases, minYear, maxYear,
@@ -88,180 +95,173 @@ export default function JurisdictionDetail({ jurisdiction }: JurisdictionDetailP
8895
</div>
8996
</div>
9097

91-
<div className="p-6 space-y-8">
98+
<div className="p-6 space-y-10">
9299
{/* Overview */}
93-
<div>
94-
<SectionLabel>Overview</SectionLabel>
95-
<p className="text-sm leading-relaxed">{info.overview}</p>
96-
</div>
100+
<section>
101+
<h2 className="text-2xl font-bold tracking-tight mb-4">\ OVERVIEW</h2>
102+
<div className="h-[3px] bg-border mb-4" />
103+
<p className="text-[15px] leading-relaxed">{info.overview}</p>
104+
</section>
97105

98-
{/* Main Privacy Laws — entire section is collapsible */}
99-
<div>
106+
{/* Severity Snapshot — yellow detail boxes matching CaseDetail */}
107+
<section>
108+
<h2 className="text-2xl font-bold tracking-tight mb-4">\ SEVERITY SNAPSHOT</h2>
109+
<div className="h-[3px] bg-border mb-4" />
110+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
111+
<div className="detail-yellow-box p-4">
112+
<p className="text-[10px] font-mono font-bold uppercase tracking-wider text-muted-foreground">Total Fines</p>
113+
<p className="text-lg font-bold mt-1">{formatCurrency(stats.totalFines)}</p>
114+
</div>
115+
<div className="detail-yellow-box p-4">
116+
<p className="text-[10px] font-mono font-bold uppercase tracking-wider text-muted-foreground">Largest Fine</p>
117+
<p className="text-lg font-bold mt-1">{formatCurrency(stats.maxFine)}</p>
118+
</div>
119+
<div className="detail-yellow-box p-4">
120+
<p className="text-[10px] font-mono font-bold uppercase tracking-wider text-muted-foreground">Avg Fine</p>
121+
<p className="text-lg font-bold mt-1">{formatCurrency(stats.avgFine)}</p>
122+
</div>
123+
<div className="detail-yellow-box p-4">
124+
<p className="text-[10px] font-mono font-bold uppercase tracking-wider text-muted-foreground">% Monetary</p>
125+
<p className="text-lg font-bold mt-1">{stats.pctMonetary}%</p>
126+
</div>
127+
</div>
128+
<p className="text-[10px] font-mono font-bold uppercase tracking-widest text-muted-foreground mt-3">
129+
{stats.totalCases} cases · {stats.minYear}{stats.maxYear}
130+
</p>
131+
</section>
132+
133+
{/* Enforcement Style */}
134+
<section>
135+
<h2 className="text-2xl font-bold tracking-tight mb-4">\ ENFORCEMENT STYLE</h2>
136+
<div className="h-[3px] bg-border mb-4" />
137+
<div className="brutalist-border info-box p-6">
138+
<p className="text-[15px] leading-relaxed">{info.enforcementStyle}</p>
139+
</div>
140+
</section>
141+
142+
{/* Main Privacy Laws — loot-drop accordion */}
143+
<section>
100144
<button
101145
type="button"
102-
onClick={() => setLawsOpen(!lawsOpen)}
103-
className="w-full flex items-center justify-between gap-2 cursor-pointer group"
146+
className="loot-drop-accordion-header"
147+
onClick={() => setLawsOpen((o) => !o)}
104148
>
105-
<SectionLabel>Main Privacy Laws</SectionLabel>
106-
<span
107-
className="text-[10px] font-mono font-bold shrink-0 transition-transform duration-200 text-muted-foreground"
108-
style={{ transform: lawsOpen ? "rotate(180deg)" : "rotate(0deg)" }}
109-
>
110-
149+
<span className="loot-drop-circle">
150+
{lawsOpen ? <Minus className="w-5 h-5" /> : <Plus className="w-5 h-5" />}
111151
</span>
152+
<span className="loot-drop-accordion-title">Main Privacy Laws</span>
153+
{lawsOpen ? <Minus className="w-6 h-6 shrink-0" /> : <Plus className="w-6 h-6 shrink-0" />}
112154
</button>
113155
{lawsOpen && (
114-
<div className="mt-3 space-y-3 animate-in fade-in slide-in-from-top-1 duration-200">
156+
<div className="loot-drop-accordion-content space-y-4">
115157
{info.laws.map((law) => (
116-
<div key={law.name} className="border-l-4 border-black pl-4">
158+
<div key={law.name} className="brutalist-border info-box p-4" style={{ borderLeftWidth: "4px", borderLeftColor: "#FFD700" }}>
117159
<p className="text-sm font-bold">
118160
{law.name} <span className="font-normal opacity-60">({law.year})</span>
119161
</p>
120-
<p className="text-xs leading-relaxed mt-1 opacity-80">{law.description}</p>
162+
<p className="text-sm leading-relaxed mt-2 opacity-80">{law.description}</p>
121163
</div>
122164
))}
123165
</div>
124166
)}
125-
</div>
126-
127-
{/* Enforcement Authority */}
128-
<div>
129-
<SectionLabel>Enforcement Authority</SectionLabel>
130-
<div className="border-l-4 border-black pl-4 mt-1">
131-
<p className="text-sm font-bold">
132-
{info.authority.name}{" "}
133-
<span className="font-normal opacity-60">({info.authority.acronym})</span>
134-
</p>
135-
<p className="text-xs leading-relaxed mt-1 opacity-80">{info.authority.role}</p>
136-
</div>
137-
</div>
138-
139-
{/* Enforcement Style */}
140-
<div>
141-
<SectionLabel>Enforcement Style</SectionLabel>
142-
<p className="text-sm leading-relaxed">{info.enforcementStyle}</p>
143-
</div>
144-
145-
<div className="border-t-2 border-dashed border-border" />
146-
147-
{/* Dataset Stats Header */}
148-
<div>
149-
<h3 className="hero-title text-2xl uppercase tracking-tight mb-1">From Our Dataset</h3>
150-
<p className="text-[10px] font-mono font-bold uppercase tracking-widest text-muted-foreground">
151-
{stats.totalCases} cases · {stats.minYear}{stats.maxYear}
152-
</p>
153-
</div>
167+
</section>
154168

155169
{/* Stats Grid */}
156-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
157-
{/* Severity Snapshot */}
158-
<div>
159-
<SectionLabel>Severity Snapshot</SectionLabel>
160-
<div className="grid grid-cols-2 gap-3 mt-1">
161-
<StatBox label="Total Fines" value={formatCurrency(stats.totalFines)} />
162-
<StatBox label="Largest Fine" value={formatCurrency(stats.maxFine)} />
163-
<StatBox label="Avg Fine" value={formatCurrency(stats.avgFine)} />
164-
<StatBox label="% Monetary" value={`${stats.pctMonetary}%`} />
165-
</div>
166-
</div>
170+
<section>
171+
<h2 className="text-2xl font-bold tracking-tight mb-4">\ FROM OUR DATASET</h2>
172+
<div className="h-[3px] bg-border mb-6" />
167173

168-
{/* Top Sectors */}
169-
<div>
170-
<SectionLabel>Top Sectors Affected</SectionLabel>
171-
<div className="space-y-2.5 mt-1">
172-
{stats.topSectors.map(([sector, count]) => (
173-
<BarRow key={sector} label={sector} count={count} total={stats.totalCases} />
174-
))}
174+
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
175+
{/* Top Sectors */}
176+
<div>
177+
<p className="text-[10px] font-mono font-bold uppercase tracking-widest text-muted-foreground mb-3">Top Sectors Affected</p>
178+
<div className="space-y-3">
179+
{stats.topSectors.map(([sector, count]) => {
180+
const pct = stats.totalCases > 0 ? (count / stats.totalCases) * 100 : 0;
181+
return (
182+
<div key={sector}>
183+
<div className="flex items-baseline justify-between mb-1">
184+
<span className="text-sm font-bold truncate">{sector}</span>
185+
<span className="text-xs font-mono font-bold ml-2 shrink-0">{count}</span>
186+
</div>
187+
<div className="h-2.5 bg-border overflow-hidden">
188+
<div className="h-full bg-black transition-all duration-500" style={{ width: `${pct}%` }} />
189+
</div>
190+
</div>
191+
);
192+
})}
193+
</div>
175194
</div>
176-
</div>
177195

178-
{/* Most Common Violations */}
179-
<div>
180-
<SectionLabel>Most Common Violations</SectionLabel>
181-
<div className="space-y-2.5 mt-1">
182-
{stats.topViolations.map(([violation, count]) => (
183-
<BarRow key={violation} label={violation} count={count} total={stats.totalCases} />
184-
))}
196+
{/* Most Common Violations */}
197+
<div>
198+
<p className="text-[10px] font-mono font-bold uppercase tracking-widest text-muted-foreground mb-3">Most Common Violations</p>
199+
<div className="space-y-3">
200+
{stats.topViolations.map(([violation, count]) => {
201+
const pct = stats.totalCases > 0 ? (count / stats.totalCases) * 100 : 0;
202+
return (
203+
<div key={violation}>
204+
<div className="flex items-baseline justify-between mb-1">
205+
<span className="text-sm font-bold truncate">{violation}</span>
206+
<span className="text-xs font-mono font-bold ml-2 shrink-0">{count}</span>
207+
</div>
208+
<div className="h-2.5 bg-border overflow-hidden">
209+
<div className="h-full bg-black transition-all duration-500" style={{ width: `${pct}%` }} />
210+
</div>
211+
</div>
212+
);
213+
})}
214+
</div>
185215
</div>
186-
</div>
187216

188-
{/* Most Common Outcomes */}
189-
<div>
190-
<SectionLabel>Most Common Outcomes</SectionLabel>
191-
<div className="space-y-2.5 mt-1">
192-
{stats.topOutcomes.map(([outcome, count]) => (
193-
<BarRow key={outcome} label={outcome} count={count} total={stats.totalCases} />
194-
))}
217+
{/* Most Common Outcomes */}
218+
<div className="md:col-span-2">
219+
<p className="text-[10px] font-mono font-bold uppercase tracking-widest text-muted-foreground mb-3">Most Common Outcomes</p>
220+
<div className="space-y-3">
221+
{stats.topOutcomes.map(([outcome, count]) => {
222+
const pct = stats.totalCases > 0 ? (count / stats.totalCases) * 100 : 0;
223+
return (
224+
<div key={outcome}>
225+
<div className="flex items-baseline justify-between mb-1">
226+
<span className="text-sm font-bold truncate">{outcome}</span>
227+
<span className="text-xs font-mono font-bold ml-2 shrink-0">{count}</span>
228+
</div>
229+
<div className="h-2.5 bg-border overflow-hidden">
230+
<div className="h-full bg-black transition-all duration-500" style={{ width: `${pct}%` }} />
231+
</div>
232+
</div>
233+
);
234+
})}
235+
</div>
195236
</div>
196237
</div>
197-
</div>
238+
</section>
198239

199240
{/* Top Companies */}
200-
<div>
201-
<SectionLabel>Top Companies by Fine</SectionLabel>
202-
<div className="border-2 border-border mt-1">
203-
<table className="w-full text-sm font-mono">
241+
<section>
242+
<h2 className="text-2xl font-bold tracking-tight mb-4">\ TOP COMPANIES</h2>
243+
<div className="h-[3px] bg-border mb-4" />
244+
<div className="border-2 border-black">
245+
<table className="w-full text-sm">
204246
<thead>
205-
<tr className="border-b-2 border-border bg-background">
206-
<th className="text-left px-3 py-2 text-[10px] font-mono font-bold uppercase tracking-widest">Company</th>
207-
<th className="text-right px-3 py-2 text-[10px] font-mono font-bold uppercase tracking-widest">Fine</th>
208-
<th className="text-right px-3 py-2 text-[10px] font-mono font-bold uppercase tracking-widest">Year</th>
247+
<tr className="border-b-2 border-black" style={{ backgroundColor: "#FFD700" }}>
248+
<th className="text-left px-4 py-2.5 text-[10px] font-mono font-bold uppercase tracking-widest">Company</th>
249+
<th className="text-right px-4 py-2.5 text-[10px] font-mono font-bold uppercase tracking-widest">Outcome</th>
250+
<th className="text-right px-4 py-2.5 text-[10px] font-mono font-bold uppercase tracking-widest">Year</th>
209251
</tr>
210252
</thead>
211-
<tbody>
253+
<tbody className="font-mono">
212254
{stats.topCompanies.map((c, i) => (
213-
<tr key={c.name + c.year} className={i % 2 === 0 ? "bg-card" : "bg-background"}>
214-
<td className="px-3 py-2 font-bold">{c.name}</td>
215-
<td className="px-3 py-2 text-right">
216-
{c.fine > 0 ? formatCurrency(c.fine) : "—"}
217-
</td>
218-
<td className="px-3 py-2 text-right">{c.year}</td>
255+
<tr key={c.name + c.year} className={`border-b border-border ${i % 2 === 0 ? "bg-card" : "bg-background"}`}>
256+
<td className="px-4 py-2.5 font-bold">{c.name}</td>
257+
<td className="px-4 py-2.5 text-right text-xs">{c.outcome}</td>
258+
<td className="px-4 py-2.5 text-right">{c.year}</td>
219259
</tr>
220260
))}
221261
</tbody>
222262
</table>
223263
</div>
224-
</div>
225-
</div>
226-
</div>
227-
);
228-
}
229-
230-
/** Consistent section label matching the site-wide pattern */
231-
function SectionLabel({ children }: { children: React.ReactNode }) {
232-
return (
233-
<p className="text-[10px] font-mono font-bold uppercase tracking-widest text-muted-foreground mb-2">
234-
{children}
235-
</p>
236-
);
237-
}
238-
239-
function StatBox({ label, value }: { label: string; value: string }) {
240-
return (
241-
<div className="border-2 border-black p-3 text-center" style={{ backgroundColor: "#FFD700" }}>
242-
<p className="text-xl font-bold">{value}</p>
243-
<p className="text-[10px] font-mono font-bold uppercase tracking-widest opacity-60 mt-0.5">{label}</p>
244-
</div>
245-
);
246-
}
247-
248-
function BarRow({ label, count, total }: { label: string; count: number; total: number }) {
249-
const pct = total > 0 ? (count / total) * 100 : 0;
250-
return (
251-
<div className="flex items-center gap-2">
252-
<div className="flex-1 min-w-0">
253-
<div className="flex items-baseline justify-between mb-1">
254-
<span className="text-xs font-mono truncate">{label}</span>
255-
<span className="text-xs font-mono font-bold ml-2 shrink-0">
256-
{Math.round(pct)}% <span className="opacity-50">({count})</span>
257-
</span>
258-
</div>
259-
<div className="h-3 bg-border rounded-sm overflow-hidden">
260-
<div
261-
className="h-full bg-black rounded-sm transition-all duration-500"
262-
style={{ width: `${pct}%` }}
263-
/>
264-
</div>
264+
</section>
265265
</div>
266266
</div>
267267
);

0 commit comments

Comments
 (0)