// ============================================================ // Shared UI components — masthead, footer, cards, icons // ============================================================ const { useState, useEffect, useRef, useMemo, useCallback } = React; // ---------- Icons (inline SVG) ---------- const Icon = { whatsapp: (p) => ( ), search: (p) => ( ), arrow: (p) => ( ), x: (p) => ( ), linkedin: (p) => ( ), link: (p) => ( ), edit: (p) => ( ), trash: (p) => ( ), eye: (p) => ( ), plus: (p) => ( ), upload: (p) => ( ), check: (p) => ( ), eyeOff: (p) => ( ), alert: (p) => ( ), image: (p) => ( ), download: (p) => ( ), }; // ---------- Brand monogram (logo) ---------- function Monogram({ size = 48 }) { return ( ); } // ---------- Masthead ---------- function Masthead({ active = 'home', onNav, categories }) { const today = new Date(); const dias = ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado']; const meses = ['JAN', 'FEV', 'MAR', 'ABR', 'MAI', 'JUN', 'JUL', 'AGO', 'SET', 'OUT', 'NOV', 'DEZ']; const dateStr = `${dias[today.getDay()]}, ${today.getDate()} DE ${meses[today.getMonth()]} DE ${today.getFullYear()}`; return (
{dateStr} EDIÇÃO BRASIL Nº 247
Santos · São Paulo
← Site Institucional Acesso Editorial (13) 97800-2785
Vol. XII · Edição diária Crônicas de Defesa OAB-SP 377.580

Angelo Santos Advogados

Crônicas, ensaios e análises do Direito Penal brasileiro
); } // ---------- Footer ---------- function Footer() { return ( ); } // ---------- Article card ---------- function ArticleCard({ article, size = 'md' }) { if (!article) return null; const cat = categoryById(article.category); const cls = size === 'sm' ? 'card card-sm' : size === 'lg' ? 'card card-lg' : 'card'; return ( {article.cover && (
)} {cat && {cat.name}}

{article.title}

{size !== 'sm' && article.subtitle &&

{article.subtitle}

}
{fmtDate(article.publishedAt)} · {readingTime(article.body)} min de leitura
); } // ---------- Toast ---------- function Toast({ message, onDone }) { useEffect(() => { if (!message) return; const t = setTimeout(onDone, 2200); return () => clearTimeout(t); }, [message, onDone]); if (!message) return null; return
{message}
; } // ---------- Hash router hook ---------- function useHashRoute() { const [hash, setHash] = useState(() => window.location.hash || '#/'); useEffect(() => { const onHash = () => { setHash(window.location.hash || '#/'); window.scrollTo({ top: 0, behavior: 'instant' }); }; window.addEventListener('hashchange', onHash); return () => window.removeEventListener('hashchange', onHash); }, []); // parse const path = hash.replace(/^#\/?/, '/').replace(/\/+$/, '') || '/'; const parts = path.split('/').filter(Boolean); return { hash, path, parts, navigate: (h) => { window.location.hash = h; } }; } Object.assign(window, { Icon, Monogram, Masthead, Footer, ArticleCard, Toast, useHashRoute });