/* UK fuel price tracker: the page's behaviour. Reads the four data files loaded before it.
   main() draws the full page; small() draws the embeddable widget (?widget=CODE) and the link-preview card (?card=1). */

const D = window.TRACKER_DATA;   /* data/tracker.js */
/* the live page data (build_fuel_price_tracker.py write_page / netlify core.mjs pageData) -> what this page reads */
(() => {
  const LA = window.TRACKER_COUNCILS, X = window.TRACKER_EXTRA;   /* data/councils.js, data/extra.js */
  D.weeks = D.weeks.map(w => ({ week: w.week, estimated: w.estimated, diesel: { premium: w.diesel }, petrol: { premium: w.petrol } }));
  D.prices.year_ago_date = D.prices.year_ago;
  D.ppm = { now: D.running.now, year_ago: D.running.year_ago, public: D.running.public };
  D.ppm_series = D.running.series;
  D.running_km_per_mile = D.running.km_per_mile;
  D.built = D.meta.built;
  D.areas = LA.areas.map(a => [a[0], a[1], a[2], a[6], a[7]]);   /* [code, name, country, all cars, all BEVs] as DVLA registers them */
  D.la_q = LA.q;
  D.map = { w: LA.w, h: LA.h, d: LA.areas.map(a => a[5]).join("") };   /* UK outline = every council's path */
  Object.assign(D, X);
})();
const LOGO = window.TRACKER_LOGO;   /* data/logo.js */   /* assets/te_logo.png as a data URI, put in by the build */
document.documentElement.style.setProperty("--logo", `url("${LOGO}")`);
const MODE = new URLSearchParams(location.search);
function main() {
const $ = id => document.getElementById(id);
const f0 = x => Math.round(x).toLocaleString("en-GB");
const f1 = x => x.toLocaleString("en-GB", { minimumFractionDigits: 1, maximumFractionDigits: 1 });
const f2 = x => x.toLocaleString("en-GB", { minimumFractionDigits: 2, maximumFractionDigits: 2 });
const bn = m => "£" + f2(m / 1000) + "bn";
const dmy = s => new Date(s + "T12:00:00").toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" });
const mon = s => new Date(s + "T12:00:00").toLocaleDateString("en-GB", { month: "long" });
const T = D.totals, P = D.prices, S = P.series, n = S.dates.length;

/* screen 1 */
const perSec = T.all.per_day * 1e6 / 86400, base = T.all.total * 1e6, t0 = Date.now();
const odo = $("odo"); let digs = [];
function odoBuild(s) { const len = s.replace(/,/g, "").length; let k = 0;
  odo.innerHTML = "<span class='s'>£</span>" + [...s].map(c => c === "," ? "<span class='s'>,</span>" : `<span class="d${++k > len - 3 ? " live" : ""}"><span>${[0,1,2,3,4,5,6,7,8,9].map(x => `<b>${x}</b>`).join("")}</span></span>`).join("");
  digs = [...odo.querySelectorAll(".d span")]; }
function tick() { const el = (Date.now() - t0) / 1000, s = Math.floor(base + perSec * el).toLocaleString("en-GB"), raw = s.replace(/,/g, "");
  if (digs.length !== raw.length) odoBuild(s);
  [...raw].forEach((c, i) => digs[i].style.transform = `translateY(-${c * 1.2}em)`);
  $("meterV").textContent = "£" + f0(perSec * el); }
tick(); setInterval(tick, 1000);
odo.setAttribute("aria-label", "About " + bn(T.all.total));
$("s1p").innerHTML = `on road fuel, on top of what the same weeks cost a year earlier. <b>${bn(T.all.total)}</b> to the week of ${dmy(D.window.end)}, of which <b>${bn(T.all.estimated)}</b> rests on estimated fuel volumes. The counter runs on at this week's rate: <b>£${f0(perSec)} a second</b>.`;
$("pumps").innerHTML = [["Petrol", "petrol"], ["Diesel", "diesel"]].map(([l, k]) => `<div class="pump"><small>${l}</small><b>${f1(P.now[k].pump)}<i>p</i></b><em>+${f1(P.now[k].pump - P.then[k].pump)}p on last year</em></div>`).join("");

/* screen 2: the pinned chart */
const si = S.dates.findIndex(d => d >= D.window.start), e = n - 1;
let pk = si; for (let i = si; i < Math.min(n, si + 14); i++) if (S.diesel[i] > S.diesel[pk]) pk = i;
let tr = pk; for (let i = pk; i < n; i++) if (S.diesel[i] < S.diesel[tr]) tr = i;
const ya = S.dates.findIndex(d => d >= P.year_ago_date);
const preAvg = a => a.slice(0, si).reduce((x, y) => x + y, 0) / si;
const STEPS = [
  { to: si - 1, h: "Two quiet years", p: `Through 2024 and 2025, diesel averaged <b>${f0(preAvg(S.diesel))}p</b> a litre and petrol <b>${f0(preAvg(S.petrol))}p</b>. Prices drifted. Nobody noticed.` },
  { to: pk, h: `Then ${mon(D.window.start)} 2026`, p: `The oil shock hit on 2 March. Within ${pk - si} weeks diesel had jumped <b>${f0(S.diesel[pk] - P.pre.diesel.pump)}p</b> to ${f1(S.diesel[pk])}p a litre.` },
  { to: tr, h: `A summer dip`, p: `Pump prices follow the price of crude oil. Oil eased in early summer, and by ${mon(S.dates[tr])} diesel had fallen <b>${f0(S.diesel[pk] - S.diesel[tr])}p</b> from its peak to ${f1(S.diesel[tr])}p. Petrol dipped to ${f1(S.petrol[tr])}p.` },
  { to: e, h: `Then higher still`, p: `From August oil climbed again and the pump followed. Diesel passed its spring peak in ${mon(S.dates[S.diesel.findIndex((v, k) => k > tr && v > S.diesel[pk])])}. This week it is <b>${f1(S.diesel[e])}p</b> and petrol is <b>${f1(S.petrol[e])}p</b>.` },
  { to: e, gap: true, h: `${f0(P.now.petrol.pump - P.then.petrol.pump)}p more than a year ago`, p: `Every litre of petrol costs <b>${f1(P.now.petrol.pump - P.then.petrol.pump)}p</b> more than the same week last year. A 50-litre fill is up <b>£${f2(50 * (P.now.petrol.pump - P.then.petrol.pump) / 100)}</b>. Diesel is up ${f1(P.now.diesel.pump - P.then.diesel.pump)}p a litre.` }
];
$("steps").innerHTML = STEPS.map((s, i) => `<div class="step" data-i="${i}"><div><span class="n">0${i + 1} / 0${STEPS.length}</span><h2>${s.h}</h2><p>${s.p}</p></div></div>`).join("");
const W = 1000, H = 560, m = { l: 50, r: 96, t: 30, b: 40 }, lo = 120, hi = Math.ceil(Math.max(...S.diesel) / 10) * 10 + 8;
const X = i => m.l + (W - m.l - m.r) * i / (n - 1), Y = v => m.t + (H - m.t - m.b) * (1 - (v - lo) / (hi - lo));
const path = a => "M" + a.map((v, i) => `${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join("L");
let g = ""; for (let v = 125; v < hi; v += 25) g += `<line x1="${m.l}" x2="${W - m.r}" y1="${Y(v)}" y2="${Y(v)}" stroke="#e7ddd4"/><text x="${m.l - 10}" y="${Y(v) + 5}" text-anchor="end" font-size="16" fill="#8a8282">${v}p</text>`;
let yrs = ""; S.dates.forEach((d, i) => { if (i === 0 || d.slice(0, 4) !== S.dates[i - 1].slice(0, 4)) yrs += `<text x="${X(i)}" y="${H - 10}" font-size="16" font-weight="700" fill="#8a8282">${d.slice(0, 4)}</text>`; });
/* both prices at week i, shown on one slide only */
const mark = (step, i) => { const yd = Y(S.diesel[i]), yp = Math.max(Y(S.petrol[i]), yd + 30), dot = (y, c) => `<circle cx="${X(i)}" cy="${y}" r="8" fill="${c}" stroke="#fff" stroke-width="3"/>`, lab = (y, c, v) => `<text x="${X(i) + 14}" y="${y + 8}" font-size="24" font-weight="900" fill="${c}">${f1(v)}p</text>`;
  return `<g class="an" data-only="${step}">${dot(yd, "#4d45ff")}${dot(Y(S.petrol[i]), "#ff8754")}${lab(yd, "#4d45ff", S.diesel[i])}${lab(yp, "#ff8754", S.petrol[i])}</g>`; };
const yD = Y(P.then.petrol.pump), yE = Y(S.petrol[e]);
$("chStory").innerHTML = `<svg viewBox="0 0 ${W} ${H}" role="img" aria-label="Weekly pump prices of diesel and petrol since 2024">
  <defs><clipPath id="rv"><rect id="rvR" x="0" y="0" width="${W}" height="${H}" style="transform-origin:0 0;transition:transform 1.4s cubic-bezier(.4,0,.2,1)"/></clipPath>
  <linearGradient id="shk" x1="0" x2="1"><stop offset="0" stop-color="#c2447a" stop-opacity=".16"/><stop offset="1" stop-color="#ff8754" stop-opacity=".04"/></linearGradient></defs>
  ${g}${yrs}
  <g clip-path="url(#rv)"><rect x="${X(si)}" y="${m.t}" width="${X(e) - X(si)}" height="${H - m.t - m.b}" fill="url(#shk)"/>
    <path d="${path(S.petrol)}" fill="none" stroke="#ff8754" stroke-width="5" stroke-linejoin="round" stroke-linecap="round"/>
    <path d="${path(S.diesel)}" fill="none" stroke="#4d45ff" stroke-width="5" stroke-linejoin="round" stroke-linecap="round"/></g>
  <g class="an" data-from="1"><line x1="${X(si)}" x2="${X(si)}" y1="${m.t}" y2="${H - m.b}" stroke="#c2447a" stroke-width="2" stroke-dasharray="5 5"/><text x="${X(si) - 10}" y="${m.t + 16}" text-anchor="end" font-size="17" font-weight="800" fill="#c2447a">2 March 2026</text></g>
  ${mark(0, si - 1)}${mark(1, pk)}${mark(2, tr)}
  <g class="an" data-from="3"><circle cx="${X(e)}" cy="${Y(S.diesel[e])}" r="8" fill="#4d45ff" stroke="#fff" stroke-width="3"/><circle cx="${X(e)}" cy="${Y(S.petrol[e])}" r="8" fill="#ff8754" stroke="#fff" stroke-width="3"/>
    <text x="${X(e) + 14}" y="${Y(S.diesel[e]) + 8}" font-size="24" font-weight="900" fill="#4d45ff">${f1(S.diesel[e])}p</text><text x="${X(e) + 14}" y="${Y(S.petrol[e]) + 8}" font-size="24" font-weight="900" fill="#ff8754">${f1(S.petrol[e])}p</text></g>
  <g class="an" data-from="4"><line x1="${X(ya)}" x2="${X(e) - 22}" y1="${yD}" y2="${yD}" stroke="#1c1c1c" stroke-width="2" stroke-dasharray="3 6"/><circle cx="${X(ya)}" cy="${yD}" r="6" fill="#1c1c1c"/>
    <text x="${X(ya)}" y="${yD + 28}" text-anchor="middle" font-size="16" font-weight="700" fill="#1c1c1c">Petrol a year ago: ${f1(P.then.petrol.pump)}p</text>
    <line x1="${X(e) - 22}" x2="${X(e) - 22}" y1="${yD}" y2="${yE + 12}" stroke="#1c1c1c" stroke-width="3"/><path d="M${X(e) - 30},${yE + 24}l8,-14l8,14z" fill="#1c1c1c"/>
    <rect x="${X(e) - 150}" y="${(yD + yE) / 2 - 22}" width="118" height="44" rx="22" fill="#1c1c1c"/><text x="${X(e) - 91}" y="${(yD + yE) / 2 + 8}" text-anchor="middle" font-size="22" font-weight="900" fill="#fff">+${f1(P.now.petrol.pump - P.then.petrol.pump)}p</text></g></svg>`;
const rvR = $("rvR"), ans = [...document.querySelectorAll(".an")], stepEls = [...document.querySelectorAll(".step")];
function setStep(i) { const s = STEPS[i]; rvR.style.transform = `scaleX(${(X(s.to) + 4) / W})`;
  ans.forEach(a => a.classList.toggle("show", a.dataset.only ? +a.dataset.only === i : i >= +a.dataset.from));
  stepEls.forEach((el, k) => el.classList.toggle("on", k === i)); }
setStep(0);
const io = new IntersectionObserver(es => es.forEach(en => { if (en.isIntersecting) setStep(+en.target.dataset.i); }), { rootMargin: matchMedia("(max-width: 760px)").matches ? "-70% 0px -20% 0px" : "-45% 0px -45% 0px" });
stepEls.forEach(el => io.observe(el));

/* screen 3: line-drawn cars */
const SHAPES = {
  saloon: ["M26,62H12c-3,0-5-2-5-5V47c0-4,3-7,7-8l33-6c11-13,22-19,40-19h30c15,0,26,6,38,20l24,5c8,2,13,7,13,14v4c0,3-2,5-5,5h-9", "M56,33c9-9,17-13,30-13h13v15H58c-2,0-3-1-2-2z", "M106,20h12c11,0,19,4,28,15h-40z", 14, 36],
  hatch: ["M26,62H12c-3,0-5-2-5-5V42c0-3,1-6,3-9l14-15c3-3,6-4,11-4h82c15,0,26,6,38,20l24,5c8,2,13,7,13,14v4c0,3-2,5-5,5h-9", "M30,34l12-13c2-2,4-2,7-2h49v15z", "M106,19h12c11,0,19,4,28,15h-40z", 14, 35],
  suv: ["M24,62H12c-3,0-5-2-5-5V30c0-6,3-10,8-13l10-6c4-2,8-3,13-3h84c14,0,24,6,34,22l26,6c8,2,13,7,13,14v7c0,3-2,5-5,5h-7", "M28,32l6-13c1-3,3-4,6-4h58v17z", "M106,15h16c10,0,17,5,24,17h-40z", 16, 33] };
const carSvg = (col, bev, shape = "saloon") => { const [body, w1, w2, r, dy] = SHAPES[shape];
  return `<svg viewBox="0 0 200 80" fill="none" stroke="${col}" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
  <path d="${body}" fill="#fff"/><path d="M${48 + r + 8},62H${162 - r - 8}"/>
  <path d="${w1}" fill="${col}" fill-opacity=".14"/><path d="${w2}" fill="${col}" fill-opacity=".14"/>
  <path d="M102,${dy}v${56 - dy}M118,44h9"/><path d="M186,46h-9" stroke-width="5"/><path d="M8,50h7" stroke-width="5" stroke="${bev ? col : "#c2447a"}"/>
  <circle cx="48" cy="62" r="${r}" fill="#fff"/><circle cx="48" cy="62" r="5"/><circle cx="162" cy="62" r="${r}" fill="#fff"/><circle cx="162" cy="62" r="5"/>
  ${bev ? `<path d="M84,41l-7,11h8l-3,10l10,-13h-8l4,-8z" fill="${col}" stroke="none"/>` : `<path d="M2,58h-9M-2,66h-8" stroke-width="2.6" opacity=".45"/>`}</svg>`; };
/* [name, note, pence per mile, colour, BEV?, lane offset on the map, side its label sits on, fuel word] */
const N = D.ppm.now, LANES = [
  ["Petrol car", "", N.petrol, "#ff8754", 0, 3.4, 1, "petrol"],
  ["Diesel car", "", N.diesel, "#4d45ff", 0, -3.4, -1, "diesel"],
  ["BEV", "mostly charged at home", D.bev_basis.home_share * D.bev_basis.night_rate / D.bev_basis.mi_per_kwh + (1 - D.bev_basis.home_share) * D.ppm.public.slow_fast * D.running.kwh_km * D.running_km_per_mile, "#039961", 1, 0, 1, "electricity"]];
/* the route: RP = [x, y, miles from Land's End, place name or null] on the council map (scripts/lejog_route.py) */
const RT = D.route, RP = RT.pts, RTOT = RT.total;
const RN = RP.map((p, k) => { const a = RP[Math.max(0, k - 1)], b = RP[Math.min(RP.length - 1, k + 1)], l = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1; return [-(b[1] - a[1]) / l, (b[0] - a[0]) / l]; });
const at = (mi, o = 0) => { mi = Math.max(0, Math.min(RTOT, mi)); let k = 1; while (k < RP.length - 1 && RP[k][2] < mi) k++;
  const a = RP[k - 1], b = RP[k], u = (mi - a[2]) / (b[2] - a[2]);
  return [a[0] + (b[0] - a[0]) * u + (RN[k - 1][0] + (RN[k][0] - RN[k - 1][0]) * u) * o, a[1] + (b[1] - a[1]) * u + (RN[k - 1][1] + (RN[k][1] - RN[k - 1][1]) * u) * o]; };
const seg = (m0, m1, o = 0) => { const pts = [at(m0, o)]; RP.forEach((p, k) => { if (p[2] > m0 && p[2] < m1) pts.push([p[0] + RN[k][0] * o, p[1] + RN[k][1] * o]); }); pts.push(at(m1, o));
  return "M" + pts.map(p => p[0].toFixed(1) + "," + p[1].toFixed(1)).join("L"); };
const nearPlace = mi => RP.filter(p => p[3]).reduce((b, p) => Math.abs(p[2] - mi) < Math.abs(b[2] - mi) ? p : b)[3];
/* the distance step: d miles laid along the road from Land's End (turning back at John o' Groats if it is longer) */
const distTxt = d => d <= RTOT ? `Land's End to near ${nearPlace(d)}` : d < 2 * RTOT ? `Land's End to John o' Groats, and ${f0(d - RTOT)} miles back` : `Land's End to John o' Groats and back`;
/* the commuting step: DfT NTS0409, the average car driver's commuting trip, 2 trips a working day, 20 working days a month */
const CM = D.commute, perDay = 2 * CM.trip_miles, perMonth = perDay * CM.days_per_month;
const plural = (v, s) => `${v} ${s}${v === "1" || v === "1.0" ? "" : "s"}`;
const timeTxt = mi => { const d = mi / perDay; return d < 10 ? plural(d < 2 ? f1(d) : f1(d), "day") : d < CM.days_per_month ? plural(f1(d / 5), "week") : plural(f1(d / CM.days_per_month), "month"); };
const MW = D.map.w, MT = RT.top, MH = D.map.h - RT.top, S0 = at(0), S1 = at(RTOT);
$("chMap").innerHTML = `<svg viewBox="0 ${MT} ${MW} ${MH.toFixed(1)}" role="img" aria-label="Map of the UK: each car's miles laid along the road from Land's End">
  <defs><clipPath id="chClip"><rect x="-30" y="${MT}" width="${MW + 60}" height="${(MH + 60).toFixed(1)}"/></clipPath></defs>
  <path d="${D.map.d}" clip-path="url(#chClip)" fill="#fff" stroke="#e7ddd4" stroke-width=".6" stroke-linejoin="round"/>
  <path d="${seg(0, RTOT)}" fill="none" stroke="#1c1c1c" stroke-opacity=".3" stroke-width="1.5" stroke-dasharray="1 4" stroke-linecap="round"/>
  ${LANES.map(l => `<path class="ln lo" pathLength="1" fill="none" stroke="${l[3]}" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round"/><path class="ln lb" pathLength="1" fill="none" stroke="${l[3]}" stroke-opacity=".55" stroke-width="3.4" stroke-linecap="round" stroke-linejoin="round" style="transition-delay:1.6s"/>`).join("")}
  <circle cx="${S0[0]}" cy="${S0[1]}" r="5" fill="#1c1c1c"/><text x="${S0[0]}" y="${S0[1] + 26}" text-anchor="middle" font-size="14" font-weight="800" fill="#1c1c1c">Land's End</text>
  <circle cx="${S1[0]}" cy="${S1[1]}" r="5" fill="#fff" stroke="#1c1c1c" stroke-width="2.5"/><text x="${S1[0] - 10}" y="${S1[1] - 12}" text-anchor="end" font-size="14" font-weight="800" fill="#1c1c1c">John o' Groats</text>
  ${LANES.map(l => `<g class="end" style="transition-delay:1.7s"><circle class="ld" r="5" fill="${l[3]}" stroke="#fff" stroke-width="1.8"/><text class="ll" font-size="14" font-weight="800" fill="${l[3]}" stroke="#fff" stroke-width="3.5" paint-order="stroke" text-anchor="${l[6] > 0 ? "start" : "end"}"></text></g>`).join("")}</svg>`;
const STEP = ["st", "eq", "st", "eq", "st", "eq", "st place"];
$("chains").innerHTML = LANES.map(l => `<div class="chain" style="--c:${l[3]}"><div class="hd">${carSvg(l[3], l[4])}<div><b>${l[0]}</b><small>${l[1] ? l[1] + " · " : ""}${f1(l[2])}p a mile</small></div></div>` +
  STEP.map((c, k) => c === "eq" ? `<div class="eq" style="transition-delay:${(k * .22).toFixed(2)}s">=</div>` : `<div class="${c}" style="transition-delay:${(k * .22).toFixed(2)}s"><span class="v"></span><span class="l"></span></div>`).join("") + `</div>`).join("");
const mapEl = c => [...document.querySelectorAll("#chMap ." + c)], LO = mapEl("lo"), LB = mapEl("lb"), LD = mapEl("ld"), LL = mapEl("ll"), CHAINS = [...document.querySelectorAll("#chains .chain")];
let amt = 50;
function chainDraw() { const dist = LANES.map(l => amt * 100 / l[2]);
  $("raceH").innerHTML = `What does <span class="grad">£${amt}</span> of fuel buy?`;
  LANES.forEach((l, i) => { const d = dist[i], st = CHAINS[i].querySelectorAll(".st");
    const put = (k, v, lab) => { st[k].querySelector(".v").textContent = v; st[k].querySelector(".l").textContent = lab; };
    put(0, "£" + amt, `of ${l[7]}`); put(1, timeTxt(d), "of commuting by car"); put(2, f0(d) + " miles", "of driving"); put(3, distTxt(d), "the same distance as");
    st[3].querySelector(".l").textContent = "the same distance as"; st[3].insertBefore(st[3].querySelector(".l"), st[3].querySelector(".v"));
    const out = Math.min(d, RTOT), back = Math.max(0, Math.min(d, 2 * RTOT) - RTOT), o = l[5], [x, y] = back ? at(RTOT - back, o + 4.2) : at(out, o);
    LO[i].setAttribute("d", seg(0, out, o)); LB[i].setAttribute("d", back ? seg(RTOT - back, RTOT, o + 4.2) : "M0,0");
    LB[i].style.visibility = back ? "visible" : "hidden";
    LD[i].setAttribute("cx", x); LD[i].setAttribute("cy", y); LL[i].setAttribute("x", l[6] > 0 ? x + 10 : x - 10); LL[i].setAttribute("y", y + 5);
    LL[i].textContent = `${l[0].replace(" car", "")} ${f0(d)} mi`; });
}
$("amt").addEventListener("click", ev => { const b = ev.target.closest("button"); if (!b) return; $("amt").querySelectorAll("button").forEach(x => x.setAttribute("aria-pressed", x === b)); amt = +b.dataset.v; chainDraw(); });
chainDraw();
/* reveal once, when the section first comes into view (and never later than a scroll past it) */
const chIn = () => $("chWrap").classList.add("in");
if ("IntersectionObserver" in window) new IntersectionObserver((es, o) => { if (es.some(e => e.isIntersecting)) { chIn(); o.disconnect(); } }, { rootMargin: "0px 0px -25% 0px" }).observe($("chWrap")); else chIn();
/* backstop where scroll events and the observer are held back (background tabs, some in-app browsers) */
const chPoll = setInterval(() => { if ($("chWrap").classList.contains("in")) return clearInterval(chPoll); if ($("chWrap").getBoundingClientRect().top < innerHeight * .75) { chIn(); clearInterval(chPoll); } }, 400);
$("raceNote").textContent = `Average car: petrol ${f1(D.running.l100.petrol)} L and diesel ${f1(D.running.l100.diesel)} L per 100 km, BEV ${f2(D.running.kwh_km)} kWh per km (DESNZ 2026), at this week's prices. BEV charged ${Math.round(D.bev_basis.home_share * 100)}% at home overnight at ${D.bev_basis.night_rate}p per kWh and ${Math.round((1 - D.bev_basis.home_share) * 100)}% on public chargers. Commuting: the average car driver's commuting trip in England is ${f1(CM.trip_miles)} miles (${CM.source}); two trips a working day, ${CM.days_per_month} working days a month, so a month of commuting is ${f0(perMonth)} miles. People who work from home some days would make the money last longer. Distances along the fastest main-road route from Land's End to John o' Groats (${f0(RTOT)} miles); places are approximate.`;

const beep = () => {};

/* shared */
const pressed = (group, fn) => group.addEventListener("click", ev => { const b = ev.target.closest("button"); if (!b) return;
  group.querySelectorAll("button").forEach(x => x.setAttribute("aria-pressed", x === b)); fn(b.dataset.v); });
const money = v => v >= 1000 ? bn(v) : "£" + (v >= 100 ? f0(v) : f1(v)) + "m";
const MI = 7000, kmY = MI * D.running_km_per_mile, avgL = D.running.l100.petrol * kmY / 100;
const extraYear = avgL * (P.now.petrol.pump - P.then.petrol.pump) / 100;
const BB = D.bev_basis, kwhMi = D.running.kwh_km * D.running_km_per_mile;
/* pence per mile for a BEV using kwh kWh a mile: home share at the night rate (the average car does BB.mi_per_kwh miles per kWh
   at home; other cars scale with their own use), the rest on public slow and fast chargers */
const bevPpm = kwh => BB.home_share * BB.night_rate * kwh / (kwhMi * BB.mi_per_kwh) + (1 - BB.home_share) * D.ppm.public.slow_fast * kwh;
const bevYear = kwh => MI * bevPpm(kwh) / 100;
const saveYear = avgL * P.now.petrol.pump / 100 - bevYear(kwhMi);
const basisTxt = `BEV charged ${f0(BB.home_share * 100)}% at home overnight at ${f0(BB.night_rate)}p per kWh and ${f0((1 - BB.home_share) * 100)}% on public chargers`;
const fillNow = 50 * P.now.petrol.pump / 100, fillUp = 50 * (P.now.petrol.pump - P.then.petrol.pump) / 100;

/* the big facts: count up on arrival, save as an image */
const ART = {   /* line drawings, 48 x 48 */
  pump: '<path d="M10 40V12a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4v28M7 40h24M14 14h10v8H14zM28 20h4a3 3 0 0 1 3 3v9a3 3 0 0 0 6 0V18l-5-5"/>',
  year: '<rect x="7" y="10" width="34" height="30" rx="4"/><path d="M7 19h34M16 6v8M32 6v8M15 27h5M26 27h7M15 34h7"/>',
  plug: '<path d="M18 5v10M30 5v10M11 15h26v7a13 13 0 0 1-26 0zM24 35v8"/><path d="M26 19l-5 7h6l-3 6" stroke-width="2"/>' };
const STATS = [
  { c: "a", k: "At the pump", art: "pump", v: fillNow, fmt: v => "£" + f2(v), p: `to fill a 50-litre petrol tank this week.`, chip: `▲ £${f2(fillUp)} on last year`, share: `to fill a 50-litre petrol tank this week` },
  { c: "b", k: "Over a year", art: "year", v: extraYear, fmt: v => "+£" + f0(v), p: `more for a year of petrol in an average car than a year ago.`, chip: `${f0(MI)} miles a year`, share: `more for a year of petrol in an average car than a year ago` },
  { c: "c", k: "The way out", art: "plug", v: saveYear / 52, fmt: v => "£" + f0(v), unit: "a week", p: `saved on fuel when an average car switches from petrol to a BEV.`, chip: `£${f0(saveYear)} a year back in your pocket`, share: `saved on fuel by switching an average petrol car to a BEV` }];
const BRAND = `<span class="brand"><span class="logo" role="img" aria-label="T&amp;E"></span></span>`;
$("stats").innerHTML = STATS.map((t, i) => `<div class="fact ${t.c}"><div class="top"><svg class="art" viewBox="0 0 48 48" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${ART[t.art]}</svg>
  <div class="k">${t.k}</div><div class="num" data-i="${i}">${t.fmt(t.v)}${t.unit ? `<small>${t.unit}</small>` : ""}</div><p>${t.p}</p><span class="chip">${t.chip}</span></div>
  <div class="stub"><button class="share" type="button" data-i="${i}">Save as image</button>${BRAND}</div></div>`).join("");
$("factsSrc").innerHTML = `Average car, ${f0(MI)} miles a year, this week's prices (week of ${dmy(P.latest)}). ${basisTxt}. DESNZ, Zapmap; T&amp;E analysis.`;
const sio = new IntersectionObserver((es, o) => es.forEach(en => { if (!en.isIntersecting) return; o.unobserve(en.target); const t = STATS[+en.target.dataset.i], st = performance.now(); let done = false;
  (function step(now) { if (done) return; const k = Math.min(1, (now - st) / 1100), ease = 1 - Math.pow(1 - k, 3); en.target.firstChild.nodeValue = t.fmt(t.v * ease); if (k < 1) requestAnimationFrame(step); })(st);
  setTimeout(() => { done = true; en.target.firstChild.nodeValue = t.fmt(t.v); }, 1300); }), { threshold: .3 });
document.querySelectorAll(".fact .num").forEach(el => sio.observe(el));
$("stats").addEventListener("click", async ev => { const b = ev.target.closest(".share"); if (!b) return; const t = STATS[+b.dataset.i];
  await document.fonts.load('700 40px "Courier Prime"'); await document.fonts.load('600 200px Oswald'); await document.fonts.load('italic 900 56px Figtree'); await document.fonts.load('800 34px Figtree'); const cv = document.createElement("canvas"); cv.width = 1080; cv.height = 1350; const x = cv.getContext("2d");
  x.fillStyle = { a: "#ff8754", b: "#c2447a", c: "#039961" }[t.c]; x.fillRect(0, 0, 1080, 1350);
  x.save(); x.translate(540, 612); x.rotate(-.025); x.scale(.9, .9); x.translate(-540, -675); x.shadowColor = "rgba(0,0,0,.3)"; x.shadowBlur = 50; x.shadowOffsetY = 20;
  x.fillStyle = "#fffef9"; x.beginPath(); x.moveTo(140, 110); x.lineTo(940, 110); x.lineTo(940, 1230); for (let q = 940; q > 140; q -= 40) { x.lineTo(q - 20, 1250); x.lineTo(q - 40, 1230); } x.closePath(); x.fill(); x.shadowColor = "transparent";
  const mono = (sz, w = 400) => `${w} ${sz}px "Courier Prime", "Courier New", monospace`, dash = y => { x.setLineDash([12, 10]); x.strokeStyle = "#999"; x.lineWidth = 3; x.beginPath(); x.moveTo(190, y); x.lineTo(890, y); x.stroke(); x.setLineDash([]); };
  x.fillStyle = "#222"; x.textAlign = "center"; x.font = mono(46, 700); x.fillText("T&E UK FORECOURT", 540, 210); x.font = mono(28); x.fillStyle = "#555"; x.fillText(`Week of ${dmy(P.latest)}`, 540, 256); dash(300);
  x.fillStyle = "#222"; x.font = mono(34, 700); x.fillText(t.k.toUpperCase(), 540, 372);
  const num = t.fmt(t.v); let fs = 300; do { x.font = `600 ${fs}px Oswald`; fs -= 10; } while (x.measureText(num).width > 660); x.fillText(num, 540, 372 + fs + 20);
  let y = 372 + fs + 90; if (t.unit) { x.font = "600 80px Oswald"; x.fillText(t.unit, 540, y + 20); y += 100; }
  dash(y); y += 62; x.font = mono(34); x.textAlign = "left"; let line = ""; for (const w of t.share.split(" ")) { if (x.measureText(line + w).width > 690) { x.fillText(line, 195, y); y += 46; line = ""; } line += w + " "; } x.fillText(line, 195, y); y += 56; x.font = mono(30, 700); x.fillText(t.chip.replace("▲ ", "+ "), 195, y);
  dash(Math.min(y + 40, 1064)); x.fillStyle = "#222"; for (let q = 195, k = 0; q < 885; k++) { const w = [4, 2, 6, 2, 3, 5][k % 6]; if (k % 2 === 0) x.fillRect(q, 1085, w, 70); q += w + 3; }
  x.textAlign = "center"; x.font = mono(26); x.fillStyle = "#555"; x.fillText("transportenvironment.org · UK fuel price tracker", 540, 1196); x.restore();
  /* T&E UK mark on the colour, under the receipt */
  const rr = (l, tp, w, h, r) => { x.beginPath(); x.moveTo(l + r, tp); x.arcTo(l + w, tp, l + w, tp + h, r); x.arcTo(l + w, tp + h, l, tp + h, r); x.arcTo(l, tp + h, l, tp, r); x.arcTo(l, tp, l + w, tp, r); x.closePath(); };
  const pl = 150, pt = 1196, pw = 292, ph = 96, cy = pt + ph / 2;
  x.save(); x.shadowColor = "rgba(0,0,0,.25)"; x.shadowBlur = 26; x.shadowOffsetY = 8; x.fillStyle = "#fff"; rr(pl, pt, pw, ph, ph / 2); x.fill(); x.restore();
  const lg = new Image(); lg.src = LOGO; await lg.decode(); const lw = 228, lh = lw * lg.height / lg.width; x.drawImage(lg, pl + (pw - lw) / 2, cy - lh / 2, lw, lh);
  x.textAlign = "left"; x.textBaseline = "middle";
  x.fillStyle = t.c === "a" ? "#1c1c1c" : "#fff"; x.font = "800 36px Figtree, Arial, sans-serif"; x.fillText("UK fuel price tracker", pl + pw + 34, cy - 20);
  x.font = "600 28px Figtree, Arial, sans-serif"; x.globalAlpha = .9; x.fillText("ukfuelpricetracker.netlify.app", pl + pw + 34, cy + 24); x.globalAlpha = 1; x.textBaseline = "alphabetic";
  cv.toBlob(bl => { const a = document.createElement("a"); a.href = URL.createObjectURL(bl); a.download = `TE_fuel_tracker_${t.k.replace(/\s+/g, "_").toLowerCase()}.png`; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 2000); b.textContent = "Saved"; beep(1250); setTimeout(() => b.textContent = "Save as image", 1800); }); });

/* your area */
const A = D.areas.map(([code, name, country, cars, bevs]) => ({ code, name, country, cars, bevs, share: bevs / cars }));
const GB = A.filter(a => a.country !== "Northern Ireland" && a.cars >= 10000).sort((a, b) => b.share - a.share);
const ukCars = A.reduce((t, a) => t + a.cars, 0), ukBevs = A.reduce((t, a) => t + a.bevs, 0);
let target = 1, picked = A.find(a => a.name === "Wigan") || A[0];
const extra = a => Math.max(0, target - a.share) * a.cars * saveYear / 1e6, today = a => a.bevs * saveYear / 1e6;
const ord = r => r + (["th", "st", "nd", "rd"][r % 100 > 10 && r % 100 < 14 ? 0 : r % 10 < 4 ? r % 10 : 0]);
function lists() {
  const row = a => `<div><div class="nm">${a.name}<span>${f1(a.share * 100)}%</span></div><div class="tr"><i class="t" style="width:${Math.max(a.share, target) * 100}%"></i><i class="n2" style="width:${a.share * 100}%"></i></div>
    <div class="mny">${money(today(a))} a year saved today${a.share >= target ? ", already past " + f0(target * 100) + "%" : ` · <b>+${money(extra(a))}</b> at ${f0(target * 100)}%`}</div></div>`;
  $("lgTop").innerHTML = GB.slice(0, 5).map(row).join(""); $("lgBot").innerHTML = GB.slice(-5).reverse().map(row).join("");
  const a = picked, rank = GB.indexOf(a), ang = Math.PI * (1 - a.share), tx = Math.PI * (1 - target);
  const arc = (from, to, col) => `<path d="M${150 + 115 * Math.cos(from)},${150 - 115 * Math.sin(from)}A115,115 0 0 1 ${150 + 115 * Math.cos(to)},${150 - 115 * Math.sin(to)}" fill="none" stroke="${col}" stroke-width="24" stroke-linecap="round"/>`;
  $("gauge").innerHTML = `<svg viewBox="0 0 300 180" role="img" aria-label="${f1(a.share * 100)}% of cars are BEVs">${arc(Math.PI, 0, "#f4ebe3")}${target > a.share ? arc(Math.PI, tx, "#73d68a") : ""}${arc(Math.PI, Math.min(Math.PI - 0.02, ang), "#039961")}
    <line x1="150" y1="150" x2="${150 + 96 * Math.cos(ang)}" y2="${150 - 96 * Math.sin(ang)}" stroke="#1c1c1c" stroke-width="6" stroke-linecap="round"/><circle cx="150" cy="150" r="10" fill="#1c1c1c"/>
    <text x="26" y="176" font-size="14" font-weight="800" fill="#8a8282">E</text><text x="262" y="176" font-size="14" font-weight="800" fill="#039961">BEV</text></svg>`;
  $("fc").innerHTML = `<h3>${a.name}</h3><p style="color:var(--ink-2);margin-top:8px"><b>${f1(a.share * 100)}%</b> of its ${f0(a.cars)} cars are BEVs${rank >= 0 ? `, ${ord(rank + 1)} of ${GB.length} in Great Britain` : ""}.</p>
    <div class="stats"><div><b>${money(today(a))}</b>a year saved today by its BEVs</div><div><b>${a.share >= target ? "£0" : "+" + money(extra(a))}</b>more a year at ${f0(target * 100)}% BEVs</div><div><b>£${f0(saveYear)}</b>a year per car that switches</div></div>`; }
pressed($("areaShare"), v => { target = v / 100; lists(); });
$("areaList").innerHTML = A.map(a => `<option value="${a.name}">`).join("");
const PC = /^[A-Z]{1,2}\d[A-Z\d]?\s*\d[A-Z]{2}$/i; let pcT;
$("areaSearch").addEventListener("input", ev => { const v = ev.target.value.trim(), a = A.find(x => x.name.toLowerCase() === v.toLowerCase());
  $("pcMsg").textContent = ""; clearTimeout(pcT);
  if (a) { picked = a; lists(); return; }
  if (PC.test(v)) pcT = setTimeout(async () => { $("pcMsg").textContent = "Looking up " + v.toUpperCase() + "...";
    try { const j = await (await fetch("https://api.postcodes.io/postcodes/" + encodeURIComponent(v.replace(/\s+/g, "")))).json();
      const hit = j.status === 200 && (A.find(x => x.code === j.result.codes.admin_district) || A.find(x => x.name.toLowerCase() === (j.result.admin_district || "").toLowerCase()));
      if (hit) { picked = hit; lists(); $("pcMsg").textContent = `${j.result.postcode} is in ${hit.name}.`; }
      else $("pcMsg").textContent = j.status === 200 ? "No car data for that council area." : "Postcode not found. Try your council's name.";
    } catch (err) { $("pcMsg").textContent = "Postcode lookup is not available here. Type your council's name."; } }, 500); });
lists();
$("areaLead").innerHTML = `A BEV costs <b>£${f0(saveYear)} a year less</b> to fuel than a petrol car at this week's prices. Today's ${f2(ukBevs / 1e6)} million BEVs already save UK drivers ${money(ukBevs * saveYear / 1e6)} a year. If every car switched: ${money(ukCars * saveYear / 1e6)}.`;
$("areaNote").textContent = `All cars as DVLA registers them, end of June ${D.la_q.slice(0, 4)} (DfT VEH0105 and VEH0142). Company cars count where the leasing company is registered, so a few fleet hubs sit high on the list. Ranking: Great Britain, areas with 10,000+ cars. Every car drives ${f0(MI)} miles a year; every non-BEV is costed as petrol. Running costs at this week's prices, not a forecast.`;

/* the bill */
function bill(fuel) { const W = 1000, H = 360, m = { l: 58, r: 16, t: 14, b: 32 }, wk = D.weeks;
  const val = w => fuel === "all" ? w.diesel.premium + w.petrol.premium : w[fuel].premium;
  let c = 0, cd = 0; const pts = wk.map(w => { c += val(w); cd += fuel === "petrol" ? 0 : w.diesel.premium; return { c, cd, est: w.estimated }; });
  const max = Math.max(...pts.map(q => q.c)) * 1.06, min = Math.min(0, ...pts.map(q => q.c));
  const x = i => m.l + (W - m.l - m.r) * i / (wk.length - 1), y = v => m.t + (H - m.t - m.b) * (1 - (v - min) / (max - min));
  const area = (k, col) => `<path d="M${x(0)},${y(0)}${pts.map((q, i) => `L${x(i)},${y(q[k])}`).join("")}L${x(pts.length - 1)},${y(0)}Z" fill="${col}"/>`;
  const fe = pts.findIndex(q => q.est), xe = fe > 0 ? x(fe - .5) : W, stepv = max > 5000 ? 2000 : max > 2500 ? 1000 : 500; let grid = "";
  for (let v = 0; v <= max; v += stepv) grid += `<line x1="${m.l}" x2="${W - m.r}" y1="${y(v)}" y2="${y(v)}" stroke="#d6cbc1"/><text x="${m.l - 8}" y="${y(v) + 5}" text-anchor="end" font-size="14" fill="#8a8282">£${v / 1000}bn</text>`;
  let months = "", last = ""; wk.forEach((w, i) => { const mo = new Date(w.week).toLocaleDateString("en-GB", { month: "short" }); if (mo !== last && i > 0) months += `<text x="${x(i)}" y="${H - 8}" text-anchor="middle" font-size="14" fill="#8a8282">${mo}</text>`; last = mo; });
  const top = fuel === "diesel" ? "#4d45ff" : "#ff8754";
  $("chBill").innerHTML = `<svg viewBox="0 0 ${W} ${H}" role="img" aria-label="Running total of extra fuel spending since 2 March"><defs><pattern id="hat" width="7" height="7" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="3" height="7" fill="#fff" opacity=".42"/></pattern>
    <clipPath id="cl"><path d="M${x(0)},${y(0)}${pts.map((q, i) => `L${x(i)},${y(q.c)}`).join("")}L${x(pts.length - 1)},${y(0)}Z"/></clipPath></defs>${grid}${area("c", top)}${fuel === "all" ? area("cd", "#4d45ff") : ""}
    <rect x="${xe}" y="${m.t}" width="${W - m.r - xe}" height="${H - m.t - m.b}" fill="url(#hat)" clip-path="url(#cl)"/><line x1="${xe}" x2="${xe}" y1="${m.t}" y2="${H - m.b}" stroke="#1c1c1c" stroke-dasharray="4 4"/>
    <text x="${xe + 8}" y="${m.t + 15}" font-size="14" font-weight="800" fill="#1c1c1c">Estimated volumes from here</text>${months}</svg>`;
  const t = T[fuel]; $("billBig").innerHTML = `${bn(t.total)}<small>of which ${bn(t.estimated)} (${f0(t.est_share * 100)}%) rests on estimated volumes · £${f1(t.per_day)}m a day</small>`;
  $("billLegend").innerHTML = (fuel === "all" ? `<span><i style="background:#4d45ff"></i>Diesel ${bn(T.diesel.total)}</span><span><i style="background:#ff8754"></i>Petrol ${bn(T.petrol.total)}</span>` : "") + `<span><i style="background:repeating-linear-gradient(45deg,#bbb 0 3px,#eee 3px 6px)"></i>Hatched: estimated volumes (HMRC data ends June 2026)</span>`; }
pressed($("billFuel"), bill); bill("all");
$("billNote").textContent = `Extra spent each week against the same week a year earlier, adjusted for inflation. Volumes are measured to ${dmy(D.window.measured_to)} (HMRC); the ${D.window.est_weeks} weeks since are T&E's estimate.`;
$("methodList").innerHTML = ["Pump prices: DESNZ weekly road fuel prices (Monday prices, UK average, including duty and VAT).",
  "Extra cost: each week's price excluding tax, minus the same week a year earlier adjusted for inflation (ONS CPI), times the litres sold (HMRC Hydrocarbon Oils Bulletin; road share of diesel from DUKES).",
  `HMRC volumes end in June 2026. Later weeks use T&E's estimate: 2025 volumes times the average March to June change. That is ${f0(T.all.est_share * 100)}% of the total.`,
  "Per car: DESNZ 2026 greenhouse gas conversion factors (real-world fuel use by market segment), 7,000 miles a year. BEV: 80% of charging at home on an overnight rate of 10p per kWh (4 miles per kWh for the average car), 20% on public slow and fast chargers (Zapmap price index).",
  "Council areas: DfT VEH0105 and VEH0142, licensed cars at the end of June 2026."].map(t => `<li>${t}</li>`).join("");

/* the embeddable widget: the tracker on its own, or with one council's line */
$("wgLa").innerHTML = `<option value="">Tracker only</option>` + [...A].sort((p1, p2) => p1.name.localeCompare(p2.name)).map(a => `<option value="${a.code}">${a.name}</option>`).join("");
const wg = () => { const code = $("wgLa").value, h = code ? 465 : 370; $("wgFrame").style.height = h + "px"; $("wgFrame").src = D.public_url + "?widget=" + code;   /* the public widget, whatever page this code is hosted on */
  $("wgCode").value = `<iframe src="${D.public_url}?widget=${code}" title="T&E UK fuel price tracker" style="width:100%;max-width:600px;height:${h}px;border:0;border-radius:16px" loading="lazy"></iframe>`; };
$("wgLa").addEventListener("change", wg); wg();
$("wgCopy").addEventListener("click", async () => { try { await navigator.clipboard.writeText($("wgCode").value); } catch (e) { $("wgCode").select(); document.execCommand("copy"); } $("wgCopy").textContent = "Copied"; beep(1250); setTimeout(() => $("wgCopy").textContent = "Copy embed code", 1600); });
$("footP").innerHTML = `Updated every Tuesday with the government's weekly fuel prices. Last update: week of ${dmy(P.latest)}.<br>Figures for an average car assume ${f0(MI)} miles a year.`;

/* header dots */
const secs = [...document.querySelectorAll("section[id]")];
$("dots").innerHTML = secs.map(x => `<a href="#${x.id}" aria-label="${x.id}"></a>`).join("");
const dots = [...document.querySelectorAll(".dots a")];
const dio = new IntersectionObserver(es => es.forEach(en => { if (en.isIntersecting) dots.forEach((d, i) => d.classList.toggle("on", secs[i] === en.target)); }), { rootMargin: "-50% 0px -50% 0px" });
secs.forEach(s => dio.observe(s));
}
/* widget (?widget=COUNCILCODE) and link-preview card (?card=1) */
function small(kind) { const f0 = x => Math.round(x).toLocaleString("en-GB"), f1 = x => x.toFixed(1), T = D.totals, P = D.prices;
  const wk = new Date(P.latest + "T12:00:00").toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" });
  const brand = `<span class="brand"><span class="logo" role="img" aria-label="T&amp;E"></span></span>`;
  document.querySelectorAll('body style, body link[rel="stylesheet"]').forEach(e => document.head.appendChild(e));   /* keep the styles when the body is replaced */
  document.body.className = kind;
  if (kind === "card") { document.body.innerHTML = `<div class="cd"><p class="eyebrow">UK fuel price tracker · week of ${wk}</p><h1>Since the oil shock, UK drivers have paid an extra</h1>
    <div class="n">£${(T.all.total / 1000).toFixed(2)}<span>bn</span></div><div class="b"><span>Petrol ${f1(P.now.petrol.pump)}p · Diesel ${f1(P.now.diesel.pump)}p a litre</span>${brand}</div></div>`; return; }
  const MI = 7000, km = MI * 1.609344, BB = D.bev_basis, kwhMi = D.running.kwh_km * D.running_km_per_mile, save = D.running.l100.petrol * km / 100 * P.now.petrol.pump / 100 - MI * (BB.home_share * BB.night_rate / BB.mi_per_kwh + (1 - BB.home_share) * D.ppm.public.slow_fast * kwhMi) / 100;
  const a = D.areas.find(x => x[0] === MODE.get("widget")), perSec = T.all.per_day * 1e6 / 86400, t0 = Date.now();
  document.body.innerHTML = `<div class="wg"><div class="top"><p class="eyebrow">UK fuel price tracker</p>${brand}</div><div class="n"><span id="wn"></span><small>extra spent on road fuel since the oil shock began on 2 March 2026</small></div>
    <div class="pp"><div>Petrol<b>${f1(P.now.petrol.pump)}p</b></div><div>Diesel<b>${f1(P.now.diesel.pump)}p</b></div><div style="font-weight:500;align-self:end">week of ${wk}</div></div>
    ${a ? `<div class="la"><b>${a[1]}:</b> ${(a[4] / a[3] * 100).toFixed(1)}% of cars are BEVs. If every car switched, drivers there would save <b>£${f0((a[3] - a[4]) * save / 1e6)}m a year</b>.</div>` : ""}
    <div class="by">Transport &amp; Environment UK · <a href="${D.page_url || D.public_url}" target="_blank" rel="noopener">See the full tracker</a></div></div>`;
  const tick = () => document.getElementById("wn").textContent = "£" + f0(T.all.total * 1e6 + perSec * (Date.now() - t0) / 1000); tick(); setInterval(tick, 1000); }
if (MODE.has("card")) small("card"); else if (MODE.has("widget")) small("widget"); else main();
