mcp-scraper 0.40.1 → 0.40.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +60220 -0
  3. package/dist/bin/api-server.cjs.map +1 -0
  4. package/dist/bin/api-server.d.cts +1 -0
  5. package/dist/bin/api-server.d.ts +1 -0
  6. package/dist/bin/api-server.js +38 -0
  7. package/dist/bin/api-server.js.map +1 -0
  8. package/dist/bin/mcp-scraper-cli.cjs +2671 -0
  9. package/dist/bin/mcp-scraper-cli.cjs.map +1 -0
  10. package/dist/bin/mcp-scraper-cli.d.cts +1 -0
  11. package/dist/bin/mcp-scraper-cli.d.ts +1 -0
  12. package/dist/bin/mcp-scraper-cli.js +742 -0
  13. package/dist/bin/mcp-scraper-cli.js.map +1 -0
  14. package/dist/bin/mcp-scraper-install.cjs +129 -0
  15. package/dist/bin/mcp-scraper-install.cjs.map +1 -0
  16. package/dist/bin/mcp-scraper-install.d.cts +1 -0
  17. package/dist/bin/mcp-scraper-install.d.ts +1 -0
  18. package/dist/bin/mcp-scraper-install.js +27 -0
  19. package/dist/bin/mcp-scraper-install.js.map +1 -0
  20. package/dist/bin/mcp-stdio-server.cjs +12532 -0
  21. package/dist/bin/mcp-stdio-server.cjs.map +1 -0
  22. package/dist/bin/mcp-stdio-server.d.cts +1 -0
  23. package/dist/bin/mcp-stdio-server.d.ts +1 -0
  24. package/dist/bin/mcp-stdio-server.js +135 -0
  25. package/dist/bin/mcp-stdio-server.js.map +1 -0
  26. package/dist/bin/paa-harvest.cjs +3808 -0
  27. package/dist/bin/paa-harvest.cjs.map +1 -0
  28. package/dist/bin/paa-harvest.d.cts +1 -0
  29. package/dist/bin/paa-harvest.d.ts +1 -0
  30. package/dist/bin/paa-harvest.js +44 -0
  31. package/dist/bin/paa-harvest.js.map +1 -0
  32. package/dist/chunk-345BQXZH.js +712 -0
  33. package/dist/chunk-345BQXZH.js.map +1 -0
  34. package/dist/chunk-3PIWJS6Y.js +276 -0
  35. package/dist/chunk-3PIWJS6Y.js.map +1 -0
  36. package/dist/chunk-44HZLHDV.js +52 -0
  37. package/dist/chunk-44HZLHDV.js.map +1 -0
  38. package/dist/chunk-7XBBFBYY.js +3410 -0
  39. package/dist/chunk-7XBBFBYY.js.map +1 -0
  40. package/dist/chunk-CB5C3BPB.js +135 -0
  41. package/dist/chunk-CB5C3BPB.js.map +1 -0
  42. package/dist/chunk-FQI5PFE7.js +1866 -0
  43. package/dist/chunk-FQI5PFE7.js.map +1 -0
  44. package/dist/chunk-G3P3ZDB4.js +69 -0
  45. package/dist/chunk-G3P3ZDB4.js.map +1 -0
  46. package/dist/chunk-GUVKHCKE.js +3050 -0
  47. package/dist/chunk-GUVKHCKE.js.map +1 -0
  48. package/dist/chunk-K443GQY5.js +24 -0
  49. package/dist/chunk-K443GQY5.js.map +1 -0
  50. package/dist/chunk-LP6E462I.js +11555 -0
  51. package/dist/chunk-LP6E462I.js.map +1 -0
  52. package/dist/chunk-NVXNEOUQ.js +158 -0
  53. package/dist/chunk-NVXNEOUQ.js.map +1 -0
  54. package/dist/chunk-QZXKQB7Y.js +414 -0
  55. package/dist/chunk-QZXKQB7Y.js.map +1 -0
  56. package/dist/chunk-SBLGBZZB.js +647 -0
  57. package/dist/chunk-SBLGBZZB.js.map +1 -0
  58. package/dist/chunk-X2LKCX6H.js +499 -0
  59. package/dist/chunk-X2LKCX6H.js.map +1 -0
  60. package/dist/chunk-XELSA2MS.js +108 -0
  61. package/dist/chunk-XELSA2MS.js.map +1 -0
  62. package/dist/chunk-XORPNO3Z.js +684 -0
  63. package/dist/chunk-XORPNO3Z.js.map +1 -0
  64. package/dist/chunk-ZUJLSICT.js +7 -0
  65. package/dist/chunk-ZUJLSICT.js.map +1 -0
  66. package/dist/db-W3CP562I.js +239 -0
  67. package/dist/db-W3CP562I.js.map +1 -0
  68. package/dist/editorial-reading-room/assets/app.js +335 -0
  69. package/dist/editorial-reading-room/assets/index.html +131 -0
  70. package/dist/editorial-reading-room/assets/styles.css +1052 -0
  71. package/dist/extract-bundle-K4PG3RZJ.js +568 -0
  72. package/dist/extract-bundle-K4PG3RZJ.js.map +1 -0
  73. package/dist/index.cjs +4160 -0
  74. package/dist/index.cjs.map +1 -0
  75. package/dist/index.d.cts +413 -0
  76. package/dist/index.d.ts +413 -0
  77. package/dist/index.js +338 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/location-data-repository-RLQX6SNM.js +35 -0
  80. package/dist/location-data-repository-RLQX6SNM.js.map +1 -0
  81. package/dist/server-KM3CFWCF.js +34674 -0
  82. package/dist/server-KM3CFWCF.js.map +1 -0
  83. package/dist/site-extract-repository-2SMMFKKL.js +62 -0
  84. package/dist/site-extract-repository-2SMMFKKL.js.map +1 -0
  85. package/dist/worker-FXAGFYOE.js +142 -0
  86. package/dist/worker-FXAGFYOE.js.map +1 -0
  87. package/package.json +4 -2
