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.0"> 6 <title>TheLatest.com</title> 7 <link rel="icon" href="/img/favicon.svg" type="image/svg+xml"> 8 <link rel="stylesheet" href="css/lateststyles.css"> 9</head> 10<body> 11<div id="header"></div> 12<!-- end header--> 13 14 15<!-- =============== LOGO + TITLE SECTION =============== --> 16<section class="logo-section"> 17<div class="container"> 18 <h1 class="title"> 19 <span class="black">THE</span> 20 <span class="red">LATEST</span> 21 </h1> 22 23 <!-- SEARCH BOX --> 24<div class="search-box"> 25 <input type="text" id="newsSearchInput" placeholder="Search"> 26 <div class="icon-wrapper" onclick="searchNews()"> 27 <span class="search-icon"></span> 28 </div> 29</div> 30 31<div class="news-results-wrapper"> 32 <div id="newsResults" class="news-grid"></div> 33</div> 34</div> 35</section> 36<div class="container"> 37<div class="category-section"> 38<div class="top-news-containers category-left"> 39 <div class="section-header"> 40 <h2>Top Stories</h2> 41</div> 42 43 <!-- dynamic category slider --> 44 <div class="category-wrapper" id="categoryWrapper"> 45 <button class="cat-arrow left" id="leftArrow" aria-label="Scroll left">â¹</button> 46 <div class="category-slider" id="categorySlider" role="tablist" aria-label="News categories"></div> 47 <button class="cat-arrow right" id="rightArrow" aria-label="Scroll right">âº</button> 48 </div> 49 50 <!-- news card --> 51 <div class="news-card" id="newsCard" aria-live="polite"> 52 <img class="news-img" id="newsImg" src="img/banner.png" alt=""> 53 <div class="news-content"> 54 <h3 class="news-title" id="newsTitle"></h3> 55 <p class="news-desc" id="newsDesc"></p> 56 <div class="news-meta" id="newsMeta"></div> 57 <a class="news-link" id="newsLink" href="#" target="_blank" rel="noopener">Read full story â</a> 58 </div> 59 </div> 60 61</div>
62<script> 63/* =============================== 64 CONFIG 65=============================== */ 66const RSS_URL = "https://rss.app/feeds/_4MQIsURyoARvPa0M.xml"; 67 68/* DOM */ 69const categorySlider = document.getElementById("categorySlider"); 70const leftArrow = document.getElementById("leftArrow"); 71const rightArrow = document.getElementById("rightArrow"); 72 73const newsImg = document.getElementById("newsImg"); 74const newsTitle = document.getElementById("newsTitle"); 75const newsDesc = document.getElementById("newsDesc"); 76const newsMeta = document.getElementById("newsMeta"); 77const newsLink = document.getElementById("newsLink"); 78 79/* GLOBALS */ 80let rssItems = []; 81let categories = []; 82 83/* =============================== 84 Helpers 85=============================== */ 86function getDomainKey(urlStr){ 87 try{ 88 const url = new URL(urlStr); 89 let host = url.hostname.toLowerCase().replace(/^www\./,''); 90 let parts = host.split("."); 91 if(parts.length>=3 && parts.at(-1).length==2){ 92 return parts.at(-3); 93 } 94 return parts.at(-2); 95 }catch(e){return "";} 96} 97 98function timeAgo(d){ 99 const date = new Date(d); 100 if(isNaN(date)) return ""; 101 const diff = Date.now()-date; 102 const mins = Math.floor(diff/60000); 103 if(mins<1) return "Just now"; 104 if(mins<60) return mins+" mins ago"; 105 const hrs = Math.floor(mins/60); 106 if(hrs<24) return hrs+" hrs ago"; 107 const days = Math.floor(hrs/24); 108 return days+" days ago"; 109} 110 111function extractImage(item){ 112 let media = item.querySelector("media\\:content, enclosure"); 113 if(media) return media.getAttribute("url"); 114 115 let d = item.querySelector("description")?.textContent || ""; 116 let t = document.createElement("div"); 117 t.innerHTML = d; 118 let img = t.querySelector("img"); 119 return img ? img.src : null; 120} 121 122function extractText(item){ 123 let d = item.querySelector("description")?.textContent || ""; 124 let t = document.createElement("div"); 125 t.innerHTML = d; 126 return t.textContent.trim().replace(/\s+/g," "); 127} 128 129/* =============================== 130 Parse RSS XML 131=============================== */ 132function parseRSS(xmlText){ 133 const parser = new DOMParser(); 134 const xml = parser.parseFromString(xmlText, "text/xml"); 135 const items = [...xml.querySelectorAll("item")]; 136 137 return items.map(n => { 138 const title = n.querySelector("title")?.textContent.trim() || ""; 139 const link = n.querySelector("link")?.textContent.trim() || ""; 140 return { 141 title, 142 link, 143 pubDate: n.querySelector("pubDate")?.textContent || "", 144 domainKey: getDomainKey(link), 145 image: extractImage(n) || "img/banner.png", 146 description: extractText(n) 147 }; 148 }); 149} 150 151/* =============================== 152 Build Categories 153=============================== */ 154function buildCategories(items){ 155 const map = new Map(); 156 items.forEach(it=>{ 157 if(!map.has(it.domainKey)){ 158 map.set(it.domainKey, it.domainKey.toUpperCase()); 159 } 160 }); 161 return [...map.entries()].map(([key,label])=>({key,label})); 162} 163 164/* =============================== 165 Build category buttons 166=============================== */ 167function buildCategoryButtons(){ 168 categorySlider.innerHTML = ""; 169 categories.forEach(cat=>{ 170 const btn = document.createElement("button"); 171 btn.className="cat-item"; 172 btn.dataset.key = cat.key; 173 btn.textContent = cat.label; 174 btn.onclick = ()=>{
175 setActive(btn); 176 loadCategory(cat.key); 177 }; 178 categorySlider.appendChild(btn); 179 }); 180 181 const first = categorySlider.querySelector(".cat-item"); 182 if(first){ 183 setActive(first); 184 loadCategory(first.dataset.key); 185 } 186} 187 188/* =============================== 189 Set Active Button (NO vertical scroll) 190=============================== */ 191function setActive(btn){ 192 categorySlider.querySelectorAll(".cat-item").forEach(b=>b.classList.remove("active")); 193 btn.classList.add("active"); 194 195 // Horizontal-only centering (no page jump) 196 categorySlider.scrollTo({ 197 left: btn.offsetLeft - categorySlider.offsetWidth / 2 + btn.offsetWidth / 2, 198 behavior: "smooth" 199 }); 200} 201 202/* =============================== 203 Load category 204=============================== */ 205function loadCategory(key){ 206 const filtered = rssItems.filter(it=>it.domainKey === key); 207 if(filtered.length === 0){ 208 renderEmpty(key); 209 return; 210 } 211 renderItem(filtered[0]); 212} 213 214function renderItem(it){ 215 newsTitle.textContent = it.title; 216 newsDesc.textContent = it.description; 217 newsImg.src = it.image; 218 newsLink.href = it.link; 219 newsMeta.textContent = `${it.domainKey}.com ⢠${timeAgo(it.pubDate)}`; 220} 221 222function renderEmpty(key){ 223 newsTitle.textContent = `No news for ${key}`; 224 newsDesc.textContent = ""; 225 newsImg.src = "img/banner.png"; 226 newsLink.href="#"; 227 newsMeta.textContent=""; 228} 229 230/* =============================== 231 ARROW SCROLL (NO scrollIntoView) 232=============================== */ 233function scrollCategories(direction){ 234 const items = [...document.querySelectorAll(".cat-item")]; 235 const activeIndex = items.findIndex(btn => btn.classList.contains("active")); 236 237 let newIndex = activeIndex + direction; 238 239 if(newIndex < 0) newIndex = items.length - 1; 240 if(newIndex >= items.length) newIndex = 0; 241 242 setActive(items[newIndex]); 243 loadCategory(items[newIndex].dataset.key); 244 245 // horizontal-only scroll 246 categorySlider.scrollTo({ 247 left: items[newIndex].offsetLeft - categorySlider.offsetWidth / 2 + items[newIndex].offsetWidth / 2, 248 behavior: "smooth" 249 }); 250} 251 252/* =============================== 253 Arrow events 254=============================== */ 255leftArrow.addEventListener("click", function(e){ 256 e.preventDefault(); 257 e.stopPropagation(); 258 this.blur(); 259 scrollCategories(-1); 260}); 261 262rightArrow.addEventListener("click", function(e){ 263 e.preventDefault(); 264 e.stopPropagation(); 265 this.blur(); 266 scrollCategories(1); 267}); 268 269/* =============================== 270 INIT 271=============================== */ 272(async function init(){ 273 try{ 274 const r = await fetch(RSS_URL); 275 const xml = await r.text(); 276 rssItems = parseRSS(xml); 277 categories = buildCategories(rssItems); 278 buildCategoryButtons(); 279 } catch(err){ 280 console.error(err); 281 renderEmpty("ALL"); 282 } 283})(); 284</script>
284 285 286 <!-- RIGHT (new sidebar video) --> 287 288 <div class="category-right"> 289 <div class="section-header"> 290 <h2>The Latest Video</h2> 291</div> 292 <div class="video-slider"> 293 294 <button class="video-arrow left">‹</button> 295 296 <div class="video-track"> 297 <!-- Slide 001 LATEST VOCAL--> 298 <div class="video-slide"> 299 <iframe src="https://www.youtube.com/embed/SBTRrhQzU4k?si=vbZ3xRH-0nwIDjdk" width="640" height="480"></iframe> 300 <h2 class="video-title" style="text-align:center;">Vokal: What Should Go Inside that Empty Storefront?</h2> 301 </div> 302 <!-- Slide 2 (MP4) JEFF --> 303 <div class="video-slide"> 304 <video controls> 305 <source src="/img/video2.mp4" type="video/mp4"> 306 </video> 307 <h2 class="video-title" style="text-align:center;"> All About THE LATEST</h2> 308 </div> 309 <!-- Slide 001 LATEST ICE --> 310 <div class="video-slide"> 311 <iframe src="https://www.youtube.com/embed/69LkGZtYK_I?si=3vxtKh22N4Gxc1FM" width="640" height="480"></iframe> 312 <h2 class="video-title" style="text-align:center;"> ICE in Minneapolis: The Latest</h2> 313 </div> 314 <!-- Slide 001 LATEST EPSTEIN --> 315 <div class="video-slide"> 316 <iframe src="https://www.youtube.com/embed/PZZ9neLj2xM?si=6Gg2GuWZc49Rprfb" width="640" height="480"></iframe> 317 <h2 class="video-title" style="text-align:center;"> Gen Z's Take on Jeffrey Epstein: The Latest</h2> 318 </div> 319 <!-- Slide 001 LATEST IRAN--> 320 <div class="video-slide"> 321 <iframe src="https://www.youtube.com/embed/BW7cHuIXg7Y?si=HDCYf0HYAmlHIyZ6" width="640" height="480"></iframe> 322 <h2 class="video-title" style="text-align:center;"> Iran: What's Next?</h2> 323 </div> 324 <!-- Slide 001 LATESTWOMEN HISTORY --> 325 <div class="video-slide"> 326 <iframe src="https://www.youtube.com/embed/9ZCSQtCmW9g?si=h52yLE3K4Sm2TZP3" width="640" height="480"></iframe> 327 <h2 class="video-title" style="text-align:center;"> Women's History: The Latest </h2> 328 </div> 329 </div> 330 331 <button class="video-arrow right">›</button> 332 333 </div> 334</div> 335 336</div> 337<style>.video-slider { 338 position: relative; 339 width: 100%; 340 overflow: hidden; 341} 342.video-track { 343 display: flex; 344 transition: transform 0.4s ease; 345} 346 347.video-slide { 348 min-width: 100%; 349 box-sizing: border-box; 350} 351 352/* Video + iframe styling */ 353.video-slide iframe, 354.video-slide video { 355 width: 100%; 356 height: 380px; 357 border-radius: 10px; 358 object-fit: cover; 359} 360 361/* Arrows */ 362.video-arrow { 363 position: absolute; 364 top: 50%; 365 transform: translateY(-50%); 366 background: #ba1717; 367 color: #fff; 368 border: none; 369 width: 40px; 370 height: 40px; 371 border-radius: 50%; 372 cursor: pointer; 373 z-index: 2; 374} 375 376.video-arrow.left { 377 left: 10px; 378} 379 380.video-arrow.right { 381 right: 10px; 382}</style>
383<script> 384const track = document.querySelector('.video-track'); 385const slides = document.querySelectorAll('.video-slide'); 386let index = 0; 387 388document.querySelector('.video-arrow.right').onclick = () => { 389 if (index < slides.length - 1) { 390 index++; 391 track.style.transform = `translateX(-${index * 100}%)`; 392 } 393}; 394 395document.querySelector('.video-arrow.left').onclick = () => { 396 if (index > 0) { 397 index--; 398 track.style.transform = `translateX(-${index * 100}%)`; 399 } 400}; 401</script>
401 402<style>/* MAIN LAYOUT */ 403.category-section { 404 display: flex; 405 align-items: flex-start; /* important */ 406 gap: 30px; 407} 408 409/* LEFT 60% */ 410.category-left { 411 width: 50%; 412} 413 414.category-left { 415 flex: 1; 416} 417 418.category-right { 419 flex: 1.2; /* makes video area wider */ 420} 421.category-right video { 422 width: 100%; 423 border-radius: 10px; 424 object-fit: cover; 425} 426 427/* Slight adjustment so slider fits nicely */ 428.category-wrapper { 429 width: 100%; 430} 431 432/* RESPONSIVE */ 433@media (max-width: 768px) { 434 .category-section { 435 flex-direction: column; 436 } 437 438 .category-left, 439 .category-right { 440 width: 100%; 441 } 442}</style> 443<div class="section-header"> 444 <h2>Roundup</h2> 445</div> 446 447<rssapp-wall id="_HFJNL4vGeQQxp8mu"></rssapp-wall>
447<script src="https://widget.rss.app/v1/wall.js" type="text/javascript" async></script>
447 448<div class="section-header"> 449 <h2>AI news</h2> 450</div> 451 452<rssapp-wall id="_4HRGgAFWTK8t9eeQ"></rssapp-wall>
452<script src="https://widget.rss.app/v1/wall.js" type="text/javascript" async></script>
452 453 <!-- TOP STORIES BLOCK --> 454 455 <div class="section-header"> 456 <h2>Gen Z</h2> 457 </div> 458 459 <rssapp-wall id="_7GVgiiUfDYCERtnD"></rssapp-wall>
459<script src="https://widget.rss.app/v1/wall.js" type="text/javascript" async></script>
459 460 461 462<div class="podcast-container"> 463 464 <div class="section-header"> 465 <h2>Podcasts</h2> 466 </div> 467 468 <div id="podcast-grid" class="podcast-grid"> 469 <!-- Videos will load here --> 470 </div> 471 472 <div class="podcast-footer"> 473 <a href="https://www.youtube.com/@ournationalconversation/videos" 474 target="_blank" class="view-all-btn"> 475 View All Episodes â 476 </a> 477 </div> 478 479</div> 480 481 482</div><!-- end container --> 483<div id="footer"></div>
484<script>async function includeHTML(id, file){ 485 const el = document.getElementById(id); 486 const html = await fetch(file).then(r => r.text()); 487 el.innerHTML = html; 488} 489 490document.addEventListener("DOMContentLoaded", function(){ 491 includeHTML("header", "header.html"); 492 includeHTML("footer", "footer.html"); 493}); 494</script>
vendor: 2 bytes, line 494
494
495<script> 496async function loadPodcasts() { 497 try { 498 const rssUrl = "https://rss.app/feeds/_prQqC2BPRFz8BR6o.xml"; 499 const response = await fetch(rssUrl); 500 const text = await response.text(); 501 502 const xml = new DOMParser().parseFromString(text, "text/xml"); 503 const items = [...xml.querySelectorAll("item")].slice(0, 3); 504 505 const container = document.getElementById("podcast-grid"); 506 507 items.forEach(item => { 508 const title = item.querySelector("title").textContent; 509 const link = item.querySelector("link").textContent; 510 511 const videoId = link.split("v=")[1]; 512 513 const card = document.createElement("div"); 514 card.classList.add("podcast-card"); 515 card.style.position = "relative"; 516 517 const iframe = document.createElement("iframe"); 518 iframe.src = `https://www.youtube.com/embed/${videoId}`; 519 iframe.width = "100%"; 520 iframe.height = "250"; 521 iframe.
521setAttribute("allowfullscreen", "true"); 522 iframe.setAttribute("loading", "lazy"); 523 524 // critical: disable iframe controls 525 iframe.style.pointerEvents = "none"; 526 527 // overlay 528 const overlay = document.createElement("div"); 529 overlay.style.position = "absolute"; 530 overlay.style.top = "0"; 531 overlay.style.left = "0"; 532 overlay.style.width = "100%"; 533 overlay.style.height = "250px"; 534 overlay.style.cursor = "pointer"; 535 536 overlay.addEventListener("click", () => { 537 window.open(link, "_blank"); 538 }); 539 540 const h3 = document.createElement("h3"); 541 h3.textContent = title; 542 543 card.appendChild(iframe); 544 card.appendChild(overlay); 545 card.appendChild(h3); 546 547 container.appendChild(card); 548 }); 549 550 } catch (err) { 551 console.error("Podcast Load Error:", err); 552 } 553} 554 555loadPodcasts(); 556</script>
vendor: 2 bytes, line 556
556
557<script> 558/* ========= TIME AGO ========= */ 559function timeAgo(dateString) { 560 const now = new Date(); 561 const past = new Date(dateString); 562 const diff = now - past; 563 564 const min = Math.floor(diff / 60000); 565 const hr = Math.floor(min / 60); 566 const day = Math.floor(hr / 24); 567 568 if (min < 1) return "Just now"; 569 if (min < 60) return min + "m"; 570 if (hr < 24) return hr + "h"; 571 return day + "d"; 572} 573 574/* ========= NORMALIZER â remove HTML, CDATA, entities, accents ========= */ 575function normalize(str) { 576 return (str || "") 577 .replace(/<!\[CDATA\[(.*?)\]\]>/gs, '$1') 578 .replace(/<[^>]+>/g, "") 579 .replace(/&/g, "&") 580 .replace(/ /g, " ") 581 .replace(/'/g, "'") 582 .replace(/"/g, '"') 583 .normalize("NFD").replace(/[\u0300-\u036f]/g, "") // remove accents 584 .replace(/\s+/g, " ") 585 .toLowerCase() 586 .trim(); 587} 588 589/* ========= RSS FEEDS ========= */ 590const feeds = [ 591 "https://rss.app/feeds/_dvflTF17jBYVShP3.xml", 592 "https://rss.app/feeds/_iIjbt3XTnFFpU0Cv.xml", 593 "https://rss.app/feeds/6iOkEIv8cZkmdYOD.xml", 594 "https://rss.app/feeds/tHgBWweO6AKTNDAN.xml", 595 "https://rss.app/feeds/tTWnpqRL1kY8uxZD.xml" 596]; 597 598 599/* ========= MAIN SEARCH FUNCTION ========= */ 600async function searchNews(limit = 0) { 601 const input = document.getElementById("newsSearchInput"); 602 const keyword = normalize(input.value); 603 const resultsDiv = document.getElementById("newsResults"); 604 605 if (!keyword) { 606 resultsDiv.innerHTML = ""; 607 return; 608 } 609 610 if (!limit) resultsDiv.innerHTML = "Searching..."; 611 612 let allItems = []; 613 614 // FETCH ALL FEEDS 615 for (const url of feeds) { 616 try { 617 const res = await fetch(url); 618 const text = await res.text(); 619 const parser = new DOMParser(); 620 const xml = parser.parseFromString(text, "text/xml"); 621 const items = xml.querySelectorAll("item"); 622 items.forEach(item => allItems.push(item)); 623 } catch (e) { 624 console.log("Feed failed:", url); 625 } 626 } 627 628 let parsed = []; 629 630 allItems.forEach(item => { 631 const title = item.querySelector("title")?.textContent || ""; 632 const link = item.querySelector("link")?.textContent || ""; 633 634 const descNode = item.querySelector("description")?.textContent || ""; 635 const contentNode = item.querySelector("content\\:encoded")?.textContent || ""; 636 const fullDescription = descNode + " " + contentNode; 637 638 const pubDate = item.querySelector("pubDate")?.textContent || 639 item.querySelector("published")?.textContent || ""; 640 641 let image = ""; 642 const mediaNode = item.querySelector("media\\:content"); 643 if (mediaNode) image = mediaNode.getAttribute("url"); 644 if (!image) { 645 const imgMatch = descNode.match(/<img[^>]+src="([^">]+)"/); 646 if (imgMatch) image = imgMatch[1]; 647 } 648 649 // EXTRACT SOURCE NAME 650 const source = item.querySelector("dc\\:creator")?.textContent || 651 item.querySelector("author")?.textContent || 652 (function(){ 653 try { return new URL(link).hostname.replace("www.", ""); } 654 catch { return "Unknown"; } 655 })(); 656 657 parsed.push({ 658 title, 659 link, 660 fullDescription, 661 pubDate: pubDate ? new Date(pubDate).getTime() : 0, 662 image, 663 source 664 }); 665 }); 666 667 // REMOVE DUPLICATES 668 parsed = parsed.filter((item, index, self) => 669 index === self.findIndex(t => t.link === item.link) 670 ); 671 672 // MULTI-WORD SEARCH 673 const keywords = keyword.split(" ").filter(k => k.length > 0); 674 parsed = parsed.filter(item => { 675 const normTitle = normalize(item.title); 676 const normDesc = normalize(item.fullDescription); 677 return keywords.some(k => normTitle.includes(k) || normDesc.includes(k)); 678 });
679 680 // SORT NEWEST FIRST 681 parsed.sort((a, b) => b.pubDate - a.pubDate); 682 683 if (limit) parsed = parsed.slice(0, limit); 684 685 // RENDER RESULTS 686 let html = ""; 687 parsed.forEach(item => { 688 html += ` 689 <a class="news-card" href="${item.link}" target="_blank"> 690 <div class="news-title">${item.title}</div> 691 ${item.image ? `<img src="${item.image}" />` : ""} 692 <div class="news-meta">${item.source} ⢠${timeAgo(item.pubDate)}</div> 693 </a> 694 `; 695 }); 696 697 resultsDiv.innerHTML = html || `<div class="no-results">No news found.</div>`; 698 resizeAllGridItems(); 699 watchImagesAndResize(); 700} 701 702/* ========= INPUT EVENTS ========= */ 703document.addEventListener("DOMContentLoaded", function () { 704 const input = document.getElementById("newsSearchInput"); 705 let debounceTimer = null; 706 707 input.addEventListener("keydown", function (e) { 708 if (e.key === "Enter") { 709 e.preventDefault(); 710 searchNews(); 711 } 712 }); 713 714 input.addEventListener("input", function () { 715 clearTimeout(debounceTimer); 716 if (input.value.trim() === "") { 717 document.getElementById("newsResults").innerHTML = ""; 718 return; 719 } 720 debounceTimer = setTimeout(() => { 721 searchNews(); 722 }, 400); 723 }); 724}); 725 726/* ========= GRID RESIZE ========= */ 727function resizeGridItem(item) { 728 const grid = document.querySelector('.news-grid'); 729 if (!grid) return; 730 const rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows')); 731 const gap = parseInt(window.getComputedStyle(grid).getPropertyValue('gap')) || 0; 732 const itemHeight = item.getBoundingClientRect().height; 733 const span = Math.ceil((itemHeight + gap) / (rowHeight + gap)); 734 item.style.gridRowEnd = 'span ' + span; 735} 736 737function resizeAllGridItems() { 738 document.querySelectorAll('.news-card').forEach(item => resizeGridItem(item)); 739} 740 741function watchImagesAndResize() { 742 document.querySelectorAll('.news-card img').forEach(img => { 743 if (img.complete) resizeGridItem(img.closest('.news-card')); 744 else { 745 img.addEventListener('load', () => resizeGridItem(img.closest('.news-card'))); 746 img.addEventListener('error', () => resizeGridItem(img.closest('.news-card'))); 747 } 748 }); 749} 750</script>
750 751 752 753</body> 754</html>
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.