/*! trentpower.fr · authored source */ /* trentpower.fr publication metadata · edition-age localisation Role: reads the publication date from and rewrites every element carrying [data-edition-age] with a relative-age string in the document language. the footer's "Published today" line reads as that on the day of publication; on later visits it reads "Published 3 days ago", "1 week ago", "2 months ago" — quiet, archival, never a live verification claim. Source: edited here as edition.template.js; compiled to /js/edition.js by generate_site.py (minified, no edition substitution — the script resolves the edition at runtime from the body attribute, so the same bytes work for every page on every build). Constraints: - no fetch; csp connect-src 'none' is preserved - no inline event handlers; csp script-src 'self' holds - no telemetry, cookies, third-party scripts - enhancement only: the html renders the literal "Published today" (or "Publiée aujourd'hui") as a static label, so the page means the same with this script absent. later relative ages are an enhancement, not a content guarantee. - local date parsing (new Date(y, m-1, d)) not UTC, because an edition date is a publication day, not an instant. - Math.floor for weeks/months/years so the site does not claim "1 month ago" after half a month. */ (function () { 'use strict'; var AGE_COPY = { en: { today: 'Published today', yesterday: 'Published yesterday', days: function (n) { return 'Published ' + n + ' days ago'; }, weeks: function (n) { return 'Published ' + n + ' week' + (n === 1 ? '' : 's') + ' ago'; }, months: function (n) { return 'Published ' + n + ' month' + (n === 1 ? '' : 's') + ' ago'; }, years: function (n) { return 'Published ' + n + ' year' + (n === 1 ? '' : 's') + ' ago'; } }, fr: { today: 'Publiée aujourd’hui', yesterday: 'Publiée hier', days: function (n) { return 'Publiée il y a ' + n + ' jours'; }, weeks: function (n) { return 'Publiée il y a ' + n + ' semaine' + (n === 1 ? '' : 's'); }, months: function (n) { return 'Publiée il y a ' + n + ' mois'; }, years: function (n) { return 'Publiée il y a ' + n + (n === 1 ? ' an' : ' ans'); } } }; function lang() { var d = (document.documentElement.lang || 'en').toLowerCase(); return d.indexOf('fr') === 0 ? 'fr' : 'en'; } // parse YYYY-MM-DD as a local-calendar day, not a utc instant. the // edition is a publication day in paris; treating it as utc midnight // would make the relative age flip a day too early or too late for // visitors in the americas / asia. function parseLocalDate(value) { var p = String(value || '').split('-'); if (p.length !== 3) return null; var y = Number(p[0]); var m = Number(p[1]); var d = Number(p[2]); if (!y || !m || !d) return null; return new Date(y, m - 1, d); } function diffDays(from, to) { var a = new Date(from.getFullYear(), from.getMonth(), from.getDate()); var b = new Date(to.getFullYear(), to.getMonth(), to.getDate()); return Math.max(0, Math.floor((b - a) / 86400000)); } function compute(ed) { var date = parseLocalDate(ed); if (!date) return null; var days = diffDays(date, new Date()); var copy = AGE_COPY[lang()]; if (days === 0) return copy.today; if (days === 1) return copy.yesterday; if (days < 7) return copy.days(days); if (days < 31) return copy.weeks(Math.max(1, Math.floor(days / 7))); if (days < 365) return copy.months(Math.max(1, Math.floor(days / 30.4375))); return copy.years(Math.max(1, Math.floor(days / 365.25))); } function run() { var ed = document.body && document.body.getAttribute('data-edition'); if (!ed) return; var text = compute(ed); if (!text) return; var nodes = document.querySelectorAll('[data-edition-age]'); for (var i = 0; i < nodes.length; i++) { nodes[i].textContent = text; } } // phase 94 · fr machine-translation disclosure as a first-visit-only // signal. the disclosure (``) and the // standard edition-age span (``) // sit side-by-side in the html: disclosure visible by default, edition- // age hidden. on repeat visits we flip them — disclosure hidden, // edition-age shown and populated by run() above. the marker key is // a publication-day string so it can be audited; resetting localStorage // makes the disclosure return on the next visit. var DISCLOSURE_KEY = 'tp-fr-disclosure-seen'; function reconcileDisclosure() { var disc = document.querySelector('[data-translation-disclosure]'); if (!disc) return; var age = null; var parent = disc.parentNode; if (parent) age = parent.querySelector('[data-edition-age]'); var seen = null; try { seen = localStorage.getItem(DISCLOSURE_KEY); } catch (_) {} if (seen) { // repeat visit — retire the disclosure, surface the edition-age. disc.hidden = true; if (age) age.hidden = false; } else { // first visit — leave the disclosure visible and stamp the marker // so the next visit retires it. use the publication day so the // value is auditable / human-readable. try { var today = new Date().toISOString().slice(0, 10); localStorage.setItem(DISCLOSURE_KEY, today); } catch (_) {} } } // phase 94e · homepage imprint as a per-language first-visit signal. // the `