PageSourceSearch

https://www.themailworks.com/static/js/animations.js?v=16a09b2ba6

js themailworks.com collected 2026-10-08 10:02:15 UTC 4,406 bytes, 128 lines download raw bytes

1/* ==========================================================================
2   Trigger the brand animations when they scroll into view.
3
4   Each fires ONCE. An animation that replays every time you scroll past turns
5   into wallpaper, and the whole point is that it reads as an event.
6   ========================================================================== */
7
8(function () {
9  'use strict';
10
11  var reduce = window.matchMedia
12    && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
13
14  var targets = [
15    // The rocket ([data-rocket], is-launching) was here until Owen asked for it
16    // to come off the homepage, which was the only page that had one.
17    { sel: '[data-flightpath]', cls: 'is-flying' },
18    { sel: '[data-roadtrip]', cls: 'is-driving' },
19    // Unlike the three above, this one loops rather than playing once. Gating it
20    // on the heading scrolling into view means it does not run for a visitor who
21    // never reaches that band at all.
22    { sel: '[data-plane-float]', cls: 'is-floating' }
23  ];
24
25  if (reduce || !('IntersectionObserver' in window)) {
26    // Nothing animates, and nothing is hidden either: the plane and the truck
27    // are decorative, so leaving them in their resting state is correct.
28    return;
29  }
30
31  targets.forEach(function (t) {
32    var els = Array.prototype.slice.call(document.querySelectorAll(t.sel));
33    if (!els.length) return;
34
35    var obs = new IntersectionObserver(function (entries) {
36      entries.forEach(function (entry) {
37        if (!entry.isIntersecting) return;
38        entry.target.classList.add(t.cls);
39        obs.unobserve(entry.target);
40      });
41    }, { threshold: 0.45 });
42
43    els.forEach(function (el) { obs.observe(el); });
44  });
45
46  /* ---- Statistic count-up -------------------------------------------------
47     Every .stat__value on the site is a figure followed by words: "1 Billion +",
48     "40+ Years", "30+ Tested". Ramp the figure and leave the words alone, so
49     the same code works on the homepage, About and all seven industry pages
50     without any per-page markup.
51
52     The ticking digits are decorative, so they are aria-hidden and a static
53     .sr-only copy of the finished figure carries the real value. Otherwise a
54     screen reader can read whatever partial number happens to be on screen.
55     ---------------------------------------------------------------------- */
56
57  var COUNT_MS = 1100;
58
59  function splitStat(text) {
60    var m = /^(\D*)(\d[\d,]*(?:\.\d+)?)([\s\S]*)$/.exec(text);
61    if (!m) return null;
62    return {
63      pre: m[1],
64      digits: m[2],
65      target: parseFloat(m[2].replace(/,/g, '')),
66      post: m[3]
67    };
68  }
69
70  /* Small targets (the "1" in "1 Billion") have nothing to count through as
71     integers, so they run to one decimal place and land on the whole number. */
72  function fmt(value, target) {
73    if (target < 10) return (Math.round(value * 10) / 10).toFixed(1);
74    return String(Math.round(value));
75  }
76
77  function countUp(el) {
78    var stat = el.__stat;
79    var start = 0;
80
81    function frame(now) {
82      if (!start) start = now;
83      var p = Math.min((now - start) / COUNT_MS, 1);
84      var eased = 1 - Math.pow(1 - p, 3);
85      el.__run.textContent = p === 1
86        ? stat.pre + stat.digits + stat.post
87        : stat.pre + fmt(stat.target * eased, stat.target) + stat.post;
88      if (p < 1) requestAnimationFrame(frame);
89    }
90
91    requestAnimationFrame(frame);
92  }
93
94  var stats = Array.prototype.slice.call(document.querySelectorAll('.stat__value'));
95
96  stats = stats.filter(function (el) {
97    var stat = splitStat(el.textContent);
98    if (!stat) return false;
99
100    var run = document.createElement('span');
101    run.setAttribute('aria-hidden', 'true');
102    run.textContent = stat.pre + fmt(0, stat.target) + stat.post;
103
104    var label = document.createElement('span');
105    label.className = 'sr-only';
106    label.textContent = el.textContent;
107
108    el.textContent = '';
109    el.appendChild(run);
110    el.appendChild(label);
111
112    el.__stat = stat;
113    el.__run = run;
114    return true;
115  });
116
117  if (stats.length) {
118    var statObs = new IntersectionObserver(function (entries) {
119      entries.forEach(function (entry) {
120        if (!entry.isIntersecting) return;
121        statObs.unobserve(entry.target);
122        countUp(entry.target);
123      });
124    }, { threshold: 0.6 });
125
126    stats.forEach(function (el) { statObs.observe(el); });
127  }
128})();

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.