1/* ===================================================================== 2 * PREVIEW 2 - news-portal front page behaviour (theage.com.au design 3 * language, TAeM tokens). A renamed adaptation of preview.js: same data 4 * sources and mechanics, [data-p2] selectors, plus a centre medium-card 5 * stack (renderStack) and a "Just in" cadence list (renderJustIn). 6 * 7 * Sources (identical to the live components, so nothing can drift): 8 * - nav + instruments: window.TAEM_NAV_ITEMS (taem-header-footer.js) 9 * - lead + stack + edition: window.__TAEM_ISSUES__ (issues-data.js) 10 * - search: /search.js lazy-loaded, window.TAEM_SEARCH.open() 11 * Also: dropdown wiring, '/' shortcut, sticky-compact masthead, skip link. 12 * ===================================================================== */ 13(function () { 14 'use strict'; 15 16 var VIEWER = 'digitalviewer/index.html'; 17 var SEARCH_SRC = '/search.js?v=20260713-w2'; 18 19 function el(name) { return document.querySelector('[data-p2="' + name + '"]'); } 20 function esc(s) { 21 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) { 22 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; 23 }); 24 } 25 function setText(name, value) { 26 var node = el(name); 27 if (node && value) node.textContent = value; 28 } 29 30 /* ââ Issue data (same gate as the renderer: released once releaseDate 31 has passed; issues-data.js lists newest first) ââââââââââââââââââââ */ 32 function releasedIssues() { 33 var data = window.__TAEM_ISSUES__; 34 if (!data || !Array.isArray(data.issues)) return []; 35 // Local date, not UTC: toISOString() would gate on the UTC day, which 36 // holds a new edition back for a reader east of UTC (and releases it 37 // early to the west). Matches isReleased() in taem-issues-renderer.js. 38 var now = new Date(); 39 var today = now.getFullYear() + '-' 40 + String(now.getMonth() + 1).padStart(2, '0') + '-' 41 + String(now.getDate()).padStart(2, '0'); 42 return data.issues.filter(function (issue) { 43 if (issue.format && issue.format !== 'digital') return false; 44 var gate = issue.releaseDate || issue.date; 45 return !gate || gate <= today; 46 }); 47 } 48 49 function itemHref(item) { 50 if (item.href) return item.href; 51 if (item.slug) return VIEWER + '?article=' + encodeURIComponent(item.slug); 52 if (item.videoId) return 'https://www.youtube.com/watch?v=' + encodeURIComponent(item.videoId); 53 return null; 54 } 55 56 // The Foundations hub-callout card rides in every issue's content list and 57 // has its own promo slot on this page, so it is excluded here. 58 function readable(issue) { 59 return (issue.content || []).filter(function (item) { 60 if (item.type !== 'pdf' && item.type !== 'article') return false; 61 return !/sun-series\/index\.html$/.test(item.href || ''); 62 }); 63 } 64 65 // What the centre stack may show. Wider than readable(): videos belong in 66 // the stack but must never be picked as an edition's cover story, which is 67 // why lead selection still runs off readable(). 68 function stackable(issue) { 69 return (issue.content || []).filter(function (item) { 70 if (item.type !== 'pdf' && item.type !== 'article' && item.type !== 'youtube') return false; 71 return !/sun-series\/index\.html$/.test(item.href || ''); 72 }); 73 } 74 75 /* ââ Lead card (the dominant cover story, content[0]) ââââââââââââââ */ 76 function renderLead(issue, items) { 77 var lead = items[0]; 78 var href = itemHref(lead) || 'previous-issues.html'; 79 80 setText('category', lead.category); 81 setText('issue', issue.title); 82 setText('time', lead.estimatedTime); 83 84 // [2026-07-28] audienceLevel no longer prints in the kicker. It gave 85 // every article two captions - a grey level ("Intermediate", "All 86 // levels") followed by the blue category ("Jyotisa Lessons", "Chief 87 // Editor's Column") - which read as one label repeated rather than two 88 // facts. The category is the one that tells a reader what the piece is. 89 // audienceLevel still lives in the issue data and on the article pages. 90 91 var title = el('title'); 92 if (title) title.innerHTML = '<a href="' + esc(href) + '">' + esc(lead.title) + '</a>'; 93 94 var cover = el('coverimg'); 95 var coverLink = el('coverlink'); 96 if (cover && issue.cover) { 97 cover.src = issue.cover; 98 cover.alt = issue.title + ' cover of The Astrological eMagazine'; 99 } 100 if (coverLink) coverLink.href = VIEWER + '?issue=' + encodeURIComponent(issue.id); 101 102 var orientation = el('orientation'); 103 if (orientation) { 104 orientation.textContent = 105 lead.orientationText || lead.description || issue.readerIntro || ''; 106 } 107 108 // The gloss prints only when it adds something the paragraph above lacks. 109 var gloss = el('gloss'); 110 if (gloss) { 111 var above = orientation ? orientation.textContent : ''; 112 var candidate = lead.glossaryHint || lead.description || ''; 113 gloss.textContent = candidate && candidate !== above ? candidate : ''; 114 } 115 116 // Byline node is omitted entirely when there is no real author. 117 var byline = el('byline'); 118 if (byline) { 119 if (lead.author) { 120 setText('author', lead.author); 121 byline.style.display = ''; 122 } else { 123 byline.style.display = 'none'; 124 } 125 } 126 127 var cta = el('cta'); 128 if (cta) cta.href = href; 129 130 // Optional second route: a plain HTML page alongside the viewer. Only 131 // items carrying htmlUrl get it -- the node stays hidden otherwise, the 132 // same way the byline node does above, so an issue without one renders 133 // exactly as it always has.
134 var ctaHtml = el('cta-html'); 135 if (ctaHtml) { 136 var alt = typeof lead.htmlUrl === 'string' ? lead.htmlUrl.trim() : ''; 137 if (alt) { 138 ctaHtml.href = alt; 139 ctaHtml.style.display = ''; 140 } else { 141 ctaHtml.removeAttribute('href'); 142 ctaHtml.style.display = 'none'; 143 } 144 } 145 } 146 147 /* ââ Centre medium-card stack (content[1..], spec §2.2 variant B) âââ */ 148 149 // How many earlier pieces an edition may carry into its stack. 150 var STACK_CARRY_COUNT = 5; 151 152 function stackCard(item, i) { 153 var href = itemHref(item); 154 var sizeClass = i === 0 ? ' p2-card--med-l' : ''; 155 var standfirst = item.orientationText || item.description || ''; 156 var titleInner = href 157 ? '<a href="' + esc(href) + '">' + esc(item.title) + '</a>' 158 : esc(item.title); 159 // Category only - see renderLead() for why audienceLevel was dropped. 160 return '<article class="p2-card p2-card--med' + sizeClass + '" data-s="lead.stack.item.' + (i + 1) + '">' + 161 '<p class="p2-card__kicker">' + 162 '<span class="p2-card__cat">' + esc(item.category || 'Article') + '</span></p>' + 163 '<h3 class="p2-card__title">' + titleInner + '</h3>' + 164 (standfirst ? '<p class="p2-card__standfirst">' + esc(standfirst) + '</p>' : '') + 165 (item.author ? '<p class="p2-card__byline">' + esc(item.author) + '</p>' : '') + 166 '</article>'; 167 } 168 169 // Resolve one carryForward reference - a slug or an href - to its item, 170 // wherever in the released editions it lives. 171 function findByRef(issues, ref) { 172 for (var i = 0; i < issues.length; i++) { 173 var items = readable(issues[i]); 174 for (var j = 0; j < items.length; j++) { 175 if (items[j].slug === ref || items[j].href === ref) return items[j]; 176 } 177 } 178 return null; 179 } 180 181 // The earlier pieces an edition carries into its stack. An explicit 182 // carryForward list in the issue data wins - that is an editorial choice, 183 // not a guess. Without one, the freshest readable pieces behind it. 184 function carrySelection(issues, idx, limit) { 185 var out = []; 186 var picks = (issues[idx] || {}).carryForward; 187 if (Array.isArray(picks) && picks.length) { 188 for (var p = 0; p < picks.length && out.length < limit; p++) { 189 var hit = findByRef(issues, picks[p]); 190 if (hit) out.push(hit); 191 } 192 return out; 193 } 194 for (var i = idx + 1; i < issues.length && out.length < limit; i++) { 195 var earlier = readable(issues[i]); 196 for (var j = 0; j < earlier.length && out.length < limit; j++) out.push(earlier[j]); 197 } 198 return out; 199 } 200
201 function renderStack(issue, items, issues, idx) { 202 var stack = el('stack'); 203 if (!stack) return; 204 // Everything the edition carries except its own cover story (the 205 // duplicate "The edition" band was removed), so the stack is uncapped. 206 var lead = items[0]; 207 var rest = stackable(issue).filter(function (item) { return item !== lead; }); 208 // Carried pieces sit beneath the edition's own, so an edition that is 209 // still filling out never leaves this column short. 210 var carried = carrySelection(issues || [], idx || 0, STACK_CARRY_COUNT); 211 if (!rest.length && !carried.length) { 212 stack.innerHTML = '<p class="p2-note">The rest of this edition is being released.</p>'; 213 return; 214 } 215 var html = rest.map(function (item, i) { 216 return stackCard(item, i); 217 }).join(''); 218 if (carried.length) { 219 html += '<p class="p2-note p2-note--divider">From recent editions</p>' + 220 carried.map(function (item, i) { 221 return stackCard(item, rest.length + i); 222 }).join(''); 223 } 224 stack.innerHTML = html; 225 } 226 227 // Kept for parity with preview.js; preview2 has no toc rail, so this is a 228 // no-op unless a [data-p2="toc"] node is present. 229 function renderToc(items) { 230 var toc = el('toc'); 231 if (!toc) return; 232 var rest = items.slice(1, 8); 233 if (!rest.length) { 234 toc.innerHTML = '<p class="p2-note">The rest of this edition is being released.</p>'; 235 return; 236 } 237 toc.innerHTML = rest.map(function (item) { 238 var href = itemHref(item); 239 var body = 240 '<span class="p2-card__cat">' + esc(item.category || 'Article') + '</span>' + 241 '<span class="p2-card__title">' + esc(item.title) + '</span>' + 242 (item.author ? '<span class="p2-card__byline">' + esc(item.author) + '</span>' : ''); 243 return href 244 ? '<a class="p2-card p2-card--rail" href="' + esc(href) + '">' + body + '</a>' 245 : '<div class="p2-card p2-card--rail">' + body + '</div>'; 246 }).join(''); 247 } 248 249 /* ââ "Just in": three freshest real things, edition/label chips only, 250 never a clock time (the brief forbids fabricated timestamps) ââââ */ 251 function renderJustIn(issue, items) { 252 var box = el('justin'); 253 if (!box) return; 254 255 var seq = 0; 256 function row(cat, title, href, chip) { 257 seq += 1; 258 return '<a class="p2-card p2-card--rail" data-s="justin.item.' + seq + '" href="' + esc(href) + '">' + 259 '<p class="p2-card__kicker"><span class="p2-card__cat">' + esc(cat) + '</span></p>' + 260 '<h4 class="p2-card__title">' + esc(title) + '</h4>' + 261 '<span class="p2-card__chip">' + esc(chip) + '</span>' + 262 '</a>'; 263 } 264 265 var rows = []; 266 rows.push(row('Transits', 'The Week in Jyotiá¹£a', 'weekly/index.html', 'Friday briefing')); 267 268 var sun = items.filter(function (i) { return /sun-series\/sun-/.test(i.href || ''); })[0]; 269 if (sun) { 270 rows.push(row('Foundations of Jyotisha', sun.title, itemHref(sun), 'Foundations')); 271 } else { 272 rows.push(row('Foundations of Jyotisha', 'The Sun Through the Houses', 273 'sun-series/index.html', 'Foundations')); 274 } 275 276 rows.push(row('Digital edition', issue.title, 277 VIEWER + '?issue=' + encodeURIComponent(issue.id), issue.title + ' edition')); 278 279 box.innerHTML = rows.join(''); 280 } 281 282 // The edition line derives from the data, never the calendar, so the 283 // masthead can never disagree with the edition rendered below it. 284 // (The topline now carries only the date; these are no-ops unless the 285 // slots return, kept so restoring the markup needs no JS change.) 286 function renderEdition(issue) { 287 var node = el('edition'); 288 if (node && issue) node.textContent = issue.title + ' edition'; 289 var read = el('read-edition'); 290 if (read && issue) read.href = VIEWER + '?issue=' + encodeURIComponent(issue.id); 291 } 292 293 /* ââ Vastu: the Chief Editor's standing monthly department. Latest 294 linkable Vastu pieces from released issues, newest edition first. ââ */ 295 function renderVastu(issues) { 296 var box = el('vastu'); 297 if (!box) return; 298 var picks = [];
299 issues.forEach(function (issue) { 300 (issue.content || []).forEach(function (item) { 301 if (picks.length >= 4) return; 302 // A Vastu piece names Vastu in its title, or is the Chief Editor's 303 // column and names it in its description (a column titled for its 304 // subject, e.g. "Choosing Between Two Apartments"). 305 var vastuTitle = /v[Äa]stu/i.test(item.title || ''); 306 var vastuColumn = /chief editor/i.test(item.category || '') && /v[Äa]stu/i.test(item.description || ''); 307 if (!vastuTitle && !vastuColumn) return; 308 var href = null, video = false; 309 if (item.type === 'pdf' || item.type === 'article') { 310 href = itemHref(item); 311 } else if (item.type === 'youtube' && item.videoId) { 312 // The Chief Editor's video classes carry a videoId, not a href. 313 href = 'https://www.youtube.com/watch?v=' + encodeURIComponent(item.videoId); 314 video = true; 315 } 316 if (!href) return; 317 picks.push({ href: href, title: item.title, chip: issue.title, video: video }); 318 }); 319 }); 320 if (!picks.length) { box.innerHTML = ''; return; } 321 box.innerHTML = picks.map(function (p, i) { 322 var status = p.video ? '<span class="p2-card__status">Video</span>' : ''; 323 var ext = p.video ? ' target="_blank" rel="noopener"' : ''; 324 return '<a class="p2-card p2-card--sm p2-guidecard" data-s="vastu.item.' + (i + 1) + '" href="' + esc(p.href) + '"' + ext + 325 ' data-taem-cta="vastu_department" data-taem-cta-type="' + (p.video ? 'video' : 'article') + '">' + 326 '<p class="p2-card__kicker">' + status + '<span class="p2-card__cat">Vastu</span></p>' + 327 '<h3 class="p2-card__title">' + esc(p.title) + '</h3>' + 328 '<span class="p2-card__chip">' + esc(p.chip) + '</span>' + 329 '</a>'; 330 }).join(''); 331 } 332 333 /* Mirror of slug_for() in generate_issue_pages.py. Every issue in 334 content/issues.json has a static page at /issues/<slug>.html, and the 335 slug comes from the TITLE, not the id: "July 2026" -> july-2026, whereas 336 the id is abbreviated ("jul-2026"). If the Python changes, so must this. */ 337 function issuePageHref(issue) { 338 var slug = String((issue && issue.title) || '') 339 .toLowerCase() 340 .replace(/[^a-z0-9]+/g, '-') 341 .replace(/^-+|-+$/g, ''); 342 return slug ? '/issues/' + slug + '.html' : '/issues/'; 343 } 344 345 /* ââ Archive: every released digital edition (including the current one), 346 as a cover strip above the print grid. 347 Covers link to the edition's own page, not straight into the viewer: 348 /issues/<slug>.html is the indexable table of contents and carries the 349 "read this issue" CTA into the viewer, so nothing is lost and the 350 destination is linkable and shareable. ââ */ 351 function renderEditions(issues) { 352 var box = el('editions'); 353 if (!box) return; 354 // Every released digital edition, in a 7-column grid at desktop; editions 355 // beyond the first seven wrap onto the next row. 356 box.innerHTML = issues.map(function (issue, i) { 357 var cover = issue.cover 358 ? '<img class="p2-editions__img" loading="lazy" alt="' + esc(issue.title) + ' cover" src="' + esc(issue.cover) + '">' 359 : ''; 360 return '<a class="p2-editions__item" data-s="archive.editions.item.' + (i + 1) + '"' + 361 ' href="' + esc(issuePageHref(issue)) + '"' + 362 ' data-taem-cta="archive_digital_edition" data-taem-cta-type="issue">' + 363 cover + 364 '<span class="p2-editions__label">' + esc(issue.title) + '</span>' + 365 '</a>'; 366 }).join(''); 367 } 368 369 // The date line: British day-month order (e.g. "Tuesday, 21 July 2026"). 370 // en-GB already yields "21 July 2026"; the weekday is prepended by the 371 // options object, giving the full string in one call. 372 function renderDate() { 373 var node = el('date'); 374 if (!node) return; 375 try { 376 node.textContent = new Date().toLocaleDateString('en-GB', 377 { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
378 } catch (e) { 379 node.textContent = ''; 380 } 381 } 382 383 /* ââ Masthead nav from the live nav data âââââââââââââââââââââââââââ */ 384 function renderNav() { 385 var nav = el('nav'); 386 var items = window.TAEM_NAV_ITEMS; 387 if (!nav) return; 388 if (!Array.isArray(items) || !items.length) { nav.style.display = 'none'; return; } 389 390 nav.innerHTML = items.map(function (item, i) { 391 var sid = 'masthead.nav.item.' + (i + 1); 392 if (item.children && item.children.length) { 393 return '<div class="p2-nav-group" data-nav-group="' + i + '" data-s="' + sid + '">' + 394 '<button type="button" class="p2-nav-toggle" aria-expanded="false" aria-haspopup="true">' + 395 esc(item.label) + '<span class="p2-nav-caret" aria-hidden="true"></span></button>' + 396 '<div class="p2-nav-panel">' + 397 item.children.map(function (child, ci) { 398 return '<a data-s="' + sid + '.child.' + (ci + 1) + '" href="' + esc(child.href) + '">' + esc(child.label) + '</a>'; 399 }).join('') + 400 '</div></div>'; 401 } 402 return '<a class="p2-nav-link" data-s="' + sid + '" href="' + esc(item.href) + '">' + esc(item.label) + '</a>'; 403 }).join(''); 404 405 // Click-to-toggle for touch and keyboard; hover still works via CSS. 406 var groups = nav.querySelectorAll('.p2-nav-group'); 407 function closeAll(except) { 408 groups.forEach(function (g) { 409 if (g !== except) { 410 g.classList.remove('open'); 411 g.querySelector('.p2-nav-toggle').setAttribute('aria-expanded', 'false'); 412 } 413 }); 414 } 415 groups.forEach(function (group) { 416 var toggle = group.querySelector('.p2-nav-toggle'); 417 toggle.addEventListener('click', function (e) { 418 e.stopPropagation(); 419 var opening = !group.classList.contains('open'); 420 closeAll(opening ? group : null); 421 group.classList.toggle('open', opening); 422 toggle.setAttribute('aria-expanded', String(opening)); 423 }); 424 }); 425 document.addEventListener('click', function () { closeAll(null); }); 426 document.addEventListener('keydown', function (e) { 427 if (e.key === 'Escape') closeAll(null); 428 }); 429 } 430 431 /* ââ Instruments (rail + footer) from the Jyotisa Tools group ââââââ */ 432 var INSTRUMENT_KINDS = { 433 'Monthly Panchanga': 'Almanac', 434 'Daily Panchanga': 'Almanac', 435 'Vedic Ephemeris': 'Tables', 436 'Default Horoscopes': 'Charts', 437 'Atmakaraka Calculator': 'Chara kÄraka' 438 }; 439 var INSTRUMENT_FALLBACK = [ 440 { label: 'Monthly Panchanga', href: '/monthly-panchanga.html' },
441 { label: 'Daily Panchanga', href: '/panchanga.html' }, 442 { label: 'Vedic Ephemeris', href: '/ephemeris.html' }, 443 { label: 'Default Horoscopes', href: '/horoscope_embed_project/default_charts.html' }, 444 { label: 'Atmakaraka Calculator', href: '/horoscope_embed_project/atmakaraka.html' } 445 ]; 446 447 function toolItems() { 448 var items = window.TAEM_NAV_ITEMS; 449 if (Array.isArray(items)) { 450 for (var i = 0; i < items.length; i++) { 451 var label = (items[i].label || '').toLowerCase(); 452 if (items[i].children && label.indexOf('tools') !== -1) return items[i].children; 453 } 454 } 455 return INSTRUMENT_FALLBACK; 456 } 457 458 function renderInstruments() { 459 var tools = toolItems(); 460 var rail = el('instruments'); 461 if (rail) { 462 rail.innerHTML = tools.map(function (tool) { 463 // The kind map keys are ASCII; fold diacritics before lookup. 464 var plain = tool.label.normalize('NFD').replace(/[Ì-ͯ]/g, ''); 465 var kind = INSTRUMENT_KINDS[plain] || 'Instrument'; 466 return '<li><a href="' + esc(tool.href) + '" data-taem-cta="preview_instrument" data-taem-cta-type="tool">' + 467 '<span>' + esc(tool.label) + '</span><em>' + esc(kind) + '</em></a></li>'; 468 }).join(''); 469 } 470 var footer = el('footer-instruments'); 471 if (footer) { 472 footer.innerHTML = tools.map(function (tool) { 473 return '<li><a href="' + esc(tool.href) + '">' + esc(tool.label) + '</a></li>'; 474 }).join(''); 475 } 476 } 477 478 /* ââ Search: lazy-load, then open (same pattern as the live header) ââ */ 479 function openSearch() { 480 if (window.TAEM_SEARCH && typeof window.TAEM_SEARCH.open === 'function') { 481 window.TAEM_SEARCH.open(); 482 return; 483 } 484 var existing = document.getElementById('taem-search-script'); 485 if (existing) return; // still loading; the load handler below will open 486 var s = document.createElement('script'); 487 s.id = 'taem-search-script'; 488 s.src = SEARCH_SRC; 489 s.onload = function () { 490 if (window.TAEM_SEARCH) window.TAEM_SEARCH.open(); 491 }; 492 document.head.appendChild(s); 493 } 494 495 /* ââ Enhance the partial's details-based nav groups: opening one closes 496 its siblings, Escape closes everything including the mobile drawer. 497 (Generated issue pages skip this file and rely on native behavior.) ââ */ 498 function wireNavGroups() { 499 var groups = [].slice.call(document.querySelectorAll('details.p2-nav-group')); 500 groups.forEach(function (g) { 501 g.addEventListener('toggle', function () { 502 if (g.open) groups.forEach(function (o) { if (o !== g) o.open = false; }); 503 }); 504 }); 505 document.addEventListener('keydown', function (e) { 506 if (e.key !== 'Escape') return; 507 groups.forEach(function (o) { o.open = false; }); 508 var drawer = document.getElementById('p2-navdrawer-check'); 509 if (drawer) drawer.checked = false; 510 }); 511 } 512 513 function wireSearch() { 514 // The masthead search control is a plain <a href="/search.html"> in the 515 // shared partial (works with zero JS on generated pages); here it is 516 // upgraded to open the overlay instead. 517 var btn = document.getElementById('p2-search-btn'); 518 if (btn) btn.addEventListener('click', function (e) { 519 e.preventDefault(); 520 openSearch(); 521 }); 522 document.addEventListener('keydown', function (e) { 523 if (e.key !== '/') return; 524 var t = e.target; 525 if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || 526 t.tagName === 'SELECT' || t.isContentEditable)) return; 527 e.preventDefault(); 528 openSearch(); 529 }); 530 } 531 532 /* ââ Translate control ââââââââââââââââââââââââââââââââââââââââââââââ 533 [MOVED 2026-07-28] The wiring that lived here (restored 2026-07-26) 534 only ran on the homepage because only the homepage loads preview2.js, 535 which left the control permanently hidden on every other page. It now 536 lives in /taem-translate.js, loaded with defer by the masthead partial 537 itself, so every page carrying the chrome gets a working control and 538 a saved language is re-applied site-wide. Do not re-add wiring here. */ 539 540 /* ââ Sticky-compact masthead ââââââââââââââââââââââââââââââââââââââ 541 [MOVED 2026-07-28] Lived here, so it ran on the homepage only: every 542 other page kept a full-height masthead pinned over the viewport. It 543 now lives in /taem-translate.js, which the masthead partial loads on 544 every page, alongside the --p2-masthead-h publisher that page-level 545 sticky bars offset against. Do not re-add it here - two observers 546 would fight over the same class. */ 547 548 /* ââ Skip link (bare #fragments break under <base>) ââââââââââââââââ */ 549 function wireSkipLink() { 550 var skip = document.getElementById('p2-skip'); 551 var main = document.getElementById('main-content'); 552 if (!skip || !main) return; 553 skip.addEventListener('click', function (e) { 554 e.preventDefault(); 555 main.setAttribute('tabindex', '-1'); 556 main.focus({ preventScroll: false }); 557 main.scrollIntoView(); 558 }); 559 } 560 561 /* ââ Boot âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 562 function start() { 563 renderDate(); 564 renderNav(); // no-op now: the nav is baked into the shared partial 565 wireNavGroups(); 566 renderInstruments(); 567 wireSearch(); 568 // translate is wired by /taem-translate.js (masthead partial), not here 569 // masthead compaction is wired by /taem-translate.js, not here 570 wireSkipLink(); 571 572 var issues = releasedIssues(); 573 renderVastu(issues); 574 renderEditions(issues); 575 for (var i = 0; i < issues.length; i++) { 576 var items = readable(issues[i]); 577 if (items.length) { 578 renderEdition(issues[i]); 579 renderLead(issues[i], items); 580 renderStack(issues[i], items, issues, i); 581 renderJustIn(issues[i], items); 582 return; 583 } 584 } 585 } 586 587 if (document.readyState === 'loading') { 588 document.addEventListener('DOMContentLoaded', start, { once: true });
589 } else { 590 start(); 591 } 592})();
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.