/** * Upheld — Indian Legal Research Platform * frontend/src/App.jsx * * Aesthetic: Editorial/authoritative. Ink-on-paper meets modern research tool. * Playfair Display headings, Source Serif 4 body, JetBrains Mono for citations. * Warm parchment (#f7f5f0), deep ink (#1a1814), muted gold accent (#8b6914). */ const { useState, useEffect, useCallback, useRef } = React; const API = ''; async function apiFetch(path, opts = {}) { const r = await fetch(API + path, { headers: { 'Content-Type': 'application/json' }, ...opts, }); if (!r.ok) throw new Error(`${r.status} ${r.statusText}`); return r.json(); } // ── Styles ──────────────────────────────────────────────────────────────────── const CSS = ` :root { --ink: #1a1814; --paper: #f7f5f0; --warm: #ede9e0; --border: #d4cfc4; --muted: #7a7468; --gold: #8b6914; --gold-lt: #b8960f; --red: #8b1a1a; --green: #1a5c2a; --blue: #1a3a5c; --serif: 'Source Serif 4',Georgia,serif; --display: 'Playfair Display',Georgia,serif; --mono: 'JetBrains Mono','Courier New',monospace; --max: 900px; --trans: 0.15s ease; } .app { min-height:100vh; display:flex; flex-direction:column; } /* Header */ .hdr { border-bottom:1px solid var(--border); background:var(--paper); position:sticky; top:0; z-index:100; } .hdr-in { max-width:var(--max); margin:0 auto; padding:0 24px; display:flex; align-items:center; gap:20px; height:54px; } .logo { font-family:var(--display); font-size:1.3rem; font-weight:700; color:var(--ink); cursor:pointer; letter-spacing:-0.01em; flex-shrink:0; } .logo em { color:var(--gold); font-style:normal; } .hdr-q { flex:1; } .hdr-q input { width:100%; padding:7px 13px; border:1px solid var(--border); background:var(--warm); border-radius:2px; font-family:var(--serif); font-size:0.9rem; color:var(--ink); outline:none; transition:border-color var(--trans),box-shadow var(--trans); } .hdr-q input:focus { border-color:var(--gold); box-shadow:0 0 0 2px rgba(139,105,20,.12); } /* Home */ .home { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:80px 24px 120px; } .home-logo { font-family:var(--display); font-size:3.2rem; font-weight:700; color:var(--ink); letter-spacing:-0.025em; margin-bottom:4px; } .home-logo em { color:var(--gold); font-style:normal; } .home-tag { font-family:var(--serif); font-size:1rem; color:var(--muted); font-style:italic; margin-bottom:44px; font-weight:300; } .home-wrap { width:100%; max-width:620px; } .home-box { display:flex; border:2px solid var(--ink); border-radius:2px; overflow:hidden; box-shadow:5px 5px 0 var(--ink); transition:box-shadow var(--trans); } .home-box:focus-within { box-shadow:5px 5px 0 var(--gold); } .home-box input { flex:1; padding:14px 18px; border:none; background:var(--paper); font-family:var(--serif); font-size:1.05rem; color:var(--ink); outline:none; } .home-box input::placeholder { color:var(--muted); font-style:italic; } .home-box button { padding:14px 22px; background:var(--ink); color:var(--paper); border:none; font-family:var(--serif); font-size:0.95rem; font-weight:600; cursor:pointer; transition:background var(--trans); letter-spacing:0.02em; } .home-box button:hover { background:var(--gold); } .home-ex { margin-top:18px; display:flex; flex-wrap:wrap; gap:7px; } .home-chip { font-family:var(--mono); font-size:0.75rem; color:var(--muted); padding:4px 10px; border:1px solid var(--border); border-radius:2px; cursor:pointer; background:var(--paper); transition:color var(--trans),border-color var(--trans); } .home-chip:hover { color:var(--gold); border-color:var(--gold); } .home-stats { margin-top:52px; display:flex; gap:48px; text-align:center; } .stat-n { font-family:var(--display); font-size:2.1rem; font-weight:700; color:var(--ink); display:block; } .stat-l { font-family:var(--serif); font-size:0.76rem; color:var(--muted); text-transform:uppercase; letter-spacing:0.08em; } /* Page */ .page { max-width:var(--max); margin:0 auto; padding:28px 24px; flex:1; } /* Filters */ .filters { display:flex; flex-wrap:wrap; gap:10px; margin-bottom:22px; padding-bottom:18px; border-bottom:1px solid var(--border); } .fl { display:flex; align-items:center; gap:6px; } .fl-lbl { font-family:var(--serif); font-size:0.76rem; color:var(--muted); text-transform:uppercase; letter-spacing:0.06em; white-space:nowrap; } .fl-sel, .fl-inp { padding:5px 10px; border:1px solid var(--border); border-radius:2px; background:var(--paper); font-family:var(--serif); font-size:0.84rem; color:var(--ink); outline:none; cursor:pointer; } .fl-inp { width:80px; } .fl-sel:focus, .fl-inp:focus { border-color:var(--gold); } .fl-clr { font-family:var(--serif); font-size:0.8rem; color:var(--muted); background:none; border:none; cursor:pointer; text-decoration:underline; } .fl-clr:hover { color:var(--red); } /* Results meta */ .r-meta { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:18px; } .r-count { font-family:var(--serif); font-size:0.86rem; color:var(--muted); } .r-count strong { color:var(--ink); font-weight:600; } /* Result card */ .card { border:1px solid var(--border); border-radius:2px; padding:19px 21px; margin-bottom:11px; background:var(--paper); cursor:pointer; transition:border-color var(--trans),box-shadow var(--trans),transform var(--trans); } .card:hover { border-color:var(--ink); box-shadow:3px 3px 0 var(--ink); transform:translate(-1px,-1px); } .card-top { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin-bottom:8px; } .card-name { font-family:var(--display); font-size:1.03rem; font-weight:600; color:var(--ink); line-height:1.3; } .card-unc { font-family:var(--mono); font-size:0.7rem; color:var(--gold); white-space:nowrap; padding:2px 7px; border:1px solid var(--border); border-radius:2px; background:var(--warm); flex-shrink:0; } .card-row { display:flex; flex-wrap:wrap; gap:14px; margin-bottom:8px; } .card-m { font-family:var(--serif); font-size:0.81rem; color:var(--muted); } .card-m strong { color:var(--ink); font-weight:600; } .bench-b { font-family:var(--serif); font-size:0.74rem; font-weight:600; padding:2px 8px; border-radius:2px; } .b1 { background:var(--warm); color:var(--muted); } .b2 { background:#e8f0e8; color:var(--green); } .b3 { background:#e8ecf4; color:var(--blue); } .b5 { background:#f5ede0; color:var(--gold); } .card-snip { font-family:var(--serif); font-size:0.86rem; color:var(--muted); line-height:1.5; font-style:italic; border-left:2px solid var(--border); padding-left:11px; } .card-foot { display:flex; align-items:center; justify-content:space-between; margin-top:11px; padding-top:9px; border-top:1px solid var(--border); } .card-stats { display:flex; gap:14px; } .card-stat { font-family:var(--serif); font-size:0.77rem; color:var(--muted); } .card-stat strong { color:var(--ink); } /* Copy button */ .cpbtn { font-family:var(--mono); font-size:0.7rem; color:var(--muted); background:none; border:1px solid var(--border); border-radius:2px; padding:3px 8px; cursor:pointer; transition:color var(--trans),border-color var(--trans); } .cpbtn:hover { color:var(--gold); border-color:var(--gold); } .cpbtn.ok { color:var(--green); border-color:var(--green); } /* Judgment */ .j-hdr { margin-bottom:28px; padding-bottom:22px; border-bottom:2px solid var(--ink); } .j-unc { font-family:var(--mono); font-size:0.88rem; color:var(--gold); display:inline-block; margin-bottom:12px; border-bottom:1px dashed var(--gold); padding-bottom:2px; } .j-title { font-family:var(--display); font-size:1.65rem; font-weight:700; color:var(--ink); line-height:1.25; margin-bottom:16px; letter-spacing:-0.01em; } .j-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:14px; margin-bottom:18px; } .j-item { display:flex; flex-direction:column; gap:2px; } .j-key { font-family:var(--serif); font-size:0.7rem; color:var(--muted); text-transform:uppercase; letter-spacing:0.08em; } .j-val { font-family:var(--serif); font-size:0.9rem; color:var(--ink); font-weight:600; } .j-acts { display:flex; gap:10px; flex-wrap:wrap; } .btn { font-family:var(--serif); font-size:0.83rem; padding:7px 14px; border-radius:2px; cursor:pointer; border:1.5px solid var(--ink); background:var(--paper); color:var(--ink); transition:background var(--trans),color var(--trans); font-weight:600; } .btn:hover { background:var(--ink); color:var(--paper); } /* AI Summary */ .ai-box { border:1px solid var(--border); border-radius:2px; padding:18px 20px; background:var(--warm); margin-bottom:26px; } .ai-hd { font-family:var(--serif); font-size:0.73rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:0.08em; margin-bottom:10px; display:flex; align-items:center; gap:8px; } .ai-badge { background:var(--ink); color:var(--paper); font-family:var(--mono); font-size:0.63rem; padding:2px 6px; border-radius:2px; } .ai-ratio { font-family:var(--display); font-size:1.03rem; font-style:italic; color:var(--ink); line-height:1.5; margin-bottom:8px; } .ai-held { font-family:var(--serif); font-size:0.86rem; color:var(--muted); border-left:2px solid var(--gold); padding-left:11px; } /* Body grid */ .j-body { display:grid; grid-template-columns:1fr 270px; gap:28px; } @media(max-width:700px){ .j-body{ grid-template-columns:1fr; } } .j-text { font-family:var(--serif); font-size:0.93rem; line-height:1.78; color:var(--ink); } .j-text p { margin-bottom:1em; } /* Citation rendering */ .citation-link { color:var(--gold); text-decoration:underline; text-decoration-style:dotted; text-underline-offset:2px; cursor:pointer; } .citation-link:hover { color:var(--gold-lt); } .citation-unresolved { color:var(--muted); } .citation-foreign { color:var(--blue); font-style:italic; } .citation-badge { font-family:var(--mono); font-size:0.63rem; padding:1px 5px; border-radius:2px; margin-left:3px; vertical-align:middle; } .badge-followed { background:#e8f0e8; color:var(--green); } .badge-distinguished{ background:#e8ecf4; color:var(--blue); } .badge-doubted { background:#f5ede0; color:var(--gold); } .badge-per-incuriam,.badge-not-followed { background:#fde8e8; color:var(--red); } .badge-explained,.badge-referred { background:var(--warm); color:var(--muted); } /* Sidebar */ .sidebar { display:flex; flex-direction:column; gap:22px; } .sb-hd { font-family:var(--serif); font-size:0.7rem; font-weight:600; color:var(--muted); text-transform:uppercase; letter-spacing:0.08em; margin-bottom:10px; padding-bottom:7px; border-bottom:1px solid var(--border); } .cit-item { padding:7px 0; border-bottom:1px solid var(--border); font-family:var(--serif); font-size:0.81rem; } .cit-item:last-child { border-bottom:none; } .cit-link { color:var(--gold); font-family:var(--mono); font-size:0.73rem; cursor:pointer; text-decoration:underline; text-underline-offset:2px; text-decoration-style:dotted; } .cit-link:hover { color:var(--gold-lt); } .cit-unres { color:var(--muted); font-family:var(--mono); font-size:0.73rem; } /* Loading */ .loading { text-align:center; padding:56px 24px; font-family:var(--serif); color:var(--muted); font-style:italic; } .dot { display:inline-block; width:6px; height:6px; border-radius:50%; background:var(--gold); margin:0 3px; animation:blink 1.2s ease-in-out infinite; } .dot:nth-child(2){ animation-delay:.2s; } .dot:nth-child(3){ animation-delay:.4s; } @keyframes blink{ 0%,80%,100%{ opacity:.2; transform:scale(.8); } 40%{ opacity:1; transform:scale(1); } } .err { border:1px solid var(--red); border-radius:2px; padding:14px 18px; background:#fdf5f5; font-family:var(--serif); font-size:0.88rem; color:var(--red); margin:18px 0; } /* Pagination */ .pages { display:flex; align-items:center; gap:8px; margin-top:28px; padding-top:22px; border-top:1px solid var(--border); justify-content:center; } .pg-btn { font-family:var(--serif); font-size:0.83rem; padding:6px 12px; border:1px solid var(--border); border-radius:2px; cursor:pointer; background:var(--paper); color:var(--ink); transition:background var(--trans),border-color var(--trans); } .pg-btn:hover:not(:disabled){ background:var(--warm); border-color:var(--ink); } .pg-btn:disabled{ opacity:.4; cursor:default; } .pg-info { font-family:var(--serif); font-size:0.8rem; color:var(--muted); } /* Empty */ .empty { text-align:center; padding:56px 24px; } .empty-t { font-family:var(--display); font-size:1.25rem; color:var(--ink); margin-bottom:7px; } .empty-s { font-family:var(--serif); font-size:0.88rem; color:var(--muted); font-style:italic; } /* Footer */ .ftr { border-top:1px solid var(--border); padding:18px 24px; font-family:var(--serif); font-size:0.76rem; color:var(--muted); display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px; } .ftr a { color:var(--muted); text-decoration:underline; } .ftr a:hover { color:var(--gold); } .fade { animation:fadeUp .18s ease; } @keyframes fadeUp{ from{ opacity:0; transform:translateY(5px); } to{ opacity:1; } } `; function injectCSS() { if (document.getElementById('upheld-css')) return; const s = document.createElement('style'); s.id = 'upheld-css'; s.textContent = CSS; document.head.appendChild(s); } // ── Helpers ─────────────────────────────────────────────────────────────────── function benchCls(n) { if (!n) return 'b1'; if (n >= 5) return 'b5'; if (n >= 3) return 'b3'; if (n >= 2) return 'b2'; return 'b1'; } function CopyBtn({ text, label = 'Copy citation' }) { const [ok, setOk] = useState(false); if (!text) return null; return ( ); } function Loader() { return (
')}}/> ) : (