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';
})();