@@ -0,0 +1,335 @@
1
+ const elements = {
2
+ home: document.querySelector("#home-view"),
3
+ article: document.querySelector("#article-view"),
4
+ loading: document.querySelector("#loading-screen"),
5
+ menuButton: document.querySelector("#menu-button"),
6
+ menuClose: document.querySelector("#menu-close"),
7
+ backdrop: document.querySelector("#drawer-backdrop"),
8
+ drawer: document.querySelector("#navigation-drawer"),
9
+ drawerNav: document.querySelector("#drawer-navigation"),
10
+ search: document.querySelector("#menu-search"),
11
+ progress: document.querySelector("#reading-progress-bar"),
12
+ desktopJumps: document.querySelector("#desktop-jump-links")
13
+ };
14
+
15
+ const state = {
16
+ data: null,
17
+ current: null,
18
+ storageKey: "editorial-reading-room",
19
+ typeIndex: Number(localStorage.getItem("editorial-reading-room:type") || 0),
20
+ completed: new Set(
21
+ JSON.parse(localStorage.getItem("editorial-reading-room:completed") || "[]")
22
+ )
23
+ };
24
+
25
+ const typeModes = ["default", "large", "larger"];
26
+
27
+ function escapeHtml(value = "") {
28
+ return value
29
+ .replace(/&/g, "&")
30
+ .replace(/</g, "&lt;")
31
+ .replace(/>/g, "&gt;")
32
+ .replace(/"/g, "&quot;");
33
+ }
34
+
35
+ function setTypeMode() {
36
+ const mode = typeModes[state.typeIndex % typeModes.length];
37
+ if (mode === "default") document.documentElement.removeAttribute("data-type");
38
+ else document.documentElement.dataset.type = mode;
39
+ localStorage.setItem(`${state.storageKey}:type`, String(state.typeIndex % typeModes.length));
40
+ }
41
+
42
+ function setTheme(theme) {
43
+ if (theme === "evening") document.documentElement.dataset.theme = "evening";
44
+ else document.documentElement.removeAttribute("data-theme");
45
+ localStorage.setItem(`${state.storageKey}:theme`, theme);
46
+ }
47
+
48
+ function routeSlug() {
49
+ return new URL(window.location.href).searchParams.get("article");
50
+ }
51
+
52
+ function articleUrl(slug, section = "") {
53
+ const url = new URL(window.location.href);
54
+ if (slug) url.searchParams.set("article", slug);
55
+ else url.searchParams.delete("article");
56
+ url.hash = section ? `#${section}` : "";
57
+ return `${url.pathname}${url.search}${url.hash}`;
58
+ }
59
+
60
+ function navigate(slug, section = "", replace = false) {
61
+ const method = replace ? "replaceState" : "pushState";
62
+ history[method]({}, "", articleUrl(slug, section));
63
+ renderRoute();
64
+ }
65
+
66
+ function openDrawer() {
67
+ document.body.classList.add("drawer-open");
68
+ elements.drawer.setAttribute("aria-hidden", "false");
69
+ elements.menuButton.setAttribute("aria-expanded", "true");
70
+ renderDrawer();
71
+ requestAnimationFrame(() => elements.search.focus());
72
+ }
73
+
74
+ function closeDrawer() {
75
+ document.body.classList.remove("drawer-open");
76
+ elements.drawer.setAttribute("aria-hidden", "true");
77
+ elements.menuButton.setAttribute("aria-expanded", "false");
78
+ elements.search.value = "";
79
+ elements.menuButton.focus();
80
+ }
81
+
82
+ function groupArticles(articles) {
83
+ const groups = new Map();
84
+ articles.forEach((article) => {
85
+ if (!groups.has(article.category)) groups.set(article.category, []);
86
+ groups.get(article.category).push(article);
87
+ });
88
+ return groups;
89
+ }
90
+
91
+ function renderDrawer() {
92
+ const query = elements.search.value.trim().toLowerCase();
93
+ const current = state.current;
94
+ const articles = state.data.articles.filter((article) => {
95
+ if (!query) return true;
96
+ return `${article.title} ${article.summary} ${article.category}`
97
+ .toLowerCase()
98
+ .includes(query);
99
+ });
100
+ const groups = groupArticles(articles);
101
+ let html = "";
102
+ for (const [category, items] of groups) {
103
+ html += `<section class="drawer-group"><p class="drawer-group-title">${escapeHtml(
104
+ category
105
+ )}</p>`;
106
+ items.forEach((article) => {
107
+ const index = String(article.order).padStart(2, "0");
108
+ const completed = state.completed.has(article.slug) ? "✓" : index;
109
+ html += `<button class="drawer-link" data-article="${article.slug}" ${
110
+ current?.slug === article.slug ? 'aria-current="page"' : ""
111
+ }><span class="drawer-index">${completed}</span><span class="drawer-title">${escapeHtml(
112
+ article.title
113
+ )}</span></button>`;
114
+ });
115
+ html += "</section>";
116
+ }
117
+ if (current && (!query || current.title.toLowerCase().includes(query))) {
118
+ html += `<section class="jump-group"><p class="drawer-group-title">Jump within this article</p>`;
119
+ current.toc.forEach((item) => {
120
+ html += `<button class="jump-link level-${item.level}" data-section="${item.id}">${escapeHtml(
121
+ item.title
122
+ )}</button>`;
123
+ });
124
+ html += "</section>";
125
+ }
126
+ elements.drawerNav.innerHTML = html || "<p>No matching articles.</p>";
127
+ }
128
+
129
+ function renderHome() {
130
+ state.current = null;
131
+ elements.home.hidden = false;
132
+ elements.article.hidden = true;
133
+ elements.progress.style.width = "0";
134
+ document.title = state.data.site.title;
135
+ document.querySelector("#masthead-product").textContent = state.data.site.product;
136
+ document.querySelector("#masthead-title").textContent = state.data.site.edition;
137
+ document.querySelector("#edition-label").textContent = state.data.site.editionLabel;
138
+ document.querySelector("#issue-label").textContent = state.data.site.issueLabel;
139
+ document.querySelector("#home-eyebrow").textContent = state.data.site.eyebrow;
140
+ document.querySelector("#home-title").textContent = state.data.site.heroTitle;
141
+ document.querySelector("#start-label").textContent = state.data.site.startLabel;
142
+ document.querySelector("#footer-title").textContent = state.data.site.title;
143
+ document.querySelector("#home-deck").textContent = state.data.deck;
144
+ document.querySelector("#article-count").textContent = `${state.data.articles.length} readings`;
145
+ document.querySelector("#generated-at").textContent = `Updated ${new Date(
146
+ state.data.generatedAt
147
+ ).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}`;
148
+
149
+ const featured = state.data.articles.slice(0, 3);
150
+ document.querySelector("#featured-grid").innerHTML = featured
151
+ .map(
152
+ (article) => `<article class="feature-card">
153
+ <button data-article="${article.slug}">
154
+ <p class="card-kicker">${escapeHtml(article.kicker)}</p>
155
+ <h2>${escapeHtml(article.title)}</h2>
156
+ <p>${escapeHtml(article.summary)}</p>
157
+ </button>
158
+ </article>`
159
+ )
160
+ .join("");
161
+
162
+ document.querySelector("#contents-list").innerHTML = state.data.articles
163
+ .map(
164
+ (article) => `<article class="content-row">
165
+ <span class="content-number">${String(article.order).padStart(2, "0")}</span>
166
+ <button data-article="${article.slug}"><h3>${escapeHtml(article.title)}</h3></button>
167
+ <p>${escapeHtml(article.summary)}</p>
168
+ <span class="content-meta">${article.readingMinutes} min${
169
+ state.completed.has(article.slug) ? " · Read" : ""
170
+ }</span>
171
+ </article>`
172
+ )
173
+ .join("");
174
+ window.scrollTo({ top: 0, behavior: "instant" });
175
+ }
176
+
177
+ function renderDesktopJumps(article) {
178
+ elements.desktopJumps.innerHTML = article.toc
179
+ .map(
180
+ (item) =>
181
+ `<a class="level-${item.level}" href="#${item.id}" data-section-link="${item.id}">${escapeHtml(
182
+ item.title
183
+ )}</a>`
184
+ )
185
+ .join("");
186
+ }
187
+
188
+ function renderArticle(article) {
189
+ state.current = article;
190
+ elements.home.hidden = true;
191
+ elements.article.hidden = false;
192
+ document.title = `${article.title} — ${state.data.site.title}`;
193
+ document.querySelector("#article-category").textContent = article.category;
194
+ document.querySelector("#article-kicker").textContent = article.kicker;
195
+ document.querySelector("#article-title").textContent = article.title;
196
+ document.querySelector("#article-summary").textContent = article.summary;
197
+ document.querySelector("#article-reading-time").textContent = `${article.readingMinutes} min read`;
198
+ document.querySelector("#article-word-count").textContent = `${article.wordCount.toLocaleString()} words`;
199
+ document.querySelector("#article-revision").textContent = article.revision
200
+ ? `Revision ${article.revision}`
201
+ : article.sourceType || "Source material";
202
+ document.querySelector("#article-body").innerHTML = article.html;
203
+ document.querySelector("#article-source").textContent = article.sourceLabel;
204
+ renderDesktopJumps(article);
205
+ renderAdjacent(article);
206
+ requestAnimationFrame(() => {
207
+ if (window.location.hash) {
208
+ document.querySelector(window.location.hash)?.scrollIntoView();
209
+ } else {
210
+ window.scrollTo({ top: 0, behavior: "instant" });
211
+ }
212
+ });
213
+ }
214
+
215
+ function renderAdjacent(article) {
216
+ const index = state.data.articles.findIndex((item) => item.slug === article.slug);
217
+ const previous = state.data.articles[index - 1];
218
+ const next = state.data.articles[index + 1];
219
+ const previousButton = document.querySelector("#previous-article");
220
+ const nextButton = document.querySelector("#next-article");
221
+
222
+ previousButton.disabled = !previous;
223
+ previousButton.dataset.article = previous?.slug || "";
224
+ previousButton.innerHTML = previous
225
+ ? `<span class="eyebrow">Previous</span><br>${escapeHtml(previous.title)}`
226
+ : "Beginning of the edition";
227
+
228
+ nextButton.disabled = !next;
229
+ nextButton.dataset.article = next?.slug || "";
230
+ nextButton.innerHTML = next
231
+ ? `<span class="eyebrow">Next</span><br>${escapeHtml(next.title)}`
232
+ : "End of the edition";
233
+ }
234
+
235
+ function renderRoute() {
236
+ const slug = routeSlug();
237
+ if (!slug) renderHome();
238
+ else {
239
+ const article = state.data.articles.find((item) => item.slug === slug);
240
+ if (article) renderArticle(article);
241
+ else navigate(null, "", true);
242
+ }
243
+ renderDrawer();
244
+ }
245
+
246
+ function updateReadingProgress() {
247
+ if (!state.current) return;
248
+ const article = document.querySelector("#article-body");
249
+ const rect = article.getBoundingClientRect();
250
+ const start = window.scrollY + rect.top - window.innerHeight * 0.18;
251
+ const end = start + article.offsetHeight - window.innerHeight * 0.55;
252
+ const percent = Math.max(0, Math.min(1, (window.scrollY - start) / Math.max(1, end - start)));
253
+ elements.progress.style.width = `${percent * 100}%`;
254
+ localStorage.setItem(`${state.storageKey}:progress:${state.current.slug}`, String(percent));
255
+ if (percent > 0.94 && !state.completed.has(state.current.slug)) {
256
+ state.completed.add(state.current.slug);
257
+ localStorage.setItem(`${state.storageKey}:completed`, JSON.stringify([...state.completed]));
258
+ }
259
+
260
+ let active = null;
261
+ for (const heading of document.querySelectorAll(".article-body h2, .article-body h3")) {
262
+ if (heading.getBoundingClientRect().top <= 150) active = heading.id;
263
+ }
264
+ document.querySelectorAll("[data-section-link]").forEach((link) => {
265
+ link.classList.toggle("active", link.dataset.sectionLink === active);
266
+ });
267
+ }
268
+
269
+ document.addEventListener("click", (event) => {
270
+ const articleButton = event.target.closest("[data-article]");
271
+ if (articleButton?.dataset.article) {
272
+ navigate(articleButton.dataset.article);
273
+ closeDrawer();
274
+ return;
275
+ }
276
+ const jump = event.target.closest("[data-section]");
277
+ if (jump && state.current) {
278
+ closeDrawer();
279
+ history.replaceState({}, "", articleUrl(state.current.slug, jump.dataset.section));
280
+ document.querySelector(`#${CSS.escape(jump.dataset.section)}`)?.scrollIntoView();
281
+ }
282
+ });
283
+
284
+ elements.menuButton.addEventListener("click", openDrawer);
285
+ elements.menuClose.addEventListener("click", closeDrawer);
286
+ elements.backdrop.addEventListener("click", closeDrawer);
287
+ elements.search.addEventListener("input", renderDrawer);
288
+ document.querySelector("#home-button").addEventListener("click", () => navigate(null));
289
+ document.querySelector("#footer-contents").addEventListener("click", () => navigate(null));
290
+ document.querySelector("#article-category").addEventListener("click", () => navigate(null));
291
+ document.querySelector("#start-reading").addEventListener("click", () =>
292
+ navigate(state.data.articles[0].slug)
293
+ );
294
+ document.querySelector("#type-button").addEventListener("click", () => {
295
+ state.typeIndex = (state.typeIndex + 1) % typeModes.length;
296
+ setTypeMode();
297
+ });
298
+ document.querySelector("#theme-button").addEventListener("click", () => {
299
+ setTheme(document.documentElement.dataset.theme === "evening" ? "day" : "evening");
300
+ });
301
+
302
+ window.addEventListener("popstate", renderRoute);
303
+ window.addEventListener("scroll", updateReadingProgress, { passive: true });
304
+ window.addEventListener("keydown", (event) => {
305
+ if (event.key === "Escape" && document.body.classList.contains("drawer-open")) closeDrawer();
306
+ if (event.key.toLowerCase() === "m" && !/input|textarea/i.test(event.target.tagName)) openDrawer();
307
+ if (!state.current || /input|textarea/i.test(event.target.tagName)) return;
308
+ const index = state.data.articles.findIndex((item) => item.slug === state.current.slug);
309
+ if (event.key === "[" && index > 0) navigate(state.data.articles[index - 1].slug);
310
+ if (event.key === "]" && index < state.data.articles.length - 1)
311
+ navigate(state.data.articles[index + 1].slug);
312
+ });
313
+
314
+ async function boot() {
315
+ const dataElement = document.querySelector("#reading-room-data");
316
+ if (!dataElement?.textContent) throw new Error("Could not load the reading-room content.");
317
+ state.data = JSON.parse(dataElement.textContent);
318
+ state.storageKey = `editorial-reading-room:${state.data.site.slug}`;
319
+ state.typeIndex = Number(localStorage.getItem(`${state.storageKey}:type`) || 0);
320
+ state.completed = new Set(
321
+ JSON.parse(localStorage.getItem(`${state.storageKey}:completed`) || "[]")
322
+ );
323
+ const savedTheme = localStorage.getItem(`${state.storageKey}:theme`);
324
+ if (savedTheme === "evening") setTheme("evening");
325
+ setTypeMode();
326
+ renderRoute();
327
+ elements.loading.classList.add("loaded");
328
+ document.querySelector("#main-content").focus({ preventScroll: true });
329
+ }
330
+
331
+ boot().catch((error) => {
332
+ elements.loading.innerHTML = `<span class="loading-mark">!</span><p>${escapeHtml(
333
+ error.message
334
+ )}</p>`;
335
+ });
@@ -0,0 +1,131 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta
7
+ name="description"
8
+ content="A mobile-first editorial reading room."
9
+ />
10
+ <meta name="theme-color" content="#f5f1e8" />
11
+ <title>Editorial Reading Room</title>
12
+ <style>/*__READING_ROOM_STYLES__*/</style>
13
+ </head>
14
+ <body>
15
+ <a class="skip-link" href="#main-content">Skip to the article</a>
16
+ <div class="reading-progress" aria-hidden="true">
17
+ <span id="reading-progress-bar"></span>
18
+ </div>
19
+
20
+ <header class="site-header">
21
+ <button
22
+ class="menu-button"
23
+ id="menu-button"
24
+ type="button"
25
+ aria-label="Open contents and jump links"
26
+ aria-controls="navigation-drawer"
27
+ aria-expanded="false"
28
+ >
29
+ <span></span><span></span><span></span>
30
+ </button>
31
+ <button class="masthead" id="home-button" type="button" aria-label="Return to contents">
32
+ <span class="masthead-product" id="masthead-product">Reading Room</span>
33
+ <span class="masthead-title" id="masthead-title">Field Notes</span>
34
+ </button>
35
+ <div class="header-tools">
36
+ <button class="text-button" id="type-button" type="button" aria-label="Change text size">
37
+ <span aria-hidden="true">A<span class="type-large">A</span></span>
38
+ <span class="tool-label">Type</span>
39
+ </button>
40
+ <button class="text-button" id="theme-button" type="button" aria-label="Toggle evening mode">
41
+ <span aria-hidden="true">◐</span>
42
+ <span class="tool-label">Evening</span>
43
+ </button>
44
+ </div>
45
+ </header>
46
+
47
+ <aside class="navigation-drawer" id="navigation-drawer" aria-hidden="true">
48
+ <div class="drawer-header">
49
+ <p class="eyebrow">Reading desk</p>
50
+ <button id="menu-close" type="button" aria-label="Close navigation">Close</button>
51
+ </div>
52
+ <label class="search-field">
53
+ <span class="sr-only">Search the reading room</span>
54
+ <input id="menu-search" type="search" placeholder="Find an article or section" />
55
+ </label>
56
+ <nav id="drawer-navigation" aria-label="Reading room contents"></nav>
57
+ </aside>
58
+ <button class="drawer-backdrop" id="drawer-backdrop" type="button" aria-label="Close navigation"></button>
59
+
60
+ <main id="main-content" tabindex="-1">
61
+ <section id="home-view" class="home-view" hidden>
62
+ <div class="issue-line">
63
+ <span id="edition-label">Reader’s edition</span>
64
+ <span id="issue-label">Current edition</span>
65
+ <span id="article-count"></span>
66
+ </div>
67
+ <section class="home-hero">
68
+ <p class="eyebrow" id="home-eyebrow">A guided collection</p>
69
+ <h1 id="home-title">Make complex material easy to explore.</h1>
70
+ <p class="home-deck" id="home-deck"></p>
71
+ <button class="primary-link" id="start-reading" type="button">
72
+ <span id="start-label">Start reading</span> <span aria-hidden="true">→</span>
73
+ </button>
74
+ </section>
75
+ <section class="featured-grid" id="featured-grid" aria-label="Featured reading"></section>
76
+ <section class="contents-section">
77
+ <div class="section-heading">
78
+ <p class="eyebrow">The complete edition</p>
79
+ <h2>Contents</h2>
80
+ </div>
81
+ <div id="contents-list" class="contents-list"></div>
82
+ </section>
83
+ </section>
84
+
85
+ <section id="article-view" class="article-view" hidden>
86
+ <div class="article-shell">
87
+ <aside class="article-rail" aria-label="In this article">
88
+ <p class="rail-label">In this article</p>
89
+ <nav id="desktop-jump-links"></nav>
90
+ </aside>
91
+ <article>
92
+ <header class="article-header">
93
+ <button class="breadcrumb" id="article-category" type="button">Field Notes</button>
94
+ <p class="article-kicker" id="article-kicker"></p>
95
+ <h1 id="article-title"></h1>
96
+ <p class="article-deck" id="article-summary"></p>
97
+ <div class="article-meta">
98
+ <span id="article-reading-time"></span>
99
+ <span id="article-word-count"></span>
100
+ <span id="article-revision"></span>
101
+ </div>
102
+ </header>
103
+ <div class="article-body" id="article-body"></div>
104
+ <footer class="article-source">
105
+ <p class="eyebrow">Source record</p>
106
+ <p id="article-source"></p>
107
+ </footer>
108
+ </article>
109
+ </div>
110
+ <nav class="next-previous" aria-label="Previous and next articles">
111
+ <button id="previous-article" type="button"></button>
112
+ <button id="next-article" type="button"></button>
113
+ </nav>
114
+ </section>
115
+ </main>
116
+
117
+ <footer class="site-footer">
118
+ <span id="footer-title">Editorial Reading Room</span>
119
+ <button id="footer-contents" type="button">Contents</button>
120
+ <span id="generated-at"></span>
121
+ </footer>
122
+
123
+ <div class="loading-screen" id="loading-screen" role="status">
124
+ <span class="loading-mark">M</span>
125
+ <p>Preparing the reading room…</p>
126
+ </div>
127
+
128
+ <script id="reading-room-data" type="application/json">/*__READING_ROOM_DATA__*/</script>
129
+ <script>/*__READING_ROOM_SCRIPT__*/</script>
130
+ </body>
131
+ </html>