/*! 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 `