Context: Era-Echo

The Era-Echo widget serves as an elegant bridge between historical records and the modern reader. It is a high-performance, interactive historical annotation tool designed to enrich text documents by bridging the gap between historical dates and verified facts.

By automatically identifying dates within your text and enabling real-time interaction, it transforms static blog posts into dynamic, research-oriented documents.

It effectively solves the problem of chronological detachment, allowing visitors to instantly grasp the atmosphere, or major events of the era they are reading about without leaving your site.

Test the functionality: Clicking on highlighted years will trigger the context lookup, providing a snapshot of history directly within the page.

The trajectory of human space exploration is marked by monumental leaps, beginning in 1957 when the Soviet Union launched Sputnik 1, the world's first artificial satellite, an event that officially ignited the Cold War-era Space Race. Just over a decade later, the paradigm shifted from robotic orbiters to unprecedented human achievement when, in 1969, NASA's Apollo 11 mission successfully landed Neil Armstrong and Buzz Aldrin on the Moon. The quest to understand the cosmos expanded further beyond our immediate celestial neighbor in 1990 with the deployment of the Hubble Space Telescope, an instrument that revolutionized astronomy by providing deep-field views that fundamentally changed our understanding of the universe's age and expansion.

Within the popover, users have access to an integrated refresh button. This allows the user to cycle through different events for the same year without needing to reload the page.

The widget also features a background preloader that silently fetches event data for every year found on the page immediately upon loading. This ensures that when a user clicks a link, the information is displayed near-instantaneously.

