PageSourceSearch

https://drglenntoby.com/assets/enhance/gt-pass2.js?v=firstscroll1

js drglenntoby.com collected 2026-10-02 11:32:30 UTC 23,880 bytes, 555 lines download raw bytes

1/* Pass 2 — six rooms in The Arrangement, a tracking Climb, a still hero.
2   Enhancement layer. Conventions carried over from btt-band.js, each
3   learned the hard way on this ecosystem: never key off builder class
4   names (find things by their text), wait for hydration to settle, and if
5   a re-render drops the work, put it back rather than assume it stays. */
6(function () {
7  "use strict";
8
9  var BTT_URL = "https://abridgetothethrone.com";
10  var COVER = "/assets/enhance/btt-cover.jpg";
11  var SERVICE_PATH = "/work-with-dr-toby";
12  var BEAT = 0.66;              // 90 BPM
13  var STEP = 0.11;              // six tiles x 0.11 = 0.55s, still one beat
14  var TILE_ID = "gt-tile-btt";
15
16  // Verbatim from abridgetothethrone.com, except `claim`, which follows the
17  // section's own "The X, verbed." pattern.
18  var BTT = {
19    category: "Book / Method",
20    title: "A Bridge to the Throne",
21    claim: "The crossing, structured.",
22    excerpt: "Structure, alignment, and the crossing from survival to legacy.",
23    chip: "Coming Soon"
24  };
25
26  var isHome = (function () {
27    var p = location.pathname.replace(/\/+$/, "") || "/";
28    return p === "/" || /\/index(\.html)?$/.test(p);
29  })();
30
31  /* The exported Framer router can update the URL without replacing the
32     server-rendered page in a static Pages deployment. Keep internal route
33     changes native so every destination receives its own complete document. */
34  window.addEventListener("click", function (event) {
35    if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
36    var link = event.target && event.target.closest && event.target.closest("a[href]");
37    if (!link || link.hasAttribute("download") || (link.target && link.target !== "_self")) return;
38    var url;
39    try { url = new URL(link.href, location.href); } catch (_) { return; }
40    if (url.origin !== location.origin || !/^https?:$/.test(url.protocol)) return;
41    var sameDocument = url.pathname === location.pathname && url.search === location.search;
42    if (sameDocument && url.hash) return;
43    if (url.href === location.href) return;
44    event.preventDefault();
45    event.stopImmediatePropagation();
46    location.assign(url.href);
47  }, true);
48
49  function txt(el) { return (el && el.textContent || "").trim(); }
50
51  function linkPath(link) {
52    try { return new URL(link.href, location.href).pathname.replace(/\.html$/, "").replace(/\/+$/, "") || "/"; }
53    catch (_) { return ""; }
54  }
55
56  function findPathLink(root, path) {
57    var links = root ? root.querySelectorAll("a[href]") : [];
58    for (var i = 0; i < links.length; i++) if (linkPath(links[i]) === path) return links[i];
59    return null;
60  }
61
62  /* Services is a first-class site destination. Clone the export's own
63     Speaking row so desktop, tablet, phone, and footer navigation retain
64     their native typography, spacing, breakpoints, and focus behavior. */
65  function integrateServices() {
66    var navs = document.querySelectorAll('nav[data-framer-name="Desktop"], nav[data-framer-name="Tablet"], nav[data-framer-name="Phone"]');
67    for (var i = 0; i < navs.length; i++) {
68      var nav = navs[i];
69      if (findPathLink(nav, SERVICE_PATH)) continue;
70      var speaking = findPathLink(nav, "/speaking");
71      var row = speaking && speaking.closest("[data-framer-name]");
72      if (!row || !row.parentNode) continue;
73      var entry = row.cloneNode(true);
74      var link = entry.querySelector("a[href]");
75      if (!link) continue;
76      entry.classList.add("gt-services-nav-entry");
77      entry.setAttribute("data-framer-name", "Services");
78      link.href = SERVICE_PATH;
79      link.textContent = "Services";
80      link.removeAttribute("data-framer-page-link-current");
81      link.removeAttribute("aria-current");
82      row.insertAdjacentElement("afterend", entry);
83    }
84
85    var groups = document.querySelectorAll('footer [data-framer-name="Quick Links"]');
86    for (var g = 0; g < groups.length; g++) {
87      var group = groups[g];
88      var existingFooterLink = findPathLink(group, SERVICE_PATH);
89      if (existingFooterLink) {
90        var existingFooterRow = existingFooterLink.closest("[data-framer-name]");
91        if (existingFooterRow) existingFooterRow.classList.add("gt-services-footer-entry", "gts-footer-entry");
92        continue;
93      }
94      var footerSpeaking = findPathLink(group, "/speaking");
95      var footerRow = footerSpeaking && footerSpeaking.closest("[data-framer-name]");
96      if (!footerRow || !footerRow.parentNode) continue;
97      var footerEntry = footerRow.cloneNode(true);
98      var footerLink = footerEntry.querySelector("a[href]");
99      if (!footerLink) continue;
100      footerEntry.classList.add("gt-services-footer-entry", "gts-footer-entry");
101      footerEntry.setAttribute("data-framer-name", "Services");
102      footerLink.href = SERVICE_PATH;
103      footerLink.textContent = "Services";
104      footerLink.removeAttribute("data-framer-page-link-current");
105      footerLink.removeAttribute("aria-current");
106      footerRow.insertAdjacentElement("afterend", footerEntry);
107    }
108  }
109
110  /* Downward arrows are excluded from link-like controls throughout this
111     site. The export can recreate the Testimonials disclosure during
112     hydration, so enforce the rule on each bounded repair pass as well. */
113  function stripDownArrows() {
114    var controls = document.querySelectorAll("a, button");
115    var arrows = /[↓↘↙↧⇩⇣▼▽▾⌄⏷⮟⭣⤵⤶⤷]/g;
116    for (var i = 0; i < controls.length; i++) {
117      var walker = document.createTreeWalker(controls[i], NodeFilter.SHOW_TEXT);
118      var node;
119      while ((node = walker.nextNode())) node.nodeValue = node.nodeValue.replace(arrows, "");
120    }
121  }
122
123  /* ── Builder subdomains -> the real domains ──────────────────────────
124     The base HTML and the runtime bundle are both corrected, so this is a
125     belt-and-braces pass: if the site is ever regenerated from the design
126     source, the old addresses come back in the output and this puts them
127     right again on load. Idempotent. */
128  var DOMAINS = [
129    ["kingsexhibition.framer.website",    "thekingsexhibition.com"],
130    ["bookbankfoundation.framer.website", "thebbf.org"],
131    ["bridgetothethrone.framer.website",  "abridgetothethrone.com"]
132  ];
133
134  function realDomains() {
135    var as = document.querySelectorAll('a[href*="framer.website"]');
136    for (var i = 0; i < as.length; i++) {
137      var href = as[i].getAttribute("href") || "";
138      for (var d = 0; d < DOMAINS.length; d++) {
139        if (href.indexOf(DOMAINS[d][0]) !== -1) {
140          as[i].setAttribute("href", "https://" + DOMAINS[d][1]);
141          break;
142        }
143      }
144    }
145    return as.length === 0;
146  }
147
148  /* ── The hero's scroll parallax ─────────────────────────────────────── */
149  function stillHero() {
150    var hero = document.querySelector("section");
151    if (!hero) return false;
152    var nodes = hero.querySelectorAll("div");
153    for (var i = 0; i < nodes.length; i++) {
154      var s = nodes[i].getAttribute("style") || "";
155      // the backdrop is the one the runtime drives with both a lift and a zoom
156      if (/scale\(1\.0[0-9]/.test(s) && /translateY\(-\d/.test(s)) {
157        nodes[i].setAttribute("data-gt-noparallax", "");
158        return true;
159      }
160    }
161    return false;
162  }
163
164  /* ── The Climb: drop the transition fighting the scroll ──────────────── */
165  function freeClimb() {
166    var n = 0;
167    // Wide layout: the drawn line is the polyline carrying a dash array.
168    // (Also handled in CSS, since this component mounts lazily.)
169    var polys = document.querySelectorAll("polyline");
170    for (var i = 0; i < polys.length; i++) {
171      if (polys[i].getAttribute("stroke-dasharray") && !polys[i].hasAttribute("data-gt-noease")) {
172        polys[i].setAttribute("data-gt-noease", "");
173        n++;
174      }
175    }
176    // Narrow layout: the same component renders a vertical bar instead, and
177    // eases its `height` on scroll. Height cannot be matched by a CSS
178    // attribute selector, so find it by the property actually transitioning.
179    var divs = document.querySelectorAll("div");
180    for (var k = 0; k < divs.length; k++) {
181      var d = divs[k];
182      if (d.hasAttribute("data-gt-noease")) continue;
183      var st = d.getAttribute("style") || "";
184      if (st.indexOf("calc(") === -1) continue;
185      var tp = window.getComputedStyle(d).transitionProperty || "";
186      if (tp.indexOf("height") !== -1) { d.setAttribute("data-gt-noease", ""); n++; }
187    }
188    return n > 0;
189  }
190
191  /* ── The Arrangement ────────────────────────────────────────────────── */
192  function findWorlds() {
193    var ps = document.querySelectorAll("p");
194    var probe = null;
195    for (var i = 0; i < ps.length; i++) {
196      if (/Six rooms, one pulse/i.test(ps[i].textContent || "")) { probe = ps[i]; break; }
197    }
198    if (!probe) return null;
199    // walk out to the section, then find the row that holds the tiles
200    var sec = probe;
201    while (sec && sec !== document.body && sec.tagName !== "SECTION") sec = sec.parentElement;
202    if (!sec) return null;
203    var links = sec.querySelectorAll("a[href]");
204    if (!links.length) return null;
205    var row = links[0].parentElement;
206    // every tile must share that parent, or we have the wrong node
207    var tiles = [];
208    for (var k = 0; k < row.children.length; k++) {
209      if (row.children[k].tagName === "A") tiles.push(row.children[k]);
210    }
211    return tiles.length >= 5 ? { section: sec, row: row, tiles: tiles } : null;
212  }
213
214  // In document order a tile reads: category, title(h3), claim, excerpt,
215  // chip, "Step inside". Structural, so no builder class names are needed.
216  function label(tile) {
217    var parts = tile.querySelectorAll("p, h3");
218    var roles = ["category", "title", "claim", "excerpt", "chip", "cta"];
219    for (var i = 0; i < parts.length && i < roles.length; i++) {
220      parts[i].setAttribute("data-gt-role", roles[i]);
221    }
222    // A tile is three bands: category strip, motif, body; the body is a top
223    // block over a footer. Tagging them structurally (not by builder class
224    // name) lets the stylesheet pin each footer to the bottom, so the chip
225    // and Step Inside button share one line across all six tiles.
226    var bands = tile.children;
227    if (bands.length >= 3) {
228      bands[0].setAttribute("data-gt-part", "cat");
229      bands[1].setAttribute("data-gt-part", "motif");
230      var body = bands[2];
231      body.setAttribute("data-gt-part", "body");
232      if (body.children.length >= 2) {
233        body.children[0].setAttribute("data-gt-part", "top");
234        body.children[1].setAttribute("data-gt-part", "foot");
235      }
236    }
237    return parts;
238  }
239
240  function buildTile(model) {
241    var tile = model.cloneNode(true);
242    tile.id = TILE_ID;
243    tile.setAttribute("href", BTT_URL);
244    tile.setAttribute("target", "_blank");
245    tile.setAttribute("rel", "noreferrer");
246    // the runtime may have left transition state on the source node
247    tile.style.removeProperty("opacity");
248    tile.style.removeProperty("transform");
249
250    var parts = label(tile);
251    var map = { category: BTT.category, title: BTT.title, claim: BTT.claim, excerpt: BTT.excerpt, chip: BTT.chip };
252    for (var i = 0; i < parts.length; i++) {
253      var role = parts[i].getAttribute("data-gt-role");
254      if (map[role]) parts[i].textContent = map[role];
255      if (role === "chip") parts[i].setAttribute("data-gt-state", "soon");
256    }
257
258    var img = tile.querySelector("img");
259    if (img) {
260      img.setAttribute("src", COVER);
261      img.setAttribute("alt", "A Bridge to the Throne — book cover");
262      img.removeAttribute("srcset");
263      img.removeAttribute("sizes");
264      img.setAttribute("loading", "lazy");
265      img.setAttribute("width", "800");
266      img.setAttribute("height", "1280");
267      img.style.objectFit = "contain";
268    }
269    return tile;
270  }
271
272  /* ── Entrance, on one downbeat ──────────────────────────────────────── */
273  function arm(el, kind, delay) {
274    if (!el || el.classList.contains("gt-rise") ||
275        el.classList.contains("gt-rise-head") || el.classList.contains("gt-rise-thread")) return;
276    el.setAttribute("data-gt-delay", delay);
277    el.style.setProperty("--gt-delay", delay + "s");
278    el.setAttribute("data-gt-kind", kind || "tile");
279  }
280
281  // The class is added only at reveal time. Until then the element is in its
282  // normal, visible state, so nothing can strand it hidden.
283  function play(el) {
284    var kind = el.getAttribute("data-gt-kind") || "tile";
285    el.classList.add(kind === "head" ? "gt-rise-head" : kind === "thread" ? "gt-rise-thread" : "gt-rise");
286  }
287
288  function reveal(root, targets) {
289    var fired = false;
290    function go() {
291      if (fired) return;
292      fired = true;
293      // Browsers pause CSS animations in a background tab, and an animation
294      // that never starts would sit on its opening frame — invisible. There
295      // is nothing to watch in a hidden tab anyway, so skip the entrance and
296      // leave the section in its normal, visible state.
297      if (document.hidden) return;
298      for (var i = 0; i < targets.length; i++) play(targets[i]);
299      // Once the entrance has run, drop every trace of it. The resting state
300      // is the visible one, so cleanup can only ever leave tiles on screen.
301      window.setTimeout(function () {
302        for (var k = 0; k < targets.length; k++) {
303          targets[k].classList.remove("gt-rise", "gt-rise-head", "gt-rise-thread");
304          targets[k].style.removeProperty("--gt-delay");
305        }
306      }, 2000);
307    }
308    if (!("IntersectionObserver" in window)) { go(); return; }
309    var io = new IntersectionObserver(function (entries) {
310      if (entries[0] && entries[0].isIntersecting) { go(); io.disconnect(); }
311    }, { threshold: 0.15 });
312    io.observe(root);
313    window.setTimeout(go, 4000);
314  }
315
316  function apply() {
317    var found = findWorlds();
318    if (!found) return false;
319    var row = found.row, tiles = found.tiles;
320
321    if (!document.getElementById(TILE_ID)) {
322      for (var i = 0; i < tiles.length; i++) label(tiles[i]);
323      row.appendChild(buildTile(tiles[tiles.length - 1]));
324    }
325    row.setAttribute("data-gt-worlds", "");
326
327    // chips read LIVE on three tiles and Live on two; normalise the wording
328    var chips = row.querySelectorAll('[data-gt-role="chip"]');
329    for (var c = 0; c < chips.length; c++) {
330      if (/^live$/i.test(txt(chips[c]))) chips[c].textContent = "LIVE";
331    }
332
333    // arm the entrance once
334    if (!row.hasAttribute("data-gt-armed")) {
335      row.setAttribute("data-gt-armed", "");
336      var targets = [];
337      var head = found.section.querySelectorAll("h2, h3");
338      var lead = head.length ? head[0].parentElement : null;
339      if (lead) { lead.setAttribute("data-gt-lead", ""); arm(lead, "head", 0); targets.push(lead); }
340
341      var all = row.children;
342      for (var t = 0; t < all.length; t++) {
343        arm(all[t], "tile", +(0.36 + t * STEP).toFixed(3));
344        targets.push(all[t]);
345      }
346      reveal(found.section, targets);
347    }
348    return true;
349  }
350
351  /* ── Parallax on the two book covers ────────────────────────────────
352     Small images, transform only, written straight to the node inside a
353     damped rAF loop. Deliberately no CSS transition: a transition on a
354     scroll-linked value is exactly what makes a parallax trail the scroll.
355
356     Re-scans on every pass rather than latching a "done" flag. The band
357     re-inserts itself and the runtime re-renders, and a one-shot guard
358     meant that once those nodes were replaced the effect never came back. */
359  var PX = { items: [], running: false, raf: 0, last: 0, wired: false };
360
361  function pxTargets() {
362    var found = [];
363    var btt = document.querySelector(".gtbtt__cover");
364    if (btt) found.push({ el: btt, range: 26 });
365
366    // The Rhythm of Revenue band: its book is the transparent PNG inside
367    // the section carrying that band's copy.
368    var ps = document.querySelectorAll("p");
369    for (var i = 0; i < ps.length; i++) {
370      if (/turned survival into strategy/i.test(ps[i].textContent || "")) {
371        var sec = ps[i];
372        while (sec && sec !== document.body && sec.tagName !== "SECTION") sec = sec.parentElement;
373        if (sec) {
374          var pngs = sec.querySelectorAll('img[src$=".png"]');
375          if (pngs.length) found.push({ el: pngs[0], range: 22 });
376        }
377        break;
378      }
379    }
380    return found;
381  }
382
383  function pxTarget(it) {
384    var r = it.el.getBoundingClientRect();
385    var vh = window.innerHeight || 1;
386    if (!r.height) return 0;
387    // -1 while the image sits below the fold, +1 once it has risen past it
388    var t = (r.top + r.height / 2 - vh / 2) / (vh / 2 + r.height / 2);
389    return -Math.max(-1, Math.min(1, t)) * it.range;
390  }
391
392  function pxFrame(now) {
393    PX.raf = 0;
394    var dt = PX.last ? Math.min(0.05, (now - PX.last) / 1000) : 0.016;
395    PX.last = now;
396    var moving = false;
397    for (var i = PX.items.length - 1; i >= 0; i--) {
398      var it = PX.items[i];
399      if (!document.contains(it.el)) { PX.items.splice(i, 1); continue; }
400      var to = pxTarget(it);
401      var distance = to - it.shown;
402      if (Math.abs(distance) > 0.08) {
403        it.shown += distance * (1 - Math.exp(-dt / 0.12));
404        moving = true;
405      } else {
406        it.shown = to;
407      }
408      it.el.style.transform = "translate3d(0," + it.shown.toFixed(2) + "px,0)";
409    }
410    if (PX.running && PX.items.length && moving) {
411      PX.raf = window.requestAnimationFrame(pxFrame);
412    } else {
413      PX.running = false;
414      PX.last = 0;
415      for (var w = 0; w < PX.items.length; w++) PX.items[w].el.style.willChange = "auto";
416    }
417  }
418
419  function pxStart() {
420    if (PX.running || document.hidden || !PX.items.length) return;
421    PX.running = true; PX.last = 0;
422    // Hold a layer only while the image is genuinely moving. The Rhythm of
423    // Revenue cover is 2090x2592, so a permanent hint is a costly texture
424    // to keep resident for an effect that only runs as it passes the screen.
425    for (var i = 0; i < PX.items.length; i++) PX.items[i].el.style.willChange = "transform";
426    PX.raf = window.requestAnimationFrame(pxFrame);
427  }
428  function pxStop() {
429    PX.running = false;
430    if (PX.raf) window.cancelAnimationFrame(PX.raf);
431    PX.raf = 0;
432    for (var i = 0; i < PX.items.length; i++) PX.items[i].el.style.willChange = "auto";
433  }
434
435  function parallax() {
436    if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return true;
437
438    // drop anything the runtime has detached, then adopt anything new
439    for (var i = PX.items.length - 1; i >= 0; i--) {
440      if (!document.contains(PX.items[i].el)) PX.items.splice(i, 1);
441    }
442    var found = pxTargets();
443    for (var f = 0; f < found.length; f++) {
444      var el = found[f].el, known = false;
445      for (var k = 0; k < PX.items.length; k++) if (PX.items[k].el === el) { known = true; break; }
446      if (known) continue;
447      el.setAttribute("data-gt-parallax", "");
448      var it = { el: el, range: found[f].range, shown: 0 };
449      it.shown = pxTarget(it);
450      el.style.transform = "translate3d(0," + it.shown.toFixed(2) + "px,0)";
451      PX.items.push(it);
452    }
453    if (!PX.items.length) return false;
454
455    if (!PX.wired) {
456      PX.wired = true;
457      document.addEventListener("visibilitychange", function () {
458        if (document.hidden) pxStop(); else pxStart();
459      });
460      window.addEventListener("scroll", pxStart, { passive: true });
461      window.addEventListener("resize", pxStart, { passive: true });
462    }
463    pxStart();
464    return true;
465  }
466
467  // A page cached from an earlier build can still carry the old hidden
468  // class. Its stylesheet is gone, but strip the classes too so nothing lingers.
469  function clearLegacy() {
470    var stale = document.querySelectorAll(".gt-anim, .gt-in");
471    for (var i = 0; i < stale.length; i++) {
472      stale[i].classList.remove("gt-anim", "gt-in", "gt-head", "gt-thread");
473      stale[i].style.removeProperty("opacity");
474      stale[i].style.removeProperty("transform");
475    }
476  }
477
478  /* ── Compositor layers ──────────────────────────────────────────────
479     The runtime marks every element carrying an appear or scroll effect
480     with `will-change: transform` and never takes it back. Each one is a
481     separate GPU layer held for the life of the page; on this homepage
482     that was 49 of them, several holding multi-megapixel artwork. The
483     browser has to build and upload that whole set on the first scroll,
484     which is what made the scroll off the hero feel stuck.
485
486     Anything still genuinely animating keeps its hint: elements running a
487     CSS animation (the marquees), and our own parallax while it is live. */
488  function trimLayers() {
489    // Scan every element, not just inline styles: the hint arrives from
490    // stylesheet rules too, and those are the ones a narrower pass misses.
491    var els = document.querySelectorAll("body *");
492    var freed = 0;
493    for (var i = 0; i < els.length; i++) {
494      var el = els[i];
495      if (el.hasAttribute("data-gt-parallax")) continue;
496      var cs = window.getComputedStyle(el);
497      if (cs.willChange === "auto") continue;
498      if (cs.animationName && cs.animationName !== "none") continue;
499      if (cs.transitionProperty && cs.transitionProperty !== "none" && cs.transitionDuration !== "0s") continue;
500      el.style.setProperty("will-change", "auto", "important");
501      freed++;
502    }
503    return freed;
504  }
505
506  function run() {
507    clearLegacy();
508    integrateServices();
509    stripDownArrows();
510    realDomains();
511    parallax();
512    var hero = stillHero();
513    var climb = freeClimb();
514    var worlds = isHome ? !!document.getElementById(TILE_ID) : true;
515    if (isHome) worlds = apply() || worlds;
516    if (isHome && typeof window.__gtImpactApply === "function") window.__gtImpactApply();
517    if (isHome && typeof window.__gtLegendaryApply === "function") window.__gtLegendaryApply();
518    return { hero: hero, climb: climb, worlds: worlds };
519  }
520
521  function boot() {
522    var tries = 0;
523    (function attempt() {
524      var st = run();
525      // Keep trying until every piece has landed, not just the tile. The
526      // Climb's SVG mounts later than the rest of the page, so a loop that
527      // stops at the tile gives up before the line is ever reached.
528      // The Climb is handled in CSS (the component mounts lazily), so it is
529      // deliberately not part of this condition.
530      var done = st.hero && (!isHome || document.getElementById(TILE_ID));
531      if (++tries < 60 && !done) window.setTimeout(attempt, 250);
532    })();
533
534    // Once entrances have had time to play, hand the layers back.
535    window.setTimeout(trimLayers, 1600);
536    window.setTimeout(trimLayers, 4000);
537
538    // the runtime re-renders on breakpoint changes; put the work back
539    if ("MutationObserver" in window) {
540      var mo = new MutationObserver(function () {
541        if (window.__gtPass2Queued) return;
542        window.__gtPass2Queued = true;
543        window.setTimeout(function () { window.__gtPass2Queued = false; run(); }, 300);
544      });
545      mo.observe(document.body, { childList: true, subtree: true });
546    }
547    window.addEventListener("resize", function () { window.setTimeout(run, 300); }, { passive: true });
548  }
549
550  if (document.readyState === "loading") {
551    document.addEventListener("DOMContentLoaded", boot);
552  } else {
553    boot();
554  }
555})();

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.