PageSourceSearch

https://pressthink.org/j/rosen-archive/frontend/components/StartHerePage.js?v=3.8.36

js pressthink.org collected 2026-10-02 04:26:42 UTC 21,611 bytes, 424 lines download raw bytes

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.