// Project detail — full-width page (not a slide-over). // Driven by the parent via prop; closes via onClose, switches via onSelect. // Hacker palette: dark terminal-paper, phosphor green primary. // Shared responsive hook — used by both ProjectDetail and DossierMain function useIsMobile(breakpoint = 768) { const [mobile, setMobile] = React.useState( typeof window !== 'undefined' && window.innerWidth <= breakpoint ); React.useEffect(() => { const check = () => setMobile(window.innerWidth <= breakpoint); window.addEventListener('resize', check); return () => window.removeEventListener('resize', check); }, [breakpoint]); return mobile; } const detailStyles = { page: { minHeight: '100vh', background: 'var(--bg)', fontFamily: "'JetBrains Mono', monospace", color: 'var(--ink)', }, topbar: { position: 'sticky', top: 0, zIndex: 20, background: '#000', borderBottom: '1px solid var(--green)', padding: '12px 24px', display: 'flex', alignItems: 'center', gap: 14, fontSize: 11, letterSpacing: 1.4, color: 'var(--green)', }, toolbar: { background: 'var(--paper-2)', borderBottom: '1px solid var(--line-2)', padding: '14px 32px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 16, flexWrap: 'wrap', }, toolGroup: { display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }, // Floating back button — stays in view as page scrolls floatingBack: { position: 'fixed', bottom: 32, left: 32, zIndex: 100, background: 'var(--green)', color: '#000', fontFamily: "'JetBrains Mono', monospace", fontSize: 12, fontWeight: 600, letterSpacing: 0.8, padding: '10px 18px', border: 'none', cursor: 'pointer', boxShadow: '4px 4px 0 rgba(57,255,122,0.25)', transition: 'transform .1s, box-shadow .1s', }, body: { maxWidth: 980, margin: '0 auto', padding: '40px 32px 80px', }, docHead: { paddingBottom: 24, borderBottom: '2px solid var(--line-2)', marginBottom: 28, }, twId: { fontSize: 11, letterSpacing: 1.4, color: 'var(--green)', marginBottom: 12, }, title: { fontFamily: "'Inter', sans-serif", fontSize: 44, fontWeight: 700, letterSpacing: -1.2, margin: 0, lineHeight: 1.05, color: '#fff', }, blurb: { fontFamily: "'Inter', sans-serif", fontSize: 17, lineHeight: 1.65, color: 'var(--ink)', marginTop: 16, maxWidth: 760, }, metaRow: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 18 }, tag: { fontSize: 12, letterSpacing: 0.4, color: 'var(--green)', border: '1px solid var(--line-2)', background: '#000', padding: '3px 9px', borderRadius: 2, fontFamily: "'JetBrains Mono', monospace", }, callout: { fontFamily: "'Inter', sans-serif", fontSize: 16, lineHeight: 1.7, background: 'var(--paper-2)', borderLeft: '3px solid var(--green)', padding: '16px 20px', color: 'var(--ink)', border: '1px solid var(--line-2)', }, body2: { fontFamily: "'Inter', sans-serif", fontSize: 16, lineHeight: 1.7, color: 'var(--ink)', }, list: { margin: '4px 0 0', padding: '0 0 0 20px', fontFamily: "'Inter', sans-serif", fontSize: 16, lineHeight: 1.75, color: 'var(--ink)', }, artifactRow: { display: 'flex', flexWrap: 'wrap', gap: 10 }, artifact: { fontSize: 12, color: 'var(--green)', border: '1px solid var(--green)', background: '#000', padding: '8px 12px', borderRadius: 2, fontFamily: "'JetBrains Mono', monospace", }, // Rich content section styles figure: { margin: '18px 0', background: '#0a0c10', border: '1px solid var(--line-2)', borderRadius: 6, overflow: 'hidden', }, figImg: { width: '100%', height: 'auto', display: 'block', }, figCaption: { padding: '10px 14px', fontSize: 13, color: 'var(--dim)', borderTop: '1px dashed var(--line-2)', fontFamily: "'JetBrains Mono', monospace", }, codeBlock: { background: '#0a0c10', color: '#c5f0c8', padding: '16px 18px', borderRadius: 6, overflow: 'auto', border: '1px solid var(--line-2)', fontSize: 13, lineHeight: 1.6, fontFamily: "'JetBrains Mono', monospace", whiteSpace: 'pre-wrap', wordBreak: 'break-word', }, codeTitle: { fontSize: 11, letterSpacing: 0.6, color: 'var(--dim)', marginBottom: 6, fontFamily: "'JetBrains Mono', monospace", }, disclosure: { margin: '12px 0', border: '1px solid var(--line-2)', borderRadius: 6, overflow: 'hidden', }, disclosureSummary: { padding: '12px 16px', background: 'var(--paper-2)', cursor: 'pointer', fontSize: 13, color: 'var(--green)', fontFamily: "'JetBrains Mono', monospace", letterSpacing: 0.4, userSelect: 'none', }, // Slider styles sliderFrame: { position: 'relative', }, sliderControls: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, padding: '10px 14px', background: 'var(--paper-2)', borderTop: '1px solid var(--line-2)', }, sliderBtn: { border: '1px solid var(--line-2)', background: '#000', color: 'var(--green)', borderRadius: 2, padding: '6px 14px', fontSize: 11, cursor: 'pointer', fontFamily: "'JetBrains Mono', monospace", letterSpacing: 0.6, }, sliderDots: { display: 'flex', gap: 6, alignItems: 'center', }, sliderDot: { width: 10, height: 10, borderRadius: '50%', border: '1px solid var(--line-2)', background: 'transparent', padding: 0, cursor: 'pointer', transition: 'background .15s', }, sliderDotActive: { background: 'var(--green)', }, video: { width: '100%', borderRadius: 6, border: '1px solid var(--line-2)', background: '#000', }, footerNav: { marginTop: 56, paddingTop: 24, borderTop: '2px solid var(--line-2)', display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16, }, navCard: { border: '1px solid var(--line-2)', background: 'var(--paper-2)', padding: '16px 18px', cursor: 'pointer', transition: 'border-color .12s, background .12s, transform .12s', }, navCardLabel: { fontSize: 10, letterSpacing: 1.4, color: 'var(--dim)', textTransform: 'uppercase', marginBottom: 6, }, navCardTitle: { fontFamily: "'Inter', sans-serif", fontSize: 16, color: '#fff', fontWeight: 600, }, }; // ─── Image Slider sub-component ─────────────────────────────────────── function ImageSlider({ images }) { const [idx, setIdx] = React.useState(0); const ds = detailStyles; const count = images.length; const show = (i) => setIdx((i + count) % count); return (
{code}
)}
{block.text}
; case 'list': const ListTag = block.ordered ? 'ol' : 'ul'; return (
{block.code}
{project.blurb}