diff --git a/web/templates/search.html b/web/templates/search.html index 75292dd..709fe6b 100644 --- a/web/templates/search.html +++ b/web/templates/search.html @@ -247,7 +247,7 @@ } }); - /* =============================== + /* =============================== Illustration of the Day (client-only, deterministic) =============================== */ (function illustrationOfTheDay(){ @@ -266,50 +266,47 @@ const today = new Date(); const ymd = today.getFullYear()*10000 + (today.getMonth()+1)*100 + today.getDate(); - // Simple string hash -> 32-bit -> xorshift for decent spread + // 32‑bit xorshift PRNG for a good daily seed function xorshift32(seed){ let x = seed | 0; x ^= x << 13; x ^= x >>> 17; x ^= x << 5; return (x >>> 0); } - function pickIndex(max, seed){ - // (0..max-1) - return xorshift32(seed) % max; + const seed = xorshift32(ymd ^ 0x9E3779B9); + + // We don’t know ID gaps, so try a deterministic pseudo‑random + // permutation over a *generous* range and stop at the first 200. + const maxGuess = Math.max(100, Math.floor(total * 4)); // cast a wide net + const maxAttempts = Math.min(600, maxGuess); // cap network work + + // Linear congruential generator style “index -> id” mapping (deterministic) + function idAt(i){ + // multiplier is odd; modulus is implicit 2^32, then map to [1..maxGuess] + const v = (Math.imul(i + 1, 1103515245) + 12345 + seed) >>> 0; + return 1 + (v % maxGuess); } - // We don’t know if IDs are contiguous, so: - // 1) choose a *candidate* ID in [1..maxIdGuess] - // 2) try up to N nearby IDs until one 200s - // Best guess for max id is "total * 1.5" (cheap heuristic). - const maxIdGuess = Math.max(total, Math.floor(total * 1.5)); - let candidate = 1 + pickIndex(maxIdGuess, ymd ^ 0x9E3779B9); - - async function tryFetch(id){ + async function fetchEntryHtml(id){ const url = entryUrlFor(id); try{ - const r = await fetch(url, { credentials:'same-origin' }); - if (r.ok) { - const html = await r.text(); - return { ok:true, id, html }; - } + const r = await fetch(url, { credentials: 'same-origin' }); + if (r.ok) return await r.text(); }catch(_){} - return { ok:false }; + return null; } function ensurePunct(str){ const s = (str || '').trim(); if (!s) return ''; - // If ends with punctuation, keep; else add period. return /[.!?…]$/.test(s) ? s : (s + '.'); } function extractSectionText(doc, label){ - // Look for a section with a label like "Illustration" / "Application" + // Look for label “Illustration” / “Application” commonly used in your templates const labels = doc.querySelectorAll('.section-label, .meta-label, h3, h4, strong'); for (const el of labels){ const t = (el.textContent || '').trim().toLowerCase(); if (t === label){ - // usual structure: label inside a .section; body nearby const section = el.closest('.section') || el.parentElement; if (section){ const body = section.querySelector('.lead-text') || @@ -319,7 +316,7 @@ } } } - // Fallback: first .lead-text / .section-body on page if label search failed + // fallback const alt = doc.querySelector('.lead-text, .section-body'); return alt ? (alt.textContent || '').trim() : ''; } @@ -328,22 +325,23 @@ const dom = new DOMParser().parseFromString(html, 'text/html'); const illustration = extractSectionText(dom, 'illustration'); const application = extractSectionText(dom, 'application'); - const merged = (ensurePunct(illustration) + (application ? ' ' + application : '')).trim(); iotdTextEl.textContent = merged || 'Open to view today’s illustration.'; iotdOpenEl.href = entryUrlFor(id); + iotdOpenEl.style.display = 'inline-block'; } (async function findAndRender(){ - // Try candidate, then probe up to +/- 10 around it - const offsets = [0,1,-1,2,-2,3,-3,4,-4,5,-5,6,-6,7,-7,8,-8,9,-9,10,-10]; - for (const off of offsets){ - const id = Math.max(1, candidate + off); - const res = await tryFetch(id); - if (res.ok) { renderIotd(id, res.html); return; } + for (let i = 0; i < maxAttempts; i++){ + const id = idAt(i); + const html = await fetchEntryHtml(id); + if (html){ + renderIotd(id, html); + return; + } } - // Give up—show a graceful fallback + // If nothing responded 200 within our attempts, fail gracefully iotdTextEl.textContent = 'Unable to load today’s illustration.'; iotdOpenEl.style.display = 'none'; })();