PageSourceSearch

https://www.ford.ch/etc/designs/guxacc/clientlibs/guxacc-init.min.js

js ford.ch collected 2026-10-02 19:02:55 UTC 73,107 bytes, 2,322 lines download raw bytes

1const breakpoints = [{
2  value: 'ui:rad:phone',
3  minWidth: 0,
4  maxWidth: 767
5}, {
6  value: 'ui:rad:tablet',
7  minWidth: 768,
8  maxWidth: 991
9}, {
10  value: 'ui:rad:desktop',
11  minWidth: 992,
12  maxWidth: 1000000 // Number.MAX_SAFE_INTEGER is not working on FF30 thus we are using a big number here
13}];
14
15fordAnalytics = window.fordAnalytics || {};
16
17for (let i = 0; i < breakpoints.length; i++) {
18  if (window.innerWidth >= breakpoints[i].minWidth
19        && window.innerWidth <= breakpoints[i].maxWidth) {
20    fordAnalytics['page.radUIVersion'] = breakpoints[i].value;
21    break;
22  }
23}
24
25// TODO: Review this:
26// These variables even though bellow they are being set on the window context these are being read in other places in the window context and in the global context
27var digitaldata = {};
28var digitalDataUtils = {};
29
30digitalDataUtils.satelliteMock = {
31  track: function() {
32  },
33  getVar: function() {
34  }
35};
36
37if (fordAnalytics) {
38  Object.keys(fordAnalytics).forEach(function(key) {
39    const keyPath = key.split('.');
40
41    assign(digitaldata, keyPath, fordAnalytics[key]);
42  });
43}
44
45function assign(obj, keyPath, value) {
46  const lastKeyIndex = keyPath.length - 1;
47
48  for (let i = 0; i < lastKeyIndex; ++i) {
49    const key = keyPath[i];
50
51    if (!(key in obj)) {
52      obj[key] = {};
53    }
54
55    obj = obj[key];
56  }
57
58  obj[keyPath[lastKeyIndex]] = value;
59}
60
61window.digitaldata = digitaldata;
62window.digitalDataUtils = digitalDataUtils;
63
64/*!
65 * modernizr v3.0.0-alpha.3
66 * Build http://v3.modernizr.com/download/#-applicationcache-audio-backgroundsize-borderimage-borderradius-boxshadow-canvas-canvastext-cssanimations-csscolumns-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-draganddrop-flexbox-flexboxlegacy-fontface-generatedcontent-geolocation-hashchange-history-hsla-indexeddb-inlinesvg-localstorage-multiplebgs-opacity-picture-postmessage-rgba-sessionstorage-smil-svg-svgclippaths-textshadow-video-webgl-websockets-websqldatabase-webworkers-dontmin
67 *
68 * Copyright (c)
69 *  Faruk Ates
70 *  Paul Irish
71 *  Alex Sexton
72 *  Ryan Seddon
73 *  Alexander Farkas
74 *  Patrick Kettner
75 *  Stu Cox
76 *  Richard Herrera
77
78 * MIT License
79 */
80
81/*
82 * Modernizr tests which native CSS3 and HTML5 features are available in the
83 * current UA and makes the results available to you in two ways: as properties on
84 * a global `Modernizr` object, and as classes on the `<html>` element. This
85 * information allows you to progressively enhance your pages with a granular level
86 * of control over the experience.
87 */
88
89(function(window, document, undefined) {
90  const classes = [];
91
92  const tests = [];
93
94  const ModernizrProto = {
95    // The current version, dummy
96    _version: '3.0.0-alpha.3',
97
98    // Any settings that don't work as separate modules
99    // can go in here as configuration.
100    _config: {
101      classPrefix: '',
102      enableClasses: true,
103      enableJSClass: true,
104      usePrefixes: true
105    },
106
107    // Queue of tests
108    _q: [],
109
110    // Stub these for people who are listening
111    on: function(test, cb) {
112      // I don't really think people should do this, but we can
113      // safe guard it a bit.
114      // -- NOTE:: this gets WAY overridden in src/addTest for
115      // actual async tests. This is in case people listen to
116      // synchronous tests. I would leave it out, but the code
117      // to *disallow* sync tests in the real version of this
118      // function is actually larger than this.
119      const self = this;
120
121      setTimeout(function() {
122        cb(self[test]);
123      }, 0);
124    },
125
126    addTest: function(name, fn, options) {
127      tests.push({ name: name, fn: fn, options: options });
128    },
129
130    addAsyncTest: function(fn) {
131      tests.push({ name: null, fn: fn });
132    }
133  };
134
135  // Fake some of Object.create
136  // so we can force non test results
137  // to be non "own" properties.
138  let Modernizr = function() {};
139
140  Modernizr.prototype = ModernizrProto;
141
142  // Leak modernizr globally when you `require` it
143  // rather than force it here.
144  // Overwrite name so constructor name is nicer :D
145  Modernizr = new Modernizr();
146
147  /*!
148     {
149     "name": "Application Cache",
150     "property": "applicationcache",
151     "caniuse": "offline-apps",
152     "tags": ["storage", "offline"],
153     "notes": [{
154     "name": "MDN documentation",
155     "href": "https://developer.mozilla.org/en/docs/HTML/Using_the_application_cache"
156     }],
157     "polyfills": ["html5gears"]
158     }
159     ! */
160  /* DOC
161     Detects support for the Application Cache, for storing data to enable web-based applications run offline.
162
163     The API has been [heavily criticized](http://alistapart.com/article/application-cache-is-a-douchebag) and discussions are underway to address this.
164     */
165
166  Modernizr.addTest('applicationcache', 'applicationCache' in window);
167
168  /*!
169     {
170     "name": "Geolocation API",
171     "property": "geolocation",
172     "caniuse": "geolocation",
173     "tags": ["media"],
174     "notes": [{
175     "name": "MDN documentation",
176     "href": "https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation"
177     }],
178     "polyfills": [
179     "joshuabell-polyfill",
180     "webshims",
181     "geo-location-javascript",
182     "geolocation-api-polyfill"
183     ]
184     }
185     ! */
186  /* DOC
187     Detects support for the Geolocation API for users to provide their location to web applications.
188     */
189
190  // geolocation is often considered a trivial feature detect...
191  // Turns out, it's quite tricky to get right:
192  //
193  // Using !!navigator.geolocation does two things we don't want. It:
194  //   1. Leaks memory in IE9: github.com/Modernizr/Modernizr/issues/513
195  //   2. Disables page caching in WebKit: webk.it/43956
196  //
197  // Meanwhile, in Firefox < 8, an about:config setting could expose
198  // a false positive that would throw an exception: bugzil.la/688158
199
200  Modernizr.addTest('geolocation', 'geolocation' in navigator);
201
202  /*!
203     {
204     "name": "History API",
205     "property": "history",
206     "caniuse": "history",
207     "tags": ["history"],
208     "authors": ["Hay Kranen", "Alexander Farkas"],
209     "notes": [{
210     "name": "W3C Spec",
211     "href": "http://www.w3.org/TR/html51/browsers.html#the-history-interface"
212     }, {
213     "name": "MDN documentation",
214     "href": "https://developer.mozilla.org/en-US/docs/Web/API/window.history"
215     }],
216     "polyfills": ["historyjs", "html5historyapi"]
217     }
218     ! */
219  /* DOC
220     Detects support for the History API for manipulating the browser session history.
221     */
222
223  Modernizr.addTest('history', function() {
224    // Issue #733
225    // The stock browser on Android 2.2 & 2.3, and 4.0.x returns positive on history support
226    // Unfortunately support is really buggy and there is no clean way to detect
227    // these bugs, so we fall back to a user agent sniff :(
228    const ua = navigator.userAgent;
229
230    // We only want Android 2 and 4.0, stock browser, and not Chrome which identifies
231    // itself as 'Mobile Safari' as well, nor Windows Phone (issue #1471).
232    if ((ua.indexOf('Android 2.') !== -1
233            || (ua.indexOf('Android 4.0') !== -1))
234            && ua.indexOf('Mobile Safari') !== -1
235            && ua.indexOf('Chrome') === -1
236            && ua.indexOf('Windows Phone') === -1) {
237      return false;
238    }
239
240    // Return the regular check
241    return (window.history && 'pushState' in window.history);
242  });
243
244  /*!
245     {
246     "name": "Local Storage",
247     "property": "localstorage",
248     "caniuse": "namevalue-storage",
249     "tags": ["storage"],
250     "knownBugs": [],
251     "notes": [],
252     "warnings": [],
253     "polyfills": [
254     "joshuabell-polyfill",
255     "cupcake",
256     "storagepolyfill",
257     "amplifyjs",
258     "yui-cacheoffline"
259     ]
260     }
261     ! */
262
263  // In FF4, if disabled, window.localStorage should === null.
264
265  // Normally, we could not test that directly and need to do a
266  //   `('localStorage' in window) && ` test first because otherwise Firefox will
267  //   throw bugzil.la/365772 if cookies are disabled
268
269  // Also in iOS5 Private Browsing mode, attempting to use localStorage.setItem
270  // will throw the exception:
271  //   QUOTA_EXCEEDED_ERRROR DOM Exception 22.
272  // Peculiarly, getItem and removeItem calls do not throw.
273
274  // Because we are forced to try/catch this, we'll go aggressive.
275
276  // Just FWIW: IE8 Compat mode supports these features completely:
277  //   www.quirksmode.org/dom/html5.html
278  // But IE8 doesn't support either with local files
279
280  Modernizr.addTest('localstorage', function() {
281    const mod = 'modernizr';
282
283    try {
284      localStorage.setItem(mod, mod);
285      localStorage.removeItem(mod);
286
287      return true;
288    } catch (e) {
289      return false;
290    }
291  });
292
293  /*!
294     {
295     "name": "postMessage",
296     "property": "postmessage",
297     "caniuse": "x-doc-messaging",
298     "notes": [{
299     "name": "W3C Spec",
300     "href": "http://www.w3.org/TR/html5/comms.html#posting-messages"
301     }],
302     "polyfills": ["easyxdm", "postmessage-jquery"]
303     }
304     ! */
305  /* DOC
306     Detects support for the `window.postMessage` protocol for cross-document messaging.
307     */
308
309  Modernizr.addTest('postmessage', 'postMessage' in window);
310
311  /*!
312     {
313     "name": "Session Storage",
314     "property": "sessionstorage",
315     "tags": ["storage"],
316     "polyfills": ["joshuabell-polyfill", "cupcake", "sessionstorage"]
317     }
318     ! */
319
320  // Because we are forced to try/catch this, we'll go aggressive.
321
322  // Just FWIW: IE8 Compat mode supports these features completely:
323  //   www.quirksmode.org/dom/html5.html
324  // But IE8 doesn't support either with local files
325  Modernizr.addTest('sessionstorage', function() {
326    const mod = 'modernizr';
327
328    try {
329      sessionStorage.setItem(mod, mod);
330      sessionStorage.removeItem(mod);
331
332      return true;
333    } catch (e) {
334      return false;
335    }
336  });
337
338  /*!
339     {
340     "name": "SVG",
341     "property": "svg",
342     "caniuse": "svg",
343     "tags": ["svg"],
344     "authors": ["Erik Dahlstrom"],
345     "polyfills": [
346     "svgweb",
347     "raphael",
348     "amplesdk",
349     "canvg",
350     "svg-boilerplate",
351     "sie",
352     "dojogfx",
353     "fabricjs"
354     ]
355     }
356     ! */
357  /* DOC
358     Detects support for SVG in `<embed>` or `<object>` elements.
359     */
360
361  Modernizr.addTest('svg', !!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect);
362
363  /*!
364     {
365     "name": "Web SQL Database",
366     "property": "websqldatabase",
367     "caniuse": "sql-storage",
368     "tags": ["storage"]
369     }
370     ! */
371
372  // Chrome incognito mode used to throw an exception when using openDatabase
373  // It doesn't anymore.
374  Modernizr.addTest('websqldatabase', 'openDatabase' in window);
375
376  /*!
377     {
378     "name": "Web Workers",
379     "property": "webworkers",
380     "caniuse" : "webworkers",
381     "tags": ["performance", "workers"],
382     "notes": [{
383     "name": "W3C Reference",
384     "href": "http://www.w3.org/TR/workers/"
385     }, {
386     "name": "HTML5 Rocks article",
387     "href": "http://www.html5rocks.com/en/tutorials/workers/basics/"
388     }, {
389     "name": "MDN documentation",
390     "href": "https://developer.mozilla.org/en-US/docs/Web/Guide/Performance/Using_web_workers"
391     }],
392     "polyfills": ["fakeworker", "html5shims"]
393     }
394     ! */
395  /* DOC
396     Detects support for the basic `Worker` API from the Web Workers spec. Web Workers provide a simple means for web content to run scripts in background threads.
397     */
398
399  Modernizr.addTest('webworkers', 'Worker' in window);
400
401  /*!
402     {
403     "name": "WebSockets Support",
404     "property": "websockets",
405     "authors": ["Phread [fearphage]", "Mike Sherov [mikesherov]", "Burak Yigit Kaya [BYK]"],
406     "caniuse": "websockets",
407     "tags": ["html5"],
408     "warnings": [
409     "This test will reject any old version of WebSockets even if it is not prefixed such as in Safari 5.1"
410     ],
411     "notes": [{
412     "name": "CLOSING State and Spec",
413     "href": "http://www.w3.org/TR/websockets/#the-websocket-interface"
414     }],
415     "polyfills": [
416     "sockjs",
417     "socketio",
418     "kaazing-websocket-gateway",
419     "websocketjs",
420     "atmosphere",
421     "graceful-websocket",
422     "portal",
423     "datachannel"
424     ]
425     }
426     ! */
427
428  Modernizr.addTest('websockets', 'WebSocket' in window && window.WebSocket.CLOSING === 2);
429
430  /**
431     * is returns a boolean for if typeof obj is exactly type.
432     */
433  function is(obj, type) {
434    return typeof obj === type;
435  }
436
437  // Run through all tests and detect their support in the current UA.
438  function testRunner() {
439    let featureNames;
440    let feature;
441    let aliasIdx;
442    let result;
443    let nameIdx;
444    let featureName;
445    let featureNameSplit;
446
447    for (const featureIdx in tests) {
448      featureNames = [];
449      feature = tests[featureIdx];
450
451      // run the test, throw the return value into the Modernizr,
452      //   then based on that boolean, define an appropriate className
453      //   and push it into an array of classes we'll join later.
454      //
455      //   If there is no name, it's an 'async' test that is run,
456      //   but not directly added to the object. That should
457      //   be done with a post-run addTest call.
458      if (feature.name) {
459        featureNames.push(feature.name.toLowerCase());
460
461        if (feature.options && feature.options.aliases && feature.options.aliases.length) {
462          // Add all the aliases into the names list
463          for (aliasIdx = 0; aliasIdx < feature.options.aliases.length; aliasIdx++) {
464            featureNames.push(feature.options.aliases[aliasIdx].toLowerCase());
465          }
466        }
467      }
468
469      // Run the test, or use the raw value if it's not a function
470      result = is(feature.fn, 'function') ? feature.fn() : feature.fn;
471
472      // Set each of the names on the Modernizr object
473      for (nameIdx = 0; nameIdx < featureNames.length; nameIdx++) {
474        featureName = featureNames[nameIdx];
475        // Support dot properties as sub tests. We don't do checking to make sure
476        // that the implied parent tests have been added. You must call them in
477        // order (either in the test, or make the parent test a dependency).
478        //
479        // Cap it to TWO to make the logic simple and because who needs that kind of subtesting
480        // hashtag famous last words
481        featureNameSplit = featureName.split('.');
482
483        if (featureNameSplit.length === 1) {
484          Modernizr[featureNameSplit[0]] = result;
485        } else {
486          // cast to a Boolean, if not one already
487          /* jshint -W053 */
488          if (Modernizr[featureNameSplit[0]] && !(Modernizr[featureNameSplit[0]] instanceof Boolean)) {
489            Modernizr[featureNameSplit[0]] = new Boolean(Modernizr[featureNameSplit[0]]);
490          }
491
492          Modernizr[featureNameSplit[0]][featureNameSplit[1]] = result;
493        }
494
495        classes.push((result ? '' : 'no-') + featureNameSplit.join('-'));
496      }
497    }
498  }
499
500  const docElement = document.documentElement;
501
502  // Pass in an and array of class names, e.g.:
503  //  ['no-webp', 'borderradius', ...]
504  function setClasses(classes) {
505    let className = docElement.className;
506    const classPrefix = Modernizr._config.classPrefix || '';
507
508    // Change `no-js` to `js` (we do this independently of the `enableClasses`
509    // option)
510    // Handle classPrefix on this too
511    if (Modernizr._config.enableJSClass) {
512      const reJS = new RegExp(`(^|\\s)${classPrefix}no-js(\\s|$)`);
513
514      className = className.replace(reJS, `$1${classPrefix}js$2`);
515    }
516
517    if (Modernizr._config.enableClasses) {
518      // Add the new classes
519      className += ` ${classPrefix}${classes.join(` ${classPrefix}`)}`;
520      docElement.className = className;
521    }
522  }
523
524  const createElement = function() {
525    if (typeof document.createElement !== 'function') {
526      // This is the case in IE7, where the type of createElement is "object".
527      // For this reason, we cannot call apply() as Object is not a Function.
528      return document.createElement(arguments[0]);
529    } else {
530      return document.createElement.apply(document, arguments);
531    }
532  };
533
534  /*!
535     {
536     "name": "Canvas",
537     "property": "canvas",
538     "caniuse": "canvas",
539     "tags": ["canvas", "graphics"],
540     "polyfills": ["flashcanvas", "excanvas", "slcanvas", "fxcanvas"]
541     }
542     ! */
543  /* DOC
544     Detects support for the `<canvas>` element for 2D drawing.
545     */
546
547  // On the S60 and BB Storm, getContext exists, but always returns undefined
548  // so we actually have to call getContext() to verify
549  // github.com/Modernizr/Modernizr/issues/issue/97/
550  Modernizr.addTest('canvas', function() {
551    const elem = createElement('canvas');
552
553    return !!(elem.getContext && elem.getContext('2d'));
554  });
555
556  /*!
557     {
558     "name": "Canvas text",
559     "property": "canvastext",
560     "caniuse": "canvas-text",
561     "tags": ["canvas", "graphics"],
562     "polyfills": ["canvastext"]
563     }
564     ! */
565  /* DOC
566     Detects support for the text APIs for `<canvas>` elements.
567     */
568
569  Modernizr.addTest('canvastext', function() {
570    if (Modernizr.canvas === false) return false;
571
572    return typeof createElement('canvas').getContext('2d').fillText === 'function';
573  });
574
575  /*!
576     {
577     "name": "CSS Multiple Backgrounds",
578     "caniuse": "multibackgrounds",
579     "property": "multiplebgs",
580     "tags": ["css"]
581     }
582     ! */
583
584  // Setting multiple images AND a color on the background shorthand property
585  // and then querying the style.background property value for the number of
586  // occurrences of "url(" is a reliable method for detecting ACTUAL support for this!
587
588  Modernizr.addTest('multiplebgs', function() {
589    const elem = createElement('div');
590    const style = elem.style;
591
592    style.cssText = 'background:url(https://),url(https://),red url(https://)';
593
594    // If the UA supports multiple backgrounds, there should be three occurrences
595    // of the string "url(" in the return value for elemStyle.background
596    return (/(url\s*\(.*?){3}/).test(style.background);
597  });
598
599  /*!
600     {
601     "name": "CSS rgba",
602     "caniuse": "css3-colors",
603     "property": "rgba",
604     "tags": ["css"],
605     "notes": [{
606     "name": "CSSTricks Tutorial",
607     "href": "http://css-tricks.com/rgba-browser-support/"
608     }]
609     }
610     ! */
611
612  Modernizr.addTest('rgba', function() {
613    const elem = createElement('div');
614    const style = elem.style;
615
616    style.cssText = 'background-color:rgba(150,255,150,.5)';
617
618    return (`${style.backgroundColor}`).indexOf('rgba') > -1;
619  });
620
621  /*!
622     {
623     "name": "Drag & Drop",
624     "property": "draganddrop",
625     "caniuse": "dragndrop",
626     "knownBugs": ["Mobile browsers like Android, iOS < 6, and Firefox OS technically support the APIs, but don't expose it to the end user, resulting in a false positive."],
627     "notes": [{
628     "name": "W3C spec",
629     "href": "http://www.w3.org/TR/2010/WD-html5-20101019/dnd.html"
630     }],
631     "polyfills": ["dropfile", "moxie", "fileapi"]
632     }
633     ! */
634  /* DOC
635     Detects support for native drag & drop of elements.
636     */
637
638  Modernizr.addTest('draganddrop', function() {
639    const div = createElement('div');
640
641    return ('draggable' in div) || ('ondragstart' in div && 'ondrop' in div);
642  });
643
644  /*!
645     {
646     "name" : "HTML5 Audio Element",
647     "property": "audio",
648     "tags" : ["html5", "audio", "media"]
649     }
650     ! */
651  /* DOC
652     Detects the audio element
653     */
654
655  // This tests evaluates support of the audio element, as well as
656  // testing what types of content it supports.
657  //
658  // We're using the Boolean constructor here, so that we can extend the value
659  // e.g.  Modernizr.audio     // true
660  //       Modernizr.audio.ogg // 'probably'
661  //
662  // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
663  //                     thx to NielsLeenheer and zcorpan
664
665  // Note: in some older browsers, "no" was a return value instead of empty string.
666  //   It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
667  //   It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
668  Modernizr.addTest('audio', function() {
669    /* jshint -W053 */
670    const elem = createElement('audio');
671    let bool = false;
672
673    try {
674      if (bool = !!elem.canPlayType) {
675        bool = new Boolean(bool);
676        bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/, '');
677        bool.mp3 = elem.canPlayType('audio/mpeg;').replace(/^no$/, '');
678        bool.opus = elem.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/, '');
679
680        // Mimetypes accepted:
681        //   developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements
682        //   bit.ly/iphoneoscodecs
683        bool.wav = elem.canPlayType('audio/wav; codecs="1"').replace(/^no$/, '');
684        bool.m4a = (elem.canPlayType('audio/x-m4a;')
685                || elem.canPlayType('audio/aac;')).replace(/^no$/, '');
686      }
687    } catch (e) { }
688
689    return bool;
690  });
691
692  /*!
693     {
694     "name": "HTML5 Video",
695     "property": "video",
696     "caniuse": "video",
697     "tags": ["html5"],
698     "knownBugs": [
699     "Without QuickTime, `Modernizr.video.h264` will be `undefined`; http://github.com/Modernizr/Modernizr/issues/546"
700     ],
701     "polyfills": [
702     "html5media",
703     "mediaelementjs",
704     "sublimevideo",
705     "videojs",
706     "leanbackplayer",
707     "videoforeverybody"
708     ]
709     }
710     ! */
711  /* DOC
712     Detects support for the video element, as well as testing what types of content it supports.
713
714     Subproperties are provided to describe support for `ogg`, `h264` and `webm` formats, e.g.:
715
716     ```javascript
717     Modernizr.video         // true
718     Modernizr.video.ogg     // 'probably'
719     ```
720     */
721
722  // Codec values from : github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845
723  //                     thx to NielsLeenheer and zcorpan
724
725  // Note: in some older browsers, "no" was a return value instead of empty string.
726  //   It was live in FF3.5.0 and 3.5.1, but fixed in 3.5.2
727  //   It was also live in Safari 4.0.0 - 4.0.4, but fixed in 4.0.5
728
729  Modernizr.addTest('video', function() {
730    /* jshint -W053 */
731    const elem = createElement('video');
732    let bool = false;
733
734    // IE9 Running on Windows Server SKU can cause an exception to be thrown, bug #224
735    try {
736      if (bool = !!elem.canPlayType) {
737        bool = new Boolean(bool);
738        bool.ogg = elem.canPlayType('video/ogg; codecs="theora"').replace(/^no$/, '');
739
740        // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
741        bool.h264 = elem.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/, '');
742
743        bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/, '');
744
745        bool.vp9 = elem.canPlayType('video/webm; codecs="vp9"').replace(/^no$/, '');
746
747        bool.hls = elem.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/, '');
748      }
749    } catch (e) {}
750
751    return bool;
752  });
753
754  /*!
755     {
756     "name": "Inline SVG",
757     "property": "inlinesvg",
758     "caniuse": "svg-html5",
759     "tags": ["svg"],
760     "notes": [{
761     "name": "Test page",
762     "href": "http://paulirish.com/demo/inline-svg"
763     }],
764     "polyfills": ["inline-svg-polyfill"]
765     }
766     ! */
767  /* DOC
768     Detects support for inline SVG in HTML (not within XHTML).
769     */
770
771  Modernizr.addTest('inlinesvg', function() {
772    const div = createElement('div');
773
774    div.innerHTML = '<svg/>';
775
776    return (div.firstChild && div.firstChild.namespaceURI) == 'http://www.w3.org/2000/svg';
777  });
778
779  /*!
780     {
781     "name": "WebGL",
782     "property": "webgl",
783     "caniuse": "webgl",
784     "tags": ["webgl", "graphics"],
785     "polyfills": ["jebgl", "cwebgl", "iewebgl"]
786     }
787     ! */
788
789  Modernizr.addTest('webgl', function() {
790    const canvas = createElement('canvas');
791    const supports = 'probablySupportsContext' in canvas ? 'probablySupportsContext' : 'supportsContext';
792
793    if (supports in canvas) {
794      return canvas[supports]('webgl') || canvas[supports]('experimental-webgl');
795    }
796
797    return 'WebGLRenderingContext' in window;
798  });
799
800  // List of property values to set for css tests. See ticket #21
801  const prefixes = (ModernizrProto._config.usePrefixes ? ' -webkit- -moz- -o- -ms- '.split(' ') : []);
802
803  // expose these for the plugin API. Look in the source for how to join() them against your input
804  ModernizrProto._prefixes = prefixes;
805
806  /*!
807     {
808     "name": "CSS Gradients",
809     "caniuse": "css-gradients",
810     "property": "cssgradients",
811     "tags": ["css"],
812     "knownBugs": ["False-positives on webOS (https://github.com/Modernizr/Modernizr/issues/202)"],
813     "notes": [{
814     "name": "Webkit Gradient Syntax",
815     "href": "http://webkit.org/blog/175/introducing-css-gradients/"
816     },{
817     "name": "Mozilla Linear Gradient Syntax",
818     "href": "http://developer.mozilla.org/en/CSS/-moz-linear-gradient"
819     },{
820     "name": "Mozilla Radial Gradient Syntax",
821     "href": "http://developer.mozilla.org/en/CSS/-moz-radial-gradient"
822     },{
823     "name": "W3C Gradient Spec",
824     "href": "dev.w3.org/csswg/css3-images/#gradients-"
825     }]
826     }
827     ! */
828
829  Modernizr.addTest('cssgradients', function() {
830    const str1 = 'background-image:';
831    const str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));';
832    const str3 = 'linear-gradient(left top,#9f9, white);';
833
834    // standard syntax             // trailing 'background-image:'
835    let css = str1 + prefixes.join(str3 + str1).slice(0, -str1.length);
836
837    if (Modernizr._config.usePrefixes) {
838      // legacy webkit syntax (FIXME: remove when syntax not in use anymore)
839      css += `${str1}-webkit-${str2}`;
840    }
841
842    const elem = createElement('div');
843    const style = elem.style;
844
845    style.cssText = css;
846
847    // IE6 returns undefined so cast to string
848    return (`${style.backgroundImage}`).indexOf('gradient') > -1;
849  });
850
851  /*!
852     {
853     "name": "CSS Opacity",
854     "caniuse": "css-opacity",
855     "property": "opacity",
856     "tags": ["css"]
857     }
858     ! */
859
860  // Browsers that actually have CSS Opacity implemented have done so
861  // according to spec, which means their return values are within the
862  // range of [0.0,1.0] - including the leading zero.
863
864  Modernizr.addTest('opacity', function() {
865    const elem = createElement('div');
866    const style = elem.style;
867
868    style.cssText = prefixes.join('opacity:.55;');
869
870    // The non-literal . in this regex is intentional:
871    // German Chrome returns this value as 0,55
872    // github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632
873    return (/^0.55$/).test(style.opacity);
874  });
875
876  /**
877     * contains returns a boolean for if substr is found within str.
878     */
879  function contains(str, substr) {
880    return !!~(`${str}`).indexOf(substr);
881  }
882
883  /*!
884     {
885     "name": "CSS HSLA Colors",
886     "caniuse": "css3-colors",
887     "property": "hsla",
888     "tags": ["css"]
889     }
890     ! */
891
892  Modernizr.addTest('hsla', function() {
893    const elem = createElement('div');
894    const style = elem.style;
895
896    style.cssText = 'background-color:hsla(120,40%,100%,.5)';
897
898    return contains(style.backgroundColor, 'rgba') || contains(style.backgroundColor, 'hsla');
899  });
900
901  /*!
902     {
903     "name": "CSS Supports",
904     "property": "supports",
905     "caniuse": "css-featurequeries",
906     "tags": ["css"],
907     "builderAliases": ["css_supports"],
908     "notes": [{
909     "name": "W3 Spec",
910     "href": "http://dev.w3.org/csswg/css3-conditional/#at-supports"
911     },{
912     "name": "Related Github Issue",
913     "href": "github.com/Modernizr/Modernizr/issues/648"
914     },{
915     "name": "W3 Info",
916     "href": "http://dev.w3.org/csswg/css3-conditional/#the-csssupportsrule-interface"
917     }]
918     }
919     ! */
920
921  const newSyntax = 'CSS' in window && 'supports' in window.CSS;
922  const oldSyntax = 'supportsCSS' in window;
923
924  Modernizr.addTest('supports', newSyntax || oldSyntax);
925
926  const toStringFn = ({}).toString;
927
928  /*!
929     {
930     "name": "SVG clip paths",
931     "property": "svgclippaths",
932     "tags": ["svg"],
933     "notes": [{
934     "name": "Demo",
935     "href": "http://srufaculty.sru.edu/david.dailey/svg/newstuff/clipPath4.svg"
936     }]
937     }
938     ! */
939  /* DOC
940     Detects support for clip paths in SVG (only, not on HTML content).
941
942     See [this discussion](http://github.com/Modernizr/Modernizr/issues/213) regarding applying SVG clip paths to HTML content.
943     */
944
945  Modernizr.addTest('svgclippaths', function() {
946    return !!document.createElementNS
947            && /SVGClipPath/.test(toStringFn.call(document.createElementNS('http://www.w3.org/2000/svg', 'clipPath')));
948  });
949
950  /*!
951     {
952     "name": "SVG SMIL animation",
953     "property": "smil",
954     "caniuse": "svg-smil",
955     "tags": ["svg"],
956     "notes": [{
957     "name": "W3C Synchronised Multimedia spec",
958     "href": "http://www.w3.org/AudioVideo/"
959     }]
960     }
961     ! */
962
963  // SVG SMIL animation
964  Modernizr.addTest('smil', function() {
965    return !!document.createElementNS
966            && /SVGAnimate/.test(toStringFn.call(document.createElementNS('http://www.w3.org/2000/svg', 'animate')));
967  });
968
969  // isEventSupported determines if the given element supports the given event
970  // kangax.github.com/iseventsupported/
971  // github.com/Modernizr/Modernizr/pull/636
972  //
973  // Known incorrects:
974  //   Modernizr.hasEvent("webkitTransitionEnd", elem) // false negative
975  //   Modernizr.hasEvent("textInput") // in Webkit. github.com/Modernizr/Modernizr/issues/333
976  const isEventSupported = (function(undefined) {
977    // Detect whether event support can be detected via `in`. Test on a DOM element
978    // using the "blur" event b/c it should always exist. bit.ly/event-detection
979    const needsFallback = !('onblur' in document.documentElement);
980
981    /**
982         * @param  {string|*}           eventName  is the name of an event to test for (e.g. "resize")
983         * @param  {(Object|string|*)=} element    is the element|document|window|tagName to test on
984         * @return {boolean}
985         */
986    function isEventSupportedInner(eventName, element) {
987      let isSupported;
988
989      if (!eventName) { return false; }
990
991      if (!element || typeof element === 'string') {
992        element = createElement(element || 'div');
993      }
994
995      // Testing via the `in` operator is sufficient for modern browsers and IE.
996      // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and
997      // "resize", whereas `in` "catches" those.
998      eventName = `on${eventName}`;
999      isSupported = eventName in element;
1000
1001      // Fallback technique for old Firefox - bit.ly/event-detection
1002      if (!isSupported && needsFallback) {
1003        if (!element.setAttribute) {
1004          // Switch to generic element if it lacks `setAttribute`.
1005          // It could be the `document`, `window`, or something else.
1006          element = createElement('div');
1007        }
1008
1009        element.setAttribute(eventName, '');
1010        isSupported = typeof element[eventName] === 'function';
1011
1012        if (element[eventName] !== undefined) {
1013          // If property was created, "remove it" by setting value to `undefined`.
1014          element[eventName] = undefined;
1015        }
1016
1017        element.removeAttribute(eventName);
1018      }
1019
1020      return isSupported;
1021    }
1022
1023    return isEventSupportedInner;
1024  }());
1025
1026  // Modernizr.hasEvent() detects support for a given event, with an optional element to test on
1027  // Modernizr.hasEvent('gesturestart', elem)
1028  const hasEvent = ModernizrProto.hasEvent = isEventSupported;
1029
1030  /*!
1031     {
1032     "name": "Hashchange event",
1033     "property": "hashchange",
1034     "caniuse": "hashchange",
1035     "tags": ["history"],
1036     "notes": [{
1037     "name": "MDN documentation",
1038     "href": "https://developer.mozilla.org/en-US/docs/Web/API/window.onhashchange"
1039     }],
1040     "polyfills": [
1041     "jquery-hashchange",
1042     "moo-historymanager",
1043     "jquery-ajaxy",
1044     "hasher",
1045     "shistory"
1046     ]
1047     }
1048     ! */
1049  /* DOC
1050     Detects support for the `hashchange` event, fired when the current location fragment changes.
1051     */
1052
1053  Modernizr.addTest('hashchange', function() {
1054    if (hasEvent('hashchange', window) === false) {
1055      return false;
1056    }
1057
1058    // documentMode logic from YUI to filter out IE8 Compat Mode
1059    //   which false positives.
1060    return (document.documentMode === undefined || document.documentMode > 7);
1061  });
1062
1063  // Helper function for converting kebab-case to camelCase,
1064  // e.g. box-sizing -> boxSizing
1065  function cssToDOM(name) {
1066    return name.replace(/([a-z])-([a-z])/g, function(str, m1, m2) {
1067      return m1 + m2.toUpperCase();
1068    }).replace(/^-/, '');
1069  }
1070
1071  function getBody() {
1072    // After page load injecting a fake body doesn't work so check if body exists
1073    let body = document.body;
1074
1075    if (!body) {
1076      // Can't use the real body create a fake one.
1077      body = createElement('body');
1078      body.fake = true;
1079    }
1080
1081    return body;
1082  }
1083
1084  // Inject element with style element and some CSS rules
1085  function injectElementWithStyles(rule, callback, nodes, testnames) {
1086    const mod = 'modernizr';
1087    let style;
1088    let ret;
1089    let node;
1090    let docOverflow;
1091    const div = createElement('div');
1092    const body = getBody();
1093
1094    if (parseInt(nodes, 10)) {
1095      // In order not to give false positives we create a node for each test
1096      // This also allows the method to scale for unspecified uses
1097      while (nodes--) {
1098        node = createElement('div');
1099        node.id = testnames ? testnames[nodes] : mod + (nodes + 1);
1100        div.appendChild(node);
1101      }
1102    }
1103
1104    // <style> elements in IE6-9 are considered 'NoScope' elements and therefore will be removed
1105    // when injected with innerHTML. To get around this you need to prepend the 'NoScope' element
1106    // with a 'scoped' element, in our case the soft-hyphen entity as it won't mess with our measurements.
1107    // msdn.microsoft.com/en-us/library/ms533897%28VS.85%29.aspx
1108    // Documents served as xml will throw if using &shy; so use xml friendly encoded version. See issue #277
1109    style = ['&#173;', '<style id="s', mod, '">', rule, '</style>'].join('');
1110    div.id = mod;
1111    // IE6 will false positive on some tests due to the style element inside the test div somehow interfering offsetHeight, so insert it into body or fakebody.
1112    // Opera will act all quirky when injecting elements in documentElement when page is served as xml, needs fakebody too. #270
1113    (!body.fake ? div : body).innerHTML += style;
1114    body.appendChild(div);
1115
1116    if (body.fake) {
1117      // avoid crashing IE8, if background image is used
1118      body.style.background = '';
1119      // Safari 5.13/5.1.4 OSX stops loading if ::-webkit-scrollbar is used and scrollbars are visible
1120      body.style.overflow = 'hidden';
1121      docOverflow = docElement.style.overflow;
1122      docElement.style.overflow = 'hidden';
1123      docElement.appendChild(body);
1124    }
1125
1126    ret = callback(div, rule);
1127
1128    // If this is done after page load we don't want to remove the body so check if body exists
1129    if (body.fake) {
1130      body.parentNode.removeChild(body);
1131      docElement.style.overflow = docOverflow;
1132      // Trigger layout so kinetic scrolling isn't disabled in iOS6+
1133      docElement.offsetHeight;
1134    } else {
1135      div.parentNode.removeChild(div);
1136    }
1137
1138    return !!ret;
1139  }
1140
1141  const testStyles = ModernizrProto.testStyles = injectElementWithStyles;
1142
1143  /*!
1144     {
1145     "name": "@font-face",
1146     "property": "fontface",
1147     "authors": ["Diego Perini", "Mat Marquis"],
1148     "tags": ["css"],
1149     "knownBugs": [
1150     "False Positive: WebOS http://github.com/Modernizr/Modernizr/issues/342",
1151     "False Postive: WP7 http://github.com/Modernizr/Modernizr/issues/538"
1152     ],
1153     "notes": [{
1154     "name": "@font-face detection routine by Diego Perini",
1155     "href": "http://javascript.nwbox.com/CSSSupport/"
1156     },{
1157     "name": "Filament Group @font-face compatibility research",
1158     "href": "https://docs.google.com/presentation/d/1n4NyG4uPRjAA8zn_pSQ_Ket0RhcWC6QlZ6LMjKeECo0/edit#slide=id.p"
1159     },{
1160     "name": "Filament Grunticon/@font-face device testing results",
1161     "href": "https://docs.google.com/spreadsheet/ccc?key=0Ag5_yGvxpINRdHFYeUJPNnZMWUZKR2ItMEpRTXZPdUE#gid=0"
1162     },{
1163     "name": "CSS fonts on Android",
1164     "href": "http://stackoverflow.com/questions/3200069/css-fonts-on-android"
1165     },{
1166     "name": "@font-face and Android",
1167     "href": "http://archivist.incutio.com/viewlist/css-discuss/115960"
1168     }]
1169     }
1170     ! */
1171
1172  const blacklist = (function() {
1173    const ua = navigator.userAgent;
1174    const wkvers = ua.match(/applewebkit\/([0-9]+)/gi) && parseFloat(RegExp.$1);
1175    const webos = ua.match(/w(eb)?osbrowser/gi);
1176    const wppre8 = ua.match(/windows phone/gi) && ua.match(/iemobile\/([0-9])+/gi) && parseFloat(RegExp.$1) >= 9;
1177    const oldandroid = wkvers < 533 && ua.match(/android/gi);
1178
1179    return webos || oldandroid || wppre8;
1180  }());
1181
1182  if (blacklist) {
1183    Modernizr.addTest('fontface', false);
1184  } else {
1185    testStyles('@font-face {font-family:"font";src:url("https://")}', function(node, rule) {
1186      const style = document.getElementById('smodernizr');
1187      const sheet = style.sheet || style.styleSheet;
1188      const cssText = sheet ? (sheet.cssRules && sheet.cssRules[0] ? sheet.cssRules[0].cssText : sheet.cssText || '') : '';
1189      const bool = /src/i.test(cssText) && cssText.indexOf(rule.split(' ')[0]) === 0;
1190
1191      Modernizr.addTest('fontface', bool);
1192    });
1193  }
1194
1195  /*!
1196     {
1197     "name": "CSS Generated Content",
1198     "property": "generatedcontent",
1199     "tags": ["css"],
1200     "warnings": ["Android won't return correct height for anything below 7px #738"],
1201     "notes": [{
1202     "name": "W3C CSS Selectors Level 3 spec",
1203     "href": "http://www.w3.org/TR/css3-selectors/#gen-content"
1204     },{
1205     "name": "MDN article on :before",
1206     "href": "https://developer.mozilla.org/en-US/docs/Web/CSS/::before"
1207     },{
1208     "name": "MDN article on :after",
1209     "href": "https://developer.mozilla.org/en-US/docs/Web/CSS/::before"
1210     }]
1211     }
1212     ! */
1213
1214  testStyles('#modernizr{font:0/0 a}#modernizr:after{content:":)";visibility:hidden;font:7px/1 a}', function(node) {
1215    Modernizr.addTest('generatedcontent', node.offsetHeight >= 7);
1216  });
1217
1218  // Following spec is to expose vendor-specific style properties as:
1219  //   elem.style.WebkitBorderRadius
1220  // and the following would be incorrect:
1221  //   elem.style.webkitBorderRadius
1222
1223  // Webkit ghosts their properties in lowercase but Opera & Moz do not.
1224  // Microsoft uses a lowercase `ms` instead of the correct `Ms` in IE8+
1225  //   erik.eae.net/archives/2008/03/10/21.48.10/
1226
1227  // More here: github.com/Modernizr/Modernizr/issues/issue/21
1228  const omPrefixes = 'Moz O ms Webkit';
1229
1230  const cssomPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.split(' ') : []);
1231
1232  ModernizrProto._cssomPrefixes = cssomPrefixes;
1233
1234  /**
1235     * atRule returns a given CSS property at-rule (eg @keyframes), possibly in
1236     * some prefixed form, or false, in the case of an unsupported rule
1237     *
1238     * @param prop - String naming the property to test
1239     */
1240
1241  const atRule = function(prop) {
1242    const length = prefixes.length;
1243    const cssrule = window.CSSRule;
1244    let rule;
1245
1246    if (typeof cssrule === 'undefined') {
1247      return undefined;
1248    }
1249
1250    if (!prop) {
1251      return false;
1252    }
1253
1254    // remove literal @ from beginning of provided property
1255    prop = prop.replace(/^@/, '');
1256
1257    // CSSRules use underscores instead of dashes
1258    rule = `${prop.replace(/-/g, '_').toUpperCase()}_RULE`;
1259
1260    if (rule in cssrule) {
1261      return `@${prop}`;
1262    }
1263
1264    for (let i = 0; i < length; i++) {
1265      // prefixes gives us something like -o-, and we want O_
1266      const prefix = prefixes[i];
1267      const thisRule = `${prefix.toUpperCase()}_${rule}`;
1268
1269      if (thisRule in cssrule) {
1270        return `@-${prefix.toLowerCase()}-${prop}`;
1271      }
1272    }
1273
1274    return false;
1275  };
1276
1277  const domPrefixes = (ModernizrProto._config.usePrefixes ? omPrefixes.toLowerCase().split(' ') : []);
1278
1279  ModernizrProto._domPrefixes = domPrefixes;
1280
1281  // Change the function's scope.
1282  function fnBind(fn, that) {
1283    return function() {
1284      return fn.apply(that, arguments);
1285    };
1286  }
1287
1288  /**
1289     * testDOMProps is a generic DOM property test; if a browser supports
1290     *   a certain property, it won't return undefined for it.
1291     */
1292  function testDOMProps(props, obj, elem) {
1293    let item;
1294
1295    for (const i in props) {
1296      if (props[i] in obj) {
1297        // return the property name as a string
1298        if (elem === false) return props[i];
1299
1300        item = obj[props[i]];
1301
1302        // let's bind a function
1303        if (is(item, 'function')) {
1304          // bind to obj unless overriden
1305          return fnBind(item, elem || obj);
1306        }
1307
1308        // return the unbound function or obj or value
1309        return item;
1310      }
1311    }
1312
1313    return false;
1314  }
1315
1316  /**
1317     * Create our "modernizr" element that we do most feature tests on.
1318     */
1319  const modElem = {
1320    elem: createElement('modernizr')
1321  };
1322
1323  // Clean up this element
1324  Modernizr._q.push(function() {
1325    delete modElem.elem;
1326  });
1327
1328  const mStyle = {
1329    style: modElem.elem.style
1330  };
1331
1332  // kill ref for gc, must happen before
1333  // mod.elem is removed, so we unshift on to
1334  // the front of the queue.
1335  Modernizr._q.unshift(function() {
1336    delete mStyle.style;
1337  });
1338
1339  // Helper function for converting camelCase to kebab-case,
1340  // e.g. boxSizing -> box-sizing
1341  function domToCSS(name) {
1342    return name.replace(/([A-Z])/g, function(str, m1) {
1343      return `-${m1.toLowerCase()}`;
1344    }).replace(/^ms-/, '-ms-');
1345  }
1346
1347  // Function to allow us to use native feature detection functionality if available.
1348  // Accepts a list of property names and a single value
1349  // Returns `undefined` if native detection not available
1350  function nativeTestProps(props, value) {
1351    let i = props.length;
1352
1353    // Start with the JS API: http://www.w3.org/TR/css3-conditional/#the-css-interface
1354    if ('CSS' in window && 'supports' in window.CSS) {
1355      // Try every prefixed variant of the property
1356      while (i--) {
1357        if (window.CSS.supports(domToCSS(props[i]), value)) {
1358          return true;
1359        }
1360      }
1361
1362      return false;
1363    }
1364    // Otherwise fall back to at-rule (for Opera 12.x)
1365    else if ('CSSSupportsRule' in window) {
1366      // Build a condition string for every prefixed variant
1367      let conditionText = [];
1368
1369      while (i--) {
1370        conditionText.push(`(${domToCSS(props[i])}:${value})`);
1371      }
1372
1373      conditionText = conditionText.join(' or ');
1374
1375      return injectElementWithStyles(`@supports (${conditionText}) { #modernizr { position: absolute; } }`, function(node) {
1376        return getComputedStyle(node, null).position == 'absolute';
1377      });
1378    }
1379
1380    return undefined;
1381  }
1382
1383  // testProps is a generic CSS / DOM property test.
1384
1385  // In testing support for a given CSS property, it's legit to test:
1386  //    `elem.style[styleName] !== undefined`
1387  // If the property is supported it will return an empty string,
1388  // if unsupported it will return undefined.
1389
1390  // We'll take advantage of this quick test and skip setting a style
1391  // on our modernizr element, but instead just testing undefined vs
1392  // empty string.
1393
1394  // Property names can be provided in either camelCase or kebab-case.
1395
1396  function testProps(props, prefixed, value, skipValueTest) {
1397    skipValueTest = is(skipValueTest, 'undefined') ? false : skipValueTest;
1398
1399    // Try native detect first
1400    if (!is(value, 'undefined')) {
1401      const result = nativeTestProps(props, value);
1402
1403      if (!is(result, 'undefined')) {
1404        return result;
1405      }
1406    }
1407
1408    // Otherwise do it properly
1409    let afterInit; let i; let propsLength; let prop; let
1410      before;
1411
1412    // If we don't have a style element, that means
1413    // we're running async or after the core tests,
1414    // so we'll need to create our own elements to use
1415    if (!mStyle.style) {
1416      afterInit = true;
1417      mStyle.modElem = createElement('modernizr');
1418      mStyle.style = mStyle.modElem.style;
1419    }
1420
1421    // Delete the objects if we
1422    // we created them.
1423    function cleanElems() {
1424      if (afterInit) {
1425        delete mStyle.style;
1426        delete mStyle.modElem;
1427      }
1428    }
1429
1430    propsLength = props.length;
1431
1432    for (i = 0; i < propsLength; i++) {
1433      prop = props[i];
1434      before = mStyle.style[prop];
1435
1436      if (contains(prop, '-')) {
1437        prop = cssToDOM(prop);
1438      }
1439
1440      if (mStyle.style[prop] !== undefined) {
1441        // If value to test has been passed in, do a set-and-check test.
1442        // 0 (integer) is a valid property value, so check that `value` isn't
1443        // undefined, rather than just checking it's truthy.
1444        if (!skipValueTest && !is(value, 'undefined')) {
1445          // Needs a try catch block because of old IE. This is slow, but will
1446          // be avoided in most cases because `skipValueTest` will be used.
1447          try {
1448            mStyle.style[prop] = value;
1449          } catch (e) {}
1450
1451          // If the property value has changed, we assume the value used is
1452          // supported. If `value` is empty string, it'll fail here (because
1453          // it hasn't changed), which matches how browsers have implemented
1454          // CSS.supports()
1455          if (mStyle.style[prop] != before) {
1456            cleanElems();
1457
1458            return prefixed == 'pfx' ? prop : true;
1459          }
1460        }
1461        // Otherwise just return true, or the property name if this is a
1462        // `prefixed()` call
1463        else {
1464          cleanElems();
1465
1466          return prefixed == 'pfx' ? prop : true;
1467        }
1468      }
1469    }
1470
1471    cleanElems();
1472
1473    return false;
1474  }
1475
1476  // Modernizr.testProp() investigates whether a given style property is recognized
1477  // Property names can be provided in either camelCase or kebab-case.
1478  // Modernizr.testProp('pointerEvents')
1479  // Also accepts optional 2nd arg, of a value to use for native feature detection, e.g.:
1480  // Modernizr.testProp('pointerEvents', 'none')
1481  const testProp = ModernizrProto.testProp = function(prop, value, useValue) {
1482    return testProps([prop], undefined, value, useValue);
1483  };
1484
1485  /*!
1486     {
1487     "name": "CSS textshadow",
1488     "property": "textshadow",
1489     "caniuse": "css-textshadow",
1490     "tags": ["css"],
1491     "knownBugs": ["FF3.0 will false positive on this test"]
1492     }
1493     ! */
1494
1495  Modernizr.addTest('textshadow', testProp('textShadow', '1px 1px'));
1496
1497  /**
1498     * testPropsAll tests a list of DOM properties we want to check against.
1499     *     We specify literally ALL possible (known and/or likely) properties on
1500     *     the element including the non-vendor prefixed one, for forward-
1501     *     compatibility.
1502     */
1503  function testPropsAll(prop, prefixed, elem, value, skipValueTest) {
1504    const ucProp = prop.charAt(0).toUpperCase() + prop.slice(1);
1505    let props = (`${prop} ${cssomPrefixes.join(`${ucProp} `)}${ucProp}`).split(' ');
1506
1507    // did they call .prefixed('boxSizing') or are we just testing a prop?
1508    if (is(prefixed, 'string') || is(prefixed, 'undefined')) {
1509      return testProps(props, prefixed, value, skipValueTest);
1510
1511      // otherwise, they called .prefixed('requestAnimationFrame', window[, elem])
1512    } else {
1513      props = (`${prop} ${(domPrefixes).join(`${ucProp} `)}${ucProp}`).split(' ');
1514
1515      return testDOMProps(props, prefixed, elem);
1516    }
1517  }
1518
1519  // Modernizr.testAllProps() investigates whether a given style property,
1520  //     or any of its vendor-prefixed variants, is recognized
1521  // Note that the property names must be provided in the camelCase variant.
1522  // Modernizr.testAllProps('boxSizing')
1523  ModernizrProto.testAllProps = testPropsAll;
1524
1525  /**
1526     * testAllProps determines whether a given CSS property, in some prefixed
1527     * form, is supported by the browser. It can optionally be given a value; in
1528     * which case testAllProps will only return true if the browser supports that
1529     * value for the named property; this latter case will use native detection
1530     * (via window.CSS.supports) if available. A boolean can be passed as a 3rd
1531     * parameter to skip the value check when native detection isn't available,
1532     * to improve performance when simply testing for support of a property.
1533     *
1534     * @param prop - String naming the property to test (either camelCase or
1535     *               kebab-case)
1536     * @param value - [optional] String of the value to test
1537     * @param skipValueTest - [optional] Whether to skip testing that the value
1538     *                        is supported when using non-native detection
1539     *                        (default: false)
1540     */
1541  function testAllProps(prop, value, skipValueTest) {
1542    return testPropsAll(prop, undefined, undefined, value, skipValueTest);
1543  }
1544
1545  ModernizrProto.testAllProps = testAllProps;
1546
1547  /*!
1548     {
1549     "name": "Background Size",
1550     "property": "backgroundsize",
1551     "tags": ["css"],
1552     "knownBugs": ["This will false positive in Opera Mini - http://github.com/Modernizr/Modernizr/issues/396"],
1553     "notes": [{
1554     "name": "Related Issue",
1555     "href": "http://github.com/Modernizr/Modernizr/issues/396"
1556     }]
1557     }
1558     ! */
1559
1560  Modernizr.addTest('backgroundsize', testAllProps('backgroundSize', '100%', true));
1561
1562  /*!
1563     {
1564     "name": "Border Image",
1565     "property": "borderimage",
1566     "caniuse": "border-image",
1567     "polyfills": ["css3pie"],
1568     "knownBugs": ["Android < 2.0 is true, but has a broken implementation"],
1569     "tags": ["css"]
1570     }
1571     ! */
1572
1573  Modernizr.addTest('borderimage', testAllProps('borderImage', 'url() 1', true));
1574
1575  /*!
1576     {
1577     "name": "Border Radius",
1578     "property": "borderradius",
1579     "caniuse": "border-radius",
1580     "polyfills": ["css3pie"],
1581     "tags": ["css"],
1582     "notes": [{
1583     "name": "Comprehensive Compat Chart",
1584     "href": "http://muddledramblings.com/table-of-css3-border-radius-compliance"
1585     }]
1586     }
1587     ! */
1588
1589  Modernizr.addTest('borderradius', testAllProps('borderRadius', '0px', true));
1590
1591  /*!
1592     {
1593     "name": "Box Shadow",
1594     "property": "boxshadow",
1595     "caniuse": "css-boxshadow",
1596     "tags": ["css"],
1597     "knownBugs": [
1598     "WebOS false positives on this test.",
1599     "The Kindle Silk browser false positives"
1600     ]
1601     }
1602     ! */
1603
1604  Modernizr.addTest('boxshadow', testAllProps('boxShadow', '1px 1px', true));
1605
1606  /*!
1607     {
1608     "name": "CSS Animations",
1609     "property": "cssanimations",
1610     "caniuse": "css-animation",
1611     "polyfills": ["transformie", "csssandpaper"],
1612     "tags": ["css"],
1613     "warnings": ["Android < 4 will pass this test, but can only animate a single property at a time"],
1614     "notes": [{
1615     "name" : "Article: 'Dispelling the Android CSS animation myths'",
1616     "href": "http://goo.gl/OGw5Gm"
1617     }]
1618     }
1619     ! */
1620  /* DOC
1621     Detects whether or not elements can be animated using CSS
1622     */
1623
1624  Modernizr.addTest('cssanimations', testAllProps('animationName', 'a', true));
1625
1626  /*!
1627     {
1628     "name": "CSS Columns",
1629     "property": "csscolumns",
1630     "caniuse": "multicolumn",
1631     "polyfills": ["css3multicolumnjs"],
1632     "tags": ["css"]
1633     }
1634     ! */
1635
1636  (function() {
1637    /* jshint -W053 */
1638    Modernizr.addTest('csscolumns', function() {
1639      let bool = false;
1640      const test = testAllProps('columnCount');
1641
1642      try {
1643        if (bool = !!test) {
1644          bool = new Boolean(bool);
1645        }
1646      } catch (e) {}
1647
1648      return bool;
1649    });
1650
1651    const props = ['Width', 'Span', 'Fill', 'Gap', 'Rule', 'RuleColor', 'RuleStyle', 'RuleWidth', 'BreakBefore', 'BreakAfter', 'BreakInside'];
1652    let name; let
1653      test;
1654
1655    for (let i = 0; i < props.length; i++) {
1656      name = props[i].toLowerCase();
1657      test = testAllProps(`column${props[i]}`);
1658
1659      // break-before, break-after & break-inside are not "column"-prefixed in spec
1660      if (name === 'breakbefore' || name === 'breakafter' || name == 'breakinside') {
1661        test = test || testAllProps(props[i]);
1662      }
1663
1664      Modernizr.addTest(`csscolumns.${name}`, test);
1665    }
1666  }());
1667
1668  /*!
1669     {
1670     "name": "CSS Reflections",
1671     "caniuse": "css-reflections",
1672     "property": "cssreflections",
1673     "tags": ["css"]
1674     }
1675     ! */
1676
1677  Modernizr.addTest('cssreflections', testAllProps('boxReflect', 'above', true));
1678
1679  /*!
1680     {
1681     "name": "CSS Transforms",
1682     "property": "csstransforms",
1683     "caniuse": "transforms2d",
1684     "tags": ["css"]
1685     }
1686     ! */
1687
1688  Modernizr.addTest('csstransforms', function() {
1689    // Android < 3.0 is buggy, so we sniff and blacklist
1690    // http://git.io/hHzL7w
1691    return navigator.userAgent.indexOf('Android 2.') === -1
1692            && testAllProps('transform', 'scale(1)', true);
1693  });
1694
1695  /*!
1696     {
1697     "name": "CSS Transforms 3D",
1698     "property": "csstransforms3d",
1699     "caniuse": "transforms3d",
1700     "tags": ["css"],
1701     "warnings": [
1702     "Chrome may occassionally fail this test on some systems; more info: https://code.google.com/p/chromium/issues/detail?id=129004"
1703     ]
1704     }
1705     ! */
1706
1707  Modernizr.addTest('csstransforms3d', function() {
1708    let ret = !!testAllProps('perspective', '1px', true);
1709    const usePrefix = Modernizr._config.usePrefixes;
1710
1711    // Webkit's 3D transforms are passed off to the browser's own graphics renderer.
1712    //   It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in
1713    //   some conditions. As a result, Webkit typically recognizes the syntax but
1714    //   will sometimes throw a false positive, thus we must do a more thorough check:
1715    if (ret && (!usePrefix || 'webkitPerspective' in docElement.style)) {
1716      let mq;
1717
1718      // Use CSS Conditional Rules if available
1719      if (Modernizr.supports) {
1720        mq = '@supports (perspective: 1px)';
1721      } else {
1722        // Otherwise, Webkit allows this media query to succeed only if the feature is enabled.
1723        // `@media (transform-3d),(-webkit-transform-3d){ ... }`
1724        mq = '@media (transform-3d)';
1725        if (usePrefix) mq += ',(-webkit-transform-3d)';
1726      }
1727
1728      // If loaded inside the body tag and the test element inherits any padding, margin or borders it will fail #740
1729      mq += '{#modernizr{left:9px;position:absolute;height:5px;margin:0;padding:0;border:0}}';
1730
1731      testStyles(mq, function(elem) {
1732        ret = elem.offsetLeft === 9 && elem.offsetHeight === 5;
1733      });
1734    }
1735
1736    return ret;
1737  });
1738
1739  /*!
1740     {
1741     "name": "CSS Transitions",
1742     "property": "csstransitions",
1743     "caniuse": "css-transitions",
1744     "tags": ["css"]
1745     }
1746     ! */
1747
1748  Modernizr.addTest('csstransitions', testAllProps('transition', 'all', true));
1749
1750  /*!
1751     {
1752     "name": "Flexbox",
1753     "property": "flexbox",
1754     "caniuse": "flexbox",
1755     "tags": ["css"],
1756     "notes": [{
1757     "name": "The _new_ flexbox",
1758     "href": "http://dev.w3.org/csswg/css3-flexbox"
1759     }],
1760     "warnings": [
1761     "A `true` result for this detect does not imply that the `flex-wrap` property is supported; see the `flexwrap` detect."
1762     ]
1763     }
1764     ! */
1765  /* DOC
1766     Detects support for the Flexible Box Layout model, a.k.a. Flexbox, which allows easy manipulation of layout order and sizing within a container.
1767     */
1768
1769  Modernizr.addTest('flexbox', testAllProps('flexBasis', '1px', true));
1770
1771  Modernizr.addTest('overflowscrolling', function() {
1772    return Modernizr.testAllProps('overflowScrolling');
1773  });
1774
1775  Modernizr.addTest('touchevents', function() {
1776    let bool;
1777
1778    if (('ontouchstart' in window) || window.DocumentTouch && document instanceof DocumentTouch) {
1779      bool = true;
1780    } else {
1781      // include the 'heartz' as a way to have a non matching MQ to help terminate the join
1782      // https://git.io/vznFH
1783      const query = ['@media (', prefixes.join('touch-enabled),('), 'heartz', ')', '{#modernizr{top:9px;position:absolute}}'].join('');
1784
1785      testStyles(query, function(node) {
1786        bool = node.offsetTop === 9;
1787      });
1788    }
1789
1790    return bool;
1791  });
1792
1793  /*!
1794     {
1795     "name": "Flexbox (legacy)",
1796     "property": "flexboxlegacy",
1797     "tags": ["css"],
1798     "polyfills": ["flexie"],
1799     "notes": [{
1800     "name": "The _old_ flexbox",
1801     "href": "http://www.w3.org/TR/2009/WD-css3-flexbox-20090723/"
1802     }]
1803     }
1804     ! */
1805
1806  Modernizr.addTest('flexboxlegacy', testAllProps('boxDirection', 'reverse', true));
1807
1808  // Modernizr.prefixed() returns the prefixed or nonprefixed property name variant of your input
1809  // Modernizr.prefixed('boxSizing') // 'MozBoxSizing'
1810
1811  // Properties can be passed as DOM-style camelCase or CSS-style kebab-case.
1812  // Return values will always be in camelCase; if you want kebab-case, use Modernizr.prefixedCSS().
1813
1814  // If you're trying to ascertain which transition end event to bind to, you might do something like...
1815  //
1816  //     var transEndEventNames = {
1817  //         'WebkitTransition' : 'webkitTransitionEnd',// Saf 6, Android Browser
1818  //         'MozTransition'    : 'transitionend',      // only for FF < 15
1819  //         'transition'       : 'transitionend'       // IE10, Opera, Chrome, FF 15+, Saf 7+
1820  //     },
1821  //     transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];
1822
1823  const prefixed = ModernizrProto.prefixed = function(prop, obj, elem) {
1824    if (prop.indexOf('@') === 0) {
1825      return atRule(prop);
1826    }
1827
1828    if (prop.indexOf('-') != -1) {
1829      // Convert kebab-case to camelCase
1830      prop = cssToDOM(prop);
1831    }
1832
1833    if (!obj) {
1834      return testPropsAll(prop, 'pfx');
1835    } else {
1836      // Testing DOM property e.g. Modernizr.prefixed('requestAnimationFrame', window) // 'mozRequestAnimationFrame'
1837      return testPropsAll(prop, obj, elem);
1838    }
1839  };
1840
1841  /*!
1842     {
1843     "name": "IndexedDB",
1844     "property": "indexeddb",
1845     "caniuse": "indexeddb",
1846     "tags": ["storage"],
1847     "polyfills": ["indexeddb"]
1848     }
1849     ! */
1850  /* DOC
1851     Detects support for the IndexedDB client-side storage API (final spec).
1852     */
1853
1854  // Vendors had inconsistent prefixing with the experimental Indexed DB:
1855  // - Webkit's implementation is accessible through webkitIndexedDB
1856  // - Firefox shipped moz_indexedDB before FF4b9, but since then has been mozIndexedDB
1857  // For speed, we don't test the legacy (and beta-only) indexedDB
1858
1859  const indexeddb = prefixed('indexedDB', window);
1860
1861  Modernizr.addTest('indexeddb', !!indexeddb);
1862
1863  if (indexeddb) {
1864    Modernizr.addTest('indexeddb.deletedatabase', 'deleteDatabase' in indexeddb);
1865  }
1866
1867  /*!
1868     {
1869     "name": "picture Element",
1870     "property": "picture",
1871     "tags": ["elem"],
1872     "authors": ["Scott Jehl", "Mat Marquis"],
1873     "notes": [{
1874     "name": "Specification",
1875     "href": "http://picture.responsiveimages.org"
1876     },{
1877     "name": "Relevant spec issue",
1878     "href": "https://github.com/ResponsiveImagesCG/picture-element/issues/87"
1879     }]
1880     }
1881     ! */
1882
1883  Modernizr.addTest('picture', 'HTMLPictureElement' in window);
1884
1885  // Run each test
1886  testRunner();
1887
1888  // Remove the "no-js" class if it exists
1889  setClasses(classes);
1890
1891  delete ModernizrProto.addTest;
1892  delete ModernizrProto.addAsyncTest;
1893
1894  // Run the things that are supposed to run after the tests
1895  for (let i = 0; i < Modernizr._q.length; i++) {
1896    Modernizr._q[i]();
1897  }
1898
1899  // Leak Modernizr namespace
1900  window.Modernizr = Modernizr;
1901}(window, document));
1902
1903var cookieServiceAnalytics;
1904
1905function isCookieCategorySelected(prevCategoryPreference) {
1906	return prevCategoryPreference ? 'yes' : 'no';
1907}
1908
1909function getOptInStatus(cookieCategories, prevCategoryPreferenceCollection) {
1910	var i,
1911		optInStatusText = '';
1912	for (i = 0; i < cookieCategories.length; i++) {
1913		optInStatusText += (i !== 0 ? '|' : '') + cookieCategories[i].id + ':' + isCookieCategorySelected(prevCategoryPreferenceCollection[cookieCategories[i].id]);
1914	}
1915	return optInStatusText;
1916}
1917
1918cookieServiceAnalytics = {
1919	getOptInStatus: getOptInStatus
1920};
1921
1922var cookieServiceModule,
1923	namespace = 'cookie-configuration',
1924	NAMES = {
1925		cookieDisclaimer: 'guxfoe-cookiedisclaimer',
1926		cookieSlideshow: 'slideshowImagToOpen',
1927		cookieComparePv: 'compare-pv',
1928		cookieCompareCv: 'compare-cv'
1929	},
1930	isAuthor = !!(typeof CQ !== 'undefined' && CQ?.WCM?.isEditMode?.(true)),
1931	isConfigurationAvailable = (window.cookieConfiguration?.cookieCategoriesConfiguration),
1932	configuration = isConfigurationAvailable ? window.cookieConfiguration : null,
1933	dayInMiliseconds = 86400000,
1934	defaultExpirationDate = dayInMiliseconds * 360,
1935	expireIn = configuration ? dayInMiliseconds * configuration.cookieCategoriesConfiguration.expirationDate : defaultExpirationDate,
1936	defaultCategoryPreferenceCollection,
1937	prevCategoryPreferenceCollection,
1938	cookiesByCategory,
1939	cookieGroupCategoryNames,
1940	defaultPreferenceCollection,
1941	preferenceMessageCollection = {
1942		cookiesAgree: 'cookieDisclaimer:agree',
1943		cookiesDisagree: 'cookieDisclaimer:disagree',
1944		cookiesManage: 'cookieDisclaimer:manage',
1945    cookiesDefault: 'cookiesDisclaimer:default'
1946	};
1947
1948if (isConfigurationAvailable) {
1949	digitaldata.user = digitaldata.user || {};
1950	defaultCategoryPreferenceCollection = getDefaultCategoryPreferences(configuration.cookieCategoriesConfiguration.categories);
1951	prevCategoryPreferenceCollection = getConfiguration() || defaultCategoryPreferenceCollection;
1952
1953	cookiesByCategory = getCookiesByCategory(configuration.cookies, configuration.cookieCategoriesConfiguration.categories);
1954	cookieGroupCategoryNames = getCookieGroupCategoryNames(configuration.cookieCategoriesConfiguration.categories);
1955	defaultPreferenceCollection = {
1956		cookiesAgree: getAgreeDefaultPreferences(configuration.cookieCategoriesConfiguration.categories),
1957		cookiesDisagree: getDisagreeDefaultPreferences(configuration.cookieCategoriesConfiguration.categories),
1958		cookiesManage: getManageDefaultPreferences(configuration.cookieCategoriesConfiguration.categories),
1959    cookiesDefault: getDefaultCategoryPreferences(configuration.cookieCategoriesConfiguration.categories)
1960	};
1961	// we need to populate this global analytics value here
1962	digitaldata.user.optInStatus = cookieServiceAnalytics.getOptInStatus(cookieGroupCategoryNames, prevCategoryPreferenceCollection);
1963
1964	setUrlCookieCombinedParameters();
1965}
1966
1967function getParameterByName(name, url = window.location.href) {
1968	name = name.replace(/[[\]]/g, '\\$&');
1969	var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)');
1970	var results = regex.exec(url);
1971
1972	if (!results) {
1973		return null;
1974	}
1975
1976	if (!results[2]) {
1977		return '';
1978	}
1979
1980	return decodeURIComponent(results[2].replace(/\+/g, ' '));
1981}
1982
1983function getCookieValue(urlParamNames) {
1984	var values = [];
1985
1986	urlParamNames.forEach(function (paramName) {
1987		var paramValue = getParameterByName(paramName);
1988
1989		if (paramValue) {
1990			values.push(`${paramName}=${paramValue}`);
1991		}
1992	});
1993
1994	return values.join('&');
1995}
1996
1997function setCookiesFromUrl(cookieConfig) {
1998	var cookieName = cookieConfig.cookieName;
1999	var category = cookieConfig.category;
2000	var urlParamNames = cookieConfig.urlParamNames;
2001	var expirationDays = cookieConfig.expirationDays;
2002	var cookieCategoryConfiguration = getConfiguration();
2003	var isValidConfig = !!(cookieName || (urlParamNames?.length) || category || expirationDays);
2004	var isCookieCategoryAcceptedByClient = cookieCategoryConfiguration?.[category];
2005	var cookieValue;
2006
2007	if (!isCookieCategoryAcceptedByClient || !isValidConfig) {
2008		return;
2009	}
2010
2011	cookieValue = getCookieValue(urlParamNames);
2012
2013	if (cookieValue) {
2014		cookieSet({
2015			name: cookieName,
2016			value: cookieValue,
2017			domain: getCurrentDomain(),
2018			expirationDays,
2019			path: '/'
2020		}, false);
2021	}
2022}
2023
2024function setUrlCookieCombinedParameters() {
2025	if (configuration?.urlCookieCombinedParameters) {
2026		configuration.urlCookieCombinedParameters.forEach(setCookiesFromUrl);
2027	}
2028}
2029
2030function getCookieGroupCategoryNames(defaultCategoryCookieConfig) {
2031	var i,
2032		cookieCategoryName = [];
2033	for (i = 0; i < defaultCategoryCookieConfig.length; i++) {
2034		cookieCategoryName.push({
2035			id: defaultCategoryCookieConfig[i].id,
2036			analyticsName: defaultCategoryCookieConfig[i].analyticsName
2037		});
2038	}
2039	return cookieCategoryName;
2040}
2041
2042function getCookiesByCategoryTemplate(categoryPreferenceList) {
2043	return categoryPreferenceList.reduce(function (prefCollection, item) {
2044		prefCollection[item.id] = [];
2045		return prefCollection;
2046	}, {});
2047}
2048
2049function getManageDefaultPreferences(categoryPreferenceList) {
2050	return categoryPreferenceList.reduce(function (prefCollection, item) {
2051		var agreeToCategory = item.defaultState !== 'MANDATORY_SELECTABLE_OFF' && item.defaultState !== 'SELECTABLE_OFF';
2052		prefCollection[item.id] = agreeToCategory;
2053		return prefCollection;
2054	}, {});
2055}
2056
2057function getDisagreeDefaultPreferences(categoryPreferenceList) {
2058	return categoryPreferenceList.reduce(function (prefCollection, item) {
2059		prefCollection[item.id] = item.alwaysOn;
2060		return prefCollection;
2061	}, {});
2062}
2063
2064function getAgreeDefaultPreferences(categoryPreferenceList) {
2065	return categoryPreferenceList.reduce(function (prefCollection, item) {
2066		var agreeToCategory = item.defaultState !== 'MANDATORY_SELECTABLE_OFF';
2067		prefCollection[item.id] = agreeToCategory;
2068		return prefCollection;
2069	}, {});
2070}
2071
2072function getDefaultCategoryPreferences(categoryPrefList) {
2073	return categoryPrefList.reduce(function (prefCollection, item) {
2074		prefCollection[item.id] = item.enabledByDefault;
2075		return prefCollection;
2076	}, {});
2077}
2078
2079function getCookieCategory(name, cookieCategoryList) {
2080	return cookieCategoryList.filter(function (item) {
2081		return item.name === name;
2082	})[0];
2083}
2084
2085function getCategoryPrefDiff(newCollection, prevCollection) {
2086	return Object.keys(newCollection).filter(function (key) {
2087		return newCollection[key] !== prevCollection[key];
2088	});
2089}
2090
2091function getCookiesByCategory(cookieCollection, categoryPreferenceList) {
2092	var cookiesByCategoryCollection = getCookiesByCategoryTemplate(categoryPreferenceList);
2093
2094	return cookieCollection.reduce(function (cookiesByCategoryCollection, item) {
2095		cookiesByCategoryCollection[item.category].push(item.name);
2096		return cookiesByCategoryCollection;
2097	}, cookiesByCategoryCollection);
2098}
2099
2100function deepCloneCollection(collection) {
2101	return JSON.parse(JSON.stringify(collection));
2102}
2103
2104function onPreferenceChange(newCategoryPreferenceCollection) {
2105	var updatedCategoryList = getCategoryPrefDiff(newCategoryPreferenceCollection, prevCategoryPreferenceCollection);
2106
2107	updatedCategoryList.forEach(function (category) {
2108		var isUpdatedCategoryOn = newCategoryPreferenceCollection[category];
2109
2110		if (!isUpdatedCategoryOn) {
2111			cookiesByCategory[category].forEach(function (cookieName) {
2112				buildCookiePathsThenDelete(cookieName);
2113			});
2114		}
2115	});
2116
2117	prevCategoryPreferenceCollection = deepCloneCollection(newCategoryPreferenceCollection);
2118}
2119
2120function storeGet(key) {
2121	const entry = JSON.parse(localStorage.getItem(key) || '0');
2122
2123	if (!entry) {
2124		return null;
2125	}
2126
2127	return entry.value;
2128}
2129
2130function setLocalStorageConfig(value) {
2131	localStorage.setItem(namespace, JSON.stringify({
2132		value: value
2133	}));
2134}
2135
2136function storeSet(value) {
2137	onPreferenceChange(value);
2138
2139	cookieSet({
2140		path: '/',
2141		expirationDays: configuration.cookieCategoriesConfiguration.expirationDate || defaultExpirationDate,
2142		name: namespace,
2143		value: JSON.stringify({
2144			timestamp: Date.now(),
2145			value
2146		})
2147	});
2148
2149	setLocalStorageConfig(value);
2150}
2151
2152function setCookieValue(contextType, callback) {
2153  if (isConfigurationAvailable) {
2154    callback(preferenceMessageCollection[contextType], defaultPreferenceCollection[contextType]);
2155    storeSet(defaultPreferenceCollection[contextType]);
2156  }
2157}
2158
2159function getConfiguration() {
2160	if (!isAuthor) {
2161		const cookieConfig = cookieGet(namespace);
2162		const cookieConfigTimestamp = cookieConfig ? JSON.parse(cookieConfig).timestamp : null;
2163		const aemConfigTimestamp = configuration ? configuration.timestamp : 0
2164		const isConfigCurrent = cookieConfigTimestamp >= aemConfigTimestamp;
2165		const isConfigExistAndIsValid = !!cookieConfig && isConfigCurrent;
2166		const cookieDisclaimerTimestamp = cookieGet(NAMES.cookieDisclaimer);
2167		const isCookieDisclaimerFresh = cookieDisclaimerTimestamp ? parseInt(cookieDisclaimerTimestamp, 10) >= aemConfigTimestamp : true;
2168
2169		if (!isCookieDisclaimerFresh) {
2170			buildCookiePathsThenDelete(NAMES.cookieDisclaimer);
2171		}
2172
2173		if (!isConfigExistAndIsValid) {
2174			buildCookiePathsThenDelete(namespace);
2175			window.localStorage.removeItem(namespace);
2176
2177			return {...defaultCategoryPreferenceCollection};
2178		} else {
2179			setLocalStorageConfig(JSON.parse(cookieConfig).value);
2180
2181			return JSON.parse(cookieConfig).value;
2182		}
2183	}
2184}
2185
2186/**
2187 * @method cookieSet - Sets a single cookie.
2188 * @param {object} - {config} - Information about the cookie you wish to set.
2189 * @param {string} - {config.name} - The name of the cookie you wish to set.
2190 * @param {string} - {config.value} - The value you wish to cookie to have.
2191 * @param {number} - {config.expirationDays} - The date you want the cookie to expire upon.
2192 * @param {string} - {config.path} - A path to a location on your site.
2193 */
2194function cookieSet(config, encode = true) {
2195	var expirationDate = config.expirationDays ? 'expires=' + new Date(Date.now() + (dayInMiliseconds * config.expirationDays)) : '',
2196		cookiePath = config.path ? ';path=' + config.path : '',
2197		cookieDomain = config.domain ? ';domain=' + config.domain : `;domain=${getCurrentDomain()}`,
2198		cookie = config.name + '=' + config.value + ';' + expirationDate + cookieDomain + cookiePath,
2199		encodedCookie = config.name + '=' + ((encode) ? encodeURIComponent(config.value) : config.value) + ';' + expirationDate + cookieDomain + cookiePath;
2200
2201	document.cookie = encodedCookie;
2202	return cookie;
2203}
2204
2205function cookieGet(name) {
2206	var cookieName = name + '=',
2207		cookieAttributes = document.cookie.split(';'),
2208		attribute,
2209		i;
2210
2211	for (i = 0; i < cookieAttributes.length; i++) {
2212		attribute = cookieAttributes[i].trim();
2213
2214		while (attribute.startsWith(' ')) {
2215			attribute = attribute.substring(1);
2216		}
2217		if (attribute.startsWith(cookieName)) {
2218			return decodeURIComponent(attribute.substring(cookieName.length, attribute.length));
2219		}
2220	}
2221	return '';
2222}
2223
2224function buildCookiePathsThenDelete(name) {
2225	var pathname = location.pathname.replace(/\/$/, ''),
2226		segments = pathname.split('/'),
2227		pathList = [],
2228		i,
2229		l = segments.length;
2230
2231	for (i = 0; i < l; i++) {
2232		const path = segments.slice(0, i + 1).join('/');
2233
2234		pathList.push(path); // as file
2235		pathList.push(path + '/'); // as directory
2236	}
2237
2238	cookieDelete({
2239		name: name,
2240		pathList: pathList
2241	});
2242}
2243
2244/**
2245 * @method cookieDelete - Removes each cookie of the name passed in under the list of paths passed in.
2246 * @param {object} - {config} - Information about the cookie you wish to remove.
2247 * @param {string} - {config.name} - The name of the cookie you wish to remove.
2248 * @param {array} - {config.pathList} - An array of 1 or more paths for the cookie you wish to remove.
2249 */
2250function cookieDelete(config) {
2251	var expires = new Date(0).toUTCString(),
2252		pathList = config.pathList ? config.pathList : ['/'],
2253		l = pathList.length,
2254		i,
2255		hostname = window.location.hostname,
2256		indexOfHostname = hostname.indexOf('.ford'),
2257		additionalDomain = hostname.substr(indexOfHostname);
2258
2259	// expire null-path cookies as well
2260	document.cookie = config.name + '=; expires=' + expires;
2261
2262	for (i = 0; i < l; i++) {
2263		document.cookie = config.name + '=; path=' + pathList[i] + '; expires=' + expires;
2264		document.cookie = config.name + '=; path=' + pathList[i] + '; expires=' + expires + '; domain=' + additionalDomain + '';
2265	}
2266}
2267
2268/**
2269 * @method validateCookieCategory - A gate to check whether a cookie is allowed to be set or not based on the users settings.
2270 * @param {object} - {config} - Information about the cookie.
2271 * @param {string} - {config.name} - The name of the cookie you wish to check.
2272 * EVERYTHING BEYOND HERE IS PASSED TO THE cookieSet()
2273 * @param {string} - {config.value} - The value you wish to cookie to have.
2274 * @param {number} - {config.expirationDays} - The date you want the cookie to expire upon.
2275 * @param {string} - {config.path} - A path to a location on your site.
2276 */
2277function validateCookieCategory(config) {
2278	var isCategoryPermitted = getConfiguration() || defaultCategoryPreferenceCollection,
2279		cookieCategory = getCookieCategory(config.name, configuration.cookies),
2280		cookieCategoryName = cookieCategory ? cookieCategory.category : null;
2281
2282	if (isCategoryPermitted[cookieCategoryName] || cookieCategoryName === null) {
2283		return cookieSet(config);
2284	}
2285}
2286
2287function getCurrentDomain() {
2288	const domain = 'ford.';
2289	const url = window.location.hostname.replace(/(https?:\/\/)?(www.)?/i, '');
2290	const prefix = url => (url.indexOf('.') > -1 ? '.' : '');
2291	const result = (url?.indexOf(domain)) > -1 ? url.slice(url.indexOf(domain)) : url;
2292
2293	return `${prefix(result)}${result}`;
2294}
2295
2296cookieServiceModule = {
2297	namespace,
2298	NAMES,
2299	configuredExpirationDate: isConfigurationAvailable ? configuration.cookieCategoriesConfiguration.expirationDate : 360,
2300	store: {
2301		get: storeGet,
2302		set: storeSet,
2303		config: getConfiguration(),
2304		getConfig: getConfiguration,
2305		getGroupCategoryNames: cookieGroupCategoryNames,
2306		defaultCategoryPreferenceCollection,
2307    setCookieValue
2308	},
2309	cookie: {
2310		get: cookieGet,
2311		set: isConfigurationAvailable ? validateCookieCategory : cookieSet,
2312		remove: cookieDelete
2313	},
2314	getCookieCategoryByCookieName(name) {
2315		if (cookiesByCategory) {
2316			return Object.keys(cookiesByCategory).find(category =>
2317				cookiesByCategory[category].find(items => items.includes(name)));
2318		}
2319	},
2320	setUrlCookieCombinedParameters
2321};
2322

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.