Read the code before you install it.
This page is the whole product. Not a summary of it, not a security badge someone sold us — the actual file, in full, below. Read it, hash it, paste it into any AI you trust and ask what it does. Install it only after that.
1. What you are about to check
One file of plain JavaScript. No build step, no minifier, no dependencies, no package
manager. The listing at the bottom of this page is that file, and the hash below is
its hash — the same bytes ship as js/auto-year.js inside the zip and
inside the WordPress plugin.
- File
auto-year.js·auto-year-free/js/auto-year.jsin the zip- Size
- 8,130 bytes
- SHA-256
7e18e734a94a085ec12fd661e4f0c10f2ddf8b8d4ae66cd86655b4a7b12badf3
Unzip what you downloaded and check that the file is the one we published:
macOS / Linux shasum -a 256 auto-year-free/js/auto-year.js Windows certutil -hashfile auto-year-free\js\auto-year.js SHA256
Two honest footnotes. The hash is of the script, not of the zip: zip archives record build timestamps, so their digest changes without a single byte of code changing — the script is what runs on your site, so the script is what we pin. And if you take the snippet by copying it off the install guide rather than by unzipping the file, you get the same code but not necessarily the same hash: a pasted block carries whatever trailing whitespace your editor decides to add. Hash the file, not the paste.
2. What it does — and what it cannot do
Every line below is in the source further down; nothing here is a promise made outside the code.
It does
- Walk the text of your page and look at 4-digit numbers.
<script>and<style>are skipped. - Update such a number only if it is a year between 1990 and today and one of two gates is open: the text carries
©,Copyrightor(c), or the element sits in the bottom 15% of the page. - In a range like
2019–2024, change the end year only. - Leave the current year alone, so running twice changes nothing.
- Re-run every 250 ms for about ten seconds after load, then stop for good. Tilda and similar builders inject the footer after first paint; without this the script would arrive before the footer does.
- Sit inside
try/catchfrom top to bottom, so a failure is silence, never a broken page. - Skip any element you mark
data-ayu-ignore, and everything inside it.
It cannot
- Talk to us, or to anyone. There is no
fetch, noXMLHttpRequest, nosendBeacon, no tracking pixel — no network call of any kind, to our servers or to a third party. - Load anything external. It pulls in no library, no font, no CDN.
- Store anything: no cookies, no
localStorage, nosessionStorage. - See your visitors. It reads no form fields, no page URL, no addresses, no identity of any kind. It never learns a site is yours.
- Build new markup or run generated code: no
innerHTML, noeval, nonew Function, nocreateElement. It assigns text to text nodes, and that is the only write it performs. - Touch anything but that text: no attributes, links, images, styles, prices or layout.
- Phone home about a licence. There is no key, no activation, no expiry. Cut us off tomorrow and it keeps working.
Those absences are greppable. On the file you downloaded, this prints nothing:
grep -nE "fetch|XMLHttpRequest|sendBeacon|localStorage|sessionStorage|document\.cookie|innerHTML|eval\(|new Function|createElement" auto-year.js
3. The limits, stated by us first
- It runs in the visitor's browser. The year on screen is always current; the HTML stored on your server still contains the old one. Anyone who views source, or browses with JavaScript off, sees the old year. That is the trade for not needing your passwords.
-
A year followed immediately by another group of digits is left alone.
© 2019 100 Main Stwill not be updated. This is deliberate. A phone number like33 3613 2018looks exactly like a year to any rule simple enough to be safe, and rewriting a customer's phone number on their live site is far worse than leaving a year stale: a stale year is visible and fixable, a broken phone number is neither. So when the two readings are ambiguous, the script does nothing. -
It only sees its own page. A year drawn inside an image, a PDF, a
<canvas>or another site's embedded widget is out of reach. -
A year with no copyright marker in the middle of a page stays put —
Established 2008,since 2009and a project calledCROC 2020are not copyright lines, and it would be wrong to touch them.
4. Don't take our word for it — ask a machine that has no stake
Copy the source below, paste it into ChatGPT, Claude, Gemini or whatever you already use, and paste this after it:
This JavaScript is about to go into the footer of my website. Read it and tell me plainly: does it send any data anywhere, does it load anything from another server, does it store anything in my visitors' browsers, can it break my page, and is there anything in it that is not needed for the job it claims to do? Quote the lines that back each answer.
An AI reading this file has no reason to be kind to us, and the file is short enough to read in full — that is the point of shipping it unminified. If an answer contradicts anything on this page, we would genuinely like to hear about it: [email protected].
Prefer tools over opinions? Upload the file to VirusTotal for 70+ engines, or paste it into JSHint — it is ordinary, standards-compliant code with no syntax anyone has to be clever about.
5. The whole thing, nothing removed
Comments included — they explain the awkward decisions, which is where a script like this would hide something if it wanted to.
/*!
* YearAlert — standalone (no-backend) copyright-year updater.
*
* Drop-in <script> for any site (verified live on Tilda, nosandson.com): keeps the
* copyright year fresh client-side with ZERO server calls and ZERO content edits.
*
* Rule: a 4-digit in-range year is updated to the current year only if it is either
* (a) in a COPYRIGHT context — same text carries ©, "copyright" or "(c)", OR
* (b) in the BOTTOM BAND — its element sits in the lowest `BAND` of the page
* (footers live at the bottom; mid-page years like "CROC 2020" are left alone).
* Ranges (2019–2024) update only the END year, and only when the range really is one —
* a phone written "3613-2018" is not. Nothing is rewritten when another group of digits
* sits right beside the candidate: a footer's phone number is not a copyright year, and
* breaking one on a customer's live site is worse than leaving a year stale (T-018).
* Idempotent (never re-touches the current year). Opt-out: add data-ayu-ignore to any
* element to exclude its subtree.
*
* Wrapped so it can NEVER throw into the host page. Tilda and other builders inject
* footer blocks asynchronously, so we re-run on a short interval after first paint.
*/
(function () {
'use strict';
var MIN_YEAR = 1990;
var DASH = '[\\-\\u2013\\u2014]'; // hyphen, en dash, em dash
var BAND = 0.85; // bottom 15% of the page counts as "footer zone"
var COPY = '©';
// What may sit between two groups of a phone number. Deliberately NOT '.' or ',':
// "Copyright 2019. 123 Main St" is a sentence stop and a street number, not a dial code.
var PHONE_SEP = '[\\s\\u00a0\\-\\u2013\\u2014()/]';
var SEP_BEFORE = new RegExp('\\d' + PHONE_SEP + '{0,2}$');
var SEP_AFTER = new RegExp('^' + PHONE_SEP + '{0,2}\\d');
function inRange(y, cur) { return y >= MIN_YEAR && y <= cur; }
function hasCopyright(text) {
if (!text) return false;
var t = text.toLowerCase();
return t.indexOf(COPY) !== -1 || t.indexOf('copyright') !== -1 || t.indexOf('(c)') !== -1;
}
/*
* A 4-digit group with ANOTHER group of digits right beside it belongs to a phone
* number, not to a copyright: "Llámanos: 33 3613 2018". The digit-run gate below only
* protects digits welded into one run (ИНН, ОГРН) — a phone is written in groups, so
* every other gate lets it through, and a footer carrying both a phone and a © is the
* normal case in our market (see GO-TO-MARKET).
*
* The lead scanner hit the same bug from the other side and fixed it the same way
* (`isPhoneFragment` in crm/server/src/checks/_year-detect.js). There a false positive
* cost us one bad lead; here it rewrites a paying customer's phone number on their own
* live site, silently. So this errs toward doing nothing: a year we skip is visible and
* recoverable, a phone we break is neither.
*
* `start`/`end` bracket the candidate inside `text`.
*/
function isPhoneFragment(text, start, end) {
return SEP_BEFORE.test(text.slice(Math.max(0, start - 14), start))
|| SEP_AFTER.test(text.slice(end, end + 14));
}
// A dash between two 4-digit groups is only a year range when the FIRST group is itself
// a plausible year — a copyright cannot begin in the future. That is what separates
// "© 2019–2024" from "3613-2018", which the range path used to rewrite to "3613-2026".
// Below MIN_YEAR the text must actually say copyright: "© 1985–2024" is a real footer,
// a bare "1234-2018" is a phone number.
function isYearRange(text, whole, first, index, cur) {
var start = parseInt(first, 10);
if (start > cur) return false;
if (start < MIN_YEAR && !hasCopyright(text)) return false;
return !isPhoneFragment(text, index, index + whole.length);
}
// True if the node's element sits within the bottom BAND of the document.
function isBottom(node) {
var el = node.parentElement;
if (!el || !el.getBoundingClientRect) return false;
var r = el.getBoundingClientRect();
var absTop = r.top + (window.pageYOffset || window.scrollY || 0);
var docH = document.documentElement.scrollHeight;
return docH > 0 && absTop >= docH * BAND;
}
function isIgnored(node) {
var el = node.parentElement;
if (!el || !el.closest) return false;
try { return !!el.closest('[data-ayu-ignore]'); } catch (e) { return false; }
}
// Core text-node transform. Exported for tests. `opts` lets tests force the gate
// (copyright / bottom) without a real layout: { context: <string>, atBottom: <bool> }.
function fixTextValue(text, cur, gateOpen) {
if (!text || !/\d{4}/.test(text)) return { text: text, changed: false };
if (!gateOpen) return { text: text, changed: false };
var cy = String(cur);
var changed = false;
var rangeSrc = '(\\d{4})(\\s*' + DASH + '\\s*)(\\d{4})';
// Does the text hold a range that is genuinely a range? A dash-separated phone is not
// one, and must not send us down the range path — that path skips single years, so a
// rejected phone would otherwise shadow the real copyright later in the same node.
var scan = new RegExp(rangeSrc, 'g');
var hit;
var hasYearRange = false;
while ((hit = scan.exec(text))) {
if (isYearRange(text, hit[0], hit[1], hit.index, cur)) { hasYearRange = true; break; }
}
if (hasYearRange) {
text = text.replace(new RegExp(rangeSrc, 'g'), function (m, a, s, b, offset, whole) {
if (!isYearRange(whole, m, a, offset, cur)) return m;
var e = parseInt(b, 10);
if (inRange(e, cur) && e !== cur) { changed = true; return a + s + cy; }
return m;
});
} else {
// Single year. Require non-digit (or string edge) on BOTH sides so a 4-digit run
// inside a longer number — Russian ИНН/ОГРН, phone, etc., which frequently share the
// copyright text node — is never rewritten. No lookbehind (universal browser support).
text = text.replace(/(\D|^)(\d{4})(?!\d)/g, function (m, pre, y, offset, whole) {
var n = parseInt(y, 10);
if (!inRange(n, cur) || n === cur) return m;
var at = offset + pre.length;
if (isPhoneFragment(whole, at, at + 4)) return m;
changed = true;
return pre + cy;
});
}
return { text: text, changed: changed };
}
function fixNode(node, cur) {
var raw = node.nodeValue;
if (!raw || !/\d{4}/.test(raw)) return;
if (isIgnored(node)) return;
var ctx = raw + ' ' + (node.parentNode ? node.parentNode.textContent : '');
var gateOpen = hasCopyright(ctx) || isBottom(node);
var res = fixTextValue(raw, cur, gateOpen);
if (res.changed) node.nodeValue = res.text;
}
function replaceYears(cur) {
cur = cur || new Date().getFullYear();
if (typeof document === 'undefined' || !document.body) return;
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
acceptNode: function (nd) {
var p = nd.parentNode;
var tg = p && p.nodeName ? p.nodeName.toLowerCase() : '';
return (tg === 'script' || tg === 'style')
? NodeFilter.FILTER_REJECT
: NodeFilter.FILTER_ACCEPT;
}
}, false);
var nd;
while ((nd = walker.nextNode())) fixNode(nd, cur);
}
function run() { try { replaceYears(); } catch (e) { /* never throw into host */ } }
// Re-run for ~10s: builder-injected footers (Tilda, etc.) appear after first paint.
function boot() {
run();
var i = 0;
var iv = setInterval(function () { run(); if (++i > 40) clearInterval(iv); }, 250);
}
// Browser auto-boot.
try {
if (typeof document !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
}
} catch (e) { /* ignore */ }
// Node/test surface.
if (typeof module !== 'undefined' && module.exports) {
module.exports = { fixTextValue: fixTextValue, hasCopyright: hasCopyright, inRange: inRange, MIN_YEAR: MIN_YEAR, BAND: BAND };
}
})();
The listing is inside an element marked data-ayu-ignore — the same opt-out
you can put on any element of your own site. That is why the years printed in it stay as
written.
Happy with what you read?
Get it — it takes two minutesAlready bought? The step-by-step is on the install guide.