PageSourceSearch

https://www.happylab.at/_Resources/Static/Packages/Happylab.Site/Frontend/js/hero.js?bust=9c563afd?h=9c563afd

js happylab.at collected 2026-10-03 23:31:38 UTC 5,440 bytes, 149 lines download raw bytes

1/* Party extras for the homepage illustration (Content.HeroAnimation) and the
2 * party spotlights (Content.EventSpotlight), until the party is over.
3 *
4 * - Hero: data-party-until in the future -> .is-party shows the party popper,
5 *   which fires a burst of confetti after the build-up and then every few
6 *   seconds while the illustration is on screen.
7 * - [data-confetti]: one burst the first time the element comes into view.
8 *
9 * Confetti is a few dozen spans moved by the Web Animations API, in the brand
10 * colours. Nothing happens with prefers-reduced-motion. */
11(function () {
12  "use strict";
13
14  var COLORS = ["#0069b4", "#ffd618", "#e94e1b", "#f29fc5", "#7e65aa", "#85c9f0", "#6ebd8f"];
15  var reduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
16
17  function random(min, max) {
18    return min + Math.random() * (max - min);
19  }
20
21  /* One burst into `host` (positioned), from origin (fractions of its size),
22   * in directions between minAngle and maxAngle (degrees, 0 = right,
23   * -90 = up). */
24  function burst(host, options) {
25    if (reduced || !host.animate) {
26      return;
27    }
28    var layer = host.querySelector(":scope > .confetti");
29    if (!layer) {
30      layer = document.createElement("div");
31      layer.className = "confetti";
32      layer.setAttribute("aria-hidden", "true");
33      host.appendChild(layer);
34    }
35    var width = host.clientWidth;
36    var height = host.clientHeight;
37    var count = options.count || 40;
38    for (var i = 0; i < count; i++) {
39      var piece = document.createElement("span");
40      var size = random(8, 14);
41      var round = Math.random() < 0.3;
42      piece.className = "confetti__piece";
43      piece.style.left = options.x * width + "px";
44      piece.style.top = options.y * height + "px";
45      piece.style.width = size + "px";
46      piece.style.height = (round ? size : size * 0.45) + "px";
47      piece.style.borderRadius = round ? "50%" : "1px";
48      piece.style.background = COLORS[i % COLORS.length];
49      layer.appendChild(piece);
50
51      var angle = random(options.minAngle, options.maxAngle) * Math.PI / 180;
52      var speed = random(0.25, 0.6) * Math.max(width, 260);
53      var dx = Math.cos(angle) * speed;
54      var dy = Math.sin(angle) * speed;
55      var fall = random(0.45, 0.9) * Math.max(height, 220);
56      var spin = random(-720, 720);
57      var duration = random(1600, 2600);
58
59      piece.animate([
60        { transform: "translate(0, 0) rotate(0deg)", opacity: 1 },
61        { transform: "translate(" + dx + "px, " + dy + "px) rotate(" + spin / 2 + "deg)", opacity: 1, offset: 0.35 },
62        { transform: "translate(" + dx * 1.3 + "px, " + (dy + fall) + "px) rotate(" + spin + "deg)", opacity: 0 }
63      ], {
64        duration: duration,
65        easing: "cubic-bezier(0.2, 0.7, 0.4, 1)",
66        fill: "forwards"
67      }).onfinish = (function (p) {
68        return function () { p.remove(); };
69      })(piece);
70    }
71  }
72
73  window.happylabConfetti = burst;
74
75  function inView(element, callback, options) {
76    if (!("IntersectionObserver" in window)) {
77      callback(true);
78      return;
79    }
80    new IntersectionObserver(function (entries) {
81      callback(entries[0].isIntersecting);
82    }, options || { threshold: 0.4 }).observe(element);
83  }
84
85  function setUpHero(hero) {
86    var until = Date.parse(hero.getAttribute("data-party-until") || "");
87    if (!until || Date.now() >= until) {
88      return;
89    }
90    hero.classList.add("is-party");
91    if (reduced) {
92      return;
93    }
94    var popper = hero.querySelector(".hero__popper");
95    var visible = false;
96    var fire = function () {
97      if (!visible || document.hidden || Date.now() >= until) {
98        return;
99      }
100      if (popper) {
101        popper.classList.remove("is-firing");
102        // Restart the jerk animation.
103        void popper.getBoundingClientRect();
104        popper.classList.add("is-firing");
105      }
106      // From the popper's mouth (top middle of the illustration), fanning out.
107      burst(hero, { x: 0.5, y: 0.04, minAngle: 150, maxAngle: 330, count: 60 });
108    };
109    inView(hero, function (isVisible) { visible = isVisible; }, { threshold: 0.2 });
110    // First shot once the icons and the popper are in (see hero.css).
111    window.setTimeout(fire, 5000);
112    window.setInterval(fire, 9000);
113  }
114
115  function setUpBurst(element) {
116    var done = false;
117    inView(element, function (isVisible) {
118      if (!isVisible || done) {
119        return;
120      }
121      done = true;
122      // Bursts fill the surrounding coloured band where there is one.
123      var host = element.closest("[class^='bg-'], [class*=' bg-']") || element;
124      if (window.getComputedStyle(host).position === "static") {
125        host.style.position = "relative";
126      }
127      var hostBox = host.getBoundingClientRect();
128      var box = element.getBoundingClientRect();
129      burst(host, {
130        x: (box.left + Math.min(box.width, 260) / 2 - hostBox.left) / hostBox.width,
131        y: (box.top - hostBox.top + 20) / hostBox.height,
132        minAngle: -160,
133        maxAngle: -20,
134        count: 50
135      });
136    });
137  }
138
139  function init() {
140    Array.prototype.forEach.call(document.querySelectorAll("[data-hero-animation]"), setUpHero);
141    Array.prototype.forEach.call(document.querySelectorAll("[data-confetti]"), setUpBurst);
142  }
143
144  if (document.readyState === "loading") {
145    document.addEventListener("DOMContentLoaded", init);
146  } else {
147    init();
148  }
149})();

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.