As we navigate through time, it is vital to maintain tools that keep our archives relevant and engaging. The Era-Echo widget ensures that whether a reader is looking at a piece written yesterday or a decade ago, they have the necessary context to appreciate the narrative.
Context: Era-Echo Widget
<script>
(function() {
    function initYearWidget() {
        const popover = document.createElement('div');
        popover.id = 'era-widget-popover';
        popover.style.cssText = `
            display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); 
            width: 340px; max-height: 400px; overflow-y: auto; background: #fff; border: 1px solid #ddd; 
            box-shadow: 0 15px 35px rgba(0,0,0,0.25); border-radius: 8px; padding: 20px; 
            font-family: system-ui, sans-serif; z-index: 999999; color: #333;
        `;
        
        popover.innerHTML = `
            <div id="era-content-container"></div>
            <div style="display: flex; justify-content: space-between; align-items: center; margin-top: 15px; min-height: 24px;">
                <div id="era-refresh-container"></div>
                <a id="sd-referral-auth" href="https://www.seanduffy.uk/" target="_blank" title="Visit seanduffy.uk" style="color: #aaa; transition: color 0.2s; display: flex; align-items: center;" onmouseover="this.style.color='#0066cc'" onmouseout="this.style.color='#aaa'">
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>
                </a>
            </div>
        `;
        document.body.appendChild(popover);

        function verifyIntegrity() {
            const link = document.getElementById('sd-referral-auth');
            if (!link || link.getAttribute('href') !== 'https://www.seanduffy.uk/') {
                console.error('Era Widget: Integrity compromised. Execution halted.');
                return false;
            }
            return true;
        }

        if (!verifyIntegrity()) return;

        const yearRegex = /(?<![\$\€\£\¥\u00A2\u00A5])\b([1-2]\d{3})\b(?!\.\d|%)/g;
        const currentYear = new Date().getFullYear();
        const factCache = {}; 
        const uniqueYears = new Set(); 
        
        const svgIcon = `<svg style="display:inline-block; vertical-align:middle; margin-left:4px; cursor:pointer;" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#0066cc" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`;

        async function fetchFactsArray(year) {
            try {
                const response = await fetch(`https://en.wikipedia.org/w/api.php?action=query&prop=extracts&explaintext=1&titles=${year}&format=json&origin=*`);
                if (!response.ok) throw new Error("Network error");
                
                const data = await response.json();
                const pages = data.query.pages;
                const pageId = Object.keys(pages)[0];
                
                if (pageId === "-1") return [];
                const text = pages[pageId].extract;

                const eventsMatch = text.match(/==\s*Events\s*==\n([\s\S]*?)(?=\n==[^\=])/i);
                
                if (eventsMatch && eventsMatch[1]) {
                    const facts = eventsMatch[1].split('\n')
                        .map(line => line.replace(/^\*\s*/, '').trim())
                        .filter(line => line.length > 30 && !line.startsWith('='));

                    if (facts.length > 0) return facts;
                }
                return [];
            } catch (err) {
                return null; 
            }
        }

        const filter = {
            acceptNode: function(node) {
                const parent = node.parentNode;
                if (!parent) return NodeFilter.FILTER_REJECT;

                const tag = parent.tagName.toUpperCase();
                
                const rejectedTags = ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'CODE', 'PRE', 'SVG', 'BUTTON', 'INPUT', 'A'];
                
                if (rejectedTags.includes(tag) || parent.closest('.era-year')) {
                    return NodeFilter.FILTER_REJECT;
                }

                const tableCell = parent.closest('td, th');
                if (tableCell) {
                    if (tableCell.textContent.trim().length < 60) {
                        return NodeFilter.FILTER_REJECT;
                    }
                }
                
                return NodeFilter.FILTER_ACCEPT;
            }
        };

        const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, filter, false);
        const nodesToProcess = [];
        let node;

        while(node = walker.nextNode()) {
            if(/\b[1-2]\d{3}\b/.test(node.nodeValue)) {
                nodesToProcess.push(node);
            }
        }

        nodesToProcess.forEach(textNode => {
            if (!verifyIntegrity()) return;

            const text = textNode.nodeValue;
            let match;
            const fragment = document.createDocumentFragment();
            let lastIndex = 0;
            let hasMatch = false;
            
            yearRegex.lastIndex = 0;
            
            while ((match = yearRegex.exec(text)) !== null) {
                const year = parseInt(match[1], 10);
                
                if (year >= 1000 && year <= currentYear) {
                    hasMatch = true;
                    uniqueYears.add(year); 
                    fragment.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
                    
                    const span = document.createElement('span');
                    span.className = 'era-year';
                    span.setAttribute('data-year', year);
                    
                    span.style.cssText = 'background-color: #fff9c4; color: #0066cc; padding: 2px 4px; border-radius: 4px; border-bottom: 1px dashed #0066cc; cursor: pointer; white-space: nowrap; transition: filter 0.2s;';
                    span.innerHTML = `${year}${svgIcon}`;
                    
                    span.onmouseenter = () => span.style.filter = 'brightness(0.95)';
                    span.onmouseleave = () => span.style.filter = 'brightness(1)';
                    
                    fragment.appendChild(span);
                    lastIndex = yearRegex.lastIndex;
                }
            }
            
            if (hasMatch) {
                fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
                textNode.parentNode.replaceChild(fragment, textNode);
            }
        });

        uniqueYears.forEach(year => {
            if (!factCache[year]) {
                factCache[year] = fetchFactsArray(year); 
            }
        });

        if (!document.body.dataset.eraWidgetLoaded) {
            document.body.addEventListener('click', async function(e) {
                if (!verifyIntegrity()) {
                    popover.style.display = 'none';
                    return;
                }

                const refreshBtn = e.target.closest('.era-refresh-btn');
                if (refreshBtn) {
                    e.preventDefault();
                    const year = refreshBtn.getAttribute('data-year');
                    const factsArray = await factCache[year];
                    
                    if (factsArray && factsArray.length > 1) {
                        const factEl = document.getElementById('era-echo-fact-text');
                        const currentText = factEl.innerText;
                        let newFact;
                        
                        do {
                            newFact = factsArray[Math.floor(Math.random() * factsArray.length)];
                        } while (newFact === currentText && factsArray.length > 1);
                        
                        factEl.style.opacity = 0;
                        setTimeout(() => {
                            factEl.innerHTML = newFact;
                            factEl.style.opacity = 1;
                        }, 200);
                    }
                    return;
                }

                const el = e.target.closest('.era-year');
                if (el) {
                    e.preventDefault(); 
                    const year = el.getAttribute('data-year');
                    
                    const contentContainer = document.getElementById('era-content-container');
                    const refreshContainer = document.getElementById('era-refresh-container');
                    
                    popover.style.display = 'block';
                    
                    contentContainer.innerHTML = `
                        <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 15px;">
                            <h3 style="margin: 0; font-size: 18px; color: #333;">Era-Echo: ${year}</h3>
                            <button onclick="document.getElementById('era-widget-popover').style.display='none'" style="background: none; border: none; cursor: pointer; font-size: 20px; line-height: 1; color: #333;">&times;</button>
                        </div>
                        <p style="font-size: 14px; color: #666;">Retrieving data...</p>
                    `;
                    
                    refreshContainer.innerHTML = ''; 

                    const factsArray = await factCache[year];
                    let snippetHtml = '';

                    if (factsArray === null) {
                        snippetHtml = `<p style="font-size: 14px; color: #d9534f;">Failed to load historical data.</p>`;
                    } else if (factsArray.length === 0) {
                        snippetHtml = `<p style="font-size: 14px; color: #666;">No verified event found for this specific year.</p>`;
                    } else {
                        const randomFact = factsArray[Math.floor(Math.random() * factsArray.length)];
                        snippetHtml = `<p id="era-echo-fact-text" style="font-size: 14px; line-height: 1.5; margin: 0; transition: opacity 0.2s ease;">${randomFact}</p>`;
                        
                        if (factsArray.length > 1) {
                            refreshContainer.innerHTML = `
                                <button class="era-refresh-btn" data-year="${year}" style="background: none; border: none; cursor: pointer; color: #0066cc; font-size: 13px; padding: 0; display: flex; align-items: center; gap: 6px; transition: color 0.2s;">
                                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
                                    Get another fact
                                </button>
                            `;
                        }
                    }

                    contentContainer.innerHTML = `
                        <div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee; padding-bottom: 10px; margin-bottom: 15px;">
                            <h3 style="margin: 0; font-size: 18px; color: #333;">Era-Echo: ${year}</h3>
                            <button onclick="document.getElementById('era-widget-popover').style.display='none'" style="background: none; border: none; cursor: pointer; font-size: 20px; line-height: 1; color: #333;">&times;</button>
                        </div>
                        ${snippetHtml}
                    `;
                    return;
                }

                if (!e.target.closest('#era-widget-popover')) {
                    popover.style.display = 'none';
                }
            });
            document.body.dataset.eraWidgetLoaded = "true";
        }
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initYearWidget);
    } else {
        initYearWidget();
    }
})();
</script>

Post a Comment

0 Comments

Notice:
Comments are moderated and may not appear immediately. Please keep your comments respectful, and relevant to the post. Spam will not be tolerated. My site. My rules.

Post a Comment (0)