← back to Rentv 2026
public/2026/elements.js
165 lines
/* RENTV 2026 — Element Numbering overlay (shared drop-in).
*
* Purpose: give every significant element on every template a STABLE number so
* Steve can reference "Template C, element 14" in future feedback.
*
* Include on any page: <script src="/2026/elements.js" defer></script>
* Toggle: the "①② Elements" button (bottom-left) · the "e" key · or ?num=1 in the URL.
*
* Numbers are assigned in DOM order over a curated selector set, so they are
* stable across reloads (as long as the page structure doesn't change). Each
* numbered element gets a badge; a searchable index panel lists them all and
* clicking an index row scrolls to + flashes that element. State persists in
* localStorage per page path. Zero dependencies; fully removable (toggle off
* restores the DOM).
*/
(function () {
var SEL = [
'header', 'nav', 'section', 'main', 'footer',
'[data-ad-slot]',
'.card', '.slot', '.vslot', '.vidcard', '.rescard', '.tile', '.panel',
'h1', 'h2', 'h3', 'h4',
'nav a', 'footer a',
'button', 'a.btn', '.btn', '.chip', '.cta',
'input', 'select', 'textarea',
'.hero', '.vhero', '.controls', '.filters', '.rate', '.invgrid', '.river', '.vidrow'
].join(',');
var KEY = 'rentv_num_' + location.pathname;
var on = false, badges = [], touched = [];
function label(el) {
var t = (el.getAttribute('aria-label') || el.getAttribute('placeholder') || '').trim();
if (!t) {
var clone = el.cloneNode(true);
// strip our own badge text if present
t = (clone.textContent || '').replace(/\s+/g, ' ').trim();
}
if (!t && el.getAttribute('data-ad-slot')) t = 'ad-slot:' + el.getAttribute('data-ad-slot');
if (!t && el.tagName === 'IMG') t = el.getAttribute('alt') || 'image';
var cls = (el.className && typeof el.className === 'string') ? '.' + el.className.split(/\s+/).slice(0, 2).join('.') : '';
return { tag: el.tagName.toLowerCase() + cls, text: t.slice(0, 60) };
}
function css() {
if (document.getElementById('rentv-num-css')) return;
var s = document.createElement('style'); s.id = 'rentv-num-css';
s.textContent = [
'.rentv-el-badge{position:absolute;z-index:2147483000;top:0;left:0;transform:translate(-4px,-9px);',
' background:#c8102e;color:#fff;font:800 10px/1 ui-monospace,Menlo,monospace;padding:2px 4px;border-radius:4px;',
' box-shadow:0 1px 4px rgba(0,0,0,.4);pointer-events:none;letter-spacing:.02em}',
'.rentv-num-mark{outline:1px dashed rgba(200,16,46,.5);outline-offset:1px}',
'.rentv-num-flash{outline:3px solid #f0b429!important;transition:outline .2s}',
'#rentv-num-toggle{position:fixed;left:14px;bottom:14px;z-index:2147483600;background:#0b0e12;color:#fff;',
' border:1px solid #c8102e;border-radius:9px;padding:9px 13px;font:800 12px Inter,system-ui,sans-serif;',
' cursor:pointer;box-shadow:0 8px 26px rgba(0,0,0,.4)}',
'#rentv-num-toggle.on{background:#c8102e}',
'#rentv-num-index{position:fixed;right:14px;top:70px;bottom:70px;width:290px;z-index:2147483500;background:#0b0e12;',
' color:#e7ecf1;border:1px solid #1c232b;border-radius:12px;display:none;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,.5)}',
'#rentv-num-index.on{display:flex}',
'#rentv-num-index .h{padding:11px 13px;border-bottom:1px solid #1c232b;display:flex;gap:8px;align-items:center}',
'#rentv-num-index .h b{font:800 12px Inter;letter-spacing:.04em}',
'#rentv-num-index .h .ct{margin-left:auto;color:#7b848d;font:700 11px Inter}',
'#rentv-num-index input{width:100%;margin:8px 10px 6px;width:calc(100% - 20px);padding:7px 9px;border:1px solid #1c232b;',
' background:#11161c;color:#e7ecf1;border-radius:7px;font:13px Inter}',
'#rentv-num-index .list{overflow:auto;flex:1;padding:2px 6px 10px}',
'#rentv-num-index .row{display:flex;gap:8px;padding:6px 8px;border-radius:6px;cursor:pointer;font:12px/1.3 Inter}',
'#rentv-num-index .row:hover{background:#161d25}',
'#rentv-num-index .row .n{color:#fff;font:800 11px ui-monospace,monospace;background:#c8102e;border-radius:4px;padding:1px 5px;height:fit-content}',
'#rentv-num-index .row .m .tg{color:#8b949e;font-weight:700}',
'#rentv-num-index .row .m .tx{color:#c3cbd3}',
'#rentv-num-index .cp{padding:8px 12px;border-top:1px solid #1c232b;color:#7b848d;font:11px Inter}'
].join('');
document.head.appendChild(s);
}
function build() {
var seen = new Set();
var els = Array.prototype.slice.call(document.querySelectorAll(SEL)).filter(function (el) {
if (seen.has(el)) return false; seen.add(el);
if (el.closest('#rentv-num-index') || el.id === 'rentv-num-toggle') return false;
var r = el.getBoundingClientRect();
return r.width > 8 && r.height > 8; // visible-ish
});
var rows = '';
els.forEach(function (el, i) {
var n = i + 1;
el.setAttribute('data-el', n);
el.classList.add('rentv-num-mark');
touched.push(el);
var cs = getComputedStyle(el);
if (cs.position === 'static') { el.style.position = 'relative'; el.setAttribute('data-num-posfix', '1'); }
var b = document.createElement('span'); b.className = 'rentv-el-badge'; b.textContent = n;
el.appendChild(b); badges.push(b);
var L = label(el);
rows += '<div class="row" data-goto="' + n + '"><span class="n">' + n + '</span><span class="m"><span class="tg">' +
L.tag + '</span> <span class="tx">' + (L.text ? L.text.replace(/[<>&]/g, '') : '') + '</span></span></div>';
});
idx.querySelector('.list').innerHTML = rows;
idx.querySelector('.ct').textContent = els.length + ' elements';
}
function clear() {
badges.forEach(function (b) { b.remove(); }); badges = [];
touched.forEach(function (el) {
el.classList.remove('rentv-num-mark'); el.removeAttribute('data-el');
if (el.getAttribute('data-num-posfix')) { el.style.position = ''; el.removeAttribute('data-num-posfix'); }
});
touched = [];
idx.querySelector('.list').innerHTML = '';
}
var idx, toggleBtn;
function ui() {
css();
toggleBtn = document.createElement('button'); toggleBtn.id = 'rentv-num-toggle';
toggleBtn.innerHTML = '①② Elements'; document.body.appendChild(toggleBtn);
idx = document.createElement('div'); idx.id = 'rentv-num-index';
idx.innerHTML = '<div class="h"><b>Element index</b><span class="ct"></span></div>' +
'<input placeholder="filter by number or text…" aria-label="Filter elements">' +
'<div class="list"></div>' +
'<div class="cp">Click a row to jump. Ref future edits as “' + pageTag() + ' · el N”.</div>';
document.body.appendChild(idx);
toggleBtn.addEventListener('click', function () { set(!on); });
document.addEventListener('keydown', function (e) {
if (e.key === 'e' && !/input|textarea|select/i.test((e.target.tagName || ''))) set(!on);
});
idx.querySelector('input').addEventListener('input', function (e) {
var q = e.target.value.toLowerCase();
idx.querySelectorAll('.row').forEach(function (r) {
r.style.display = (!q || r.textContent.toLowerCase().indexOf(q) >= 0) ? '' : 'none';
});
});
idx.querySelector('.list').addEventListener('click', function (e) {
var r = e.target.closest('[data-goto]'); if (!r) return;
var el = document.querySelector('[data-el="' + r.dataset.goto + '"]'); if (!el) return;
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
el.classList.add('rentv-num-flash'); setTimeout(function () { el.classList.remove('rentv-num-flash'); }, 1400);
});
}
function pageTag() {
var m = location.pathname.match(/2026\/([a-e])\.html/); if (m) return 'Template ' + m[1].toUpperCase();
if (/2026\/?$/.test(location.pathname)) return 'Selector';
if (/advertise/.test(location.pathname)) return 'Advertise';
return location.pathname;
}
function set(v) {
on = v; toggleBtn.classList.toggle('on', on); idx.classList.toggle('on', on);
toggleBtn.innerHTML = on ? '✕ Hide numbers' : '①② Elements';
if (on) build(); else clear();
try { localStorage.setItem(KEY, on ? '1' : '0'); } catch (e) { }
}
function boot() {
ui();
var auto = /[?&]num=1/.test(location.search);
try { if (!auto && localStorage.getItem(KEY) === '1') auto = true; } catch (e) { }
if (auto) set(true);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot();
window.RENTVNumber = { on: function () { set(true); }, off: function () { set(false); } };
})();