1import { useEffect, useMemo, useRef } from 'react'; 2import { html } from '../html.js?v=3.8.36'; 3import { 4 AlertCircle, 5 ArrowLeft, 6 ArrowRight, 7 BarChart3, 8 BookOpen, 9 Boxes, 10 Compass, 11 FolderOpen, 12 Library, 13 Network, 14 Monitor, 15 Search, 16 Sparkles 17} from 'lucide-react'; 18import { resolveSitePath } from '../utils/pathResolver.js?v=3.8.36'; 19import ArchiveRouteHeader from './ArchiveRouteHeader.js?v=3.8.36'; 20 21const normalizeTitle = (title = '') => title 22 .toLowerCase() 23 .normalize('NFKD') 24 .replace(/[\u0300-\u036f]/g, '') 25 .replace(/\s*\(\d{4}\)\s*$/, '') 26 .replace(/^pressthink:\s*/, '') 27 .replace(/[^a-z0-9]+/g, ' ') 28 .trim(); 29 30const CURATED_SEQUENCE = [ 31 { 32 title: 'The Impossible Press (1986)', 33 folio: '01', 34 form: 'PhD dissertation', 35 year: '1986', 36 note: 'Begin with the argument underneath the archive: journalism cannot be understood apart from the public life it is meant to serve.' 37 }, 38 { 39 title: 'What Are Journalists For?', 40 folio: '02', 41 form: 'Book', 42 year: '1999', 43 note: 'That question becomes a practical experiment in public journalismâan attempt to reconnect reporting with communities acting on shared problems.' 44 }, 45 { 46 title: 'The View from Nowhere', 47 folio: '03', 48 form: 'Essay and key concept', 49 year: '2010', 50 note: 'The trail arrives at a critique of professional detachment, asking what journalists should disclose about where they stand and why they should be trusted.' 51 } 52]; 53 54const findFeaturedRecords = (records) => { 55 const selectedIds = new Set(); 56 57 return CURATED_SEQUENCE.map((entry) => { 58 const target = normalizeTitle(entry.title); 59 const candidates = records 60 .filter((record) => record.type !== 'social') 61 .map((record) => { 62 const title = normalizeTitle(record.title); 63 const titleScore = title === target ? 3 : title.startsWith(`${target} `) ? 2 : 0; 64 const recordYear = String(record.year || record.date || '').slice(0, 4); 65 const score = titleScore > 0 ? titleScore + (recordYear === entry.year ? 4 : 0) : 0; 66 return { record, score }; 67 }) 68 .filter(({ record, score }) => score > 0 && !selectedIds.has(record.id)) 69 .sort((a, b) => b.score - a.score || String(a.record.title).localeCompare(String(b.record.title))); 70 71 if (candidates.length === 0) return null; 72 const record = candidates[0].record; 73 selectedIds.add(record.id); 74 return { record, entry }; 75 }).filter(Boolean); 76}; 77 78export const SelectedFindings = ({ 79 records = [], 80 onSelectRecord, 81 onBrowseArchive, 82}) => { 83 const featuredRecords = useMemo(() => findFeaturedRecords(records), [records]); 84 85 return html` 86 <section id="highlights" aria-labelledby="highlights-title" className="mb-16 scroll-mt-24"> 87 <div className="mb-6 flex items-end justify-between gap-4 border-b border-stone-300 pb-2"> 88 <div> 89 <p className="mb-1 font-body text-xs font-bold uppercase tracking-wider text-stone-500">Curated entry points</p> 90 <h2 id="highlights-title" tabIndex="-1" className="font-display text-2xl font-bold text-stone-900 outline-none">Three works, one developing argument</h2> 91 <p className="mt-2 max-w-2xl font-body text-sm leading-relaxed text-stone-600">A short path through nearly twenty-five years of questions about journalism, public life, and professional authority.</p> 92 </div> 93 </div> 94 95 ${featuredRecords.length > 0 ? html` 96 <div className="selected-findings-grid grid gap-6 md:grid-cols-3"> 97 ${featuredRecords.map(({ record, entry }) => html` 98 <button 99 key=${record.id} 100 type="button" 101 onClick=${() => onSelectRecord && onSelectRecord(record.id)} 102 className="archive-panel archive-path-card group flex flex-col p-5 text-left" 103 aria-label=${`Open ${record.title} in the archive`} 104 > 105 <span className="mb-4 flex w-full items-center justify-between border-b border-stone-200 pb-2 font-body text-[10px] font-bold uppercase tracking-wider text-stone-500">
106 <span>Folio ${entry.folio}</span> 107 <span>${entry.year} · ${entry.form}</span> 108 </span> 109 <span className="block font-display text-lg font-bold leading-tight text-stone-900">${record.title}</span> 110 <span className="mt-3 flex-grow font-body text-sm leading-relaxed text-stone-600">${entry.note}</span> 111 <span className="mt-4 inline-flex w-full items-center justify-between border-t border-stone-200 pt-4 font-body text-xs font-bold uppercase tracking-wider text-stone-800"> 112 Open archive record 113 <${ArrowRight} className="h-4 w-4 transition-transform group-hover:translate-x-1" aria-hidden="true" /> 114 </span> 115 </button> 116 `)} 117 </div> 118 ` : html` 119 <div className="archive-panel"> 120 <p className="font-body text-sm leading-relaxed text-stone-600">Curated records will appear here once the archive data has loaded. You can still search or browse the full collection now.</p> 121 <div className="mt-4"> 122 <button 123 type="button" 124 onClick=${onBrowseArchive} 125 className="archive-action archive-action--primary" 126 > 127 <${Search} className="h-4 w-4" aria-hidden="true" /> 128 Browse the archive 129 </button> 130 </div> 131 </div> 132 `} 133 </section> 134 `; 135}; 136 137const StartHerePage = ({ 138 onBack, 139 records = [], 140 onNavigate, 141 onOpenBugReport, 142 onSelectRecord, 143 embedded = false, 144}) => { 145 const titleRef = useRef(null); 146 147 useEffect(() => { 148 if (embedded) return; 149 window.scrollTo({ top: 0, left: 0, behavior: 'auto' }); 150 titleRef.current?.focus({ preventScroll: true }); 151 }, [embedded]); 152 153 const stats = useMemo(() => { 154 const years = records 155 .map((record) => Number.parseInt(record.year, 10)) 156 .filter(Number.isFinite); 157 const categories = new Set(records.flatMap((record) => record.categories || []).filter(Boolean)); 158 159 return { 160 total: records.length, 161 categories: categories.size, 162 minYear: years.length > 0 ? Math.min(...years) : null, 163 maxYear: years.length > 0 ? Math.max(...years) : null 164 }; 165 }, [records]); 166 167 const ContentTag = embedded ? 'div' : 'main'; 168 const ParticipateTag = embedded ? 'section' : 'aside'; 169 170 const navigate = (route) => { 171 if (onNavigate) onNavigate(route); 172 }; 173 174 const scrollTo = (id) => { 175 const target = document.getElementById(id); 176 if (!target) return; 177 const disclosure = target.querySelector('details'); 178 if (disclosure) disclosure.open = true; 179 const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; 180 target.scrollIntoView({ behavior: reduceMotion ? 'auto' : 'smooth', block: 'start' }); 181 const focusTarget = target.querySelector('h2, summary'); 182 focusTarget?.focus({ preventScroll: true }); 183 }; 184 185 const routeButton = (route, label, Icon = ArrowRight) => html` 186 <button 187 type="button" 188 onClick=${() => navigate(route)} 189 className="archive-action archive-action--primary" 190 > 191 <${Icon} className="h-4 w-4" aria-hidden="true" /> 192 ${label} 193 </button> 194 `; 195 196 const guideItem = ({ id, icon: Icon, title, children, action }) => html` 197 <article id=${id} className="scroll-mt-24 border-t border-stone-200 py-8 sm:flex gap-4"> 198 <div className="mb-3 flex h-12 w-12 items-center justify-center bg-stone-100 text-stone-700"> 199 <${Icon} className="h-5 w-5" aria-hidden="true" /> 200 </div> 201 <div className="flex-1"> 202 <h3 className="mb-2 font-display text-xl font-bold text-stone-900">${title}</h3> 203 <div className="space-y-3 font-body text-sm leading-relaxed text-stone-600">${children}</div> 204 ${action && html`<div className="mt-4">${action}</div>`} 205 </div> 206 </article> 207 `; 208 209 return html` 210 <div className=${`${embedded ? 'desktop-start-here-embedded' : 'min-h-screen archive-canvas'}`}> 211 ${!embedded && html`<${ArchiveRouteHeader} onBack=${onBack} sectionTitle="Start here" />`} 212 213 <${ContentTag} 214 id=${embedded ? undefined : 'main-content'} 215 className=${`container mx-auto max-w-5xl px-4 ${embedded ? 'py-6 md:py-8' : 'py-8 md:py-16'}`} 216 > 217 <section aria-labelledby=${embedded ? undefined : 'start-here-title'}
217 className="archive-orientation-hero mb-12 pb-8"> 218 <div className="mb-4 flex items-center gap-3"> 219 <div className="bg-stone-900 p-2 text-white"> 220 <${Compass} className="h-6 w-6" aria-hidden="true" /> 221 </div> 222 <p className="archive-section-label">Visitor guide</p> 223 </div> 224 <h1 ref=${titleRef} tabIndex="-1" data-route-entry-focus id="start-here-title" className="max-w-3xl font-display text-3xl font-bold leading-tight text-stone-900 outline-none md:text-5xl"> 225 Start here 226 </h1> 227 <p className="mt-4 max-w-3xl font-body text-lg leading-relaxed text-stone-600"> 228 Jay Rosen's Internet Archive brings together decades of writing, teaching, interviews, and public commentary about journalism and democratic life. Search directly, follow a curated trail, or learn how the collection is organized. 229 </p> 230 231 ${!embedded && html` 232 <div className="mt-8 flex flex-wrap gap-3"> 233 <button 234 type="button" 235 aria-label="Explore the archive desktop, beta" 236 onClick=${() => navigate('desktop')} 237 className="archive-action archive-action--secondary" 238 > 239 <${Monitor} className="h-4 w-4" aria-hidden="true" /> 240 Explore the archive desktop 241 <span className="archive-section-label">Beta</span> 242 </button> 243 </div> 244 <p className="mt-3 max-w-2xl font-body text-xs leading-relaxed text-stone-500"> 245 The archive desktop is still in development. This optional view arranges the same archive paths as shortcuts, folders, and windows. 246 </p> 247 `} 248 249 ${stats.total > 0 && html` 250 <p className="mt-4 font-body text-xs text-stone-500"> 251 ${stats.total.toLocaleString()} records 252 ${stats.minYear !== null ? ` · ${stats.minYear}â${stats.maxYear}` : ''} 253 ${stats.categories > 0 ? ` · ${stats.categories.toLocaleString()} thematic collections` : ''} 254 </p> 255 `} 256 </section> 257 258 <nav aria-labelledby="choose-path-title" className="mb-16"> 259 <div className="mb-6"> 260 <h2 id="choose-path-title" className="font-display text-2xl font-bold text-stone-900">Choose a starting point</h2> 261 <p className="mt-2 max-w-2xl font-body text-sm leading-relaxed text-stone-600">Pick the route that best matches what you came to do. You can switch paths at any time.</p> 262 </div> 263 264 <div className="start-here-path-grid grid md:grid-cols-3"> 265 <a 266 href="#guide" 267 onClick=${(event) => { event.preventDefault(); scrollTo('guide'); }} 268 aria-controls="guide" 269 className="archive-panel archive-path-card archive-path-card--folder group text-left" 270 > 271 <span className="archive-folder-tab archive-path-card__tab" aria-hidden="true"><span>Guide</span></span> 272 <${Compass} className="mb-4 h-6 w-6 text-sky-700" aria-hidden="true" /> 273 <span className="block font-display text-xl font-bold text-stone-900">Learn how the archive works</span> 274 <span className="mt-3 block font-body text-sm leading-relaxed text-stone-600">Open the complete field guide to records, folders, entities, research tools, and archive terminology.</span> 275 <span className="mt-4 inline-flex items-center gap-2 font-body text-xs font-bold uppercase tracking-wider text-stone-800">Open the field guide <${ArrowRight} className="h-4 w-4 transition-transform group-hover:translate-x-1" aria-hidden="true" /></span> 276 </a> 277 278 <a 279 href="#highlights" 280 onClick=${(event) => { event.preventDefault(); scrollTo('highlights'); }} 281 aria-controls="highlights" 282 className="archive-panel archive-path-card archive-path-card--folder group text-left" 283 > 284 <span className="archive-folder-tab archive-path-card__tab" aria-hidden="true"><span>Highlights</span></span> 285 <${Sparkles} className="mb-4 h-6 w-6 text-amber-700" aria-hidden="true" />
286 <span className="block font-display text-xl font-bold text-stone-900">Show me the highlights</span> 287 <span className="mt-3 block font-body text-sm leading-relaxed text-stone-600">Begin with a few works that open onto the archive's recurring questions and ideas.</span> 288 <span className="mt-4 inline-flex items-center gap-2 font-body text-xs font-bold uppercase tracking-wider text-stone-800">See selected records <${ArrowRight} className="h-4 w-4 transition-transform group-hover:translate-x-1" aria-hidden="true" /></span> 289 </a> 290 291 <button 292 type="button" 293 onClick=${() => navigate('entities')} 294 className="archive-panel archive-path-card archive-path-card--folder group text-left" 295 > 296 <span className="archive-folder-tab archive-path-card__tab" aria-hidden="true"><span>Research</span></span> 297 <${Network} className="mb-4 h-6 w-6 text-stone-700" aria-hidden="true" /> 298 <span className="block font-display text-xl font-bold text-stone-900">Research a topic or idea</span> 299 <span className="mt-3 block font-body text-sm leading-relaxed text-stone-600">Trace people, organizations, concepts, and the records that connect them.</span> 300 <span className="mt-4 inline-flex items-center gap-2 font-body text-xs font-bold uppercase tracking-wider text-stone-800">Explore entities <${ArrowRight} className="h-4 w-4 transition-transform group-hover:translate-x-1" aria-hidden="true" /></span> 301 </button> 302 </div> 303 </nav> 304 305 <${SelectedFindings} 306 records=${records} 307 onSelectRecord=${onSelectRecord} 308 onBrowseArchive=${() => navigate('archive')} 309 /> 310 311 <section id="guide" aria-labelledby="guide-title" className="mb-16 scroll-mt-24"> 312 <details className="archive-panel px-5 py-2 md:p-8"> 313 <summary className="cursor-pointer py-6 font-display text-stone-900 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2"> 314 <span className="block font-body text-xs font-bold uppercase tracking-wider text-stone-500">Complete visitor field guide · 7 sections</span> 315 <span id="guide-title" tabIndex="-1" className="mt-1 block text-2xl font-bold outline-none">How to use the archive</span> 316 <span className="mt-2 block font-body text-sm font-normal leading-relaxed text-stone-600">Expand for a practical reference to every way into the collection, plus archive terminology and problem reporting.</span> 317 </summary> 318 319 <div className="border-t border-stone-200"> 320 321 ${guideItem({ 322 id: 'guide-records', 323 icon: Search, 324 title: 'Records and search', 325 children: html`<p>A record is the archive's basic unit: an article, post, interview, clipping, thread, or other item connected to Jay Rosen's work. Search looks across metadata, available article text, and the full source text of published social posts and served threads; filters help narrow the results by topic, era, year, publication, and format.</p><p>Open a record to read its summary, follow the original source, copy a citation, share it, or continue through related material.</p>`, 326 action: routeButton('archive', 'Search records', Search) 327 })} 328 329 ${guideItem({ 330 id: 'guide-folders', 331 icon: FolderOpen, 332 title: 'Folders', 333 children: html`<p>Folders are a visual way to enter the archive through its major subject categories. Choose one when you want a broad theme rather than a particular title or phrase.</p>`, 334 action: routeButton('folders', 'Browse folders', FolderOpen) 335 })} 336 337 ${guideItem({ 338 id: 'guide-entities', 339 icon: Network, 340 title: 'Entities and connections', 341 children: html`<p>The entity explorer indexes people, organizations, concepts, works, events, and locations mentioned across the collection. Use it to move from a name or idea to the records where it appears, then examine what frequently appears alongside it.</p>`, 342 action: routeButton('entities', 'Explore entities', Network) 343 })} 344 345 ${guideItem({ 346 id: 'guide-dissertation', 347 icon: BookOpen, 348 title: 'The dissertation', 349 children: html`<p><em>The Impossible Press: American Journalism and the Decline of Public Life</em>, Rosen's 1986 NYU dissertation, has its own reading and exploration tools. Read the full text or use the mind map to understand its structure and enduring questions.</p>`, 350 action: routeButton('dissertation', 'Explore the dissertation', BookOpen) 351 })} 352 353 ${guideItem({ 354 id: 'guide-analytics', 355 icon: BarChart3, 356 title: 'Analytics', 357 children: html`<p>The analytics dashboard shows the collection at a larger scale: output over time, recurring categories and concepts, frequently mentioned people, and other patterns. It is useful for forming a question before returning to individual records.<
357/p>`, 358 action: routeButton('analytics', 'View archive analytics', BarChart3) 359 })} 360 361 ${guideItem({ 362 id: 'guide-archive-vault', 363 icon: Boxes, 364 title: 'Internet Archive and Vault', 365 children: html`<p>On PressThink, <strong>Internet Archive</strong> means this curated, searchable collection of records and connections. <strong>Vault (2003âpresent)</strong> refers to the older PressThink blog archive. The Vault preserves the blog's original chronology; this archive brings many kinds of material together and adds summaries, categories, entities, and research tools.</p>` 366 })} 367 368 ${guideItem({ 369 id: 'guide-reporting', 370 icon: AlertCircle, 371 title: 'Report a problem', 372 children: html`<p>The collection is maintained over time. If a link is broken, a record is incomplete, or something looks incorrect, use the in-archive report form. Your report can include the record you were viewing and helps the curator investigate the problem.</p>`, 373 action: html` 374 <button 375 type="button" 376 onClick=${onOpenBugReport} 377 className="archive-action archive-action--primary" 378 > 379 <${AlertCircle} className="h-4 w-4" aria-hidden="true" /> 380 Report an archive problem 381 </button> 382 ` 383 })} 384 </div> 385 </details> 386 </section> 387 388 <${ParticipateTag} className="archive-panel archive-panel--accent archive-density--spacious mb-8 md:flex md:items-center md:justify-between md:gap-8" aria-labelledby="participate-callout-title"> 389 <div> 390 <p className="archive-section-label">A living collection</p> 391 <h2 id="participate-callout-title" className="mt-1 font-display text-2xl font-bold text-stone-900">Help keep the archive useful.</h2> 392 <p className="mt-2 max-w-2xl font-body text-sm leading-relaxed text-stone-600">Share a record, suggest something missing, report a barrier, follow new additions, or reuse the project.</p> 393 </div> 394 <a href=${resolveSitePath('features/participate/')} className="archive-action archive-action--primary mt-4 shrink-0 md:mt-0">Ways to participate<${ArrowRight} className="h-4 w-4" aria-hidden="true" /></a> 395 <//> 396 397 <section aria-labelledby="continue-title" className="archive-panel p-6 md:flex md:items-center md:justify-between md:gap-8 md:p-8"> 398 <div> 399 <div className="mb-2 flex items-center gap-2 text-stone-500"> 400 <${Library} className="h-5 w-5" aria-hidden="true" /> 401 <span className="font-body text-xs font-bold uppercase tracking-wider">Continue exploring</span> 402 </div> 403 <h2 id="continue-title" className="font-display text-2xl font-bold text-stone-900">There is no required path through the archive.</h2> 404 <p className="mt-2 max-w-2xl font-body text-sm leading-relaxed text-stone-600">Search for something specific, browse a broad category, or follow the connections that catch your attention.</p> 405 </div> 406 <div className="mt-4 shrink-0">${routeButton('archive', 'Enter the archive', ArrowRight)}</div> 407 </section> 408 409 ${!embedded && html`<div className="pb-4 pt-8 text-center"> 410 <button 411 type="button" 412 onClick=${onBack} 413 className="archive-action archive-action--quiet" 414 > 415 <${ArrowLeft} className="h-4 w-4" aria-hidden="true" /> 416 Back to archive 417 </button> 418 </div>`} 419 <//> 420 </div> 421 `; 422}; 423 424export default StartHerePage;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.