PageSourceSearch

https://www.driversnote.se/_marketing/animations/website-hero/animation.js

js driversnote.se collected 2026-10-06 04:08:46 UTC 8,459 bytes, 217 lines download raw bytes

1/* Driversnote phone animation — locale: en-US
2 * Requires: mockup-locales.js must be loaded before this script.
3 */
4(function () {
5
6  // Inject SVG sprite
7  var holder = document.createElement('div');
8  holder.setAttribute('aria-hidden', 'true');
9  holder.innerHTML = DN_SPRITE_HTML;
10  document.body.insertBefore(holder, document.body.firstChild);
11
12  var TOTAL_MS = 36000;
13
14  var phone        = document.getElementById('anim-phone');
15  var sceneLock    = document.getElementById('scene-lockscreen');
16  var sceneInbox   = document.getElementById('scene-inbox');
17  var sceneReport  = document.getElementById('scene-report');
18  var notification = document.getElementById('anim-notification');
19  var inboxCount   = document.getElementById('anim-inbox-count');
20  var reviewAll    = document.getElementById('anim-review-all');
21  var sendName     = document.getElementById('anim-send-name');
22  var sendEmail    = document.getElementById('anim-send-email');
23  var noteTyped    = document.getElementById('anim-note-typed');
24  var inboxCat     = document.getElementById('anim-inbox-cat');
25  var progressFill = document.getElementById('anim-progress-fill');
26  var ctrlIcon     = document.getElementById('ctrl-icon');
27  var ctrlLabel    = document.getElementById('ctrl-label');
28
29  function typewriter(el, text, msPerChar) {
30    el.classList.add('is-visible');
31    el.textContent = '';
32    var i = 0;
33    function next() {
34      if (i < text.length) {
35        el.textContent += text[i++];
36        timers.push(setTimeout(next, msPerChar));
37      }
38    }
39    next();
40  }
41
42  // Resolves demo copy the timeline types out. TIMELINE is built before
43  // applyLocale() picks a locale, so this must be read when the callback fires —
44  // reading it here at construction time would pin every market to the first locale.
45  // Absent means undefined/null only — an empty string is a real value, matching
46  // the [data-l] walk below. Some locales legitimately ship "" (strings.medical
47  // and strings.charity are empty outside the US variant).
48  var activeLocaleData = null;
49  function sampleText(key, fallback) {
50    var sample = activeLocaleData && activeLocaleData.sample;
51    var val = sample ? sample[key] : undefined;
52    return (val === undefined || val === null) ? fallback : val;
53  }
54
55  var TIMELINE = [
56    [0,     function() { sceneLock.classList.add('is-visible'); }],
57    [800,   function() { notification.classList.add('is-visible'); }],
58    [2700,  function() { notification.classList.add('is-tapped'); }],
59    [3140,  function() { notification.classList.remove('is-tapped'); }],
60    [4100,  function() { sceneInbox.classList.add('is-visible'); }],
61    [4900,  function() { sceneLock.classList.remove('is-visible'); }],
62    [8740,  function() {
63      sceneInbox.classList.add('anim-note-active');
64      typewriter(noteTyped, sampleText('note_text', 'Sales meeting'), 80);
65    }],
66    [14300, function() { sceneInbox.classList.add('anim-review-btn-pressed'); }],
67    [14660, function() {
68      sceneInbox.classList.add('anim-trip-reviewed');
69      inboxCount.textContent = '0';
70      reviewAll.setAttribute('data-disabled', 'true');
71    }],
72    [18260, function() { sceneInbox.classList.remove('is-visible'); }],
73    [19060, function() { sceneReport.classList.add('is-visible'); }],
74    [23860, function() { sceneReport.classList.add('anim-modal-visible'); }],
75    [27660, function() { typewriter(sendName, sampleText('recipient_name', 'Jane (Accountant)'), 65); }],
76    [30460, function() { typewriter(sendEmail, sampleText('recipient_email', '[email protected]'), 60); }],
77    [34260, function() { sceneReport.classList.add('anim-send-pressed'); }],
78    [35060, function() { sceneReport.classList.remove('is-visible'); }],
79  ];
80
81  var timers    = [];
82  var rafId     = null;
83  var isPlaying = true;
84  var startTime = 0;
85  var pausedAt  = 0;
86
87  function resetAll() {
88    sceneLock.classList.remove('is-visible');
89    sceneInbox.classList.remove('is-visible', 'anim-note-active', 'anim-trip-reviewed', 'anim-review-btn-pressed');
90    sceneReport.classList.remove('is-visible', 'anim-modal-visible', 'anim-send-pressed');
91    notification.style.transition = 'none';
92    notification.classList.remove('is-visible', 'is-tapped');
93    notification.offsetHeight; // force reflow
94    notification.style.transition = '';
95    sendName.classList.remove('is-visible');
96    sendEmail.classList.remove('is-visible');
97    noteTyped.textContent  = '';
98    sendName.textContent   = '';
99    sendEmail.textContent  = '';
100    inboxCount.textContent = '1';
101    reviewAll.removeAttribute('data-disabled');
102  }
103
104  function clearTimers() {
105    timers.forEach(clearTimeout);
106    timers = [];
107    cancelAnimationFrame(rafId);
108  }
109
110  function scheduleFrom(offsetMs) {
111    TIMELINE.forEach(function(entry) {
112      var delay = entry[0] - offsetMs;
113      if (delay >= 0) timers.push(setTimeout(entry[1], delay));
114    });
115    timers.push(setTimeout(function() {
116      resetAll();
117      scheduleFrom(0);
118      startTime = Date.now();
119    }, TOTAL_MS - offsetMs));
120  }
121
122  function tickProgress() {
123    if (progressFill) {
124      var elapsed = (Date.now() - startTime) % TOTAL_MS;
125      progressFill.style.width = (elapsed / TOTAL_MS * 100).toFixed(2) + '%';
126    }
127    rafId = requestAnimationFrame(tickProgress);
128  }
129
130  function setPlayIcon(showPlay) {
131    if (!ctrlIcon || !ctrlLabel) return;
132    if (showPlay) {
133      ctrlIcon.innerHTML = '<polygon points="2,1.5 10.5,6 2,10.5"/>';
134      ctrlLabel.textContent = 'Play';
135    } else {
136      ctrlIcon.innerHTML = '<rect x="1.5" y="1.5" width="3.5" height="9" rx="1"/><rect x="7" y="1.5" width="3.5" height="9" rx="1"/>';
137      ctrlLabel.textContent = 'Pause';
138    }
139  }
140
141  function play(offsetMs) {
142    offsetMs = offsetMs || 0;
143    startTime = Date.now() - offsetMs;
144    scheduleFrom(offsetMs);
145    tickProgress();
146    isPlaying = true;
147    setPlayIcon(false);
148  }
149
150  function pause() {
151    pausedAt  = (Date.now() - startTime) % TOTAL_MS;
152    clearTimers();
153    isPlaying = false;
154    setPlayIcon(true);
155  }
156
157  var playpauseBtn = document.getElementById('ctrl-playpause');
158  if (playpauseBtn) {
159    playpauseBtn.addEventListener('click', function() {
160      if (isPlaying) pause();
161      else play(pausedAt);
162    });
163  }
164  var restartBtn = document.getElementById('ctrl-restart');
165  if (restartBtn) {
166    restartBtn.addEventListener('click', function() {
167      clearTimers();
168      resetAll();
169      pausedAt = 0;
170      play(0);
171    });
172  }
173
174  var inboxMapImage = document.getElementById('anim-inbox-map-image');
175
176  function applyLocale(localeKey) {
177    var entry = DN_LOCALES[localeKey];
178    if (!entry || !entry.data) return;
179    var data = entry.data;
180    activeLocaleData = data;
181    document.querySelectorAll('[data-l]').forEach(function(el) {
182      var parts   = el.getAttribute('data-l').split('.');
183      var section = parts[0];
184      var key     = parts[1];
185      var val     = data[section] && data[section][key];
186      if (val !== undefined && val !== null) el.textContent = val;
187    });
188    if (inboxMapImage && data.sample && data.sample.map_image_1) {
189      // Trip image paths are stored relative to dist/. In the exported zip,
190      // animation.html sits at the bundle root next to assets/, so we strip
191      // the leading "../" and resolve to "assets/maps/{locale}/..." directly.
192      inboxMapImage.setAttribute('src', data.sample.map_image_1.replace(/^\.\.\//, ''));
193    }
194    var variant = (data.meta && data.meta.category_variant) || 'standard';
195    inboxCat.className = 'dn-mockup__category-selector dn-mockup__category-selector--' + variant;
196    // Most European markets write the currency after the amount (10,03 €).
197    // Absent meta, keep the historical prefix layout.
198    // Toggled on the currency row only — the distance row shares the same class
199    // and must keep its number-then-unit order.
200    var currencySuffixed = !!(data.meta && data.meta.currencyPosition === 'suffix');
201    document.querySelectorAll('.dn-mockup__map-meta-currency').forEach(function(el) {
202      if (el.parentNode && el.parentNode.classList) {
203        el.parentNode.classList.toggle('dn-mockup__map-meta-value--currency-suffix', currencySuffixed);
204      }
205    });
206    clearTimers();
207    resetAll();
208    if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
209      sceneInbox.classList.add('is-visible');
210    } else {
211      play(0);
212    }
213  }
214
215  applyLocale(Object.keys(DN_LOCALES)[0]);
216
217})();

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.