Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/feedthejoe / src/ui/feed.js |
| Date | Not specified in source |
| Repository | Janady13/feedthejoe |
| Revision | 0e608a2b515a959bf35d2783428766624400ca3c |
| Branch | main |
| Path or archive member | src/ui/feed.js |
| Original SHA-256 | b6dc5d9fc03382219b304adc793dd3a660673d9d213752bf9da383c177d00955 |
| Git blob SHA | efb1777afb90666818cbfc3a14bf3b549b6e5ad9 |
| Reading-copy SHA-256 | b6dc5d9fc03382219b304adc793dd3a660673d9d213752bf9da383c177d00955 |
Recovered source transcript
Download complete source text ↓105 lines · 3.6 KB
/**
* Roster rendering.
*
* Rendering only — computes nothing, stores nothing. It receives an ordered
* list from the ranker and makes the DOM match it.
*
* The important idea here is that RANK IS EXPRESSED AS LIGHT. Each row gets a
* `--lit` value in [0,1] and the stylesheet spends it on colour, weight, size
* and contrast at once. The model's opinion arrives as illumination rather than
* as a number, which is both more legible at a glance and more honest: an entry
* in shadow is dimmer, never removed. Everything stays in the document.
*
* The falloff is exponential rather than linear. A linear ramp across forty-two
* entries gives every row a slightly different grey and reads as mush; an
* exponential one puts three or four entries genuinely in the light and lets
* the rest recede, which is how a painting directs attention.
*/
const TERMS = [
['affinity', 'affinity', false],
['semantic', 'semantic', false],
['freshness', 'freshness', false],
['fatigue', 'fatigue', true],
];
/** Rank -> illumination. */
function illumination(i) {
return Math.max(0.06, Math.exp(-i / 7));
}
function paintScore(row, parts) {
const pips = row.querySelectorAll('[data-score] span');
if (pips.length !== 4) return;
const vals = [parts.affinity, parts.semantic, parts.freshness, parts.fatigue];
vals.forEach((v, i) => pips[i].style.setProperty('--w', Math.min(1, Math.abs(v)).toFixed(3)));
}
function paintWhy(row, parts) {
const panel = row.querySelector('[data-whypanel]');
if (!panel) return;
panel.innerHTML = TERMS.map(([key, label, cost]) => {
const raw = parts[key];
const v = cost ? -raw : raw;
return `<div class="whyrow">
<span>${label}</span>
<span class="whybar${cost ? ' is-cost' : ''}"><i style="--v:${Math.min(1, Math.abs(v)).toFixed(3)}"></i></span>
<span class="whyv">${v >= 0 ? '+' : '−'}${Math.abs(v).toFixed(2)}</span>
</div>`;
}).join('');
}
/**
* Apply a new order to the DOM.
* @param {HTMLElement} container
* @param {Array<{item: object, parts: object}>} ranked
* @param {{animate?: boolean}} [opts]
*/
export function applyOrder(container, ranked, opts = {}) {
const nodes = new Map(
[...container.querySelectorAll('[data-item-id]')].map((el) => [el.dataset.itemId, el]),
);
const commit = () => {
const frag = document.createDocumentFragment();
ranked.forEach(({ item, parts }, i) => {
const el = nodes.get(item.id);
if (!el) return;
el.style.setProperty('--lit', illumination(i).toFixed(4));
const n = el.querySelector('[data-rank]');
if (n) n.textContent = String(i + 1).padStart(2, '0');
el.__parts = parts;
paintScore(el, parts);
if (el.classList.contains('is-open')) paintWhy(el, parts);
el.classList.toggle('is-deferred', i >= 8);
frag.appendChild(el);
});
container.appendChild(frag);
};
if (opts.animate && typeof document.startViewTransition === 'function') {
document.startViewTransition(commit);
} else {
commit();
}
}
/** Wire the score disclosures. Delegated, so it survives re-ordering. */
export function bindWhy(container) {
container.addEventListener('click', (ev) => {
const btn = ev.target.closest?.('[data-why]');
if (!btn) return;
ev.preventDefault();
const row = btn.closest('[data-item-id]');
if (!row) return;
const open = !row.classList.contains('is-open');
row.classList.toggle('is-open', open);
btn.setAttribute('aria-expanded', String(open));
btn.textContent = open ? 'hide' : 'why this one';
if (open && row.__parts) paintWhy(row, row.__parts);
});
}