PageSourceSearch

https://www.cumulocity.com/js/ledger-spotlight.js

js cumulocity.com collected 2026-10-02 07:06:12 UTC 3,681 bytes, 90 lines download raw bytes

1/* Spotlight for the homepage outcome ledger (partials/home/panel-journey.html).
2 *
3 * Keeps the row nearest the viewport's reading line at full contrast and dims
4 * the rest, so a reader meets one outcome per beat instead of six bodies at
5 * once. Rows also rise in the first time they enter the viewport.
6 *
7 * Progressive: this script only ever ADDS state. Without it -- no JavaScript,
8 * or a visitor who prefers reduced motion -- the stylesheet leaves every row at
9 * full contrast and nothing moves. The dimming is gated on the `is-spotlit`
10 * class this script sets, never on the markup.
11 *
12 * Deliberately not GSAP: this is an IntersectionObserver for the reveal and one
13 * rAF-throttled scroll handler for the spotlight, which is inside the motion
14 * stack design.md sanctions. ScrollTrigger buys nothing here that a pinned
15 * header and a nearest-row test do not already give.
16 */
17(function () {
18  'use strict';
19
20  var root = document.querySelector('[data-ledger]');
21  if (!root) return;
22
23  if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
24
25  var rows = Array.prototype.slice.call(root.querySelectorAll('[data-ledger-row]'));
26  if (rows.length < 2) return;
27
28  root.classList.add('is-spotlit');
29
30  /* 1. Reveal: each row rises in once, the first time it enters the viewport. */
31  if ('IntersectionObserver' in window) {
32    var io = new IntersectionObserver(function (entries) {
33      entries.forEach(function (entry) {
34        if (!entry.isIntersecting) return;
35        entry.target.classList.add('is-seen');
36        io.unobserve(entry.target);
37      });
38    }, { rootMargin: '0px 0px -12% 0px' });
39    rows.forEach(function (row) { io.observe(row); });
40  } else {
41    rows.forEach(function (row) { row.classList.add('is-seen'); });
42  }
43
44  /* 2. Spotlight. A row is lit until its TOP edge passes the anchor, then the
45   *    next one lights. The anchor is the scroll position at which the header
46   *    STICKS: the header sits `padding-top` below the row's top inside its
47   *    column, and pins when that natural position reaches its sticky `top`,
48   *    so the row's top is at (sticky top - padding) at that instant. Row 1
49   *    therefore lets go exactly as the header locks, and every later row lets
50   *    go at the same line. A fixed pixel line, never a fraction of the
51   *    viewport: a fraction sits lower on a taller window and made the first
52   *    row let go while still mid-screen.
53   *    While the list is still arriving, the first row is lit; once every row
54   *    has passed the anchor, the last one stays lit. */
55  var pin = root.querySelector('.ledger__pin');
56  var headCol = pin ? pin.parentElement : null;
57  var offset = parseFloat(root.getAttribute('data-reading-offset'));
58  if (isNaN(offset)) offset = 0;
59
60  function anchor() {
61    var top = pin ? parseFloat(window.getComputedStyle(pin).top) : NaN;
62    if (isNaN(top)) return 96 + offset;   // header not pinned (narrow viewports)
63    var pad = headCol ? parseFloat(window.getComputedStyle(headCol).paddingTop) : 0;
64    return top - (isNaN(pad) ? 0 : pad) + offset;
65  }
66
67  var frame = 0;
68
69  function pick() {
70    frame = 0;
71    var line = anchor();
72    var best = rows[rows.length - 1];
73
74    for (var i = 0; i < rows.length; i++) {
75      if (rows[i].getBoundingClientRect().top >= line) { best = rows[i]; break; }
76    }
77
78    rows.forEach(function (row) {
79      row.classList.toggle('is-active', row === best);
80    });
81  }
82
83  function schedule() {
84    if (!frame) frame = window.requestAnimationFrame(pick);
85  }
86
87  window.addEventListener('scroll', schedule, { passive: true });
88  window.addEventListener('resize', schedule);
89  pick();
90})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.