diff --git a/achieveunlocked2/achievementunlocked2.swf b/achieveunlocked2/achievementunlocked2.swf new file mode 100644 index 00000000..8e14be7a Binary files /dev/null and b/achieveunlocked2/achievementunlocked2.swf differ diff --git a/achieveunlocked2/icon.png b/achieveunlocked2/icon.png new file mode 100644 index 00000000..d52e00c3 Binary files /dev/null and b/achieveunlocked2/icon.png differ diff --git a/achieveunlocked2/index.html b/achieveunlocked2/index.html new file mode 100644 index 00000000..89c4e0fc --- /dev/null +++ b/achieveunlocked2/index.html @@ -0,0 +1,24 @@ + + + + Achievement Unlocked 2 + + + +
+ + + + diff --git a/changelog.html b/changelog.html index b3e21ae4..b4c73a26 100644 --- a/changelog.html +++ b/changelog.html @@ -17,6 +17,8 @@

e-gamepass

+

v. 2023.05.24

+

Added 4 games (Slope, This is the only level, chrome dino, achievement unlocked 2)
Fixed web analytics (only affects me lol)

v. 2023.05.23

Major update?!
Added saves
Complete backend remake
Fixed flash pages to make them full screen automatically
Added 2 games (Cell Machine and Death Run 3D)
Added more bookmarklets
Tetris Update!

Back to home diff --git a/chrome-dino/1_hurdling.html b/chrome-dino/1_hurdling.html new file mode 100644 index 00000000..cfbb7e0b --- /dev/null +++ b/chrome-dino/1_hurdling.html @@ -0,0 +1,135 @@ + + + + + + + + Chrome Dino: Olympic Hurdling | 3kh0 + + + + + + + + + + +
+
+ +
+

+ Press space/up to play +

+

+ Change theme: + +
+ Refresh the page to change theme +

+
+
+
+ + + Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape. When you hear an audio cue, press space to jump over obstacles. + +
+
+
+ +
+ +
+
+ + + +
+ + + + + \ No newline at end of file diff --git a/chrome-dino/2_gymnastics.html b/chrome-dino/2_gymnastics.html new file mode 100644 index 00000000..22648006 --- /dev/null +++ b/chrome-dino/2_gymnastics.html @@ -0,0 +1,135 @@ + + + + + + + + Chrome Dino: Olympic Gymnastics | 3kh0 + + + + + + + + + + +
+
+ +
+

+ Press space/up to play +

+

+ Change theme: + +
+ Refresh the page to change theme +

+
+
+
+ + + Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape. When you hear an audio cue, press space to jump over obstacles. + +
+
+
+ +
+ +
+
+ + + +
+ + + + + diff --git a/chrome-dino/3_surfing.html b/chrome-dino/3_surfing.html new file mode 100644 index 00000000..dd8cf1d4 --- /dev/null +++ b/chrome-dino/3_surfing.html @@ -0,0 +1,135 @@ + + + + + + + + Chrome Dino: Olympic Surfing | 3kh0 + + + + + + + + + + +
+
+ +
+

+ Press space/up to play +

+

+ Change theme: + +
+ Refresh the page to change theme +

+
+
+
+ + + Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape. When you hear an audio cue, press space to jump over obstacles. + +
+
+
+ +
+ +
+
+ + + +
+ + + + + diff --git a/chrome-dino/4_swimming.html b/chrome-dino/4_swimming.html new file mode 100644 index 00000000..927a7c28 --- /dev/null +++ b/chrome-dino/4_swimming.html @@ -0,0 +1,135 @@ + + + + + + + + Chrome Dino: Olympic Swimming | 3kh0 + + + + + + + + + + +
+
+ +
+

+ Press space/up to play +

+

+ Change theme: + +
+ Refresh the page to change theme +

+
+
+
+ + + Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape. When you hear an audio cue, press space to jump over obstacles. + +
+
+
+ +
+ +
+
+ + + +
+ + + + + diff --git a/chrome-dino/5_equestrian.html b/chrome-dino/5_equestrian.html new file mode 100644 index 00000000..ab237e45 --- /dev/null +++ b/chrome-dino/5_equestrian.html @@ -0,0 +1,135 @@ + + + + + + + + Chrome Dino: Olympic Equestrian + + + + + + + + + + +
+
+ +
+

+ Press space/up to play +

+

+ Change theme: + +
+ Refresh the page to change theme +

+
+
+
+ + + Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape. When you hear an audio cue, press space to jump over obstacles. + +
+
+
+ +
+ +
+
+ + + +
+ + + + + diff --git a/chrome-dino/appmanifest.json b/chrome-dino/appmanifest.json new file mode 100644 index 00000000..9ce41e72 --- /dev/null +++ b/chrome-dino/appmanifest.json @@ -0,0 +1,37 @@ +{ + "name": "Chrome Dino", + "short_name": "Chrome Dino", + "description": "Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape.", + "version": "2.0", + "start_url": "index.html", + "display": "fullscreen", + "orientation": "any", + "background_color": "#ffffff", + "icons": [ + { + "src": "icons/icon-16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "icons/icon-32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "icons/icon-114.png", + "sizes": "114x114", + "type": "image/png" + }, + { + "src": "icons/icon-128.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "icons/icon-256.png", + "sizes": "256x256", + "type": "image/png" + } + ] +} \ No newline at end of file diff --git a/chrome-dino/favicon.ico b/chrome-dino/favicon.ico new file mode 100644 index 00000000..c520571a Binary files /dev/null and b/chrome-dino/favicon.ico differ diff --git a/chrome-dino/icons/icon-114.png b/chrome-dino/icons/icon-114.png new file mode 100644 index 00000000..20244c69 Binary files /dev/null and b/chrome-dino/icons/icon-114.png differ diff --git a/chrome-dino/icons/icon-128.png b/chrome-dino/icons/icon-128.png new file mode 100644 index 00000000..f8f56864 Binary files /dev/null and b/chrome-dino/icons/icon-128.png differ diff --git a/chrome-dino/icons/icon-16.png b/chrome-dino/icons/icon-16.png new file mode 100644 index 00000000..5e2585b3 Binary files /dev/null and b/chrome-dino/icons/icon-16.png differ diff --git a/chrome-dino/icons/icon-256.png b/chrome-dino/icons/icon-256.png new file mode 100644 index 00000000..855763f4 Binary files /dev/null and b/chrome-dino/icons/icon-256.png differ diff --git a/chrome-dino/icons/icon-32.png b/chrome-dino/icons/icon-32.png new file mode 100644 index 00000000..16e15ca5 Binary files /dev/null and b/chrome-dino/icons/icon-32.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-disabled.png b/chrome-dino/images/default_100_percent/offline/100-disabled.png new file mode 100644 index 00000000..526075e6 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-disabled.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-error-offline.png b/chrome-dino/images/default_100_percent/offline/100-error-offline.png new file mode 100644 index 00000000..aab2ed03 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-error-offline.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-offline-sprite.png b/chrome-dino/images/default_100_percent/offline/100-offline-sprite.png new file mode 100644 index 00000000..5edd3cd2 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-offline-sprite.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-olympic-firemedal-sprite.png b/chrome-dino/images/default_100_percent/offline/100-olympic-firemedal-sprite.png new file mode 100644 index 00000000..d83fccf9 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-olympic-firemedal-sprite.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-olympics-equestrian-sprite.png b/chrome-dino/images/default_100_percent/offline/100-olympics-equestrian-sprite.png new file mode 100644 index 00000000..7067a999 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-olympics-equestrian-sprite.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-olympics-gymnastics-sprite.png b/chrome-dino/images/default_100_percent/offline/100-olympics-gymnastics-sprite.png new file mode 100644 index 00000000..84db320c Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-olympics-gymnastics-sprite.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-olympics-hurdling-sprite.png b/chrome-dino/images/default_100_percent/offline/100-olympics-hurdling-sprite.png new file mode 100644 index 00000000..0314a6a5 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-olympics-hurdling-sprite.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-olympics-surfing-sprite.png b/chrome-dino/images/default_100_percent/offline/100-olympics-surfing-sprite.png new file mode 100644 index 00000000..17e900d1 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-olympics-surfing-sprite.png differ diff --git a/chrome-dino/images/default_100_percent/offline/100-olympics-swimming-sprite.png b/chrome-dino/images/default_100_percent/offline/100-olympics-swimming-sprite.png new file mode 100644 index 00000000..d8934364 Binary files /dev/null and b/chrome-dino/images/default_100_percent/offline/100-olympics-swimming-sprite.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-disabled.png b/chrome-dino/images/default_200_percent/offline/200-disabled.png new file mode 100644 index 00000000..ea2b90ec Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-disabled.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-error-offline.png b/chrome-dino/images/default_200_percent/offline/200-error-offline.png new file mode 100644 index 00000000..79cae841 Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-error-offline.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-offline-sprite.png b/chrome-dino/images/default_200_percent/offline/200-offline-sprite.png new file mode 100644 index 00000000..2270e89a Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-offline-sprite.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-olympic-firemedal-sprite.png b/chrome-dino/images/default_200_percent/offline/200-olympic-firemedal-sprite.png new file mode 100644 index 00000000..e252509e Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-olympic-firemedal-sprite.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-olympics-equestrian-sprite.png b/chrome-dino/images/default_200_percent/offline/200-olympics-equestrian-sprite.png new file mode 100644 index 00000000..a05eac83 Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-olympics-equestrian-sprite.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-olympics-gymnastics-sprite.png b/chrome-dino/images/default_200_percent/offline/200-olympics-gymnastics-sprite.png new file mode 100644 index 00000000..76e5c188 Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-olympics-gymnastics-sprite.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-olympics-hurdling-sprite.png b/chrome-dino/images/default_200_percent/offline/200-olympics-hurdling-sprite.png new file mode 100644 index 00000000..313e8f72 Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-olympics-hurdling-sprite.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-olympics-surfing-sprite.png b/chrome-dino/images/default_200_percent/offline/200-olympics-surfing-sprite.png new file mode 100644 index 00000000..b80037ff Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-olympics-surfing-sprite.png differ diff --git a/chrome-dino/images/default_200_percent/offline/200-olympics-swimming-sprite.png b/chrome-dino/images/default_200_percent/offline/200-olympics-swimming-sprite.png new file mode 100644 index 00000000..80903ce4 Binary files /dev/null and b/chrome-dino/images/default_200_percent/offline/200-olympics-swimming-sprite.png differ diff --git a/chrome-dino/index.html b/chrome-dino/index.html new file mode 100644 index 00000000..9f07e0d4 --- /dev/null +++ b/chrome-dino/index.html @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + Chrome Dino | 3kh0 + + + + + + + + + +
+
+ +
+

+ Press space/up to play +

+

+ Change theme: + +
+ Refresh the page to change theme +

+
+
+
+ + + Dino game. A pixelated dinosaur dodges cacti and pterodactyls as it runs across a desolate landscape. When you hear an audio cue, press space to jump over obstacles. + +
+
+
+
+ +
+
+ + + +
+ + + + + + diff --git a/chrome-dino/offline.js b/chrome-dino/offline.js new file mode 100644 index 00000000..6259ff9d --- /dev/null +++ b/chrome-dino/offline.js @@ -0,0 +1 @@ +{"version":2.0,"fileList":["1_hurdling.html","2_gymnastics.html","3_surfing.html","4_swimming.html","5_equestrian.html","appmanifest.json","index.html","register-sw.js","sw.js","icons/icon-114.png","icons/icon-128.png","icons/icon-16.png","icons/icon-256.png","icons/icon-32.png","images/default_100_percent/offline/100-disabled.png","images/default_100_percent/offline/100-error-offline.png","images/default_100_percent/offline/100-offline-sprite.png","images/default_100_percent/offline/100-olympic-firemedal-sprite.png","images/default_100_percent/offline/100-olympics-equestrian-sprite.png","images/default_100_percent/offline/100-olympics-gymnastics-sprite.png","images/default_100_percent/offline/100-olympics-hurdling-sprite.png","images/default_100_percent/offline/100-olympics-surfing-sprite.png","images/default_100_percent/offline/100-olympics-swimming-sprite.png","images/default_200_percent/offline/200-disabled.png","images/default_200_percent/offline/200-error-offline.png","images/default_200_percent/offline/200-offline-sprite.png","images/default_200_percent/offline/200-olympic-firemedal-sprite.png","images/default_200_percent/offline/200-olympics-equestrian-sprite.png","images/default_200_percent/offline/200-olympics-gymnastics-sprite.png","images/default_200_percent/offline/200-olympics-hurdling-sprite.png","images/default_200_percent/offline/200-olympics-surfing-sprite.png","images/default_200_percent/offline/200-olympics-swimming-sprite.png","scripts/load_time_data.js","scripts/neterror.slim.js","scripts/offline.js","scripts/offline-sprite-definitions.js","scripts/strings.js","styles/interstitial_common.css","styles/interstitial_core.css","styles/neterror.css"]} \ No newline at end of file diff --git a/chrome-dino/register-sw.js b/chrome-dino/register-sw.js new file mode 100644 index 00000000..14397502 --- /dev/null +++ b/chrome-dino/register-sw.js @@ -0,0 +1,3 @@ +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('sw.js'); +} \ No newline at end of file diff --git a/chrome-dino/scripts/load_time_data.js b/chrome-dino/scripts/load_time_data.js new file mode 100644 index 00000000..a22baeaa --- /dev/null +++ b/chrome-dino/scripts/load_time_data.js @@ -0,0 +1,215 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview This file defines a singleton which provides access to all data + * that is available as soon as the page's resources are loaded (before DOM + * content has finished loading). This data includes both localized strings and + * any data that is important to have ready from a very early stage (e.g. things + * that must be displayed right away). + * + * Note that loadTimeData is not guaranteed to be consistent between page + * refreshes (https://crbug.com/740629) and should not contain values that might + * change if the page is re-opened later. + */ + +/** @type {!LoadTimeData} */ +// eslint-disable-next-line no-var +var loadTimeData; + +class LoadTimeData { + constructor() { + /** @type {Object} */ + this.data_; + } + + /** + * Sets the backing object. + * + * Note that there is no getter for |data_| to discourage abuse of the form: + * + * var value = loadTimeData.data()['key']; + * + * @param {Object} value The de-serialized page data. + */ + set data(value) { + expect(!this.data_, 'Re-setting data.'); + this.data_ = value; + } + + /** + * @param {string} id An ID of a value that might exist. + * @return {boolean} True if |id| is a key in the dictionary. + */ + valueExists(id) { + return id in this.data_; + } + + /** + * Fetches a value, expecting that it exists. + * @param {string} id The key that identifies the desired value. + * @return {*} The corresponding value. + */ + getValue(id) { + expect(this.data_, 'No data. Did you remember to include strings.js?'); + const value = this.data_[id]; + expect(typeof value !== 'undefined', 'Could not find value for ' + id); + return value; + } + + /** + * As above, but also makes sure that the value is a string. + * @param {string} id The key that identifies the desired string. + * @return {string} The corresponding string value. + */ + getString(id) { + const value = this.getValue(id); + expectIsType(id, value, 'string'); + return /** @type {string} */ (value); + } + + /** + * Returns a formatted localized string where $1 to $9 are replaced by the + * second to the tenth argument. + * @param {string} id The ID of the string we want. + * @param {...(string|number)} var_args The extra values to include in the + * formatted output. + * @return {string} The formatted string. + */ + getStringF(id, var_args) { + const value = this.getString(id); + if (!value) { + return ''; + } + + const args = Array.prototype.slice.call(arguments); + args[0] = value; + return this.substituteString.apply(this, args); + } + + /** + * Returns a formatted localized string where $1 to $9 are replaced by the + * second to the tenth argument. Any standalone $ signs must be escaped as + * $$. + * @param {string} label The label to substitute through. + * This is not an resource ID. + * @param {...(string|number)} var_args The extra values to include in the + * formatted output. + * @return {string} The formatted string. + */ + substituteString(label, var_args) { + const varArgs = arguments; + return label.replace(/\$(.|$|\n)/g, function(m) { + expect(m.match(/\$[$1-9]/), 'Unescaped $ found in localized string.'); + return m === '$$' ? '$' : varArgs[m[1]]; + }); + } + + /** + * Returns a formatted string where $1 to $9 are replaced by the second to + * tenth argument, split apart into a list of pieces describing how the + * substitution was performed. Any standalone $ signs must be escaped as $$. + * @param {string} label A localized string to substitute through. + * This is not an resource ID. + * @param {...(string|number)} var_args The extra values to include in the + * formatted output. + * @return {!Array} The formatted + * string pieces. + */ + getSubstitutedStringPieces(label, var_args) { + const varArgs = arguments; + // Split the string by separately matching all occurrences of $1-9 and of + // non $1-9 pieces. + const pieces = (label.match(/(\$[1-9])|(([^$]|\$([^1-9]|$))+)/g) || + []).map(function(p) { + // Pieces that are not $1-9 should be returned after replacing $$ + // with $. + if (!p.match(/^\$[1-9]$/)) { + expect( + (p.match(/\$/g) || []).length % 2 === 0, + 'Unescaped $ found in localized string.'); + return {value: p.replace(/\$\$/g, '$'), arg: null}; + } + + // Otherwise, return the substitution value. + return {value: varArgs[p[1]], arg: p}; + }); + + return pieces; + } + + /** + * As above, but also makes sure that the value is a boolean. + * @param {string} id The key that identifies the desired boolean. + * @return {boolean} The corresponding boolean value. + */ + getBoolean(id) { + const value = this.getValue(id); + expectIsType(id, value, 'boolean'); + return /** @type {boolean} */ (value); + } + + /** + * As above, but also makes sure that the value is an integer. + * @param {string} id The key that identifies the desired number. + * @return {number} The corresponding number value. + */ + getInteger(id) { + const value = this.getValue(id); + expectIsType(id, value, 'number'); + expect(value === Math.floor(value), 'Number isn\'t integer: ' + value); + return /** @type {number} */ (value); + } + + /** + * Override values in loadTimeData with the values found in |replacements|. + * @param {Object} replacements The dictionary object of keys to replace. + */ + overrideValues(replacements) { + expect( + typeof replacements === 'object', + 'Replacements must be a dictionary object.'); + for (const key in replacements) { + this.data_[key] = replacements[key]; + } + } + + /** Reset loadTimeData's data to empty. Should only be used in tests. */ + resetForTesting() { + this.data_ = {}; + } +} + + /** + * Checks condition, throws error message if expectation fails. + * @param {*} condition The condition to check for truthiness. + * @param {string} message The message to display if the check fails. + */ + function expect(condition, message) { + if (!condition) { + throw new Error( + 'Unexpected condition on ' + document.location.href + ': ' + message); + } + } + + /** + * Checks that the given value has the given type. + * @param {string} id The id of the value (only used for error message). + * @param {*} value The value to check the type on. + * @param {string} type The type we expect |value| to be. + */ + function expectIsType(id, value, type) { + expect( + typeof value === type, '[' + value + '] (' + id + ') is not a ' + type); + } + + expect(!loadTimeData, 'should only include this file once'); + loadTimeData = new LoadTimeData; + + // Expose |loadTimeData| directly on |window|, since within a JS module the + // scope is local and not all files have been updated to import the exported + // |loadTimeData| explicitly. + window.loadTimeData = loadTimeData; + + // console.warn('crbug/1173575, non-JS module files deprecated.'); \ No newline at end of file diff --git a/chrome-dino/scripts/neterror.slim.js b/chrome-dino/scripts/neterror.slim.js new file mode 100644 index 00000000..5681dac2 --- /dev/null +++ b/chrome-dino/scripts/neterror.slim.js @@ -0,0 +1,39 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const HIDDEN_CLASS = 'hidden'; + +// Subframes use a different layout but the same html file. This is to make it +// easier to support platforms that load the error page via different +// mechanisms (Currently just iOS). We also use the subframe style for portals +// as they are embedded like subframes and can't be interacted with by the user. +let isSubFrame = false; +if (window.top.location !== window.location || window.portalHost) { + document.documentElement.setAttribute('subframe', ''); + isSubFrame = true; +} + +// Adds an icon class to the list and removes classes previously set. +function updateIconClass(newClass) { + const frameSelector = isSubFrame ? '#sub-frame-error' : '#main-frame-error'; + const iconEl = document.querySelector(frameSelector + ' .icon'); + + if (iconEl.classList.contains(newClass)) { + return; + } + + iconEl.className = 'icon ' + newClass; +} + + +function onDocumentLoad() { + const iconClass = loadTimeData.valueExists('iconClass') && + loadTimeData.getValue('iconClass'); + updateIconClass(iconClass); + if (!isSubFrame && iconClass === 'icon-offline') { + document.documentElement.classList.add('offline'); + new Runner('.interstitial-wrapper'); + } +} +document.addEventListener('DOMContentLoaded', onDocumentLoad); \ No newline at end of file diff --git a/chrome-dino/scripts/offline-sprite-definitions.js b/chrome-dino/scripts/offline-sprite-definitions.js new file mode 100644 index 00000000..62073a43 --- /dev/null +++ b/chrome-dino/scripts/offline-sprite-definitions.js @@ -0,0 +1,687 @@ +// Copyright (c) 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/* @const */ +const GAME_TYPE = ['type_1', 'type_2', 'type_3', 'type_4', 'type_5']; + +/** + * Obstacle definitions. + * minGap: minimum pixel space between obstacles. + * multipleSpeed: Speed at which multiples are allowed. + * speedOffset: speed faster / slower than the horizon. + * minSpeed: Minimum speed which the obstacle can make an appearance. + * + * @typedef {{ + * type: string, + * width: number, + * height: number, + * yPos: number, + * multipleSpeed: number, + * minGap: number, + * minSpeed: number, + * collisionBoxes: Array, + * }} + */ +let ObstacleType; + +/** + * T-Rex runner sprite definitions. + */ +Runner.spriteDefinitionByType = { + original: { + LDPI: { + BACKGROUND_EL: {x: 86, y: 2}, + CACTUS_LARGE: {x: 332, y: 2}, + CACTUS_SMALL: {x: 228, y: 2}, + OBSTACLE_2: {x: 332, y: 2}, + OBSTACLE: {x: 228, y: 2}, + CLOUD: {x: 86, y: 2}, + HORIZON: {x: 2, y: 54}, + MOON: {x: 484, y: 2}, + PTERODACTYL: {x: 134, y: 2}, + RESTART: {x: 2, y: 68}, + TEXT_SPRITE: {x: 655, y: 2}, + TREX: {x: 848, y: 2}, + STAR: {x: 645, y: 2}, + COLLECTABLE: {x: 2, y: 2}, + ALT_GAME_END: {x: 121, y: 2} + }, + HDPI: { + BACKGROUND_EL: {x: 166, y: 2}, + CACTUS_LARGE: {x: 652, y: 2}, + CACTUS_SMALL: {x: 446, y: 2}, + OBSTACLE_2: {x: 652, y: 2}, + OBSTACLE: {x: 446, y: 2}, + CLOUD: {x: 166, y: 2}, + HORIZON: {x: 2, y: 104}, + MOON: {x: 954, y: 2}, + PTERODACTYL: {x: 260, y: 2}, + RESTART: {x: 2, y: 130}, + TEXT_SPRITE: {x: 1294, y: 2}, + TREX: {x: 1678, y: 2}, + STAR: {x: 1276, y: 2}, + COLLECTABLE: {x: 4, y: 4}, + ALT_GAME_END: {x: 242, y: 4} + }, + MAX_GAP_COEFFICIENT: 1.5, + MAX_OBSTACLE_LENGTH: 3, + HAS_CLOUDS: 1, + BOTTOM_PAD: 10, + TREX: { + WAITING_1: {x: 44, w: 44, h: 47, xOffset: 0}, + WAITING_2: {x: 0, w: 44, h: 47, xOffset: 0}, + RUNNING_1: {x: 88, w: 44, h: 47, xOffset: 0}, + RUNNING_2: {x: 132, w: 44, h: 47, xOffset: 0}, + JUMPING: {x: 0, w: 44, h: 47, xOffset: 0}, + CRASHED: {x: 220, w: 44, h: 47, xOffset: 0}, + COLLISION_BOXES: [ + new CollisionBox(22, 0, 17, 16), new CollisionBox(1, 18, 30, 9), + new CollisionBox(10, 35, 14, 8), new CollisionBox(1, 24, 29, 5), + new CollisionBox(5, 30, 21, 4), new CollisionBox(9, 34, 15, 4) + ] + }, + /** @type {Array} */ + OBSTACLES: [ + { + type: 'CACTUS_SMALL', + width: 17, + height: 35, + yPos: 105, + multipleSpeed: 4, + minGap: 120, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(0, 7, 5, 27), new CollisionBox(4, 0, 6, 34), + new CollisionBox(10, 4, 7, 14) + ] + }, + { + type: 'CACTUS_LARGE', + width: 25, + height: 50, + yPos: 90, + multipleSpeed: 7, + minGap: 120, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(0, 12, 7, 38), new CollisionBox(8, 0, 7, 49), + new CollisionBox(13, 10, 10, 38) + ] + }, + { + type: 'PTERODACTYL', + width: 46, + height: 40, + yPos: [100, 75, 50], // Variable height. + yPosMobile: [100, 50], // Variable height mobile. + multipleSpeed: 999, + minSpeed: 8.5, + minGap: 150, + collisionBoxes: [ + new CollisionBox(15, 15, 16, 5), new CollisionBox(18, 21, 24, 6), + new CollisionBox(2, 14, 4, 3), new CollisionBox(6, 10, 4, 7), + new CollisionBox(10, 8, 6, 9) + ], + numFrames: 2, + frameRate: 1000 / 6, + speedOffset: .8 + }, + { + type: 'COLLECTABLE', + width: 12, + height: 38, + yPos: 90, + multipleSpeed: 999, + minGap: 999, + minSpeed: 0, + collisionBoxes: [new CollisionBox(0, 0, 12, 38)] + } + ], + BACKGROUND_EL: { + 'CLOUD': { + HEIGHT: 14, + MAX_CLOUD_GAP: 400, + MAX_SKY_LEVEL: 30, + MIN_CLOUD_GAP: 100, + MIN_SKY_LEVEL: 71, + OFFSET: 4, + WIDTH: 46, + X_POS: 1, + Y_POS: 120 + } + }, + BACKGROUND_EL_CONFIG: { + MAX_BG_ELS: 1, + MAX_GAP: 400, + MIN_GAP: 100, + POS: 0, + SPEED: 0.5, + Y_POS: 125 + }, + LINES: [ + {SOURCE_X: 2, SOURCE_Y: 52, WIDTH: 600, HEIGHT: 12, YPOS: 127}, + ], + ALT_GAME_END_CONFIG: { + WIDTH: 15, + HEIGHT: 17, + X_OFFSET: 0, + Y_OFFSET: 0, + }, + ALT_GAME_OVER_TEXT_CONFIG: { + TEXT_X: 14, + TEXT_Y: 2, + TEXT_WIDTH: 108, + TEXT_HEIGHT: 15, + FLASH_DURATION: 1500 + } + }, + type_1: { + LDPI: { + OBSTACLE_1: {x: 631, y: 2}, + OBSTACLE_2: {x: 656, y: 2}, + OBSTACLE_3: {x: 697, y: 2}, + OBSTACLE_4: {x: 754, y: 2}, + OBSTACLE_5: {x: 781, y: 2}, + OBSTACLE_6: {x: 826, y: 2}, + BACKGROUND_EL: {x: 0, y: 120}, + CLOUD: {x: 890, y: 2}, + HORIZON: {x: 2, y: 54}, + TREX: {x: 252, y: 2} + }, + HDPI: { + OBSTACLE_1: {x: 1262, y: 2}, + OBSTACLE_2: {x: 1312, y: 2}, + OBSTACLE_3: {x: 1394, y: 2}, + OBSTACLE_4: {x: 1508, y: 2}, + OBSTACLE_5: {x: 1562, y: 2}, + OBSTACLE_6: {x: 1652, y: 2}, + BACKGROUND_EL: {x: 0, y: 240}, + CLOUD: {x: 1780, y: 3}, + HORIZON: {x: 4, y: 108}, + TREX: {x: 504, y: 2} + }, + ALT_GAME_END_CONFIG: {WIDTH: 15, HEIGHT: 17, X_OFFSET: 19, Y_OFFSET: 17}, + MAX_GAP_COEFFICIENT: 0.56, + MAX_OBSTACLE_LENGTH: 1, + HAS_CLOUDS: 1, + BOTTOM_PAD: 10, + TREX: { + MAX_JUMP_HEIGHT: 50, + MIN_JUMP_HEIGHT: 50, + INITIAL_JUMP_VELOCITY: -10, + RUNNING_1: {x: 137, w: 44, h: 49, xOffset: 0}, + RUNNING_2: {x: 183, w: 44, h: 47, xOffset: 0}, + CRASHED: {x: 335, w: 44, h: 47, xOffset: 0}, + JUMPING: {x: 230, w: 59, h: 49, xOffset: 6}, + COLLISION_BOXES: [ + new CollisionBox(22, 0, 17, 16), new CollisionBox(0, 16, 32, 9), + new CollisionBox(3, 24, 27, 6), new CollisionBox(5, 30, 21, 4) + ] + }, + OBSTACLES: [ + { + type: 'OBSTACLE_1', + width: 24, + height: 60, + yPos: 106, + multipleSpeed: 4, + minGap: 70, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(0, 0, 3, 26), new CollisionBox(3, 5, 8, 31), + new CollisionBox(11, 24, 11, 10) + ] + }, + { + type: 'OBSTACLE_2', + width: 40, + height: 60, + yPos: 106, + multipleSpeed: 4, + minGap: 90, + minSpeed: 5, + collisionBoxes: [ + new CollisionBox(0, 0, 3, 26), new CollisionBox(3, 5, 24, 31), + new CollisionBox(27, 24, 11, 10) + ] + }, + { + type: 'OBSTACLE_3', + width: 57, + height: 60, + yPos: 106, + multipleSpeed: 4, + minGap: 100, + minSpeed: 7, + collisionBoxes: [ + new CollisionBox(0, 0, 3, 26), new CollisionBox(3, 5, 40, 31), + new CollisionBox(27, 43, 11, 10) + ] + }, + { + type: 'OBSTACLE_4', + width: 27, + height: 44, + yPos: 102, + multipleSpeed: 7, + minGap: 110, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(0, 0, 3, 26), new CollisionBox(3, 3, 8, 31), + new CollisionBox(11, 24, 11, 10) + ] + }, + { + type: 'OBSTACLE_5', + width: 45, + height: 44, + yPos: 102, + multipleSpeed: 7, + minGap: 120, + minSpeed: 7.5, + collisionBoxes: [ + new CollisionBox(0, 0, 4, 26), new CollisionBox(4, 3, 26, 31), + new CollisionBox(30, 30, 12, 11) + ] + }, + { + type: 'OBSTACLE_6', + width: 63, + height: 44, + yPos: 102, + multipleSpeed: 7, + minGap: 140, + minSpeed: 7.5, + collisionBoxes: [ + new CollisionBox(0, 0, 3, 26), new CollisionBox(4, 3, 44, 39), + new CollisionBox(48, 24, 12, 11) + ] + } + ], + BACKGROUND_EL: { + 'BACKGROUND_0': {HEIGHT: 93, WIDTH: 423, Y_POS: 120, X_POS: 1, OFFSET: 4} + }, + BACKGROUND_EL_CONFIG: { + MAX_BG_ELS: 1, + MAX_GAP: 600, + MIN_GAP: 600, + POS: 0, + SPEED: 0.2, + Y_POS: 125 + }, + LINES: [ + {SOURCE_X: 2, SOURCE_Y: 54, WIDTH: 600, HEIGHT: 12, YPOS: 125}, + {SOURCE_X: 2, SOURCE_Y: 84, WIDTH: 600, HEIGHT: 12, YPOS: 138} + ] + }, + type_2: { + LDPI: { + OBSTACLE_1: {x: 655, y: 2}, + BACKGROUND_EL: {x: 0, y: 60}, + CLOUD: {x: 963, y: 3}, + HORIZON: {x: 2, y: 54}, + TREX: {x: 252, y: 2} + }, + HDPI: { + OBSTACLE_1: {x: 1310, y: 2}, + BACKGROUND_EL: {x: 0, y: 120}, + CLOUD: {x: 1926, y: 3}, + HORIZON: {x: 4, y: 108}, + TREX: {x: 504, y: 2}, + }, + ALT_GAME_END_CONFIG: {WIDTH: 15, HEIGHT: 17, X_OFFSET: 19, Y_OFFSET: 18}, + MAX_GAP_COEFFICIENT: 0.56, + MAX_OBSTACLE_LENGTH: 1, + HAS_CLOUDS: 0, + BOTTOM_PAD: 10, + TREX: { + MAX_JUMP_HEIGHT: 30, + MIN_JUMP_HEIGHT: 30, + INITIAL_JUMP_VELOCITY: -19, + RUNNING_1: {x: 137, w: 44, h: 49, xOffset: 0}, + RUNNING_2: {x: 183, w: 44, h: 49, xOffset: 0}, + CRASHED: {x: 359, w: 44, h: 43, xOffset: 0}, + JUMPING: {x: 228, w: 43, h: 44, xOffset: 2.5}, + COLLISION_BOXES: [ + new CollisionBox(22, 0, 17, 16), new CollisionBox(17, 37, 7, 7), + new CollisionBox(10, 17, 19, 20) + ] + }, + OBSTACLES: [{ + type: 'OBSTACLE_1', + width: 54, + height: 54, + yPos: 90, + multipleSpeed: 4, + minGap: 70, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(0, 8, 20, 43), new CollisionBox(21, 6, 8, 42), + new CollisionBox(32, 2, 18, 49) + ] + }], + BACKGROUND_EL: { + 'BACKGROUND_0': {HEIGHT: 120, WIDTH: 89, Y_POS: 40, X_POS: 1, OFFSET: 4}, + 'BACKGROUND_1': + {HEIGHT: 108, WIDTH: 130, Y_POS: 40, X_POS: 92, OFFSET: 4}, + 'BACKGROUND_2': + {HEIGHT: 28, WIDTH: 204, Y_POS: 40, X_POS: 223, OFFSET: 4}, + }, + BACKGROUND_EL_CONFIG: { + MAX_BG_ELS: 2, + MAX_GAP: 550, + MIN_GAP: 400, + POS: 0, + SPEED: 0.5, + Y_POS: 125 + }, + LINES: [{SOURCE_X: 2, SOURCE_Y: 54, WIDTH: 600, HEIGHT: 5, YPOS: 125}] + }, + type_3: { + LDPI: { + OBSTACLE_1: {x: 611, y: 2}, + OBSTACLE_2: {x: 634, y: 2}, + OBSTACLE_3: {x: 671, y: 2}, + OBSTACLE_4: {x: 722, y: 2}, + OBSTACLE_5: {x: 762, y: 2}, + OBSTACLE_6: {x: 806, y: 2}, + BACKGROUND_EL: {x: 0, y: 65}, + CLOUD: {x: 888, y: 2}, + HORIZON: {x: 2, y: 58}, + TREX: {x: 252, y: 2} + }, + HDPI: { + OBSTACLE_1: {x: 1222, y: 2}, + OBSTACLE_2: {x: 1268, y: 2}, + OBSTACLE_3: {x: 1342, y: 2}, + OBSTACLE_4: {x: 1444, y: 2}, + OBSTACLE_5: {x: 1524, y: 2}, + OBSTACLE_6: {x: 1612, y: 2}, + BACKGROUND_EL: {x: 0, y: 130}, + CLOUD: {x: 1776, y: 3}, + HORIZON: {x: 4, y: 116}, + TREX: {x: 504, y: 2} + }, + ALT_GAME_END_CONFIG: {WIDTH: 15, HEIGHT: 17, X_OFFSET: 23, Y_OFFSET: 17}, + MAX_GAP_COEFFICIENT: 0.56, + MAX_OBSTACLE_LENGTH: 1, + BOTTOM_PAD: 10, + HAS_CLOUDS: 1, + TREX: { + MAX_JUMP_HEIGHT: 45, + MIN_JUMP_HEIGHT: 30, + INITIAL_JUMP_VELOCITY: -10, + RUNNING_1: {x: 104, w: 51, h: 57, xOffset: 0}, + RUNNING_2: {x: 156, w: 51, h: 57, xOffset: 0}, + CRASHED: {x: 309, w: 51, h: 57, xOffset: 0}, + JUMPING: {x: 208, w: 51, h: 57, xOffset: 0}, + COLLISION_BOXES: + [new CollisionBox(28, 35, 19, 11), new CollisionBox(3, 44, 26, 4)] + }, + OBSTACLES: [ + { + type: 'OBSTACLE_1', + width: 24, + height: 18, + yPos: 117, + multipleSpeed: 4, + minGap: 50, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(11, 2, 3, 2), new CollisionBox(7, 4, 11, 10), + new CollisionBox(2, 9, 5, 6) + ] + }, + { + type: 'OBSTACLE_2', + width: 40, + height: 22, + yPos: 117, + multipleSpeed: 4, + minGap: 60, + minSpeed: 4.5, + collisionBoxes: [ + new CollisionBox(11, 2, 3, 2), new CollisionBox(7, 5, 23, 10), + new CollisionBox(2, 9, 5, 6) + ] + }, + { + type: 'OBSTACLE_3', + width: 49, + height: 22, + yPos: 117, + multipleSpeed: 4, + minGap: 80, + minSpeed: 7, + collisionBoxes: [ + new CollisionBox(11, 2, 3, 2), new CollisionBox(8, 5, 39, 10), + new CollisionBox(2, 9, 5, 6) + ] + }, + { + type: 'OBSTACLE_4', + width: 37, + height: 26, + yPos: 113, + multipleSpeed: 7, + minGap: 120, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(4, 16, 5, 8), new CollisionBox(9, 12, 7, 12), + new CollisionBox(16, 5, 10, 19) + ] + }, + { + type: 'OBSTACLE_5', + width: 45, + height: 30, + yPos: 113, + multipleSpeed: 7, + minGap: 120, + minSpeed: 5.5, + collisionBoxes: [ + new CollisionBox(4, 16, 5, 8), new CollisionBox(9, 12, 7, 12), + new CollisionBox(16, 5, 10, 19), new CollisionBox(26, 14, 13, 11) + ] + }, + { + type: 'OBSTACLE_6', + width: 79, + height: 30, + yPos: 113, + multipleSpeed: 7, + minGap: 150, + minSpeed: 7, + collisionBoxes: [ + new CollisionBox(4, 16, 5, 8), new CollisionBox(9, 12, 7, 12), + new CollisionBox(16, 5, 10, 19), new CollisionBox(26, 14, 13, 11), + new CollisionBox(40, 18, 10, 6), new CollisionBox(50, 12, 10, 12), + new CollisionBox(57, 5, 10, 19) + ] + } + ], + BACKGROUND_EL: { + 'BACKGROUND_0': { + HEIGHT: 78, + OFFSET: 6, + WIDTH: 105, + X_POS: 425, + FIXED_X_POS: 0, + FIXED_Y_POS_1: 54, + FIXED_Y_POS_2: 51, + FIXED: true + } + }, + BACKGROUND_EL_CONFIG: { + MAX_BG_ELS: 1, + MAX_GAP: 550, + MIN_GAP: 400, + POS: 0, + SPEED: 0.2, + Y_POS: 125, + MS_PER_FRAME: 250 + }, + LINES: [ + {SOURCE_X: 2, SOURCE_Y: 58, WIDTH: 600, HEIGHT: 8, YPOS: 125}, + ] + }, + type_4: { + LDPI: { + OBSTACLE_1: {x: 514, y: 2}, + OBSTACLE_2: {x: 543, y: 2}, + OBSTACLE_3: {x: 599, y: 2}, + OBSTACLE_4: {x: 643, y: 2}, + BACKGROUND_EL: {x: 811, y: 2}, + CLOUD: {x: 888, y: 2}, + WALL: {x: 2, y: 54}, + HORIZON: {x: 2, y: 81}, + TREX: {x: 252, y: 2} + }, + HDPI: { + OBSTACLE_1: {x: 1028, y: 2}, + OBSTACLE_2: {x: 1086, y: 2}, + OBSTACLE_3: {x: 1198, y: 2}, + OBSTACLE_4: {x: 1286, y: 2}, + BACKGROUND_EL: {x: 1622, y: 4}, + CLOUD: {x: 1776, y: 3}, + WALL: {x: 2, y: 108}, + HORIZON: {x: 4, y: 162}, + TREX: {x: 504, y: 2} + }, + ALT_GAME_END_CONFIG: {WIDTH: 15, HEIGHT: 17, X_OFFSET: 38, Y_OFFSET: 16}, + MAX_GAP_COEFFICIENT: 0.56, + MAX_OBSTACLE_LENGTH: 1, + BOTTOM_PAD: 43, + HAS_CLOUDS: 0, + TREX: { + GRAVITY: 0.36, + MAX_JUMP_HEIGHT: 20, + MIN_JUMP_HEIGHT: 18, + INITIAL_JUMP_VELOCITY: -20, + INVERT_JUMP: 1, + RUNNING_1: {x: 0, w: 65, h: 30, xOffset: 0}, + RUNNING_2: {x: 67, w: 65, h: 30, xOffset: 0}, + CRASHED: {x: 196, w: 65, h: 30, xOffset: 0}, + JUMPING: {x: 133.5, w: 65, h: 30, xOffset: 0}, + COLLISION_BOXES: [ + new CollisionBox(17, 4, 49, 9), new CollisionBox(20, 17, 23, 4), + new CollisionBox(19, 20, 10, 7), new CollisionBox(17, 13, 42, 4) + ] + }, + OBSTACLES: [ + { + type: 'OBSTACLE_1', + width: 27, + height: 11, + yPos: 80, + multipleSpeed: 4, + minGap: 120, + minSpeed: 0, + collisionBoxes: [new CollisionBox(0, 2, 27, 8)] + }, + { + type: 'OBSTACLE_2', + width: 54, + height: 11, + yPos: 80, + multipleSpeed: 4, + minGap: 140, + minSpeed: 7, + collisionBoxes: [new CollisionBox(0, 2, 52, 8)] + }, + { + type: 'OBSTACLE_3', + width: 42, + height: 16, + yPos: 76, + multipleSpeed: 4, + minGap: 170, + minSpeed: 3, + collisionBoxes: [new CollisionBox(0, 2, 40, 14)] + } + ], + BACKGROUND_EL_CONFIG: { + SPEED: 0.5, + POS: 0, + MAX_BG_ELS: 3, + MIN_GAP: 100, + MAX_GAP: 400, + Y_POS: 100 + }, + BACKGROUND_EL: { + 'BACKGROUND_0': + {HEIGHT: 32, WIDTH: 30, Y_POS: 2, X_POS: 811, OFFSET: -65}, + 'BACKGROUND_1': + {HEIGHT: 37, WIDTH: 40, Y_POS: 2, X_POS: 842, OFFSET: -13}, + 'BACKGROUND_2': {HEIGHT: 33, WIDTH: 82, Y_POS: 2, X_POS: 727, OFFSET: -40} + }, + LINES: [ + {SOURCE_X: 2, SOURCE_Y: 81, WIDTH: 600, HEIGHT: 12, YPOS: 78}, + {SOURCE_X: 2, SOURCE_Y: 54, WIDTH: 600, HEIGHT: 12, YPOS: 56} + ] + }, + type_5: { + LDPI: { + OBSTACLE_1: {x: 458, y: 2}, + OBSTACLE_2: {x: 458, y: 2}, + BACKGROUND_EL: {x: 0, y: 0}, + CLOUD: {x: 482, y: 2}, + WALL: {x: 2, y: 54}, + HORIZON: {x: 2, y: 77}, + TREX: {x: 252, y: 2} + }, + HDPI: { + OBSTACLE_1: {x: 916, y: 2}, + OBSTACLE_2: {x: 916, y: 2}, + BACKGROUND_EL: {x: 0, y: 0}, + CLOUD: {x: 963, y: 3}, + WALL: {x: 2, y: 108}, + HORIZON: {x: 4, y: 154}, + TREX: {x: 504, y: 2} + }, + ALT_GAME_END_CONFIG: {WIDTH: 15, HEIGHT: 17, X_OFFSET: 24, Y_OFFSET: 23}, + MAX_GAP_COEFFICIENT: 2.5, + MAX_OBSTACLE_LENGTH: 1, + BOTTOM_PAD: 12, + HAS_CLOUDS: 1, + TREX: { + MAX_JUMP_HEIGHT: 30, + MIN_JUMP_HEIGHT: 30, + INITIAL_JUMP_VELOCITY: -10, + RUNNING_1: {x: 0, w: 51, h: 67, xOffset: 0}, + RUNNING_2: {x: 50, w: 51, h: 67, xOffset: 0}, + CRASHED: {x: 156, w: 51, h: 67, xOffset: 0}, + JUMPING: {x: 103, w: 54, h: 67, xOffset: 0}, + COLLISION_BOXES: [ + new CollisionBox(35, 30, 13, 9), new CollisionBox(19, 51, 22, 9), + new CollisionBox(9, 51, 9, 13), new CollisionBox(4, 27, 31, 28) + ] + }, + OBSTACLES: [{ + type: 'OBSTACLE_1', + width: 21, + height: 57, + yPos: 93, + multipleSpeed: 999, + minGap: 40, + minSpeed: 0, + collisionBoxes: [ + new CollisionBox(0, 0, 3, 41), new CollisionBox(3, 5, 14, 39), + new CollisionBox(16, 7, 4, 43) + ] + }], + BACKGROUND_EL_CONFIG: { + MAX_BG_ELS: 4, + MAX_GAP: 420, + MIN_GAP: 320, + POS: 0, + SPEED: 0.3, + Y_POS: 125 + }, + BACKGROUND_EL: { + 'BACKGROUND_0': {HEIGHT: 40, WIDTH: 170, Y_POS: 100, X_POS: 0, OFFSET: 10} + }, + LINES: [{SOURCE_X: 2, SOURCE_Y: 71, WIDTH: 600, HEIGHT: 12, YPOS: 123}] + } +}; \ No newline at end of file diff --git a/chrome-dino/scripts/offline.js b/chrome-dino/scripts/offline.js new file mode 100644 index 00000000..a9978b3d --- /dev/null +++ b/chrome-dino/scripts/offline.js @@ -0,0 +1,4196 @@ +function _isIpad() { + var isIpad = navigator.userAgent.toLowerCase().indexOf('ipad') !== -1; + + if (!isIpad && navigator.userAgent.match(/Mac/) && navigator.maxTouchPoints && navigator.maxTouchPoints > 2) { + return true; + } + + return isIpad; +} + +// Copyright (c) 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * T-Rex runner. + * @param {string} outerContainerId Outer containing element id. + * @param {!Object=} opt_config + * @constructor + * @implements {EventListener} + * @export + */ +function Runner(outerContainerId, opt_config) { + // Singleton + if (Runner.instance_) { + return Runner.instance_; + } + Runner.instance_ = this; + + this.outerContainerEl = document.querySelector(outerContainerId); + this.containerEl = null; + this.snackbarEl = null; + // A div to intercept touch events. Only set while (playing && useTouch). + this.touchController = null; + + this.config = opt_config || Object.assign(Runner.config, Runner.normalConfig); + // Logical dimensions of the container. + this.dimensions = Runner.defaultDimensions; + + this.gameType = null; + Runner.spriteDefinition = Runner.spriteDefinitionByType['original']; + + this.altGameImageSprite = null; + this.altGameModeActive = false; + this.altGameModeFlashTimer = null; + this.fadeInTimer = 0; + + this.canvas = null; + this.canvasCtx = null; + + this.tRex = null; + + this.distanceMeter = null; + this.distanceRan = 0; + + this.highestScore = window.localStorage.getItem("chrome-dino"); + this.syncHighestScore = false; + + this.time = 0; + this.runningTime = 0; + this.msPerFrame = 1000 / FPS; + this.currentSpeed = this.config.SPEED; + Runner.slowDown = false; + + this.obstacles = []; + + this.activated = false; // Whether the easter egg has been activated. + this.playing = false; // Whether the game is currently in play state. + this.crashed = false; + this.paused = false; + this.inverted = false; + this.invertTimer = 0; + this.resizeTimerId_ = null; + + this.playCount = 0; + + // Sound FX. + this.audioBuffer = null; + + /** @type {Object} */ + this.soundFx = {}; + this.generatedSoundFx = null; + + // Global web audio context for playing sounds. + this.audioContext = null; + + // Images. + this.images = {}; + this.imagesLoaded = 0; + + // Gamepad state. + this.pollingGamepads = false; + this.gamepadIndex = undefined; + this.previousGamepad = null; + + if (this.isDisabled()) { + this.setupDisabledRunner(); + } else { + if (Runner.isAltGameModeEnabled()) { + this.initAltGameType(); + Runner.gameType = this.gameType; + } + this.loadImages(); + + window['initializeEasterEggHighScore'] = + this.initializeHighScore.bind(this); + } +} + +/** + * Default game width. + * @const + */ +const DEFAULT_WIDTH = 600; + +/** + * Frames per second. + * @const + */ +const FPS = 60; + +/** @const */ +const IS_HIDPI = window.devicePixelRatio > 1; + +/** @const */ +const IS_IOS = !!window.navigator.userAgent.match(/iP(hone|ad|od)/i) && !!window.navigator.userAgent.match(/Safari/i) || + _isIpad() || /CriOS/.test(window.navigator.userAgent) || /FxiOS/.test(window.navigator.userAgent); + +/** @const */ +const IS_MOBILE = /Android/.test(window.navigator.userAgent) || IS_IOS; + +/** @const */ +const IS_RTL = document.querySelector('html').dir == 'rtl'; + +/** @const */ +const ARCADE_MODE_URL = 'chrome://dino/'; + +/** @const */ +const RESOURCE_POSTFIX = 'offline-resources-'; + +/** @const */ +const A11Y_STRINGS = { + ariaLabel: 'dinoGameA11yAriaLabel', + description: 'dinoGameA11yDescription', + gameOver: 'dinoGameA11yGameOver', + highScore: 'dinoGameA11yHighScore', + jump: 'dinoGameA11yJump', + started: 'dinoGameA11yStartGame', + speedLabel: 'dinoGameA11ySpeedToggle', +}; + +/** + * Default game configuration. + * Shared config for all versions of the game. Additional parameters are + * defined in Runner.normalConfig and Runner.slowConfig. + */ +Runner.config = { + AUDIOCUE_PROXIMITY_THRESHOLD: 190, + AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 250, + BG_CLOUD_SPEED: 0.2, + BOTTOM_PAD: 10, + // Scroll Y threshold at which the game can be activated. + CANVAS_IN_VIEW_OFFSET: -10, + CLEAR_TIME: 3000, + CLOUD_FREQUENCY: 0.5, + FADE_DURATION: 1, + FLASH_DURATION: 1000, + GAMEOVER_CLEAR_TIME: 1200, + INITIAL_JUMP_VELOCITY: 12, + INVERT_FADE_DURATION: 12000, + MAX_BLINK_COUNT: 3, + MAX_CLOUDS: 6, + MAX_OBSTACLE_LENGTH: 3, + MAX_OBSTACLE_DUPLICATION: 2, + RESOURCE_TEMPLATE_ID: 'audio-resources', + SPEED: 6, + SPEED_DROP_COEFFICIENT: 3, + ARCADE_MODE_INITIAL_TOP_POSITION: 35, + ARCADE_MODE_TOP_POSITION_PERCENT: 0.1, +}; + +Runner.normalConfig = { + ACCELERATION: 0.001, + AUDIOCUE_PROXIMITY_THRESHOLD: 190, + AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 250, + GAP_COEFFICIENT: 0.6, + INVERT_DISTANCE: 700, + MAX_SPEED: 13, + MOBILE_SPEED_COEFFICIENT: 1.2, + SPEED: 6, +}; + + +Runner.slowConfig = { + ACCELERATION: 0.0005, + AUDIOCUE_PROXIMITY_THRESHOLD: 170, + AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y: 220, + GAP_COEFFICIENT: 0.3, + INVERT_DISTANCE: 350, + MAX_SPEED: 9, + MOBILE_SPEED_COEFFICIENT: 1.5, + SPEED: 4.2, +}; + + +/** + * Default dimensions. + */ +Runner.defaultDimensions = { + WIDTH: DEFAULT_WIDTH, + HEIGHT: 150, +}; + + +/** + * CSS class names. + * @enum {string} + */ +Runner.classes = { + ARCADE_MODE: 'arcade-mode', + CANVAS: 'runner-canvas', + CONTAINER: 'runner-container', + CRASHED: 'crashed', + ICON: 'icon-offline', + INVERTED: 'inverted', + SNACKBAR: 'snackbar', + SNACKBAR_SHOW: 'snackbar-show', + TOUCH_CONTROLLER: 'controller', +}; + + +/** + * Sound FX. Reference to the ID of the audio tag on interstitial page. + * @enum {string} + */ +Runner.sounds = { + BUTTON_PRESS: 'offline-sound-press', + HIT: 'offline-sound-hit', + SCORE: 'offline-sound-reached', +}; + + +/** + * Key code mapping. + * @enum {Object} + */ +Runner.keycodes = { + JUMP: {'38': 1, '32': 1}, // Up, spacebar + DUCK: {'40': 1}, // Down + RESTART: {'13': 1}, // Enter +}; + + +/** + * Runner event names. + * @enum {string} + */ +Runner.events = { + ANIM_END: 'webkitAnimationEnd', + CLICK: 'click', + KEYDOWN: 'keydown', + KEYUP: 'keyup', + POINTERDOWN: 'pointerdown', + POINTERUP: 'pointerup', + RESIZE: 'resize', + TOUCHEND: 'touchend', + TOUCHSTART: 'touchstart', + VISIBILITY: 'visibilitychange', + BLUR: 'blur', + FOCUS: 'focus', + LOAD: 'load', + GAMEPADCONNECTED: 'gamepadconnected', +}; + +Runner.prototype = { + /** + * Initialize alternative game type. + */ + initAltGameType() { + if (GAME_TYPE.length > 0) { + this.gameType = loadTimeData && loadTimeData.valueExists('altGameType') ? + GAME_TYPE[parseInt(loadTimeData.getValue('altGameType'), 10) - 1] : + ''; + } + }, + + /** + * Whether the easter egg has been disabled. CrOS enterprise enrolled devices. + * @return {boolean} + */ + isDisabled() { + return loadTimeData && loadTimeData.valueExists('disabledEasterEgg'); + }, + + /** + * For disabled instances, set up a snackbar with the disabled message. + */ + setupDisabledRunner() { + this.containerEl = document.createElement('div'); + this.containerEl.className = Runner.classes.SNACKBAR; + this.containerEl.textContent = loadTimeData.getValue('disabledEasterEgg'); + this.outerContainerEl.appendChild(this.containerEl); + + // Show notification when the activation key is pressed. + document.addEventListener(Runner.events.KEYDOWN, function(e) { + if (Runner.keycodes.JUMP[e.keyCode]) { + this.containerEl.classList.add(Runner.classes.SNACKBAR_SHOW); + document.querySelector('.icon').classList.add('icon-disabled'); + } + }.bind(this)); + }, + + /** + * Setting individual settings for debugging. + * @param {string} setting + * @param {number|string} value + */ + updateConfigSetting(setting, value) { + if (setting in this.config && value !== undefined) { + this.config[setting] = value; + + switch (setting) { + case 'GRAVITY': + case 'MIN_JUMP_HEIGHT': + case 'SPEED_DROP_COEFFICIENT': + this.tRex.config[setting] = value; + break; + case 'INITIAL_JUMP_VELOCITY': + this.tRex.setJumpVelocity(value); + break; + case 'SPEED': + this.setSpeed(/** @type {number} */ (value)); + break; + } + } + }, + + /** + * Creates an on page image element from the base 64 encoded string source. + * @param {string} resourceName Name in data object, + * @return {HTMLImageElement} The created element. + */ + createImageElement(resourceName) { + const imgSrc = loadTimeData && loadTimeData.valueExists(resourceName) ? + loadTimeData.getString(resourceName) : + null; + + if (imgSrc) { + const el = + /** @type {HTMLImageElement} */ (document.createElement('img')); + el.id = resourceName; + el.src = imgSrc; + document.getElementById('offline-resources').appendChild(el); + return el; + } + return null; + }, + + /** + * Cache the appropriate image sprite from the page and get the sprite sheet + * definition. + */ + loadImages() { + let scale = '1x'; + this.spriteDef = Runner.spriteDefinition.LDPI; + if (IS_HIDPI) { + scale = '2x'; + this.spriteDef = Runner.spriteDefinition.HDPI; + } + + Runner.imageSprite = /** @type {HTMLImageElement} */ + (document.getElementById(RESOURCE_POSTFIX + scale)); + + if (this.gameType) { + Runner.altGameImageSprite = /** @type {HTMLImageElement} */ + (this.createImageElement('altGameSpecificImage' + scale)); + Runner.altCommonImageSprite = /** @type {HTMLImageElement} */ + (this.createImageElement('altGameCommonImage' + scale)); + } + Runner.origImageSprite = Runner.imageSprite; + + // Disable the alt game mode if the sprites can't be loaded. + if (!Runner.altGameImageSprite || !Runner.altCommonImageSprite) { + Runner.isAltGameModeEnabled = () => false; + this.altGameModeActive = false; + } + + if (Runner.imageSprite.complete) { + this.init(); + } else { + // If the images are not yet loaded, add a listener. + Runner.imageSprite.addEventListener(Runner.events.LOAD, + this.init.bind(this)); + } + }, + + /** + * Load and decode base 64 encoded sounds. + */ + loadSounds() { + if (!IS_IOS) { + this.audioContext = new AudioContext(); + + const resourceTemplate = + document.getElementById(this.config.RESOURCE_TEMPLATE_ID).content; + + for (const sound in Runner.sounds) { + let soundSrc = + resourceTemplate.getElementById(Runner.sounds[sound]).src; + soundSrc = soundSrc.substr(soundSrc.indexOf(',') + 1); + const buffer = decodeBase64ToArrayBuffer(soundSrc); + + // Async, so no guarantee of order in array. + this.audioContext.decodeAudioData(buffer, function(index, audioData) { + this.soundFx[index] = audioData; + }.bind(this, sound)); + } + } + }, + + /** + * Sets the game speed. Adjust the speed accordingly if on a smaller screen. + * @param {number=} opt_speed + */ + setSpeed(opt_speed) { + const speed = opt_speed || this.currentSpeed; + + // Reduce the speed on smaller mobile screens. + if (this.dimensions.WIDTH < DEFAULT_WIDTH) { + const mobileSpeed = Runner.slowDown ? speed : + speed * this.dimensions.WIDTH / + DEFAULT_WIDTH * this.config.MOBILE_SPEED_COEFFICIENT; + this.currentSpeed = mobileSpeed > speed ? speed : mobileSpeed; + } else if (opt_speed) { + this.currentSpeed = opt_speed; + } + }, + + /** + * Game initialiser. + */ + init() { + // Hide the static icon. + document.querySelector('.' + Runner.classes.ICON).style.visibility = + 'hidden'; + + this.adjustDimensions(); + this.setSpeed(); + + const ariaLabel = getA11yString(A11Y_STRINGS.ariaLabel); + this.containerEl = document.createElement('div'); + this.containerEl.setAttribute('role', IS_MOBILE ? 'button' : 'application'); + this.containerEl.setAttribute('tabindex', '0'); + this.containerEl.setAttribute('title', ariaLabel); + + this.containerEl.className = Runner.classes.CONTAINER; + + // Player canvas container. + this.canvas = createCanvas(this.containerEl, this.dimensions.WIDTH, + this.dimensions.HEIGHT); + + // Live region for game status updates. + this.a11yStatusEl = document.createElement('span'); + this.a11yStatusEl.className = 'offline-runner-live-region'; + this.a11yStatusEl.setAttribute('aria-live', 'assertive'); + this.a11yStatusEl.textContent = ''; + Runner.a11yStatusEl = this.a11yStatusEl; + + // Add checkbox to slow down the game. + this.slowSpeedCheckboxLabel = document.createElement('label'); + this.slowSpeedCheckboxLabel.className = 'slow-speed-option hidden'; + this.slowSpeedCheckboxLabel.textContent = + getA11yString(A11Y_STRINGS.speedLabel); + + this.slowSpeedCheckbox = document.createElement('input'); + this.slowSpeedCheckbox.setAttribute('type', 'checkbox'); + this.slowSpeedCheckbox.setAttribute( + 'title', getA11yString(A11Y_STRINGS.speedLabel)); + this.slowSpeedCheckbox.setAttribute('tabindex', '0'); + this.slowSpeedCheckbox.setAttribute('checked', 'checked'); + + this.slowSpeedToggleEl = document.createElement('span'); + this.slowSpeedToggleEl.className = 'slow-speed-toggle'; + + this.slowSpeedCheckboxLabel.appendChild(this.slowSpeedCheckbox); + this.slowSpeedCheckboxLabel.appendChild(this.slowSpeedToggleEl); + + if (IS_IOS) { + this.outerContainerEl.appendChild(this.a11yStatusEl); + } else { + this.containerEl.appendChild(this.a11yStatusEl); + } + + announcePhrase(getA11yString(A11Y_STRINGS.description)); + + this.generatedSoundFx = new GeneratedSoundFx(); + + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d')); + this.canvasCtx.fillStyle = '#f7f7f7'; + this.canvasCtx.fill(); + Runner.updateCanvasScaling(this.canvas); + + // Horizon contains clouds, obstacles and the ground. + this.horizon = new Horizon(this.canvas, this.spriteDef, this.dimensions, + this.config.GAP_COEFFICIENT); + + // Distance meter + this.distanceMeter = new DistanceMeter(this.canvas, + this.spriteDef.TEXT_SPRITE, this.dimensions.WIDTH); + + // Draw t-rex + this.tRex = new Trex(this.canvas, this.spriteDef.TREX); + + this.outerContainerEl.appendChild(this.containerEl); + this.outerContainerEl.appendChild(this.slowSpeedCheckboxLabel); + + this.startListening(); + this.update(); + + window.addEventListener(Runner.events.RESIZE, + this.debounceResize.bind(this)); + + // Handle dark mode + const darkModeMediaQuery = + window.matchMedia('(prefers-color-scheme: dark)'); + this.isDarkMode = darkModeMediaQuery && darkModeMediaQuery.matches; + darkModeMediaQuery.addListener((e) => { + this.isDarkMode = e.matches; + }); + }, + + /** + * Create the touch controller. A div that covers whole screen. + */ + createTouchController() { + this.touchController = document.createElement('div'); + this.touchController.className = Runner.classes.TOUCH_CONTROLLER; + this.touchController.addEventListener(Runner.events.TOUCHSTART, this); + this.touchController.addEventListener(Runner.events.TOUCHEND, this); + this.outerContainerEl.appendChild(this.touchController); + }, + + /** + * Debounce the resize event. + */ + debounceResize() { + if (!this.resizeTimerId_) { + this.resizeTimerId_ = + setInterval(this.adjustDimensions.bind(this), 250); + } + }, + + /** + * Adjust game space dimensions on resize. + */ + adjustDimensions() { + clearInterval(this.resizeTimerId_); + this.resizeTimerId_ = null; + + const boxStyles = window.getComputedStyle(this.outerContainerEl); + const padding = Number(boxStyles.paddingLeft.substr(0, + boxStyles.paddingLeft.length - 2)); + + this.dimensions.WIDTH = this.outerContainerEl.offsetWidth - padding * 2; + if (this.isArcadeMode()) { + this.dimensions.WIDTH = Math.min(DEFAULT_WIDTH, this.dimensions.WIDTH); + if (this.activated) { + this.setArcadeModeContainerScale(); + } + } + + // Redraw the elements back onto the canvas. + if (this.canvas) { + this.canvas.width = this.dimensions.WIDTH; + this.canvas.height = this.dimensions.HEIGHT; + + Runner.updateCanvasScaling(this.canvas); + + this.distanceMeter.calcXPos(this.dimensions.WIDTH); + this.clearCanvas(); + this.horizon.update(0, 0, true); + this.tRex.update(0); + + // Outer container and distance meter. + if (this.playing || this.crashed || this.paused) { + this.containerEl.style.width = this.dimensions.WIDTH + 'px'; + this.containerEl.style.height = this.dimensions.HEIGHT + 'px'; + this.distanceMeter.update(0, Math.ceil(this.distanceRan)); + this.stop(); + } else { + this.tRex.draw(0, 0); + } + + // Game over panel. + if (this.crashed && this.gameOverPanel) { + this.gameOverPanel.updateDimensions(this.dimensions.WIDTH); + this.gameOverPanel.draw(this.altGameModeActive, this.tRex); + } + } + }, + + /** + * Play the game intro. + * Canvas container width expands out to the full width. + */ + playIntro() { + if (!this.activated && !this.crashed) { + this.playingIntro = true; + this.tRex.playingIntro = true; + if (window.localStorage.getItem("chrome-dino") !== null) { + this.distanceMeter.setHighScore(window.localStorage.getItem("chrome-dino")); + } + + // CSS animation definition. + const keyframes = '@-webkit-keyframes intro { ' + + 'from { width:' + Trex.config.WIDTH + 'px }' + + 'to { width: ' + this.dimensions.WIDTH + 'px }' + + '}'; + // document.styleSheets[0].insertRule(keyframes, 0); + + // create a style sheet to put the keyframe rule in + // and then place the style sheet in the html head + var sheet = document.createElement('style'); + sheet.innerHTML = keyframes; + document.head.appendChild(sheet); + + this.containerEl.addEventListener(Runner.events.ANIM_END, + this.startGame.bind(this)); + + this.containerEl.style.webkitAnimation = 'intro .4s ease-out 1 both'; + this.containerEl.style.width = this.dimensions.WIDTH + 'px'; + + this.setPlayStatus(true); + this.activated = true; + } else if (this.crashed) { + this.restart(); + } + }, + + + /** + * Update the game status to started. + */ + startGame() { + if (this.isArcadeMode()) { + this.setArcadeMode(); + } + this.toggleSpeed(); + this.runningTime = 0; + this.playingIntro = false; + this.tRex.playingIntro = false; + this.containerEl.style.webkitAnimation = ''; + this.playCount++; + this.generatedSoundFx.background(); + announcePhrase(getA11yString(A11Y_STRINGS.started)); + + if (Runner.audioCues) { + this.containerEl.setAttribute('title', getA11yString(A11Y_STRINGS.jump)); + } + + // Handle tabbing off the page. Pause the current game. + document.addEventListener(Runner.events.VISIBILITY, + this.onVisibilityChange.bind(this)); + + window.addEventListener(Runner.events.BLUR, + this.onVisibilityChange.bind(this)); + + window.addEventListener(Runner.events.FOCUS, + this.onVisibilityChange.bind(this)); + }, + + clearCanvas() { + this.canvasCtx.clearRect(0, 0, this.dimensions.WIDTH, + this.dimensions.HEIGHT); + }, + + /** + * Checks whether the canvas area is in the viewport of the browser + * through the current scroll position. + * @return boolean. + */ + isCanvasInView() { + return this.containerEl.getBoundingClientRect().top > + Runner.config.CANVAS_IN_VIEW_OFFSET; + }, + + /** + * Enable the alt game mode. Switching out the sprites. + */ + enableAltGameMode() { + Runner.imageSprite = Runner.altGameImageSprite; + Runner.spriteDefinition = Runner.spriteDefinitionByType[Runner.gameType]; + + if (IS_HIDPI) { + this.spriteDef = Runner.spriteDefinition.HDPI; + } else { + this.spriteDef = Runner.spriteDefinition.LDPI; + } + + this.altGameModeActive = true; + this.tRex.enableAltGameMode(this.spriteDef.TREX); + this.horizon.enableAltGameMode(this.spriteDef); + this.generatedSoundFx.background(); + }, + + /** + * Update the game frame and schedules the next one. + */ + update() { + this.updatePending = false; + + const now = getTimeStamp(); + let deltaTime = now - (this.time || now); + + // Flashing when switching game modes. + if (this.altGameModeFlashTimer < 0 || this.altGameModeFlashTimer === 0) { + this.altGameModeFlashTimer = null; + this.tRex.setFlashing(false); + this.enableAltGameMode(); + } else if (this.altGameModeFlashTimer > 0) { + this.altGameModeFlashTimer -= deltaTime; + this.tRex.update(deltaTime); + deltaTime = 0; + } + + this.time = now; + + if (this.playing) { + this.clearCanvas(); + + // Additional fade in - Prevents jump when switching sprites + if (this.altGameModeActive && + this.fadeInTimer <= this.config.FADE_DURATION) { + this.fadeInTimer += deltaTime / 1000; + this.canvasCtx.globalAlpha = this.fadeInTimer; + } else { + this.canvasCtx.globalAlpha = 1; + } + + if (this.tRex.jumping) { + this.tRex.updateJump(deltaTime); + } + + this.runningTime += deltaTime; + const hasObstacles = this.runningTime > this.config.CLEAR_TIME; + + // First jump triggers the intro. + if (this.tRex.jumpCount === 1 && !this.playingIntro) { + this.playIntro(); + } + + // The horizon doesn't move until the intro is over. + if (this.playingIntro) { + this.horizon.update(0, this.currentSpeed, hasObstacles); + } else if (!this.crashed) { + const showNightMode = this.isDarkMode ^ this.inverted; + deltaTime = !this.activated ? 0 : deltaTime; + this.horizon.update( + deltaTime, this.currentSpeed, hasObstacles, showNightMode); + } + + // Check for collisions. + let collision = hasObstacles && + checkForCollision(this.horizon.obstacles[0], this.tRex); + + // For a11y, audio cues. + if (Runner.audioCues && hasObstacles) { + const jumpObstacle = + this.horizon.obstacles[0].typeConfig.type != 'COLLECTABLE'; + + if (!this.horizon.obstacles[0].jumpAlerted) { + const threshold = Runner.isMobileMouseInput ? + Runner.config.AUDIOCUE_PROXIMITY_THRESHOLD_MOBILE_A11Y : + Runner.config.AUDIOCUE_PROXIMITY_THRESHOLD; + const adjProximityThreshold = threshold + + (threshold * Math.log10(this.currentSpeed / Runner.config.SPEED)); + + if (this.horizon.obstacles[0].xPos < adjProximityThreshold) { + if (jumpObstacle) { + this.generatedSoundFx.jump(); + } + this.horizon.obstacles[0].jumpAlerted = true; + } + } + } + + // Activated alt game mode. + if (Runner.isAltGameModeEnabled() && collision && + this.horizon.obstacles[0].typeConfig.type == 'COLLECTABLE') { + this.horizon.removeFirstObstacle(); + this.tRex.setFlashing(true); + collision = false; + this.altGameModeFlashTimer = this.config.FLASH_DURATION; + this.runningTime = 0; + this.generatedSoundFx.collect(); + } + + if (!collision) { + this.distanceRan += this.currentSpeed * deltaTime / this.msPerFrame; + + if (this.currentSpeed < this.config.MAX_SPEED) { + this.currentSpeed += this.config.ACCELERATION; + } + } else { + this.gameOver(); + } + + const playAchievementSound = this.distanceMeter.update(deltaTime, + Math.ceil(this.distanceRan)); + + if (!Runner.audioCues && playAchievementSound) { + this.playSound(this.soundFx.SCORE); + } + + // Night mode. + if (!Runner.isAltGameModeEnabled()) { + if (this.invertTimer > this.config.INVERT_FADE_DURATION) { + this.invertTimer = 0; + this.invertTrigger = false; + this.invert(false); + } else if (this.invertTimer) { + this.invertTimer += deltaTime; + } else { + const actualDistance = + this.distanceMeter.getActualDistance(Math.ceil(this.distanceRan)); + + if (actualDistance > 0) { + this.invertTrigger = + !(actualDistance % this.config.INVERT_DISTANCE); + + if (this.invertTrigger && this.invertTimer === 0) { + this.invertTimer += deltaTime; + this.invert(false); + } + } + } + } + } + + if (this.playing || (!this.activated && + this.tRex.blinkCount < Runner.config.MAX_BLINK_COUNT)) { + this.tRex.update(deltaTime); + this.scheduleNextUpdate(); + } + }, + + /** + * Event handler. + * @param {Event} e + */ + handleEvent(e) { + return (function(evtType, events) { + switch (evtType) { + case events.KEYDOWN: + case events.TOUCHSTART: + case events.POINTERDOWN: + this.onKeyDown(e); + break; + case events.KEYUP: + case events.TOUCHEND: + case events.POINTERUP: + this.onKeyUp(e); + break; + case events.GAMEPADCONNECTED: + this.onGamepadConnected(e); + break; + } + }.bind(this))(e.type, Runner.events); + }, + + /** + * Initialize audio cues if activated by focus on the canvas element. + * @param {Event} e + */ + handleCanvasKeyPress(e) { + if (!this.activated && !Runner.audioCues) { + this.toggleSpeed(); + Runner.audioCues = true; + this.generatedSoundFx.init(); + Runner.generatedSoundFx = this.generatedSoundFx; + Runner.config.CLEAR_TIME *= 1.2; + } else if (e.keyCode && Runner.keycodes.JUMP[e.keyCode]) { + this.onKeyDown(e); + } + }, + + /** + * Prevent space key press from scrolling. + * @param {Event} e + */ + preventScrolling(e) { + if (e.keyCode === 32) { + e.preventDefault(); + } + }, + + /** + * Toggle speed setting if toggle is shown. + */ + toggleSpeed() { + if (Runner.audioCues) { + const speedChange = Runner.slowDown != this.slowSpeedCheckbox.checked; + + if (speedChange) { + Runner.slowDown = this.slowSpeedCheckbox.checked; + const updatedConfig = + Runner.slowDown ? Runner.slowConfig : Runner.normalConfig; + + Runner.config = Object.assign(Runner.config, updatedConfig); + this.currentSpeed = updatedConfig.SPEED; + this.tRex.enableSlowConfig(); + this.horizon.adjustObstacleSpeed(); + } + if (this.playing) { + this.disableSpeedToggle(true); + } + } + }, + + /** + * Show the speed toggle. + * From focus event or when audio cues are activated. + * @param {Event=} e + */ + showSpeedToggle(e) { + const isFocusEvent = e && e.type == 'focus'; + if (Runner.audioCues || isFocusEvent) { + this.slowSpeedCheckboxLabel.classList.toggle( + HIDDEN_CLASS, isFocusEvent ? false : !this.crashed); + } + }, + + /** + * Disable the speed toggle. + * @param {boolean} disable + */ + disableSpeedToggle(disable) { + if (disable) { + this.slowSpeedCheckbox.setAttribute('disabled', 'disabled'); + } else { + this.slowSpeedCheckbox.removeAttribute('disabled'); + } + }, + + /** + * Bind relevant key / mouse / touch listeners. + */ + startListening() { + // A11y keyboard / screen reader activation. + this.containerEl.addEventListener( + Runner.events.KEYDOWN, this.handleCanvasKeyPress.bind(this)); + if (!IS_MOBILE) { + this.containerEl.addEventListener( + Runner.events.FOCUS, this.showSpeedToggle.bind(this)); + } + this.canvas.addEventListener( + Runner.events.KEYDOWN, this.preventScrolling.bind(this)); + this.canvas.addEventListener( + Runner.events.KEYUP, this.preventScrolling.bind(this)); + + // Keys. + document.addEventListener(Runner.events.KEYDOWN, this); + document.addEventListener(Runner.events.KEYUP, this); + + // Touch / pointer. + this.containerEl.addEventListener(Runner.events.TOUCHSTART, this); + document.addEventListener(Runner.events.POINTERDOWN, this); + document.addEventListener(Runner.events.POINTERUP, this); + + if (this.isArcadeMode()) { + // Gamepad + window.addEventListener(Runner.events.GAMEPADCONNECTED, this); + } + }, + + /** + * Remove all listeners. + */ + stopListening() { + document.removeEventListener(Runner.events.KEYDOWN, this); + document.removeEventListener(Runner.events.KEYUP, this); + + if (this.touchController) { + this.touchController.removeEventListener(Runner.events.TOUCHSTART, this); + this.touchController.removeEventListener(Runner.events.TOUCHEND, this); + } + + this.containerEl.removeEventListener(Runner.events.TOUCHSTART, this); + document.removeEventListener(Runner.events.POINTERDOWN, this); + document.removeEventListener(Runner.events.POINTERUP, this); + + if (this.isArcadeMode()) { + window.removeEventListener(Runner.events.GAMEPADCONNECTED, this); + } + }, + + /** + * Process keydown. + * @param {Event} e + */ + onKeyDown(e) { + // Prevent native page scrolling whilst tapping on mobile. + if (IS_MOBILE && this.playing) { + e.preventDefault(); + } + + if (this.isCanvasInView()) { + // Allow toggling of speed toggle. + if (Runner.keycodes.JUMP[e.keyCode] && + e.target == this.slowSpeedCheckbox) { + return; + } + + if (!this.crashed && !this.paused) { + // For a11y, screen reader activation. + const isMobileMouseInput = IS_MOBILE && + e.type === Runner.events.POINTERDOWN && + e.pointerType == 'mouse' && e.target == this.containerEl || + (IS_IOS && e.pointerType == 'touch' && + document.activeElement == this.containerEl); + + if (Runner.keycodes.JUMP[e.keyCode] || + e.type === Runner.events.TOUCHSTART || isMobileMouseInput || + (Runner.keycodes.DUCK[e.keyCode] && this.altGameModeActive)) { + e.preventDefault(); + // Starting the game for the first time. + if (!this.playing) { + // Started by touch so create a touch controller. + if (!this.touchController && e.type === Runner.events.TOUCHSTART) { + this.createTouchController(); + } + + if (isMobileMouseInput) { + this.handleCanvasKeyPress(e); + } + this.loadSounds(); + this.setPlayStatus(true); + this.update(); + if (window.errorPageController) { + errorPageController.trackEasterEgg(); + } + } + // Start jump. + if (!this.tRex.jumping && !this.tRex.ducking) { + if (Runner.audioCues) { + this.generatedSoundFx.cancelFootSteps(); + } else { + this.playSound(this.soundFx.BUTTON_PRESS); + } + this.tRex.startJump(this.currentSpeed); + } + // Ducking is disabled on alt game modes. + } else if ( + !this.altGameModeActive && this.playing && + Runner.keycodes.DUCK[e.keyCode]) { + e.preventDefault(); + if (this.tRex.jumping) { + // Speed drop, activated only when jump key is not pressed. + this.tRex.setSpeedDrop(); + } else if (!this.tRex.jumping && !this.tRex.ducking) { + // Duck. + this.tRex.setDuck(true); + } + } + } + } + }, + + /** + * Process key up. + * @param {Event} e + */ + onKeyUp(e) { + const keyCode = String(e.keyCode); + const isjumpKey = Runner.keycodes.JUMP[keyCode] || + e.type === Runner.events.TOUCHEND || e.type === Runner.events.POINTERUP; + + if (this.isRunning() && isjumpKey) { + this.tRex.endJump(); + } else if (Runner.keycodes.DUCK[keyCode]) { + this.tRex.speedDrop = false; + this.tRex.setDuck(false); + } else if (this.crashed) { + // Check that enough time has elapsed before allowing jump key to restart. + const deltaTime = getTimeStamp() - this.time; + + if (this.isCanvasInView() && + (Runner.keycodes.RESTART[keyCode] || this.isLeftClickOnCanvas(e) || + (deltaTime >= this.config.GAMEOVER_CLEAR_TIME && + Runner.keycodes.JUMP[keyCode]))) { + this.handleGameOverClicks(e); + } + } else if (this.paused && isjumpKey) { + // Reset the jump state + this.tRex.reset(); + this.play(); + } + }, + + /** + * Process gamepad connected event. + * @param {Event} e + */ + onGamepadConnected(e) { + if (!this.pollingGamepads) { + this.pollGamepadState(); + } + }, + + /** + * rAF loop for gamepad polling. + */ + pollGamepadState() { + const gamepads = navigator.getGamepads(); + this.pollActiveGamepad(gamepads); + + this.pollingGamepads = true; + requestAnimationFrame(this.pollGamepadState.bind(this)); + }, + + /** + * Polls for a gamepad with the jump button pressed. If one is found this + * becomes the "active" gamepad and all others are ignored. + * @param {!Array} gamepads + */ + pollForActiveGamepad(gamepads) { + for (let i = 0; i < gamepads.length; ++i) { + if (gamepads[i] && gamepads[i].buttons.length > 0 && + gamepads[i].buttons[0].pressed) { + this.gamepadIndex = i; + this.pollActiveGamepad(gamepads); + return; + } + } + }, + + /** + * Polls the chosen gamepad for button presses and generates KeyboardEvents + * to integrate with the rest of the game logic. + * @param {!Array} gamepads + */ + pollActiveGamepad(gamepads) { + if (this.gamepadIndex === undefined) { + this.pollForActiveGamepad(gamepads); + return; + } + + const gamepad = gamepads[this.gamepadIndex]; + if (!gamepad) { + this.gamepadIndex = undefined; + this.pollForActiveGamepad(gamepads); + return; + } + + // The gamepad specification defines the typical mapping of physical buttons + // to button indicies: https://w3c.github.io/gamepad/#remapping + this.pollGamepadButton(gamepad, 0, 38); // Jump + if (gamepad.buttons.length >= 2) { + this.pollGamepadButton(gamepad, 1, 40); // Duck + } + if (gamepad.buttons.length >= 10) { + this.pollGamepadButton(gamepad, 9, 13); // Restart + } + + this.previousGamepad = gamepad; + }, + + /** + * Generates a key event based on a gamepad button. + * @param {!Gamepad} gamepad + * @param {number} buttonIndex + * @param {number} keyCode + */ + pollGamepadButton(gamepad, buttonIndex, keyCode) { + const state = gamepad.buttons[buttonIndex].pressed; + let previousState = false; + if (this.previousGamepad) { + previousState = this.previousGamepad.buttons[buttonIndex].pressed; + } + // Generate key events on the rising and falling edge of a button press. + if (state !== previousState) { + const e = new KeyboardEvent(state ? Runner.events.KEYDOWN + : Runner.events.KEYUP, + { keyCode: keyCode }); + document.dispatchEvent(e); + } + }, + + /** + * Handle interactions on the game over screen state. + * A user is able to tap the high score twice to reset it. + * @param {Event} e + */ + handleGameOverClicks(e) { + if (e.target != this.slowSpeedCheckbox) { + e.preventDefault(); + if (this.distanceMeter.hasClickedOnHighScore(e) && this.highestScore) { + if (this.distanceMeter.isHighScoreFlashing()) { + // Subsequent click, reset the high score. + this.saveHighScore(0, true); + this.distanceMeter.resetHighScore(); + this.distanceMeter.setHighScore(window.localStorage.removeItem('chrome-dino')); + } else { + // First click, flash the high score. + this.distanceMeter.startHighScoreFlashing(); + } + } else { + this.distanceMeter.cancelHighScoreFlashing(); + this.restart(); + } + } + }, + + /** + * Returns whether the event was a left click on canvas. + * On Windows right click is registered as a click. + * @param {Event} e + * @return {boolean} + */ + isLeftClickOnCanvas(e) { + return e.button != null && e.button < 2 && + e.type === Runner.events.POINTERUP && + (e.target === this.canvas || + (IS_MOBILE && Runner.audioCues && e.target === this.containerEl)); + }, + + /** + * RequestAnimationFrame wrapper. + */ + scheduleNextUpdate() { + if (!this.updatePending) { + this.updatePending = true; + this.raqId = requestAnimationFrame(this.update.bind(this)); + } + }, + + /** + * Whether the game is running. + * @return {boolean} + */ + isRunning() { + return !!this.raqId; + }, + + /** + * Set the initial high score as stored in the user's profile. + * @param {number} highScore + */ + initializeHighScore(highScore) { + this.syncHighestScore = true; + highScore = Math.ceil(highScore); + if (highScore < this.highestScore) { + if (window.errorPageController) { + errorPageController.updateEasterEggHighScore(this.highestScore); + } + return; + } + this.highestScore = highScore; + this.distanceMeter.setHighScore(this.highestScore); + }, + + /** + * Sets the current high score and saves to the profile if available. + * @param {number} distanceRan Total distance ran. + * @param {boolean=} opt_resetScore Whether to reset the score. + */ + saveHighScore(distanceRan, opt_resetScore) { + this.highestScore = Math.ceil(distanceRan); + this.distanceMeter.setHighScore(this.highestScore); + + // Store the new high score in the profile. + if (this.syncHighestScore && window.errorPageController) { + if (opt_resetScore) { + errorPageController.resetEasterEggHighScore(); + } else { + errorPageController.updateEasterEggHighScore(this.highestScore); + } + } + }, + + /** + * Game over state. + */ + gameOver() { + this.playSound(this.soundFx.HIT); + vibrate(200); + + this.stop(); + this.crashed = true; + this.distanceMeter.achievement = false; + + this.tRex.update(100, Trex.status.CRASHED); + + // Game over panel. + if (!this.gameOverPanel) { + const origSpriteDef = IS_HIDPI ? + Runner.spriteDefinitionByType.original.HDPI : + Runner.spriteDefinitionByType.original.LDPI; + + if (this.canvas) { + if (Runner.isAltGameModeEnabled) { + this.gameOverPanel = new GameOverPanel( + this.canvas, origSpriteDef.TEXT_SPRITE, origSpriteDef.RESTART, + this.dimensions, origSpriteDef.ALT_GAME_END, + this.altGameModeActive); + } else { + this.gameOverPanel = new GameOverPanel( + this.canvas, origSpriteDef.TEXT_SPRITE, origSpriteDef.RESTART, + this.dimensions); + } + } + } + + this.gameOverPanel.draw(this.altGameModeActive, this.tRex); + + // Update the high score. + if (this.distanceRan > this.highestScore) { + this.saveHighScore(this.distanceRan); + window.localStorage.setItem('chrome-dino', this.distanceRan); + } + + // Reset the time clock. + this.time = getTimeStamp(); + + if (Runner.audioCues) { + this.generatedSoundFx.stopAll(); + announcePhrase( + getA11yString(A11Y_STRINGS.gameOver) + .replace( + '$1', + this.distanceMeter.getActualDistance(this.distanceRan) + .toString()) + + ' ' + + getA11yString(A11Y_STRINGS.highScore) + .replace( + '$1', + + this.distanceMeter.getActualDistance(this.highestScore) + .toString())); + this.containerEl.setAttribute( + 'title', getA11yString(A11Y_STRINGS.ariaLabel)); + } + this.showSpeedToggle(); + this.disableSpeedToggle(false); + }, + + stop() { + this.setPlayStatus(false); + this.paused = true; + cancelAnimationFrame(this.raqId); + this.raqId = 0; + this.generatedSoundFx.stopAll(); + }, + + play() { + if (!this.crashed) { + this.setPlayStatus(true); + this.paused = false; + this.tRex.update(0, Trex.status.RUNNING); + this.time = getTimeStamp(); + this.update(); + this.generatedSoundFx.background(); + } + }, + + restart() { + if (!this.raqId) { + this.playCount++; + this.runningTime = 0; + this.setPlayStatus(true); + this.toggleSpeed(); + this.paused = false; + this.crashed = false; + this.distanceRan = 0; + this.setSpeed(this.config.SPEED); + this.time = getTimeStamp(); + this.containerEl.classList.remove(Runner.classes.CRASHED); + this.clearCanvas(); + this.distanceMeter.reset(); + this.horizon.reset(); + this.tRex.reset(); + this.playSound(this.soundFx.BUTTON_PRESS); + this.invert(true); + this.flashTimer = null; + this.update(); + this.gameOverPanel.reset(); + this.generatedSoundFx.background(); + this.containerEl.setAttribute('title', getA11yString(A11Y_STRINGS.jump)); + announcePhrase(getA11yString(A11Y_STRINGS.started)); + } + }, + + setPlayStatus(isPlaying) { + if (this.touchController) { + this.touchController.classList.toggle(HIDDEN_CLASS, !isPlaying); + } + this.playing = isPlaying; + }, + + /** + * Whether the game should go into arcade mode. + * @return {boolean} + */ + isArcadeMode() { + // In RTL languages the title is wrapped with the left to right mark + // control characters ‪ and ‬ but are invisible. + /* return IS_RTL ? document.title.indexOf(ARCADE_MODE_URL) == 1 : + document.title === ARCADE_MODE_URL; */ + return true; + }, + + /** + * Hides offline messaging for a fullscreen game only experience. + */ + setArcadeMode() { + document.body.classList.add(Runner.classes.ARCADE_MODE); + this.setArcadeModeContainerScale(); + }, + + /** + * Sets the scaling for arcade mode. + */ + setArcadeModeContainerScale() { + const windowHeight = window.innerHeight; + const scaleHeight = windowHeight / this.dimensions.HEIGHT; + const scaleWidth = window.innerWidth / this.dimensions.WIDTH; + const scale = Math.max(1, Math.min(scaleHeight, scaleWidth)); + const scaledCanvasHeight = this.dimensions.HEIGHT * scale; + // Positions the game container at 10% of the available vertical window + // height minus the game container height. + const translateY = Math.ceil(Math.max(0, (windowHeight - scaledCanvasHeight - + Runner.config.ARCADE_MODE_INITIAL_TOP_POSITION) * + Runner.config.ARCADE_MODE_TOP_POSITION_PERCENT)) * + window.devicePixelRatio; + + const cssScale = IS_RTL ? -scale + ',' + scale : scale; + this.containerEl.style.transform = + 'scale(' + cssScale + ') translateY(' + translateY + 'px)'; + }, + + /** + * Pause the game if the tab is not in focus. + */ + onVisibilityChange(e) { + if (document.hidden || document.webkitHidden || e.type === 'blur' || + document.visibilityState !== 'visible') { + this.stop(); + } else if (!this.crashed) { + this.tRex.reset(); + this.play(); + } + }, + + /** + * Play a sound. + * @param {AudioBuffer} soundBuffer + */ + playSound(soundBuffer) { + if (soundBuffer) { + const sourceNode = this.audioContext.createBufferSource(); + sourceNode.buffer = soundBuffer; + sourceNode.connect(this.audioContext.destination); + sourceNode.start(0); + } + }, + + /** + * Inverts the current page / canvas colors. + * @param {boolean} reset Whether to reset colors. + */ + invert(reset) { + const htmlEl = document.firstElementChild; + + if (reset) { + htmlEl.classList.toggle(Runner.classes.INVERTED, + false); + this.invertTimer = 0; + this.inverted = false; + } else { + this.inverted = htmlEl.classList.toggle( + Runner.classes.INVERTED, this.invertTrigger); + } + }, +}; + + +/** + * Updates the canvas size taking into + * account the backing store pixel ratio and + * the device pixel ratio. + * + * See article by Paul Lewis: + * http://www.html5rocks.com/en/tutorials/canvas/hidpi/ + * + * @param {HTMLCanvasElement} canvas + * @param {number=} opt_width + * @param {number=} opt_height + * @return {boolean} Whether the canvas was scaled. + */ +Runner.updateCanvasScaling = function(canvas, opt_width, opt_height) { + const context = + /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); + + // Query the various pixel ratios + const devicePixelRatio = Math.floor(window.devicePixelRatio) || 1; + /** @suppress {missingProperties} */ + const backingStoreRatio = + Math.floor(context.webkitBackingStorePixelRatio) || 1; + const ratio = devicePixelRatio / backingStoreRatio; + + // Upscale the canvas if the two ratios don't match + if (devicePixelRatio !== backingStoreRatio) { + const oldWidth = opt_width || canvas.width; + const oldHeight = opt_height || canvas.height; + + canvas.width = oldWidth * ratio; + canvas.height = oldHeight * ratio; + + canvas.style.width = oldWidth + 'px'; + canvas.style.height = oldHeight + 'px'; + + // Scale the context to counter the fact that we've manually scaled + // our canvas element. + context.scale(ratio, ratio); + return true; + } else if (devicePixelRatio === 1) { + // Reset the canvas width / height. Fixes scaling bug when the page is + // zoomed and the devicePixelRatio changes accordingly. + canvas.style.width = canvas.width + 'px'; + canvas.style.height = canvas.height + 'px'; + } + return false; +}; + + +/** + * Whether events are enabled. + * @return {boolean} + */ +Runner.isAltGameModeEnabled = function() { + return loadTimeData && loadTimeData.valueExists('enableAltGameMode'); +}; + + +/** + * Generated sound FX class for audio cues. + * @constructor + */ +function GeneratedSoundFx() { + this.audioCues = false; + this.context = null; + this.panner = null; +} + +GeneratedSoundFx.prototype = { + init() { + this.audioCues = true; + if (!this.context) { + // iOS only supports the webkit version. + this.context = window.webkitAudioContext ? new webkitAudioContext() : + new AudioContext(); + if (IS_IOS) { + this.context.onstatechange = (function() { + if (this.context.state != 'running') { + this.context.resume(); + } + }).bind(this); + this.context.resume(); + } + this.panner = this.context.createStereoPanner ? + this.context.createStereoPanner() : + null; + } + }, + + stopAll() { + this.cancelFootSteps(); + }, + + /** + * Play oscillators at certain frequency and for a certain time. + * @param {number} frequency + * @param {number} startTime + * @param {number} duration + * @param {?number=} opt_vol + * @param {number=} opt_pan + */ + playNote(frequency, startTime, duration, opt_vol, opt_pan) { + const osc1 = this.context.createOscillator(); + const osc2 = this.context.createOscillator(); + const volume = this.context.createGain(); + + // Set oscillator wave type + osc1.type = 'triangle'; + osc2.type = 'triangle'; + volume.gain.value = 0.1; + + // Set up node routing + if (this.panner) { + this.panner.pan.value = opt_pan || 0; + osc1.connect(volume).connect(this.panner); + osc2.connect(volume).connect(this.panner); + this.panner.connect(this.context.destination); + } else { + osc1.connect(volume); + osc2.connect(volume); + volume.connect(this.context.destination); + } + + // Detune oscillators for chorus effect + osc1.frequency.value = frequency + 1; + osc2.frequency.value = frequency - 2; + + // Fade out + volume.gain.setValueAtTime(opt_vol || 0.01, startTime + duration - 0.05); + volume.gain.linearRampToValueAtTime(0.00001, startTime + duration); + + // Start oscillators + osc1.start(startTime); + osc2.start(startTime); + // Stop oscillators + osc1.stop(startTime + duration); + osc2.stop(startTime + duration); + }, + + background() { + if (this.audioCues) { + const now = this.context.currentTime; + this.playNote(493.883, now, 0.116); + this.playNote(659.255, now + 0.116, 0.232); + this.loopFootSteps(); + } + }, + + loopFootSteps() { + if (this.audioCues && !this.bgSoundIntervalId) { + this.bgSoundIntervalId = setInterval(function() { + this.playNote(73.42, this.context.currentTime, 0.05, 0.16); + this.playNote(69.30, this.context.currentTime + 0.116, 0.116, 0.16); + }.bind(this), 280); + } + }, + + cancelFootSteps() { + if (this.audioCues && this.bgSoundIntervalId) { + clearInterval(this.bgSoundIntervalId); + this.bgSoundIntervalId = null; + this.playNote(103.83, this.context.currentTime, 0.232, 0.02); + this.playNote(116.54, this.context.currentTime + 0.116, 0.232, 0.02); + } + }, + + collect() { + if (this.audioCues) { + this.cancelFootSteps(); + const now = this.context.currentTime; + this.playNote(830.61, now, 0.116); + this.playNote(1318.51, now + 0.116, 0.232); + } + }, + + jump() { + if (this.audioCues) { + const now = this.context.currentTime; + this.playNote(659.25, now, 0.116, 0.3, -0.6); + this.playNote(880, now + 0.116, 0.232, 0.3, -0.6); + } + }, +}; + + +/** + * Speak a phrase using Speech Synthesis API for a11y. + * @param {string} phrase Sentence to speak. + */ +function speakPhrase(phrase) { + if ('speechSynthesis' in window) { + const msg = new SpeechSynthesisUtterance(phrase); + const voices = window.speechSynthesis.getVoices(); + msg.text = phrase; + speechSynthesis.speak(msg); + } +} + + +/** + * For screen readers make an announcement to the live region. + * @param {string} phrase Sentence to speak. + */ +function announcePhrase(phrase) { + if (Runner.a11yStatusEl) { + Runner.a11yStatusEl.textContent = ''; + Runner.a11yStatusEl.textContent = phrase; + } +} + + +/** + * Returns a string from loadTimeData data object. + * @param {string} stringName + * @return {string} + */ +function getA11yString(stringName) { + return loadTimeData && loadTimeData.valueExists(stringName) ? + loadTimeData.getString(stringName) : + ''; +} + + +/** + * Get random number. + * @param {number} min + * @param {number} max + */ +function getRandomNum(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + + +/** + * Vibrate on mobile devices. + * @param {number} duration Duration of the vibration in milliseconds. + */ +function vibrate(duration) { + if (IS_MOBILE && window.navigator.vibrate) { + window.navigator.vibrate(duration); + } +} + + +/** + * Create canvas element. + * @param {Element} container Element to append canvas to. + * @param {number} width + * @param {number} height + * @param {string=} opt_classname + * @return {HTMLCanvasElement} + */ +function createCanvas(container, width, height, opt_classname) { + const canvas = + /** @type {!HTMLCanvasElement} */ (document.createElement('canvas')); + canvas.className = opt_classname ? Runner.classes.CANVAS + ' ' + + opt_classname : Runner.classes.CANVAS; + canvas.width = width; + canvas.height = height; + container.appendChild(canvas); + + return canvas; +} + + +/** + * Decodes the base 64 audio to ArrayBuffer used by Web Audio. + * @param {string} base64String + */ +function decodeBase64ToArrayBuffer(base64String) { + const len = (base64String.length / 4) * 3; + const str = atob(base64String); + const arrayBuffer = new ArrayBuffer(len); + const bytes = new Uint8Array(arrayBuffer); + + for (let i = 0; i < len; i++) { + bytes[i] = str.charCodeAt(i); + } + return bytes.buffer; +} + + +/** + * Return the current timestamp. + * @return {number} + */ +function getTimeStamp() { + return IS_IOS ? new Date().getTime() : performance.now(); +} + + +//****************************************************************************** + + +/** + * Game over panel. + * @param {!HTMLCanvasElement} canvas + * @param {Object} textImgPos + * @param {Object} restartImgPos + * @param {!Object} dimensions Canvas dimensions. + * @param {Object=} opt_altGameEndImgPos + * @param {boolean=} opt_altGameActive + * @constructor + */ +function GameOverPanel( + canvas, textImgPos, restartImgPos, dimensions, opt_altGameEndImgPos, + opt_altGameActive) { + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); + this.canvasDimensions = dimensions; + this.textImgPos = textImgPos; + this.restartImgPos = restartImgPos; + this.altGameEndImgPos = opt_altGameEndImgPos; + this.altGameModeActive = opt_altGameActive; + + // Retry animation. + this.frameTimeStamp = 0; + this.animTimer = 0; + this.currentFrame = 0; + + this.gameOverRafId = null; + + this.flashTimer = 0; + this.flashCounter = 0; + this.originalText = true; +} + +GameOverPanel.RESTART_ANIM_DURATION = 875; +GameOverPanel.LOGO_PAUSE_DURATION = 875; +GameOverPanel.FLASH_ITERATIONS = 5; + +/** + * Animation frames spec. + */ +GameOverPanel.animConfig = { + frames: [0, 36, 72, 108, 144, 180, 216, 252], + msPerFrame: GameOverPanel.RESTART_ANIM_DURATION / 8, +}; + +/** + * Dimensions used in the panel. + * @enum {number} + */ +GameOverPanel.dimensions = { + TEXT_X: 0, + TEXT_Y: 13, + TEXT_WIDTH: 191, + TEXT_HEIGHT: 11, + RESTART_WIDTH: 36, + RESTART_HEIGHT: 32, +}; + + +GameOverPanel.prototype = { + /** + * Update the panel dimensions. + * @param {number} width New canvas width. + * @param {number} opt_height Optional new canvas height. + */ + updateDimensions(width, opt_height) { + this.canvasDimensions.WIDTH = width; + if (opt_height) { + this.canvasDimensions.HEIGHT = opt_height; + } + this.currentFrame = GameOverPanel.animConfig.frames.length - 1; + }, + + drawGameOverText(dimensions, opt_useAltText) { + const centerX = this.canvasDimensions.WIDTH / 2; + let textSourceX = dimensions.TEXT_X; + let textSourceY = dimensions.TEXT_Y; + let textSourceWidth = dimensions.TEXT_WIDTH; + let textSourceHeight = dimensions.TEXT_HEIGHT; + + const textTargetX = Math.round(centerX - (dimensions.TEXT_WIDTH / 2)); + const textTargetY = Math.round((this.canvasDimensions.HEIGHT - 25) / 3); + const textTargetWidth = dimensions.TEXT_WIDTH; + const textTargetHeight = dimensions.TEXT_HEIGHT; + + if (IS_HIDPI) { + textSourceY *= 2; + textSourceX *= 2; + textSourceWidth *= 2; + textSourceHeight *= 2; + } + + if (!opt_useAltText) { + textSourceX += this.textImgPos.x; + textSourceY += this.textImgPos.y; + } + + const spriteSource = + opt_useAltText ? Runner.altCommonImageSprite : Runner.origImageSprite; + + this.canvasCtx.save(); + + if (IS_RTL) { + this.canvasCtx.translate(this.canvasDimensions.WIDTH, 0); + this.canvasCtx.scale(-1, 1); + } + + // Game over text from sprite. + this.canvasCtx.drawImage( + spriteSource, textSourceX, textSourceY, textSourceWidth, + textSourceHeight, textTargetX, textTargetY, textTargetWidth, + textTargetHeight); + + this.canvasCtx.restore(); + }, + + /** + * Draw additional adornments for alternative game types. + */ + drawAltGameElements(tRex) { + // Additional adornments. + if (this.altGameModeActive && Runner.spriteDefinition.ALT_GAME_END_CONFIG) { + const altGameEndConfig = Runner.spriteDefinition.ALT_GAME_END_CONFIG; + + let altGameEndSourceWidth = altGameEndConfig.WIDTH; + let altGameEndSourceHeight = altGameEndConfig.HEIGHT; + const altGameEndTargetX = tRex.xPos + altGameEndConfig.X_OFFSET; + const altGameEndTargetY = tRex.yPos + altGameEndConfig.Y_OFFSET; + + if (IS_HIDPI) { + altGameEndSourceWidth *= 2; + altGameEndSourceHeight *= 2; + } + + this.canvasCtx.drawImage( + Runner.altCommonImageSprite, this.altGameEndImgPos.x, + this.altGameEndImgPos.y, altGameEndSourceWidth, + altGameEndSourceHeight, altGameEndTargetX, altGameEndTargetY, + altGameEndConfig.WIDTH, altGameEndConfig.HEIGHT); + } + }, + + /** + * Draw restart button. + */ + drawRestartButton() { + const dimensions = GameOverPanel.dimensions; + let framePosX = GameOverPanel.animConfig.frames[this.currentFrame]; + let restartSourceWidth = dimensions.RESTART_WIDTH; + let restartSourceHeight = dimensions.RESTART_HEIGHT; + const restartTargetX = + (this.canvasDimensions.WIDTH / 2) - (dimensions.RESTART_WIDTH / 2); + const restartTargetY = this.canvasDimensions.HEIGHT / 2; + + if (IS_HIDPI) { + restartSourceWidth *= 2; + restartSourceHeight *= 2; + framePosX *= 2; + } + + this.canvasCtx.save(); + + if (IS_RTL) { + this.canvasCtx.translate(this.canvasDimensions.WIDTH, 0); + this.canvasCtx.scale(-1, 1); + } + + this.canvasCtx.drawImage( + Runner.origImageSprite, this.restartImgPos.x + framePosX, + this.restartImgPos.y, restartSourceWidth, restartSourceHeight, + restartTargetX, restartTargetY, dimensions.RESTART_WIDTH, + dimensions.RESTART_HEIGHT); + this.canvasCtx.restore(); + }, + + + /** + * Draw the panel. + * @param {boolean} opt_altGameModeActive + * @param {!Trex} opt_tRex + */ + draw(opt_altGameModeActive, opt_tRex) { + if (opt_altGameModeActive) { + this.altGameModeActive = opt_altGameModeActive; + } + + this.drawGameOverText(GameOverPanel.dimensions, false); + this.drawRestartButton(); + this.drawAltGameElements(opt_tRex); + this.update(); + }, + + /** + * Update animation frames. + */ + update() { + const now = getTimeStamp(); + const deltaTime = now - (this.frameTimeStamp || now); + + this.frameTimeStamp = now; + this.animTimer += deltaTime; + this.flashTimer += deltaTime; + + // Restart Button + if (this.currentFrame == 0 && + this.animTimer > GameOverPanel.LOGO_PAUSE_DURATION) { + this.animTimer = 0; + this.currentFrame++; + this.drawRestartButton(); + } else if ( + this.currentFrame > 0 && + this.currentFrame < GameOverPanel.animConfig.frames.length) { + if (this.animTimer >= GameOverPanel.animConfig.msPerFrame) { + this.currentFrame++; + this.drawRestartButton(); + } + } else if ( + !this.altGameModeActive && + this.currentFrame == GameOverPanel.animConfig.frames.length) { + this.reset(); + return; + } + + // Game over text + if (this.altGameModeActive && + Runner.spriteDefinitionByType.original.ALT_GAME_OVER_TEXT_CONFIG) { + const altTextConfig = + Runner.spriteDefinitionByType.original.ALT_GAME_OVER_TEXT_CONFIG; + + if (this.flashCounter < GameOverPanel.FLASH_ITERATIONS && + this.flashTimer > altTextConfig.FLASH_DURATION) { + this.flashTimer = 0; + this.originalText = !this.originalText; + + this.clearGameOverTextBounds(); + if (this.originalText) { + this.drawGameOverText(GameOverPanel.dimensions, false); + this.flashCounter++; + } else { + this.drawGameOverText(altTextConfig, true); + } + } else if (this.flashCounter >= GameOverPanel.FLASH_ITERATIONS) { + this.reset(); + return; + } + } + + this.gameOverRafId = requestAnimationFrame(this.update.bind(this)); + }, + + /** + * Clear game over text. + */ + clearGameOverTextBounds() { + this.canvasCtx.save(); + + this.canvasCtx.clearRect( + Math.round( + this.canvasDimensions.WIDTH / 2 - + (GameOverPanel.dimensions.TEXT_WIDTH / 2)), + Math.round((this.canvasDimensions.HEIGHT - 25) / 3), + GameOverPanel.dimensions.TEXT_WIDTH, + GameOverPanel.dimensions.TEXT_HEIGHT + 4); + this.canvasCtx.restore(); + }, + + reset() { + if (this.gameOverRafId) { + cancelAnimationFrame(this.gameOverRafId); + this.gameOverRafId = null; + } + this.animTimer = 0; + this.frameTimeStamp = 0; + this.currentFrame = 0; + this.flashTimer = 0; + this.flashCounter = 0; + this.originalText = true; + }, +}; + + +//****************************************************************************** + +/** + * Check for a collision. + * @param {!Obstacle} obstacle + * @param {!Trex} tRex T-rex object. + * @param {CanvasRenderingContext2D=} opt_canvasCtx Optional canvas context for + * drawing collision boxes. + * @return {Array|undefined} + */ +function checkForCollision(obstacle, tRex, opt_canvasCtx) { + const obstacleBoxXPos = Runner.defaultDimensions.WIDTH + obstacle.xPos; + + // Adjustments are made to the bounding box as there is a 1 pixel white + // border around the t-rex and obstacles. + const tRexBox = new CollisionBox( + tRex.xPos + 1, + tRex.yPos + 1, + tRex.config.WIDTH - 2, + tRex.config.HEIGHT - 2); + + const obstacleBox = new CollisionBox( + obstacle.xPos + 1, + obstacle.yPos + 1, + obstacle.typeConfig.width * obstacle.size - 2, + obstacle.typeConfig.height - 2); + + // Debug outer box + if (opt_canvasCtx) { + drawCollisionBoxes(opt_canvasCtx, tRexBox, obstacleBox); + } + + // Simple outer bounds check. + if (boxCompare(tRexBox, obstacleBox)) { + const collisionBoxes = obstacle.collisionBoxes; + let tRexCollisionBoxes = []; + + if (Runner.isAltGameModeEnabled()) { + tRexCollisionBoxes = Runner.spriteDefinition.TREX.COLLISION_BOXES; + } else { + tRexCollisionBoxes = tRex.ducking ? Trex.collisionBoxes.DUCKING : + Trex.collisionBoxes.RUNNING; + } + + // Detailed axis aligned box check. + for (let t = 0; t < tRexCollisionBoxes.length; t++) { + for (let i = 0; i < collisionBoxes.length; i++) { + // Adjust the box to actual positions. + const adjTrexBox = + createAdjustedCollisionBox(tRexCollisionBoxes[t], tRexBox); + const adjObstacleBox = + createAdjustedCollisionBox(collisionBoxes[i], obstacleBox); + const crashed = boxCompare(adjTrexBox, adjObstacleBox); + + // Draw boxes for debug. + if (opt_canvasCtx) { + drawCollisionBoxes(opt_canvasCtx, adjTrexBox, adjObstacleBox); + } + + if (crashed) { + return [adjTrexBox, adjObstacleBox]; + } + } + } + } +} + + +/** + * Adjust the collision box. + * @param {!CollisionBox} box The original box. + * @param {!CollisionBox} adjustment Adjustment box. + * @return {CollisionBox} The adjusted collision box object. + */ +function createAdjustedCollisionBox(box, adjustment) { + return new CollisionBox( + box.x + adjustment.x, + box.y + adjustment.y, + box.width, + box.height); +} + + +/** + * Draw the collision boxes for debug. + */ +function drawCollisionBoxes(canvasCtx, tRexBox, obstacleBox) { + canvasCtx.save(); + canvasCtx.strokeStyle = '#f00'; + canvasCtx.strokeRect(tRexBox.x, tRexBox.y, tRexBox.width, tRexBox.height); + + canvasCtx.strokeStyle = '#0f0'; + canvasCtx.strokeRect(obstacleBox.x, obstacleBox.y, + obstacleBox.width, obstacleBox.height); + canvasCtx.restore(); +} + + +/** + * Compare two collision boxes for a collision. + * @param {CollisionBox} tRexBox + * @param {CollisionBox} obstacleBox + * @return {boolean} Whether the boxes intersected. + */ +function boxCompare(tRexBox, obstacleBox) { + let crashed = false; + const tRexBoxX = tRexBox.x; + const tRexBoxY = tRexBox.y; + + const obstacleBoxX = obstacleBox.x; + const obstacleBoxY = obstacleBox.y; + + // Axis-Aligned Bounding Box method. + if (tRexBox.x < obstacleBoxX + obstacleBox.width && + tRexBox.x + tRexBox.width > obstacleBoxX && + tRexBox.y < obstacleBox.y + obstacleBox.height && + tRexBox.height + tRexBox.y > obstacleBox.y) { + crashed = true; + } + + return crashed; +} + + +//****************************************************************************** + +/** + * Collision box object. + * @param {number} x X position. + * @param {number} y Y Position. + * @param {number} w Width. + * @param {number} h Height. + * @constructor + */ +function CollisionBox(x, y, w, h) { + this.x = x; + this.y = y; + this.width = w; + this.height = h; +} + + +//****************************************************************************** + +/** + * Obstacle. + * @param {CanvasRenderingContext2D} canvasCtx + * @param {ObstacleType} type + * @param {Object} spriteImgPos Obstacle position in sprite. + * @param {Object} dimensions + * @param {number} gapCoefficient Mutipler in determining the gap. + * @param {number} speed + * @param {number=} opt_xOffset + * @param {boolean=} opt_isAltGameMode + * @constructor + */ +function Obstacle( + canvasCtx, type, spriteImgPos, dimensions, gapCoefficient, speed, + opt_xOffset, opt_isAltGameMode) { + this.canvasCtx = canvasCtx; + this.spritePos = spriteImgPos; + this.typeConfig = type; + this.gapCoefficient = Runner.slowDown ? gapCoefficient * 2 : gapCoefficient; + this.size = getRandomNum(1, Obstacle.MAX_OBSTACLE_LENGTH); + this.dimensions = dimensions; + this.remove = false; + this.xPos = dimensions.WIDTH + (opt_xOffset || 0); + this.yPos = 0; + this.width = 0; + this.collisionBoxes = []; + this.gap = 0; + this.speedOffset = 0; + this.altGameModeActive = opt_isAltGameMode; + this.imageSprite = this.typeConfig.type == 'COLLECTABLE' ? + Runner.altCommonImageSprite : + this.altGameModeActive ? Runner.altGameImageSprite : Runner.imageSprite; + + // For animated obstacles. + this.currentFrame = 0; + this.timer = 0; + + this.init(speed); +} + +/** + * Coefficient for calculating the maximum gap. + */ +Obstacle.MAX_GAP_COEFFICIENT = 1.5; + +/** + * Maximum obstacle grouping count. + */ +Obstacle.MAX_OBSTACLE_LENGTH = 3; + + +Obstacle.prototype = { + /** + * Initialise the DOM for the obstacle. + * @param {number} speed + */ + init(speed) { + this.cloneCollisionBoxes(); + + // Only allow sizing if we're at the right speed. + if (this.size > 1 && this.typeConfig.multipleSpeed > speed) { + this.size = 1; + } + + this.width = this.typeConfig.width * this.size; + + // Check if obstacle can be positioned at various heights. + if (Array.isArray(this.typeConfig.yPos)) { + const yPosConfig = + IS_MOBILE ? this.typeConfig.yPosMobile : this.typeConfig.yPos; + this.yPos = yPosConfig[getRandomNum(0, yPosConfig.length - 1)]; + } else { + this.yPos = this.typeConfig.yPos; + } + + this.draw(); + + // Make collision box adjustments, + // Central box is adjusted to the size as one box. + // ____ ______ ________ + // _| |-| _| |-| _| |-| + // | |<->| | | |<--->| | | |<----->| | + // | | 1 | | | | 2 | | | | 3 | | + // |_|___|_| |_|_____|_| |_|_______|_| + // + if (this.size > 1) { + this.collisionBoxes[1].width = this.width - this.collisionBoxes[0].width - + this.collisionBoxes[2].width; + this.collisionBoxes[2].x = this.width - this.collisionBoxes[2].width; + } + + // For obstacles that go at a different speed from the horizon. + if (this.typeConfig.speedOffset) { + this.speedOffset = Math.random() > 0.5 ? this.typeConfig.speedOffset : + -this.typeConfig.speedOffset; + } + + this.gap = this.getGap(this.gapCoefficient, speed); + + // Increase gap for audio cues enabled. + if (Runner.audioCues) { + this.gap *= 2; + } + }, + + /** + * Draw and crop based on size. + */ + draw() { + let sourceWidth = this.typeConfig.width; + let sourceHeight = this.typeConfig.height; + + if (IS_HIDPI) { + sourceWidth = sourceWidth * 2; + sourceHeight = sourceHeight * 2; + } + + // X position in sprite. + let sourceX = + (sourceWidth * this.size) * (0.5 * (this.size - 1)) + this.spritePos.x; + + // Animation frames. + if (this.currentFrame > 0) { + sourceX += sourceWidth * this.currentFrame; + } + + this.canvasCtx.drawImage( + this.imageSprite, sourceX, this.spritePos.y, sourceWidth * this.size, + sourceHeight, this.xPos, this.yPos, this.typeConfig.width * this.size, + this.typeConfig.height); + }, + + /** + * Obstacle frame update. + * @param {number} deltaTime + * @param {number} speed + */ + update(deltaTime, speed) { + if (!this.remove) { + if (this.typeConfig.speedOffset) { + speed += this.speedOffset; + } + this.xPos -= Math.floor((speed * FPS / 1000) * deltaTime); + + // Update frame + if (this.typeConfig.numFrames) { + this.timer += deltaTime; + if (this.timer >= this.typeConfig.frameRate) { + this.currentFrame = + this.currentFrame === this.typeConfig.numFrames - 1 ? + 0 : + this.currentFrame + 1; + this.timer = 0; + } + } + this.draw(); + + if (!this.isVisible()) { + this.remove = true; + } + } + }, + + /** + * Calculate a random gap size. + * - Minimum gap gets wider as speed increses + * @param {number} gapCoefficient + * @param {number} speed + * @return {number} The gap size. + */ + getGap(gapCoefficient, speed) { + const minGap = Math.round( + this.width * speed + this.typeConfig.minGap * gapCoefficient); + const maxGap = Math.round(minGap * Obstacle.MAX_GAP_COEFFICIENT); + return getRandomNum(minGap, maxGap); + }, + + /** + * Check if obstacle is visible. + * @return {boolean} Whether the obstacle is in the game area. + */ + isVisible() { + return this.xPos + this.width > 0; + }, + + /** + * Make a copy of the collision boxes, since these will change based on + * obstacle type and size. + */ + cloneCollisionBoxes() { + const collisionBoxes = this.typeConfig.collisionBoxes; + + for (let i = collisionBoxes.length - 1; i >= 0; i--) { + this.collisionBoxes[i] = new CollisionBox( + collisionBoxes[i].x, collisionBoxes[i].y, collisionBoxes[i].width, + collisionBoxes[i].height); + } + }, +}; + + +//****************************************************************************** +/** + * T-rex game character. + * @param {HTMLCanvasElement} canvas + * @param {Object} spritePos Positioning within image sprite. + * @constructor + */ +function Trex(canvas, spritePos) { + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); + this.spritePos = spritePos; + this.xPos = 0; + this.yPos = 0; + this.xInitialPos = 0; + // Position when on the ground. + this.groundYPos = 0; + this.currentFrame = 0; + this.currentAnimFrames = []; + this.blinkDelay = 0; + this.blinkCount = 0; + this.animStartTime = 0; + this.timer = 0; + this.msPerFrame = 1000 / FPS; + this.config = Object.assign(Trex.config, Trex.normalJumpConfig); + // Current status. + this.status = Trex.status.WAITING; + this.jumping = false; + this.ducking = false; + this.jumpVelocity = 0; + this.reachedMinHeight = false; + this.speedDrop = false; + this.jumpCount = 0; + this.jumpspotX = 0; + this.altGameModeEnabled = false; + this.flashing = false; + + this.init(); +} + + +/** + * T-rex player config. + */ +Trex.config = { + DROP_VELOCITY: -5, + FLASH_OFF: 175, + FLASH_ON: 100, + HEIGHT: 47, + HEIGHT_DUCK: 25, + INTRO_DURATION: 1500, + SPEED_DROP_COEFFICIENT: 3, + SPRITE_WIDTH: 262, + START_X_POS: 50, + WIDTH: 44, + WIDTH_DUCK: 59, +}; + +Trex.slowJumpConfig = { + GRAVITY: 0.25, + MAX_JUMP_HEIGHT: 50, + MIN_JUMP_HEIGHT: 45, + INITIAL_JUMP_VELOCITY: -20, +}; + +Trex.normalJumpConfig = { + GRAVITY: 0.6, + MAX_JUMP_HEIGHT: 30, + MIN_JUMP_HEIGHT: 30, + INITIAL_JUMP_VELOCITY: -10, +}; + +/** + * Used in collision detection. + * @enum {Array} + */ +Trex.collisionBoxes = { + DUCKING: [new CollisionBox(1, 18, 55, 25)], + RUNNING: [ + new CollisionBox(22, 0, 17, 16), + new CollisionBox(1, 18, 30, 9), + new CollisionBox(10, 35, 14, 8), + new CollisionBox(1, 24, 29, 5), + new CollisionBox(5, 30, 21, 4), + new CollisionBox(9, 34, 15, 4), + ], +}; + + +/** + * Animation states. + * @enum {string} + */ +Trex.status = { + CRASHED: 'CRASHED', + DUCKING: 'DUCKING', + JUMPING: 'JUMPING', + RUNNING: 'RUNNING', + WAITING: 'WAITING', +}; + +/** + * Blinking coefficient. + * @const + */ +Trex.BLINK_TIMING = 7000; + + +/** + * Animation config for different states. + * @enum {Object} + */ +Trex.animFrames = { + WAITING: { + frames: [44, 0], + msPerFrame: 1000 / 3, + }, + RUNNING: { + frames: [88, 132], + msPerFrame: 1000 / 12, + }, + CRASHED: { + frames: [220], + msPerFrame: 1000 / 60, + }, + JUMPING: { + frames: [0], + msPerFrame: 1000 / 60, + }, + DUCKING: { + frames: [264, 323], + msPerFrame: 1000 / 8, + }, +}; + + +Trex.prototype = { + /** + * T-rex player initaliser. + * Sets the t-rex to blink at random intervals. + */ + init() { + this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT - + Runner.config.BOTTOM_PAD; + this.yPos = this.groundYPos; + this.minJumpHeight = this.groundYPos - this.config.MIN_JUMP_HEIGHT; + + this.draw(0, 0); + this.update(0, Trex.status.WAITING); + }, + + /** + * Assign the appropriate jump parameters based on the game speed. + */ + enableSlowConfig: function() { + const jumpConfig = + Runner.slowDown ? Trex.slowJumpConfig : Trex.normalJumpConfig; + Trex.config = Object.assign(Trex.config, jumpConfig); + + this.adjustAltGameConfigForSlowSpeed(); + }, + + /** + * Enables the alternative game. Redefines the dino config. + * @param {Object} spritePos New positioning within image sprite. + */ + enableAltGameMode: function(spritePos) { + this.altGameModeEnabled = true; + this.spritePos = spritePos; + const spriteDefinition = Runner.spriteDefinition['TREX']; + + // Update animation frames. + Trex.animFrames.RUNNING.frames = + [spriteDefinition.RUNNING_1.x, spriteDefinition.RUNNING_2.x]; + Trex.animFrames.CRASHED.frames = [spriteDefinition.CRASHED.x]; + + if (typeof spriteDefinition.JUMPING.x == 'object') { + Trex.animFrames.JUMPING.frames = spriteDefinition.JUMPING.x; + } else { + Trex.animFrames.JUMPING.frames = [spriteDefinition.JUMPING.x]; + } + + Trex.animFrames.DUCKING.frames = + [spriteDefinition.RUNNING_1.x, spriteDefinition.RUNNING_2.x]; + + // Update Trex config + Trex.config.GRAVITY = spriteDefinition.GRAVITY || Trex.config.GRAVITY; + Trex.config.HEIGHT = spriteDefinition.RUNNING_1.h, + Trex.config.INITIAL_JUMP_VELOCITY = spriteDefinition.INITIAL_JUMP_VELOCITY; + Trex.config.MAX_JUMP_HEIGHT = spriteDefinition.MAX_JUMP_HEIGHT; + Trex.config.MIN_JUMP_HEIGHT = spriteDefinition.MIN_JUMP_HEIGHT; + Trex.config.WIDTH = spriteDefinition.RUNNING_1.w; + Trex.config.WIDTH_JUMP = spriteDefinition.JUMPING.w; + Trex.config.INVERT_JUMP = spriteDefinition.INVERT_JUMP; + + this.adjustAltGameConfigForSlowSpeed(spriteDefinition.GRAVITY); + this.config = Trex.config; + + // Adjust bottom horizon placement. + this.groundYPos = Runner.defaultDimensions.HEIGHT - this.config.HEIGHT - + Runner.spriteDefinition['BOTTOM_PAD']; + this.yPos = this.groundYPos; + this.reset(); + }, + + /** + * Slow speeds adjustments for the alt game modes. + * @param {number=} opt_gravityValue + */ + adjustAltGameConfigForSlowSpeed: function(opt_gravityValue) { + if (Runner.slowDown) { + if (opt_gravityValue) { + Trex.config.GRAVITY = opt_gravityValue / 1.5; + } + Trex.config.MIN_JUMP_HEIGHT *= 1.5; + Trex.config.MAX_JUMP_HEIGHT *= 1.5; + Trex.config.INITIAL_JUMP_VELOCITY = + Trex.config.INITIAL_JUMP_VELOCITY * 1.5; + } + }, + + /** + * Setter whether dino is flashing. + * @param {boolean} status + */ + setFlashing: function(status) { + this.flashing = status; + }, + + /** + * Setter for the jump velocity. + * The approriate drop velocity is also set. + * @param {number} setting + */ + setJumpVelocity(setting) { + this.config.INITIAL_JUMP_VELOCITY = -setting; + this.config.DROP_VELOCITY = -setting / 2; + }, + + /** + * Set the animation status. + * @param {!number} deltaTime + * @param {Trex.status=} opt_status Optional status to switch to. + */ + update(deltaTime, opt_status) { + this.timer += deltaTime; + + // Update the status. + if (opt_status) { + this.status = opt_status; + this.currentFrame = 0; + this.msPerFrame = Trex.animFrames[opt_status].msPerFrame; + this.currentAnimFrames = Trex.animFrames[opt_status].frames; + + if (opt_status === Trex.status.WAITING) { + this.animStartTime = getTimeStamp(); + this.setBlinkDelay(); + } + } + // Game intro animation, T-rex moves in from the left. + if (this.playingIntro && this.xPos < this.config.START_X_POS) { + this.xPos += Math.round((this.config.START_X_POS / + this.config.INTRO_DURATION) * deltaTime); + this.xInitialPos = this.xPos; + } + + if (this.status === Trex.status.WAITING) { + this.blink(getTimeStamp()); + } else { + this.draw(this.currentAnimFrames[this.currentFrame], 0); + } + + // Update the frame position. + if (!this.flashing && this.timer >= this.msPerFrame) { + this.currentFrame = this.currentFrame == + this.currentAnimFrames.length - 1 ? 0 : this.currentFrame + 1; + this.timer = 0; + } + + if (!this.altGameModeEnabled) { + // Speed drop becomes duck if the down key is still being pressed. + if (this.speedDrop && this.yPos === this.groundYPos) { + this.speedDrop = false; + this.setDuck(true); + } + } + }, + + /** + * Draw the t-rex to a particular position. + * @param {number} x + * @param {number} y + */ + draw(x, y) { + let sourceX = x; + let sourceY = y; + let sourceWidth = this.ducking && this.status !== Trex.status.CRASHED ? + this.config.WIDTH_DUCK : + this.config.WIDTH; + let sourceHeight = this.config.HEIGHT; + const outputHeight = sourceHeight; + + let jumpOffset = Runner.spriteDefinition.TREX.JUMPING.xOffset; + + // Width of sprite changes on jump. + if (this.altGameModeEnabled && this.jumping && + this.status !== Trex.status.CRASHED) { + sourceWidth = this.config.WIDTH_JUMP; + } + + if (IS_HIDPI) { + sourceX *= 2; + sourceY *= 2; + sourceWidth *= 2; + sourceHeight *= 2; + jumpOffset *= 2; + } + + // Adjustments for sprite sheet position. + sourceX += this.spritePos.x; + sourceY += this.spritePos.y; + + // Flashing. + if (this.flashing) { + if (this.timer < this.config.FLASH_ON) { + this.canvasCtx.globalAlpha = 0.5; + } else if (this.timer > this.config.FLASH_OFF) { + this.timer = 0; + } + } + + // Ducking. + if (!this.altGameModeEnabled && this.ducking && + this.status !== Trex.status.CRASHED) { + this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY, + sourceWidth, sourceHeight, + this.xPos, this.yPos, + this.config.WIDTH_DUCK, outputHeight); + } else if ( + this.altGameModeEnabled && this.jumping && + this.status !== Trex.status.CRASHED) { + // Jumping with adjustments. + this.canvasCtx.drawImage( + Runner.imageSprite, sourceX, sourceY, sourceWidth, sourceHeight, + this.xPos - jumpOffset, this.yPos, this.config.WIDTH_JUMP, + outputHeight); + } else { + // Crashed whilst ducking. Trex is standing up so needs adjustment. + if (this.ducking && this.status === Trex.status.CRASHED) { + this.xPos++; + } + // Standing / running + this.canvasCtx.drawImage(Runner.imageSprite, sourceX, sourceY, + sourceWidth, sourceHeight, + this.xPos, this.yPos, + this.config.WIDTH, outputHeight); + } + this.canvasCtx.globalAlpha = 1; + }, + + /** + * Sets a random time for the blink to happen. + */ + setBlinkDelay() { + this.blinkDelay = Math.ceil(Math.random() * Trex.BLINK_TIMING); + }, + + /** + * Make t-rex blink at random intervals. + * @param {number} time Current time in milliseconds. + */ + blink(time) { + const deltaTime = time - this.animStartTime; + + if (deltaTime >= this.blinkDelay) { + this.draw(this.currentAnimFrames[this.currentFrame], 0); + + if (this.currentFrame === 1) { + // Set new random delay to blink. + this.setBlinkDelay(); + this.animStartTime = time; + this.blinkCount++; + } + } + }, + + /** + * Initialise a jump. + * @param {number} speed + */ + startJump(speed) { + if (!this.jumping) { + this.update(0, Trex.status.JUMPING); + // Tweak the jump velocity based on the speed. + this.jumpVelocity = this.config.INITIAL_JUMP_VELOCITY - (speed / 10); + this.jumping = true; + this.reachedMinHeight = false; + this.speedDrop = false; + + if (this.config.INVERT_JUMP) { + this.minJumpHeight = this.groundYPos + this.config.MIN_JUMP_HEIGHT; + } + } + }, + + /** + * Jump is complete, falling down. + */ + endJump() { + if (this.reachedMinHeight && + this.jumpVelocity < this.config.DROP_VELOCITY) { + this.jumpVelocity = this.config.DROP_VELOCITY; + } + }, + + /** + * Update frame for a jump. + * @param {number} deltaTime + */ + updateJump(deltaTime) { + const msPerFrame = Trex.animFrames[this.status].msPerFrame; + const framesElapsed = deltaTime / msPerFrame; + + // Speed drop makes Trex fall faster. + if (this.speedDrop) { + this.yPos += Math.round(this.jumpVelocity * + this.config.SPEED_DROP_COEFFICIENT * framesElapsed); + } else if (this.config.INVERT_JUMP) { + this.yPos -= Math.round(this.jumpVelocity * framesElapsed); + } else { + this.yPos += Math.round(this.jumpVelocity * framesElapsed); + } + + this.jumpVelocity += this.config.GRAVITY * framesElapsed; + + // Minimum height has been reached. + if (this.config.INVERT_JUMP && (this.yPos > this.minJumpHeight) || + !this.config.INVERT_JUMP && (this.yPos < this.minJumpHeight) || + this.speedDrop) { + this.reachedMinHeight = true; + } + + // Reached max height. + if (this.config.INVERT_JUMP && (this.yPos > -this.config.MAX_JUMP_HEIGHT) || + !this.config.INVERT_JUMP && (this.yPos < this.config.MAX_JUMP_HEIGHT) || + this.speedDrop) { + this.endJump(); + } + + // Back down at ground level. Jump completed. + if ((this.config.INVERT_JUMP && this.yPos) < this.groundYPos || + (!this.config.INVERT_JUMP && this.yPos) > this.groundYPos) { + this.reset(); + this.jumpCount++; + + if (Runner.audioCues) { + Runner.generatedSoundFx.loopFootSteps(); + } + } + }, + + /** + * Set the speed drop. Immediately cancels the current jump. + */ + setSpeedDrop() { + this.speedDrop = true; + this.jumpVelocity = 1; + }, + + /** + * @param {boolean} isDucking + */ + setDuck(isDucking) { + if (isDucking && this.status !== Trex.status.DUCKING) { + this.update(0, Trex.status.DUCKING); + this.ducking = true; + } else if (this.status === Trex.status.DUCKING) { + this.update(0, Trex.status.RUNNING); + this.ducking = false; + } + }, + + /** + * Reset the t-rex to running at start of game. + */ + reset() { + this.xPos = this.xInitialPos; + this.yPos = this.groundYPos; + this.jumpVelocity = 0; + this.jumping = false; + this.ducking = false; + this.update(0, Trex.status.RUNNING); + this.midair = false; + this.speedDrop = false; + this.jumpCount = 0; + }, +}; + + +//****************************************************************************** + +/** + * Handles displaying the distance meter. + * @param {!HTMLCanvasElement} canvas + * @param {Object} spritePos Image position in sprite. + * @param {number} canvasWidth + * @constructor + */ +function DistanceMeter(canvas, spritePos, canvasWidth) { + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); + this.image = Runner.imageSprite; + this.spritePos = spritePos; + this.x = 0; + this.y = 5; + + this.currentDistance = 0; + this.maxScore = 0; + this.highScore = '0'; + this.container = null; + + this.digits = []; + this.achievement = false; + this.defaultString = ''; + this.flashTimer = 0; + this.flashIterations = 0; + this.invertTrigger = false; + this.flashingRafId = null; + this.highScoreBounds = {}; + this.highScoreFlashing = false; + + this.config = DistanceMeter.config; + this.maxScoreUnits = this.config.MAX_DISTANCE_UNITS; + this.canvasWidth = canvasWidth; + this.init(canvasWidth); +} + + +/** + * @enum {number} + */ +DistanceMeter.dimensions = { + WIDTH: 10, + HEIGHT: 13, + DEST_WIDTH: 11, +}; + + +/** + * Y positioning of the digits in the sprite sheet. + * X position is always 0. + * @type {Array} + */ +DistanceMeter.yPos = [0, 13, 27, 40, 53, 67, 80, 93, 107, 120]; + + +/** + * Distance meter config. + * @enum {number} + */ +DistanceMeter.config = { + // Number of digits. + MAX_DISTANCE_UNITS: 5, + + // Distance that causes achievement animation. + ACHIEVEMENT_DISTANCE: 100, + + // Used for conversion from pixel distance to a scaled unit. + COEFFICIENT: 0.025, + + // Flash duration in milliseconds. + FLASH_DURATION: 1000 / 4, + + // Flash iterations for achievement animation. + FLASH_ITERATIONS: 3, + + // Padding around the high score hit area. + HIGH_SCORE_HIT_AREA_PADDING: 4, +}; + + +DistanceMeter.prototype = { + /** + * Initialise the distance meter to '00000'. + * @param {number} width Canvas width in px. + */ + init(width) { + let maxDistanceStr = ''; + + this.calcXPos(width); + this.maxScore = this.maxScoreUnits; + for (let i = 0; i < this.maxScoreUnits; i++) { + this.draw(i, 0); + this.defaultString += '0'; + maxDistanceStr += '9'; + } + + this.maxScore = parseInt(maxDistanceStr, 10); + }, + + /** + * Calculate the xPos in the canvas. + * @param {number} canvasWidth + */ + calcXPos(canvasWidth) { + this.x = canvasWidth - (DistanceMeter.dimensions.DEST_WIDTH * + (this.maxScoreUnits + 1)); + }, + + /** + * Draw a digit to canvas. + * @param {number} digitPos Position of the digit. + * @param {number} value Digit value 0-9. + * @param {boolean=} opt_highScore Whether drawing the high score. + */ + draw(digitPos, value, opt_highScore) { + let sourceWidth = DistanceMeter.dimensions.WIDTH; + let sourceHeight = DistanceMeter.dimensions.HEIGHT; + let sourceX = DistanceMeter.dimensions.WIDTH * value; + let sourceY = 0; + + const targetX = digitPos * DistanceMeter.dimensions.DEST_WIDTH; + const targetY = this.y; + const targetWidth = DistanceMeter.dimensions.WIDTH; + const targetHeight = DistanceMeter.dimensions.HEIGHT; + + // For high DPI we 2x source values. + if (IS_HIDPI) { + sourceWidth *= 2; + sourceHeight *= 2; + sourceX *= 2; + } + + sourceX += this.spritePos.x; + sourceY += this.spritePos.y; + + this.canvasCtx.save(); + + if (IS_RTL) { + if (opt_highScore) { + this.canvasCtx.translate( + this.canvasWidth - + (DistanceMeter.dimensions.WIDTH * (this.maxScoreUnits + 3)), + this.y); + } else { + this.canvasCtx.translate( + this.canvasWidth - DistanceMeter.dimensions.WIDTH, this.y); + } + this.canvasCtx.scale(-1, 1); + } else { + const highScoreX = + this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH; + if (opt_highScore) { + this.canvasCtx.translate(highScoreX, this.y); + } else { + this.canvasCtx.translate(this.x, this.y); + } + } + + this.canvasCtx.drawImage( + this.image, + sourceX, + sourceY, + sourceWidth, + sourceHeight, + targetX, + targetY, + targetWidth, + targetHeight, + ); + + this.canvasCtx.restore(); + }, + + /** + * Covert pixel distance to a 'real' distance. + * @param {number} distance Pixel distance ran. + * @return {number} The 'real' distance ran. + */ + getActualDistance(distance) { + return distance ? Math.round(distance * this.config.COEFFICIENT) : 0; + }, + + /** + * Update the distance meter. + * @param {number} distance + * @param {number} deltaTime + * @return {boolean} Whether the acheivement sound fx should be played. + */ + update(deltaTime, distance) { + let paint = true; + let playSound = false; + + if (!this.achievement) { + distance = this.getActualDistance(distance); + // Score has gone beyond the initial digit count. + if (distance > this.maxScore && this.maxScoreUnits == + this.config.MAX_DISTANCE_UNITS) { + this.maxScoreUnits++; + this.maxScore = parseInt(this.maxScore + '9', 10); + } else { + this.distance = 0; + } + + if (distance > 0) { + // Achievement unlocked. + if (distance % this.config.ACHIEVEMENT_DISTANCE === 0) { + // Flash score and play sound. + this.achievement = true; + this.flashTimer = 0; + playSound = true; + } + + // Create a string representation of the distance with leading 0. + const distanceStr = (this.defaultString + + distance).substr(-this.maxScoreUnits); + this.digits = distanceStr.split(''); + } else { + this.digits = this.defaultString.split(''); + } + } else { + // Control flashing of the score on reaching acheivement. + if (this.flashIterations <= this.config.FLASH_ITERATIONS) { + this.flashTimer += deltaTime; + + if (this.flashTimer < this.config.FLASH_DURATION) { + paint = false; + } else if (this.flashTimer > this.config.FLASH_DURATION * 2) { + this.flashTimer = 0; + this.flashIterations++; + } + } else { + this.achievement = false; + this.flashIterations = 0; + this.flashTimer = 0; + } + } + + // Draw the digits if not flashing. + if (paint) { + for (let i = this.digits.length - 1; i >= 0; i--) { + this.draw(i, parseInt(this.digits[i], 10)); + } + } + + this.drawHighScore(); + return playSound; + }, + + /** + * Draw the high score. + */ + drawHighScore() { + if (parseInt(this.highScore, 10) > 0) { + this.canvasCtx.save(); + this.canvasCtx.globalAlpha = .8; + for (let i = this.highScore.length - 1; i >= 0; i--) { + this.draw(i, parseInt(this.highScore[i], 10), true); + } + this.canvasCtx.restore(); + } + }, + + /** + * Set the highscore as a array string. + * Position of char in the sprite: H - 10, I - 11. + * @param {number} distance Distance ran in pixels. + */ + setHighScore(distance) { + distance = this.getActualDistance(distance); + const highScoreStr = (this.defaultString + + distance).substr(-this.maxScoreUnits); + + this.highScore = ['10', '11', ''].concat(highScoreStr.split('')); + }, + + + /** + * Whether a clicked is in the high score area. + * @param {Event} e Event object. + * @return {boolean} Whether the click was in the high score bounds. + */ + hasClickedOnHighScore(e) { + let x = 0; + let y = 0; + + if (e.touches) { + // Bounds for touch differ from pointer. + const canvasBounds = this.canvas.getBoundingClientRect(); + x = e.touches[0].clientX - canvasBounds.left; + y = e.touches[0].clientY - canvasBounds.top; + } else { + x = e.offsetX; + y = e.offsetY; + } + + this.highScoreBounds = this.getHighScoreBounds(); + return x >= this.highScoreBounds.x && x <= + this.highScoreBounds.x + this.highScoreBounds.width && + y >= this.highScoreBounds.y && y <= + this.highScoreBounds.y + this.highScoreBounds.height; + }, + + /** + * Get the bounding box for the high score. + * @return {Object} Object with x, y, width and height properties. + */ + getHighScoreBounds() { + return { + x: (this.x - (this.maxScoreUnits * 2) * DistanceMeter.dimensions.WIDTH) - + DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING, + y: this.y, + width: DistanceMeter.dimensions.WIDTH * (this.highScore.length + 1) + + DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING, + height: DistanceMeter.dimensions.HEIGHT + + (DistanceMeter.config.HIGH_SCORE_HIT_AREA_PADDING * 2), + }; + }, + + /** + * Animate flashing the high score to indicate ready for resetting. + * The flashing stops following this.config.FLASH_ITERATIONS x 2 flashes. + */ + flashHighScore() { + const now = getTimeStamp(); + const deltaTime = now - (this.frameTimeStamp || now); + let paint = true; + this.frameTimeStamp = now; + + // Reached the max number of flashes. + if (this.flashIterations > this.config.FLASH_ITERATIONS * 2) { + this.cancelHighScoreFlashing(); + return; + } + + this.flashTimer += deltaTime; + + if (this.flashTimer < this.config.FLASH_DURATION) { + paint = false; + } else if (this.flashTimer > this.config.FLASH_DURATION * 2) { + this.flashTimer = 0; + this.flashIterations++; + } + + if (paint) { + this.drawHighScore(); + } else { + this.clearHighScoreBounds(); + } + // Frame update. + this.flashingRafId = + requestAnimationFrame(this.flashHighScore.bind(this)); + }, + + /** + * Draw empty rectangle over high score. + */ + clearHighScoreBounds() { + this.canvasCtx.save(); + this.canvasCtx.fillStyle = '#fff'; + this.canvasCtx.rect(this.highScoreBounds.x, this.highScoreBounds.y, + this.highScoreBounds.width, this.highScoreBounds.height); + this.canvasCtx.fill(); + this.canvasCtx.restore(); + }, + + /** + * Starts the flashing of the high score. + */ + startHighScoreFlashing() { + this.highScoreFlashing = true; + this.flashHighScore(); + }, + + /** + * Whether high score is flashing. + * @return {boolean} + */ + isHighScoreFlashing() { + return this.highScoreFlashing; + }, + + /** + * Stop flashing the high score. + */ + cancelHighScoreFlashing() { + if (this.flashingRafId) { + cancelAnimationFrame(this.flashingRafId); + } + this.flashIterations = 0; + this.flashTimer = 0; + this.highScoreFlashing = false; + this.clearHighScoreBounds(); + this.drawHighScore(); + }, + + /** + * Clear the high score. + */ + resetHighScore() { + this.setHighScore(0); + this.cancelHighScoreFlashing(); + }, + + /** + * Reset the distance meter back to '00000'. + */ + reset() { + this.update(0, 0); + this.achievement = false; + }, +}; + + +//****************************************************************************** + +/** + * Cloud background item. + * Similar to an obstacle object but without collision boxes. + * @param {HTMLCanvasElement} canvas Canvas element. + * @param {Object} spritePos Position of image in sprite. + * @param {number} containerWidth + * @constructor + */ +function Cloud(canvas, spritePos, containerWidth) { + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d')); + this.spritePos = spritePos; + this.containerWidth = containerWidth; + this.xPos = containerWidth; + this.yPos = 0; + this.remove = false; + this.gap = + getRandomNum(Cloud.config.MIN_CLOUD_GAP, Cloud.config.MAX_CLOUD_GAP); + + this.init(); +} + + +/** + * Cloud object config. + * @enum {number} + */ +Cloud.config = { + HEIGHT: 14, + MAX_CLOUD_GAP: 400, + MAX_SKY_LEVEL: 30, + MIN_CLOUD_GAP: 100, + MIN_SKY_LEVEL: 71, + WIDTH: 46, +}; + + +Cloud.prototype = { + /** + * Initialise the cloud. Sets the Cloud height. + */ + init() { + this.yPos = getRandomNum(Cloud.config.MAX_SKY_LEVEL, + Cloud.config.MIN_SKY_LEVEL); + this.draw(); + }, + + /** + * Draw the cloud. + */ + draw() { + this.canvasCtx.save(); + let sourceWidth = Cloud.config.WIDTH; + let sourceHeight = Cloud.config.HEIGHT; + const outputWidth = sourceWidth; + const outputHeight = sourceHeight; + if (IS_HIDPI) { + sourceWidth = sourceWidth * 2; + sourceHeight = sourceHeight * 2; + } + + this.canvasCtx.drawImage(Runner.imageSprite, this.spritePos.x, + this.spritePos.y, + sourceWidth, sourceHeight, + this.xPos, this.yPos, + outputWidth, outputHeight); + + this.canvasCtx.restore(); + }, + + /** + * Update the cloud position. + * @param {number} speed + */ + update(speed) { + if (!this.remove) { + this.xPos -= Math.ceil(speed); + this.draw(); + + // Mark as removeable if no longer in the canvas. + if (!this.isVisible()) { + this.remove = true; + } + } + }, + + /** + * Check if the cloud is visible on the stage. + * @return {boolean} + */ + isVisible() { + return this.xPos + Cloud.config.WIDTH > 0; + }, +}; + + +/** + * Background item. + * Similar to cloud, without random y position. + * @param {HTMLCanvasElement} canvas Canvas element. + * @param {Object} spritePos Position of image in sprite. + * @param {number} containerWidth + * @param {string} type Element type. + * @constructor + */ +function BackgroundEl(canvas, spritePos, containerWidth, type) { + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d')); + this.spritePos = spritePos; + this.containerWidth = containerWidth; + this.xPos = containerWidth; + this.yPos = 0; + this.remove = false; + this.type = type; + this.gap = + getRandomNum(BackgroundEl.config.MIN_GAP, BackgroundEl.config.MAX_GAP); + this.animTimer = 0; + this.switchFrames = false; + + this.spriteConfig = {}; + this.init(); +} + +/** + * Background element object config. + * Real values assigned when game type changes. + * @enum {number} + */ +BackgroundEl.config = { + MAX_BG_ELS: 0, + MAX_GAP: 0, + MIN_GAP: 0, + POS: 0, + SPEED: 0, + Y_POS: 0, + MS_PER_FRAME: 0, // only needed when BACKGROUND_EL.FIXED is true +}; + + +BackgroundEl.prototype = { + /** + * Initialise the element setting the y position. + */ + init() { + this.spriteConfig = Runner.spriteDefinition.BACKGROUND_EL[this.type]; + if (this.spriteConfig.FIXED) { + this.xPos = this.spriteConfig.FIXED_X_POS; + } + this.yPos = BackgroundEl.config.Y_POS - this.spriteConfig.HEIGHT + + this.spriteConfig.OFFSET; + this.draw(); + }, + + /** + * Draw the element. + */ + draw() { + this.canvasCtx.save(); + let sourceWidth = this.spriteConfig.WIDTH; + let sourceHeight = this.spriteConfig.HEIGHT; + let sourceX = this.spriteConfig.X_POS; + const outputWidth = sourceWidth; + const outputHeight = sourceHeight; + + if (IS_HIDPI) { + sourceWidth *= 2; + sourceHeight *= 2; + sourceX *= 2; + } + + this.canvasCtx.drawImage( + Runner.imageSprite, sourceX, this.spritePos.y, sourceWidth, + sourceHeight, this.xPos, this.yPos, outputWidth, outputHeight); + + this.canvasCtx.restore(); + }, + + /** + * Update the background element position. + * @param {number} speed + */ + update(speed) { + if (!this.remove) { + if (this.spriteConfig.FIXED) { + this.animTimer += speed; + if (this.animTimer > BackgroundEl.config.MS_PER_FRAME) { + this.animTimer = 0; + this.switchFrames = !this.switchFrames; + } + + if (this.spriteConfig.FIXED_Y_POS_1 && + this.spriteConfig.FIXED_Y_POS_2) { + this.yPos = this.switchFrames ? this.spriteConfig.FIXED_Y_POS_1 : + this.spriteConfig.FIXED_Y_POS_2; + } + } else { + // Fixed speed, regardless of actual game speed. + this.xPos -= BackgroundEl.config.SPEED; + } + this.draw(); + + // Mark as removable if no longer in the canvas. + if (!this.isVisible()) { + this.remove = true; + } + } + }, + + /** + * Check if the element is visible on the stage. + * @return {boolean} + */ + isVisible() { + return this.xPos + this.spriteConfig.WIDTH > 0; + }, +}; + + + +//****************************************************************************** + +/** + * Nightmode shows a moon and stars on the horizon. + * @param {HTMLCanvasElement} canvas + * @param {number} spritePos + * @param {number} containerWidth + * @constructor + */ +function NightMode(canvas, spritePos, containerWidth) { + this.spritePos = spritePos; + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); + this.xPos = containerWidth - 50; + this.yPos = 30; + this.currentPhase = 0; + this.opacity = 0; + this.containerWidth = containerWidth; + this.stars = []; + this.drawStars = false; + this.placeStars(); +} + +/** + * @enum {number} + */ +NightMode.config = { + FADE_SPEED: 0.035, + HEIGHT: 40, + MOON_SPEED: 0.25, + NUM_STARS: 2, + STAR_SIZE: 9, + STAR_SPEED: 0.3, + STAR_MAX_Y: 70, + WIDTH: 20, +}; + +NightMode.phases = [140, 120, 100, 60, 40, 20, 0]; + +NightMode.prototype = { + /** + * Update moving moon, changing phases. + * @param {boolean} activated Whether night mode is activated. + */ + update(activated) { + // Moon phase. + if (activated && this.opacity === 0) { + this.currentPhase++; + + if (this.currentPhase >= NightMode.phases.length) { + this.currentPhase = 0; + } + } + + // Fade in / out. + if (activated && (this.opacity < 1 || this.opacity === 0)) { + this.opacity += NightMode.config.FADE_SPEED; + } else if (this.opacity > 0) { + this.opacity -= NightMode.config.FADE_SPEED; + } + + // Set moon positioning. + if (this.opacity > 0) { + this.xPos = this.updateXPos(this.xPos, NightMode.config.MOON_SPEED); + + // Update stars. + if (this.drawStars) { + for (let i = 0; i < NightMode.config.NUM_STARS; i++) { + this.stars[i].x = + this.updateXPos(this.stars[i].x, NightMode.config.STAR_SPEED); + } + } + this.draw(); + } else { + this.opacity = 0; + this.placeStars(); + } + this.drawStars = true; + }, + + updateXPos(currentPos, speed) { + if (currentPos < -NightMode.config.WIDTH) { + currentPos = this.containerWidth; + } else { + currentPos -= speed; + } + return currentPos; + }, + + draw() { + let moonSourceWidth = this.currentPhase === 3 ? NightMode.config.WIDTH * 2 : + NightMode.config.WIDTH; + let moonSourceHeight = NightMode.config.HEIGHT; + let moonSourceX = this.spritePos.x + NightMode.phases[this.currentPhase]; + const moonOutputWidth = moonSourceWidth; + let starSize = NightMode.config.STAR_SIZE; + let starSourceX = Runner.spriteDefinitionByType.original.LDPI.STAR.x; + + if (IS_HIDPI) { + moonSourceWidth *= 2; + moonSourceHeight *= 2; + moonSourceX = this.spritePos.x + + (NightMode.phases[this.currentPhase] * 2); + starSize *= 2; + starSourceX = Runner.spriteDefinitionByType.original.HDPI.STAR.x; + } + + this.canvasCtx.save(); + this.canvasCtx.globalAlpha = this.opacity; + + // Stars. + if (this.drawStars) { + for (let i = 0; i < NightMode.config.NUM_STARS; i++) { + this.canvasCtx.drawImage( + Runner.origImageSprite, starSourceX, this.stars[i].sourceY, + starSize, starSize, Math.round(this.stars[i].x), this.stars[i].y, + NightMode.config.STAR_SIZE, NightMode.config.STAR_SIZE); + } + } + + // Moon. + this.canvasCtx.drawImage( + Runner.origImageSprite, moonSourceX, this.spritePos.y, moonSourceWidth, + moonSourceHeight, Math.round(this.xPos), this.yPos, moonOutputWidth, + NightMode.config.HEIGHT); + + this.canvasCtx.globalAlpha = 1; + this.canvasCtx.restore(); + }, + + // Do star placement. + placeStars() { + const segmentSize = Math.round(this.containerWidth / + NightMode.config.NUM_STARS); + + for (let i = 0; i < NightMode.config.NUM_STARS; i++) { + this.stars[i] = {}; + this.stars[i].x = getRandomNum(segmentSize * i, segmentSize * (i + 1)); + this.stars[i].y = getRandomNum(0, NightMode.config.STAR_MAX_Y); + + if (IS_HIDPI) { + this.stars[i].sourceY = + Runner.spriteDefinitionByType.original.HDPI.STAR.y + + NightMode.config.STAR_SIZE * 2 * i; + } else { + this.stars[i].sourceY = + Runner.spriteDefinitionByType.original.LDPI.STAR.y + + NightMode.config.STAR_SIZE * i; + } + } + }, + + reset() { + this.currentPhase = 0; + this.opacity = 0; + this.update(false); + }, + +}; + + +//****************************************************************************** + +/** + * Horizon Line. + * Consists of two connecting lines. Randomly assigns a flat / bumpy horizon. + * @param {HTMLCanvasElement} canvas + * @param {Object} lineConfig Configuration object. + * @constructor + */ +function HorizonLine(canvas, lineConfig) { + let sourceX = lineConfig.SOURCE_X; + let sourceY = lineConfig.SOURCE_Y; + + if (IS_HIDPI) { + sourceX *= 2; + sourceY *= 2; + } + + this.spritePos = {x: sourceX, y: sourceY}; + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (canvas.getContext('2d')); + this.sourceDimensions = {}; + this.dimensions = lineConfig; + + this.sourceXPos = [this.spritePos.x, this.spritePos.x + + this.dimensions.WIDTH]; + this.xPos = []; + this.yPos = 0; + this.bumpThreshold = 0.5; + + this.setSourceDimensions(lineConfig); + this.draw(); +} + + +/** + * Horizon line dimensions. + * @enum {number} + */ +HorizonLine.dimensions = { + WIDTH: 600, + HEIGHT: 12, + YPOS: 127, +}; + + +HorizonLine.prototype = { + /** + * Set the source dimensions of the horizon line. + */ + setSourceDimensions(newDimensions) { + for (const dimension in newDimensions) { + if (dimension !== 'SOURCE_X' && dimension !== 'SOURCE_Y') { + if (IS_HIDPI) { + if (dimension !== 'YPOS') { + this.sourceDimensions[dimension] = newDimensions[dimension] * 2; + } + } else { + this.sourceDimensions[dimension] = newDimensions[dimension]; + } + this.dimensions[dimension] = newDimensions[dimension]; + } + } + + this.xPos = [0, newDimensions.WIDTH]; + this.yPos = newDimensions.YPOS; + }, + + /** + * Return the crop x position of a type. + */ + getRandomType() { + return Math.random() > this.bumpThreshold ? this.dimensions.WIDTH : 0; + }, + + /** + * Draw the horizon line. + */ + draw() { + this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[0], + this.spritePos.y, + this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, + this.xPos[0], this.yPos, + this.dimensions.WIDTH, this.dimensions.HEIGHT); + + this.canvasCtx.drawImage(Runner.imageSprite, this.sourceXPos[1], + this.spritePos.y, + this.sourceDimensions.WIDTH, this.sourceDimensions.HEIGHT, + this.xPos[1], this.yPos, + this.dimensions.WIDTH, this.dimensions.HEIGHT); + }, + + /** + * Update the x position of an indivdual piece of the line. + * @param {number} pos Line position. + * @param {number} increment + */ + updateXPos(pos, increment) { + const line1 = pos; + const line2 = pos === 0 ? 1 : 0; + + this.xPos[line1] -= increment; + this.xPos[line2] = this.xPos[line1] + this.dimensions.WIDTH; + + if (this.xPos[line1] <= -this.dimensions.WIDTH) { + this.xPos[line1] += this.dimensions.WIDTH * 2; + this.xPos[line2] = this.xPos[line1] - this.dimensions.WIDTH; + this.sourceXPos[line1] = this.getRandomType() + this.spritePos.x; + } + }, + + /** + * Update the horizon line. + * @param {number} deltaTime + * @param {number} speed + */ + update(deltaTime, speed) { + const increment = Math.floor(speed * (FPS / 1000) * deltaTime); + + if (this.xPos[0] <= 0) { + this.updateXPos(0, increment); + } else { + this.updateXPos(1, increment); + } + this.draw(); + }, + + /** + * Reset horizon to the starting position. + */ + reset() { + this.xPos[0] = 0; + this.xPos[1] = this.dimensions.WIDTH; + }, +}; + + +//****************************************************************************** + +/** + * Horizon background class. + * @param {HTMLCanvasElement} canvas + * @param {Object} spritePos Sprite positioning. + * @param {Object} dimensions Canvas dimensions. + * @param {number} gapCoefficient + * @constructor + */ +function Horizon(canvas, spritePos, dimensions, gapCoefficient) { + this.canvas = canvas; + this.canvasCtx = + /** @type {CanvasRenderingContext2D} */ (this.canvas.getContext('2d')); + this.config = Horizon.config; + this.dimensions = dimensions; + this.gapCoefficient = gapCoefficient; + this.obstacles = []; + this.obstacleHistory = []; + this.horizonOffsets = [0, 0]; + this.cloudFrequency = this.config.CLOUD_FREQUENCY; + this.spritePos = spritePos; + this.nightMode = null; + this.altGameModeActive = false; + + // Cloud + this.clouds = []; + this.cloudSpeed = this.config.BG_CLOUD_SPEED; + + // Background elements + this.backgroundEls = []; + this.lastEl = null; + this.backgroundSpeed = this.config.BG_CLOUD_SPEED; + + // Horizon + this.horizonLine = null; + this.horizonLines = []; + this.init(); +} + + +/** + * Horizon config. + * @enum {number} + */ +Horizon.config = { + BG_CLOUD_SPEED: 0.2, + BUMPY_THRESHOLD: .3, + CLOUD_FREQUENCY: .5, + HORIZON_HEIGHT: 16, + MAX_CLOUDS: 6, +}; + + +Horizon.prototype = { + /** + * Initialise the horizon. Just add the line and a cloud. No obstacles. + */ + init() { + Obstacle.types = Runner.spriteDefinitionByType.original.OBSTACLES; + this.addCloud(); + // Multiple Horizon lines + for (let i = 0; i < Runner.spriteDefinition.LINES.length; i++) { + this.horizonLines.push( + new HorizonLine(this.canvas, Runner.spriteDefinition.LINES[i])); + } + + this.nightMode = new NightMode(this.canvas, this.spritePos.MOON, + this.dimensions.WIDTH); + }, + + /** + * Update obstacle definitions based on the speed of the game. + */ + adjustObstacleSpeed: function() { + for (let i = 0; i < Obstacle.types.length; i++) { + if (Runner.slowDown) { + Obstacle.types[i].multipleSpeed = Obstacle.types[i].multipleSpeed / 2; + Obstacle.types[i].minGap *= 1.5; + Obstacle.types[i].minSpeed = Obstacle.types[i].minSpeed / 2; + + // Convert variable y position obstacles to fixed. + if (typeof (Obstacle.types[i].yPos) == 'object') { + Obstacle.types[i].yPos = Obstacle.types[i].yPos[0]; + Obstacle.types[i].yPosMobile = Obstacle.types[i].yPos[0]; + } + } + } + }, + + /** + * Update sprites to correspond to change in sprite sheet. + * @param {number} spritePos + */ + enableAltGameMode: function(spritePos) { + // Clear existing horizon objects. + this.clouds = []; + this.backgroundEls = []; + + this.altGameModeActive = true; + this.spritePos = spritePos; + + Obstacle.types = Runner.spriteDefinition.OBSTACLES; + this.adjustObstacleSpeed(); + + Obstacle.MAX_GAP_COEFFICIENT = Runner.spriteDefinition.MAX_GAP_COEFFICIENT; + Obstacle.MAX_OBSTACLE_LENGTH = Runner.spriteDefinition.MAX_OBSTACLE_LENGTH; + + BackgroundEl.config = Runner.spriteDefinition.BACKGROUND_EL_CONFIG; + + this.horizonLines = []; + for (let i = 0; i < Runner.spriteDefinition.LINES.length; i++) { + this.horizonLines.push( + new HorizonLine(this.canvas, Runner.spriteDefinition.LINES[i])); + } + this.reset(); + }, + + /** + * @param {number} deltaTime + * @param {number} currentSpeed + * @param {boolean} updateObstacles Used as an override to prevent + * the obstacles from being updated / added. This happens in the + * ease in section. + * @param {boolean} showNightMode Night mode activated. + */ + update(deltaTime, currentSpeed, updateObstacles, showNightMode) { + this.runningTime += deltaTime; + + if (this.altGameModeActive) { + this.updateBackgroundEls(deltaTime, currentSpeed); + } + + for (let i = 0; i < this.horizonLines.length; i++) { + this.horizonLines[i].update(deltaTime, currentSpeed); + } + + if (!this.altGameModeActive || Runner.spriteDefinition.HAS_CLOUDS) { + this.nightMode.update(showNightMode); + this.updateClouds(deltaTime, currentSpeed); + } + + if (updateObstacles) { + this.updateObstacles(deltaTime, currentSpeed); + } + }, + + /** + * Update background element positions. Also handles creating new elements. + * @param {number} elSpeed + * @param {Array} bgElArray + * @param {number} maxBgEl + * @param {Function} bgElAddFunction + * @param {number} frequency + */ + updateBackgroundEl(elSpeed, bgElArray, maxBgEl, bgElAddFunction, frequency) { + const numElements = bgElArray.length; + + if (numElements) { + for (let i = numElements - 1; i >= 0; i--) { + bgElArray[i].update(elSpeed); + } + + const lastEl = bgElArray[numElements - 1]; + + // Check for adding a new element. + if (numElements < maxBgEl && + (this.dimensions.WIDTH - lastEl.xPos) > lastEl.gap && + frequency > Math.random()) { + bgElAddFunction(); + } + } else { + bgElAddFunction(); + } + }, + + /** + * Update the cloud positions. + * @param {number} deltaTime + * @param {number} speed + */ + updateClouds(deltaTime, speed) { + const elSpeed = this.cloudSpeed / 1000 * deltaTime * speed; + this.updateBackgroundEl( + elSpeed, this.clouds, this.config.MAX_CLOUDS, this.addCloud.bind(this), + this.cloudFrequency); + + // Remove expired elements. + this.clouds = this.clouds.filter((obj) => !obj.remove); + }, + + /** + * Update the background element positions. + * @param {number} deltaTime + * @param {number} speed + */ + updateBackgroundEls(deltaTime, speed) { + this.updateBackgroundEl( + deltaTime, this.backgroundEls, BackgroundEl.config.MAX_BG_ELS, + this.addBackgroundEl.bind(this), this.cloudFrequency); + + // Remove expired elements. + this.backgroundEls = this.backgroundEls.filter((obj) => !obj.remove); + }, + + /** + * Update the obstacle positions. + * @param {number} deltaTime + * @param {number} currentSpeed + */ + updateObstacles(deltaTime, currentSpeed) { + const updatedObstacles = this.obstacles.slice(0); + + for (let i = 0; i < this.obstacles.length; i++) { + const obstacle = this.obstacles[i]; + obstacle.update(deltaTime, currentSpeed); + + // Clean up existing obstacles. + if (obstacle.remove) { + updatedObstacles.shift(); + } + } + this.obstacles = updatedObstacles; + + if (this.obstacles.length > 0) { + const lastObstacle = this.obstacles[this.obstacles.length - 1]; + + if (lastObstacle && !lastObstacle.followingObstacleCreated && + lastObstacle.isVisible() && + (lastObstacle.xPos + lastObstacle.width + lastObstacle.gap) < + this.dimensions.WIDTH) { + this.addNewObstacle(currentSpeed); + lastObstacle.followingObstacleCreated = true; + } + } else { + // Create new obstacles. + this.addNewObstacle(currentSpeed); + } + }, + + removeFirstObstacle() { + this.obstacles.shift(); + }, + + /** + * Add a new obstacle. + * @param {number} currentSpeed + */ + addNewObstacle(currentSpeed) { + const obstacleCount = + Obstacle.types[Obstacle.types.length - 1].type != 'COLLECTABLE' || + (Runner.isAltGameModeEnabled() && !this.altGameModeActive || + this.altGameModeActive) ? + Obstacle.types.length - 1 : + Obstacle.types.length - 2; + const obstacleTypeIndex = + obstacleCount > 0 ? getRandomNum(0, obstacleCount) : 0; + const obstacleType = Obstacle.types[obstacleTypeIndex]; + + // Check for multiples of the same type of obstacle. + // Also check obstacle is available at current speed. + if ((obstacleCount > 0 && this.duplicateObstacleCheck(obstacleType.type)) || + currentSpeed < obstacleType.minSpeed) { + this.addNewObstacle(currentSpeed); + } else { + const obstacleSpritePos = this.spritePos[obstacleType.type]; + + this.obstacles.push(new Obstacle( + this.canvasCtx, obstacleType, obstacleSpritePos, this.dimensions, + this.gapCoefficient, currentSpeed, obstacleType.width, + this.altGameModeActive)); + + this.obstacleHistory.unshift(obstacleType.type); + + if (this.obstacleHistory.length > 1) { + this.obstacleHistory.splice(Runner.config.MAX_OBSTACLE_DUPLICATION); + } + } + }, + + /** + * Returns whether the previous two obstacles are the same as the next one. + * Maximum duplication is set in config value MAX_OBSTACLE_DUPLICATION. + * @return {boolean} + */ + duplicateObstacleCheck(nextObstacleType) { + let duplicateCount = 0; + + for (let i = 0; i < this.obstacleHistory.length; i++) { + duplicateCount = + this.obstacleHistory[i] === nextObstacleType ? duplicateCount + 1 : 0; + } + return duplicateCount >= Runner.config.MAX_OBSTACLE_DUPLICATION; + }, + + /** + * Reset the horizon layer. + * Remove existing obstacles and reposition the horizon line. + */ + reset() { + this.obstacles = []; + for (let l = 0; l < this.horizonLines.length; l++) { + this.horizonLines[l].reset(); + } + + this.nightMode.reset(); + }, + + /** + * Update the canvas width and scaling. + * @param {number} width Canvas width. + * @param {number} height Canvas height. + */ + resize(width, height) { + this.canvas.width = width; + this.canvas.height = height; + }, + + /** + * Add a new cloud to the horizon. + */ + addCloud() { + this.clouds.push(new Cloud(this.canvas, this.spritePos.CLOUD, + this.dimensions.WIDTH)); + }, + + /** + * Add a random background element to the horizon. + */ + addBackgroundEl() { + const backgroundElTypes = + Object.keys(Runner.spriteDefinition.BACKGROUND_EL); + + if (backgroundElTypes.length > 0) { + let index = getRandomNum(0, backgroundElTypes.length - 1); + let type = backgroundElTypes[index]; + + // Add variation if available. + while (type == this.lastEl && backgroundElTypes.length > 1) { + index = getRandomNum(0, backgroundElTypes.length - 1); + type = backgroundElTypes[index]; + } + + this.lastEl = type; + this.backgroundEls.push(new BackgroundEl( + this.canvas, this.spritePos.BACKGROUND_EL, this.dimensions.WIDTH, + type)); + } + }, +}; diff --git a/chrome-dino/scripts/strings.js b/chrome-dino/scripts/strings.js new file mode 100644 index 00000000..d8326761 --- /dev/null +++ b/chrome-dino/scripts/strings.js @@ -0,0 +1,2 @@ +var pageData = {"altGameCommonImage1x":"images/default_100_percent/offline/100-olympic-firemedal-sprites.png","altGameCommonImage2x":"images/default_200_percent/offline/200-olympic-firemedal-sprites.png","dinoGameA11yAriaLabel":"","dinoGameA11yGameOver":"Game over, your score is $1.","dinoGameA11yHighScore":"Your highest score is $1.","dinoGameA11yJump":"Jump!","dinoGameA11ySpeedToggle":"Start slower","dinoGameA11yStartGame":"Game started.","enableAltGameMode":false,"errorCode":"","fontfamily":"'Segoe UI', Tahoma, sans-serif","fontsize":"75%","heading":{"hostName":"dino","msg":"Press space to play"},"iconClass":"icon-offline","language":"en","textdirection":"ltr","title":"chrome://dino/"}; +loadTimeData.data = pageData; \ No newline at end of file diff --git a/chrome-dino/style.css b/chrome-dino/style.css new file mode 100644 index 00000000..579e7261 --- /dev/null +++ b/chrome-dino/style.css @@ -0,0 +1,209 @@ +/* Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +html, body { + padding: 0; + margin: 0; + width: 100%; + height: 100%; +} + +.icon { + -webkit-user-select: none; + user-select: none; + display: inline-block; +} + + +.hidden { + display: none; +} + + +/* Offline page */ +.offline { + transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1), + background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); +} + +.offline body { + transition: background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); +} +.offline.inverted { + background-color: #000; + filter: invert(1); +} + +.offline.inverted body { + background-color: #fff; +} + +.offline .interstitial-wrapper { + color: #2b2b2b; + font-size: 0.8em; + line-height: 1.55; + margin: 0 auto; + max-width: 600px; + padding-top: 150px; + width: 100%; +} + +.offline .runner-container { + direction: ltr; + height: 150px; + max-width: 600px; + overflow: hidden; + position: absolute; + top: 35px; + width: 44px; +} + +.offline .runner-canvas { + height: 150px; + max-width: 600px; + opacity: 1; + overflow: hidden; + position: absolute; + top: 0; + z-index: 10; +} + +.offline .controller { + background: rgba(247, 247, 247, .1); + height: 100vh; + left: 0; + position: absolute; + top: 0; + width: 100vw; + z-index: 1; +} + +#offline-resources { + display: none; +} + + +.arcade-mode, +.arcade-mode .runner-container, +.arcade-mode .runner-canvas { + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + max-width: 100%; + overflow: hidden; +} + +.arcade-mode #buttons, +.arcade-mode #main-content { + opacity: 0; + overflow: hidden; +} + +.arcade-mode .interstitial-wrapper { + height: 100vh; + max-width: 100%; + overflow: hidden; +} + +.arcade-mode .runner-container { + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + left: 0; + margin: auto; + right: 0; + transform-origin: top center; + transition: transform 250ms cubic-bezier(0.4, 0, 1, 1) 400ms; + z-index: 2; +} + + +@media (prefers-color-scheme: dark) { + + body { + background-color: #000; + } + + .offline .runner-canvas { + filter: invert(1); + } + + .offline.inverted { + background-color: #fff; + filter: invert(1); + } + + .offline.inverted body { + background-color: #fff; + } + + h1{filter: invert(1);} +} + +@media (max-width: 420px) { + .suggested-left > #control-buttons, .suggested-right > #control-buttons { + float: none; + } + .snackbar { + left: 0; + bottom: 0; + width: 100%; + border-radius: 0; + } +} + +@media (max-height: 350px) { + h1 { + margin: 0 0 15px; + } + .icon-offline { + margin: 0 0 10px; + } + .interstitial-wrapper { + margin-top: 5%; + } + .nav-wrapper { + margin-top: 30px; + } +} + +@media (min-width: 600px) and (max-width: 736px) and (orientation: landscape) { + .offline .interstitial-wrapper { + margin-left: 0; + margin-right: 0; + } +} + +@media (min-width: 420px) and (max-width: 736px) and (min-height: 240px) and (max-height: 420px) and (orientation:landscape) { + .interstitial-wrapper { + margin-bottom: 100px; + } +} + +@media (min-height: 240px) and (orientation: landscape) { + .offline .interstitial-wrapper { + margin-bottom: 90px; + } + .icon-offline { + margin-bottom: 20px; + } +} + +@media (max-height: 320px) and (orientation: landscape) { + .icon-offline { + margin-bottom: 0; + } + .offline .runner-container { + top: 10px; + } +} + +@media (max-width: 240px) { + .interstitial-wrapper { + overflow: inherit; + padding: 0 8px; + } +} \ No newline at end of file diff --git a/chrome-dino/styles/interstitial_common.css b/chrome-dino/styles/interstitial_common.css new file mode 100644 index 00000000..38b07598 --- /dev/null +++ b/chrome-dino/styles/interstitial_common.css @@ -0,0 +1,517 @@ +/* Copyright 2014 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. */ + +button { + border: 0; + border-radius: 4px; + box-sizing: border-box; + color: var(--primary-button-text-color); + cursor: pointer; + float: right; + font-size: .875em; + margin: 0; + padding: 8px 16px; + transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1); + user-select: none; +} + +[dir='rtl'] button { + float: left; +} + +.bad-clock button, +.captive-portal button, +.https-only button, +.insecure-form button, +.lookalike-url button, +.main-frame-blocked button, +.neterror button, +.pdf button, +.ssl button, +.safe-browsing-billing button { + background: var(--primary-button-fill-color); +} + +button:active { + background: var(--primary-button-fill-color-active); + outline: 0; +} + +#debugging { + display: inline; + overflow: auto; +} + +.debugging-content { + line-height: 1em; + margin-bottom: 0; + margin-top: 1em; +} + +.debugging-content-fixed-width { + display: block; + font-family: monospace; + font-size: 1.2em; + margin-top: 0.5em; +} + +.debugging-title { + font-weight: bold; +} + +#details { + margin: 0 0 50px; +} + +#details p:not(:first-of-type) { + margin-top: 20px; +} + +.secondary-button:active { + border-color: white; + box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .3), + 0 2px 6px 2px rgba(60, 64, 67, .15); +} + +.secondary-button:hover { + background: var(--secondary-button-hover-fill-color); + border-color: var(--secondary-button-hover-border-color); + text-decoration: none; +} + +.error-code { + color: var(--error-code-color); + font-size: .8em; + margin-top: 12px; + text-transform: uppercase; +} + +#error-debugging-info { + font-size: 0.8em; +} + +h1 { + color: var(--heading-color); + font-size: 1.6em; + font-weight: normal; + line-height: 1.25em; + margin-bottom: 16px; +} + +h2 { + font-size: 1.2em; + font-weight: normal; +} + +.icon { + height: 72px; + margin: 0 0 40px; + width: 72px; +} + +input[type=checkbox] { + opacity: 0; +} + +input[type=checkbox]:focus ~ .checkbox::after { + outline: -webkit-focus-ring-color auto 5px; +} + +.interstitial-wrapper { + box-sizing: border-box; + font-size: 1em; + line-height: 1.6em; + margin: 14vh auto 0; + max-width: 600px; + width: 100%; +} + +#main-message > p { + display: inline; +} + +#extended-reporting-opt-in { + font-size: .875em; + margin-top: 32px; +} + +#extended-reporting-opt-in label { + display: grid; + grid-template-columns: 1.8em 1fr; + position: relative; +} + +#enhanced-protection-message { + border-radius: 4px; + font-size: 1em; + margin-top: 32px; + padding: 10px 5px; +} + +#enhanced-protection-message label { + display: grid; + grid-template-columns: 2.5em 1fr; + position: relative; +} + +#enhanced-protection-message div { + margin: 0.5em; +} + +#enhanced-protection-message .icon { + height: 1.5em; + vertical-align: middle; + width: 1.5em; +} + +.nav-wrapper { + margin-top: 51px; +} + +.nav-wrapper::after { + clear: both; + content: ''; + display: table; + width: 100%; +} + +.small-link { + color: var(--small-link-color); + font-size: .875em; +} + +.checkboxes { + flex: 0 0 24px; +} + +.checkbox { + --padding: .9em; + background: transparent; + display: block; + height: 1em; + left: -1em; + padding-inline-start: var(--padding); + position: absolute; + right: 0; + top: -.5em; + width: 1em; +} + +.checkbox::after { + border: 1px solid white; + border-radius: 2px; + content: ''; + height: 1em; + left: var(--padding); + position: absolute; + top: var(--padding); + width: 1em; +} + +.checkbox::before { + background: transparent; + border: 2px solid white; + border-inline-end-width: 0; + border-top-width: 0; + content: ''; + height: .2em; + left: calc(.3em + var(--padding)); + opacity: 0; + position: absolute; + top: calc(.3em + var(--padding)); + transform: rotate(-45deg); + width: .5em; +} + +input[type=checkbox]:checked ~ .checkbox::before { + opacity: 1; +} + +#recurrent-error-message { + background: #ededed; + border-radius: 4px; + margin-bottom: 16px; + margin-top: 12px; + padding: 12px 16px; +} + +.showing-recurrent-error-message #extended-reporting-opt-in { + margin-top: 16px; +} + +.showing-recurrent-error-message #enhanced-protection-message { + margin-top: 16px; +} + +@media (max-width: 700px) { + .interstitial-wrapper { + padding: 0 10%; + } + + #error-debugging-info { + overflow: auto; + } +} + +@media (max-width: 420px) { + button, + [dir='rtl'] button, + .small-link { + float: none; + font-size: .825em; + font-weight: 500; + margin: 0; + width: 100%; + } + + button { + padding: 16px 24px; + } + + #details { + margin: 20px 0 20px 0; + } + + #details p:not(:first-of-type) { + margin-top: 10px; + } + + .secondary-button:not(.hidden) { + display: block; + margin-top: 20px; + text-align: center; + width: 100%; + } + + .interstitial-wrapper { + padding: 0 5%; + } + + #extended-reporting-opt-in { + margin-top: 24px; + } + + #enhanced-protection-message { + margin-top: 24px; + } + + .nav-wrapper { + margin-top: 30px; + } +} + +/** + * Mobile specific styling. + * Navigation buttons are anchored to the bottom of the screen. + * Details message replaces the top content in its own scrollable area. + */ + +@media (max-width: 420px) { + .nav-wrapper .secondary-button { + border: 0; + margin: 16px 0 0; + margin-inline-end: 0; + padding-bottom: 16px; + padding-top: 16px; + } +} + +/* Fixed nav. */ +@media (min-width: 240px) and (max-width: 420px) and + (min-height: 401px), + (min-width: 421px) and (min-height: 240px) and + (max-height: 560px) { + body .nav-wrapper { + background: var(--background-color); + bottom: 0; + box-shadow: 0 -12px 24px var(--background-color); + left: 0; + margin: 0 auto; + max-width: 736px; + padding-inline-end: 24px; + padding-inline-start: 24px; + position: fixed; + right: 0; + width: 100%; + z-index: 2; + } + + .interstitial-wrapper { + max-width: 736px; + } + + #details, + #main-content { + padding-bottom: 40px; + } + + #details { + padding-top: 5.5vh; + } + + button.small-link { + color: var(--google-blue-600); + } +} + +@media (max-width: 420px) and (orientation: portrait), + (max-height: 560px) { + body { + margin: 0 auto; + } + + button, + [dir='rtl'] button, + button.small-link, + .nav-wrapper .secondary-button { + font-family: Roboto-Regular,Helvetica; + font-size: .933em; + margin: 6px 0; + transform: translatez(0); + } + + .nav-wrapper { + box-sizing: border-box; + padding-bottom: 8px; + width: 100%; + } + + #details { + box-sizing: border-box; + height: auto; + margin: 0; + opacity: 1; + transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1); + } + + #details.hidden, + #main-content.hidden { + height: 0; + opacity: 0; + overflow: hidden; + padding-bottom: 0; + transition: none; + } + + h1 { + font-size: 1.5em; + margin-bottom: 8px; + } + + .icon { + margin-bottom: 5.69vh; + } + + .interstitial-wrapper { + box-sizing: border-box; + margin: 7vh auto 12px; + padding: 0 24px; + position: relative; + } + + .interstitial-wrapper p { + font-size: .95em; + line-height: 1.61em; + margin-top: 8px; + } + + #main-content { + margin: 0; + transition: opacity 100ms cubic-bezier(0.4, 0, 0.2, 1); + } + + .small-link { + border: 0; + } + + .suggested-left > #control-buttons, + .suggested-right > #control-buttons { + float: none; + margin: 0; + } +} + +@media (min-width: 421px) and (min-height: 500px) and (max-height: 560px) { + .interstitial-wrapper { + margin-top: 10vh; + } +} + +@media (min-height: 400px) and (orientation:portrait) { + .interstitial-wrapper { + margin-bottom: 145px; + } +} + +@media (min-height: 299px) { + .nav-wrapper { + padding-bottom: 16px; + } +} + +@media (max-height: 560px) and (min-height: 240px) and (orientation:landscape) { + .extended-reporting-has-checkbox #details { + padding-bottom: 80px; + } +} + +@media (min-height: 500px) and (max-height: 650px) and (max-width: 414px) and + (orientation: portrait) { + .interstitial-wrapper { + margin-top: 7vh; + } +} + +@media (min-height: 650px) and (max-width: 414px) and (orientation: portrait) { + .interstitial-wrapper { + margin-top: 10vh; + } +} + +/* Small mobile screens. No fixed nav. */ +@media (max-height: 400px) and (orientation: portrait), + (max-height: 239px) and (orientation: landscape), + (max-width: 419px) and (max-height: 399px) { + .interstitial-wrapper { + display: flex; + flex-direction: column; + margin-bottom: 0; + } + + #details { + flex: 1 1 auto; + order: 0; + } + + #main-content { + flex: 1 1 auto; + order: 0; + } + + .nav-wrapper { + flex: 0 1 auto; + margin-top: 8px; + order: 1; + padding-inline-end: 0; + padding-inline-start: 0; + position: relative; + width: 100%; + } + + button, + .nav-wrapper .secondary-button { + padding: 16px 24px; + } + + button.small-link { + color: var(--google-blue-600); + } +} + +@media (max-width: 239px) and (orientation: portrait) { + .nav-wrapper { + padding-inline-end: 0; + padding-inline-start: 0; + } +} diff --git a/chrome-dino/styles/interstitial_core.css b/chrome-dino/styles/interstitial_core.css new file mode 100644 index 00000000..cd249d1a --- /dev/null +++ b/chrome-dino/styles/interstitial_core.css @@ -0,0 +1,100 @@ +:root { + --background-color: #fff; + --google-blue-100: rgb(210, 227, 252); + --google-blue-300: rgb(138, 180, 248); + --google-blue-600: rgb(26, 115, 232); + --google-blue-700: rgb(25, 103, 210); + --google-gray-100: rgb(241, 243, 244); + --google-gray-300: rgb(218, 220, 224); + --google-gray-500: rgb(154, 160, 166); + --google-gray-50: rgb(248, 249, 250); + --google-gray-600: rgb(128, 134, 139); + --google-gray-700: rgb(95, 99, 104); + --google-gray-800: rgb(60, 64, 67); + --google-gray-900: rgb(32, 33, 36); +} + + +/* Copyright 2017 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +a { + color: var(--link-color); +} + +body { + --background-color: #fff; + --error-code-color: var(--google-gray-700); + --google-blue-100: rgb(210, 227, 252); + --google-blue-300: rgb(138, 180, 248); + --google-blue-600: rgb(26, 115, 232); + --google-blue-700: rgb(25, 103, 210); + --google-gray-100: rgb(241, 243, 244); + --google-gray-300: rgb(218, 220, 224); + --google-gray-500: rgb(154, 160, 166); + --google-gray-50: rgb(248, 249, 250); + --google-gray-600: rgb(128, 134, 139); + --google-gray-700: rgb(95, 99, 104); + --google-gray-800: rgb(60, 64, 67); + --google-gray-900: rgb(32, 33, 36); + --heading-color: var(--google-gray-900); + --link-color: rgb(88, 88, 88); + --popup-container-background-color: rgba(0,0,0,.65); + --primary-button-fill-color-active: var(--google-blue-700); + --primary-button-fill-color: var(--google-blue-600); + --primary-button-text-color: #fff; + --quiet-background-color: rgb(247, 247, 247); + --secondary-button-border-color: var(--google-gray-500); + --secondary-button-fill-color: #fff; + --secondary-button-hover-border-color: var(--google-gray-600); + --secondary-button-hover-fill-color: var(--google-gray-50); + --secondary-button-text-color: var(--google-gray-700); + --small-link-color: var(--google-gray-700); + --text-color: var(--google-gray-700); + background: var(--background-color); + color: var(--text-color); + word-wrap: break-word; +} + +.nav-wrapper .secondary-button { + background: var(--secondary-button-fill-color); + border: 1px solid var(--secondary-button-border-color); + color: var(--secondary-button-text-color); + float: none; + margin: 0; + padding: 8px 16px; +} + +.hidden { + display: none; +} + +html { + -webkit-text-size-adjust: 100%; + font-size: 125%; +} + +.icon { + background-repeat: no-repeat; + background-size: 100%; +} + +@media (prefers-color-scheme: dark) { + body { + --background-color: var(--google-gray-900); + --error-code-color: var(--google-gray-500); + --heading-color: var(--google-gray-500); + --link-color: var(--google-blue-300); + --primary-button-fill-color-active: rgb(129, 162, 208); + --primary-button-fill-color: var(--google-blue-300); + --primary-button-text-color: var(--google-gray-900); + --quiet-background-color: var(--background-color); + --secondary-button-border-color: var(--google-gray-700); + --secondary-button-fill-color: var(--google-gray-900); + --secondary-button-hover-fill-color: rgb(48, 51, 57); + --secondary-button-text-color: var(--google-blue-300); + --small-link-color: var(--google-blue-300); + --text-color: var(--google-gray-500); + } +} diff --git a/chrome-dino/styles/neterror.css b/chrome-dino/styles/neterror.css new file mode 100644 index 00000000..78d08300 --- /dev/null +++ b/chrome-dino/styles/neterror.css @@ -0,0 +1,429 @@ +* { + font-family: system-ui, 'Segoe UI', Tahoma, sans-serif; +} + +/* Copyright 2013 The Chromium Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. */ + +/* Don't use the main frame div when the error is in a subframe. */ +html[subframe] #main-frame-error { + display: none; +} + +/* Don't use the subframe error div when the error is in a main frame. */ +html:not([subframe]) #sub-frame-error { + display: none; +} + +h1 { + margin-top: 0; + word-wrap: break-word; +} + +h1 span { + font-weight: 500; +} + +a { + text-decoration: none; +} + +.icon { + -webkit-user-select: none; + display: inline-block; +} + +.icon-offline { + content: -webkit-image-set( + url(../images/default_100_percent/offline/100-error-offline.png) 1x, + url(../images/default_200_percent/offline/200-error-offline.png) 2x); + position: relative; +} + +.hidden { + display: none; +} + +/* Offline page */ +html[dir='rtl'] .runner-container, +html[dir='rtl'].offline .icon-offline { + transform: scaleX(-1); +} + +.offline { + transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1), + background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); +} + +.offline body { + transition: background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); +} + +.offline #main-message > p { + display: none; +} + +.offline.inverted { + background-color: #dfdedb; /* --google-gray-900 inverted value */ + filter: invert(1); +} + +.offline.inverted body { + background-color: #dfdedb; +} + +.offline .interstitial-wrapper { + color: var(--text-color); + font-size: 1em; + line-height: 1.55; + margin: 0 auto; + max-width: 600px; + padding-top: 100px; + position: relative; + width: 100%; +} + +.offline .runner-container { + direction: ltr; + height: 150px; + max-width: 600px; + overflow: hidden; + position: absolute; + top: 35px; + width: 44px; +} + +.offline .runner-container:focus { + outline: none; +} + +.offline .runner-container:focus-visible { + /* outline: 3px solid var(--google-blue-300); */ + outline: none; +} + +.offline .runner-canvas { + height: 150px; + max-width: 600px; + opacity: 1; + overflow: hidden; + position: absolute; + top: 0; + z-index: 10; +} + +.offline .controller { + height: 100vh; + left: 0; + position: absolute; + top: 0; + width: 100vw; + z-index: 9; +} + +#offline-resources { + display: none; +} + +#offline-instruction { + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 60px; + width: fit-content; +} + +.offline-runner-live-region { + bottom: 0; + clip-path: polygon(0 0, 0 0, 0 0); + color: var(--background-color); + display: block; + font-size: xx-small; + overflow: hidden; + position: absolute; + text-align: center; + transition: color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); + user-select: none; +} + +/* Custom toggle */ +.slow-speed-option { + align-items: center; + background: var(--google-gray-50); + border-radius: 24px/50%; + bottom: 0; + color: var(--error-code-color); + /*display: inline-flex;*/ + display: none; + font-size: 1em; + left: 0; + line-height: 1.1em; + margin: 5px auto; + padding: 2px 12px 3px 20px; + position: absolute; + right: 0; + width: max-content; + z-index: 999; +} + +.slow-speed-option.hidden { + display: none; +} + +.slow-speed-option [type=checkbox] { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.slow-speed-option .slow-speed-toggle { + cursor: pointer; + margin-inline-start: 8px; + padding: 8px 4px; + position: relative; +} + +.slow-speed-option [type=checkbox]:disabled ~ .slow-speed-toggle { + cursor: default; +} + +.slow-speed-option-label [type=checkbox] { + opacity: 0; + pointer-events: none; + position: absolute; +} + +.slow-speed-option .slow-speed-toggle::before, +.slow-speed-option .slow-speed-toggle::after { + content: ''; + display: block; + margin: 0 3px; + transition: all 100ms cubic-bezier(0.4, 0, 1, 1); +} + +.slow-speed-option .slow-speed-toggle::before { + background: rgb(189,193,198); + border-radius: 0.65em; + height: 0.9em; + width: 2em; +} + +.slow-speed-option .slow-speed-toggle::after { + background: #fff; + border-radius: 50%; + box-shadow: 0 1px 3px 0 rgb(0 0 0 / 40%); + height: 1.2em; + position: absolute; + top: 51%; + transform: translate(-20%, -50%); + width: 1.1em; +} + +.slow-speed-option [type=checkbox]:focus + .slow-speed-toggle { + box-shadow: 0 0 8px rgb(94, 158, 214); + outline: 1px solid rgb(93, 157, 213); +} + +.slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::before { + background: var(--google-blue-600); + opacity: 0.5; +} + +.slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::after { + background: var(--google-blue-600); + transform: translate(calc(2em - 90%), -50%); +} + +.slow-speed-option [type=checkbox]:checked:disabled + + .slow-speed-toggle::before { + background: rgb(189,193,198); +} + +.slow-speed-option [type=checkbox]:checked:disabled + + .slow-speed-toggle::after { + background: var(--google-gray-50); +} + +@media (max-width: 420px) { + #download-button { + padding-bottom: 12px; + padding-top: 12px; + } + + .suggested-left > #control-buttons, + .suggested-right > #control-buttons { + float: none; + } + + .snackbar { + border-radius: 0; + bottom: 0; + left: 0; + width: 100%; + } +} + +@media (max-height: 350px) { + h1 { + margin: 0 0 15px; + } + + .icon-offline { + margin: 0 0 10px; + } + + .interstitial-wrapper { + margin-top: 5%; + } + + .nav-wrapper { + margin-top: 30px; + } +} + +@media (min-width: 420px) and (max-width: 736px) and + (min-height: 240px) and (max-height: 420px) and + (orientation:landscape) { + .interstitial-wrapper { + margin-bottom: 100px; + } +} + +@media (max-width: 360px) and (max-height: 480px) { + .offline .interstitial-wrapper { + padding-top: 60px; + } + + .offline .runner-container { + top: 8px; + } +} + +@media (min-height: 240px) and (orientation: landscape) { + .offline .interstitial-wrapper { + margin-bottom: 90px; + } + + .icon-offline { + margin-bottom: 20px; + } +} + +@media (max-height: 320px) and (orientation: landscape) { + .icon-offline { + margin-bottom: 0; + } + + .offline .runner-container { + top: 10px; + } +} + +@media (max-width: 240px) { + button { + padding-inline-end: 12px; + padding-inline-start: 12px; + } + + .interstitial-wrapper { + overflow: inherit; + padding: 0 8px; + } +} + +@media (max-width: 120px) { + button { + width: auto; + } +} + +.arcade-mode, +.arcade-mode .runner-container, +.arcade-mode .runner-canvas { + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-crisp-edges; + image-rendering: pixelated; + image-rendering: crisp-edges; + max-width: 100%; + overflow: hidden; +} + +.arcade-mode #buttons, +.arcade-mode #main-content { + opacity: 0; + overflow: hidden; +} + +.arcade-mode .interstitial-wrapper { + height: 100vh; + max-width: 100%; + overflow: hidden; +} + +.arcade-mode .runner-container { + left: 0; + margin: auto; + right: 0; + transform-origin: top center; + transition: transform 250ms cubic-bezier(0.4, 0, 1, 1) 400ms; + z-index: 2; +} + +@media (prefers-color-scheme: dark) { + .icon { + filter: invert(1); + } + + .offline .runner-canvas { + filter: invert(1); + transition: filter 1.5s cubic-bezier(0.65, 0.05, 0.36, 1), + background-color 1.5s cubic-bezier(0.65, 0.05, 0.36, 1); + } + + .offline.inverted .runner-canvas { + filter: invert(0); + } + + .offline.inverted { + background-color: var(--background-color); + filter: invert(0); + } + + .offline.inverted body { + background-color: #fff; + } + + .offline.inverted .offline-runner-live-region { + color: #fff; + } + + #suggestions-list a { + color: var(--link-color); + } + + #error-information-button { + filter: invert(0.6); + } + + .slow-speed-option { + background: var(--google-gray-800); + color: var(--google-gray-100); + } + + .slow-speed-option .slow-speed-toggle::before, + .slow-speed-option [type=checkbox]:checked:disabled + + .slow-speed-toggle::before { + background: rgb(189,193,198); + } + + .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::after, + .slow-speed-option [type=checkbox]:checked + .slow-speed-toggle::before { + background: var(--google-blue-300); + } +} diff --git a/chrome-dino/sw.js b/chrome-dino/sw.js new file mode 100644 index 00000000..bf4ccad0 --- /dev/null +++ b/chrome-dino/sw.js @@ -0,0 +1,399 @@ +"use strict"; + +const OFFLINE_DATA_FILE = "offline.js"; +const CACHE_NAME_PREFIX = "chrome-dino"; +const BROADCASTCHANNEL_NAME = "offline"; +const CONSOLE_PREFIX = "[SW] "; +const LAZYLOAD_KEYNAME = ""; + +// Create a BroadcastChannel if supported. +const broadcastChannel = (typeof BroadcastChannel === "undefined" ? null : new BroadcastChannel(BROADCASTCHANNEL_NAME)); + +////////////////////////////////////// +// Utility methods +function PostBroadcastMessage(o) +{ + if (!broadcastChannel) + return; // not supported + + // Impose artificial (and arbitrary!) delay of 3 seconds to make sure client is listening by the time the message is sent. + // Note we could remove the delay on some messages, but then we create a race condition where sometimes messages can arrive + // in the wrong order (e.g. "update ready" arrives before "started downloading update"). So to keep the consistent ordering, + // delay all messages by the same amount. + setTimeout(() => broadcastChannel.postMessage(o), 3000); +}; + +function Broadcast(type) +{ + PostBroadcastMessage({ + "type": type + }); +}; + +function BroadcastDownloadingUpdate(version) +{ + PostBroadcastMessage({ + "type": "downloading-update", + "version": version + }); +} + +function BroadcastUpdateReady(version) +{ + PostBroadcastMessage({ + "type": "update-ready", + "version": version + }); +} + +function IsUrlInLazyLoadList(url, lazyLoadList) +{ + if (!lazyLoadList) + return false; // presumably lazy load list failed to load + + try { + for (const lazyLoadRegex of lazyLoadList) + { + if (new RegExp(lazyLoadRegex).test(url)) + return true; + } + } + catch (err) + { + console.error(CONSOLE_PREFIX + "Error matching in lazy-load list: ", err); + } + + return false; +}; + +function WriteLazyLoadListToStorage(lazyLoadList) +{ + if (typeof localforage === "undefined") + return Promise.resolve(); // bypass if localforage not imported + else + return localforage.setItem(LAZYLOAD_KEYNAME, lazyLoadList) +}; + +function ReadLazyLoadListFromStorage() +{ + if (typeof localforage === "undefined") + return Promise.resolve([]); // bypass if localforage not imported + else + return localforage.getItem(LAZYLOAD_KEYNAME); +}; + +function GetCacheBaseName() +{ + // Include the scope to avoid name collisions with any other SWs on the same origin. + // e.g. "c3offline-https://example.com/foo/" (won't collide with anything under bar/) + return CACHE_NAME_PREFIX + "-" + self.registration.scope; +}; + +function GetCacheVersionName(version) +{ + // Append the version number to the cache name. + // e.g. "c3offline-https://example.com/foo/-v2" + return GetCacheBaseName() + "-v" + version; +}; + +// Return caches.keys() filtered down to just caches we're interested in (with the right base name). +// This filters out caches from unrelated scopes. +async function GetAvailableCacheNames() +{ + const cacheNames = await caches.keys(); + const cacheBaseName = GetCacheBaseName(); + return cacheNames.filter(n => n.startsWith(cacheBaseName)); +}; + +// Identify if an update is pending, which is the case when we have 2 or more available caches. +// One must be an update that is waiting, since the next navigate that does an upgrade will +// delete all the old caches leaving just one currently-in-use cache. +async function IsUpdatePending() +{ + const availableCacheNames = await GetAvailableCacheNames(); + return (availableCacheNames.length >= 2); +}; + +// Automatically deduce the main page URL (e.g. index.html or main.aspx) from the available browser windows. +// This prevents having to hard-code an index page in the file list, implicitly caching it like AppCache did. +async function GetMainPageUrl() +{ + const allClients = await clients.matchAll({ + includeUncontrolled: true, + type: "window" + }); + + for (const c of allClients) + { + // Parse off the scope from the full client URL, e.g. https://example.com/index.html -> index.html + let url = c.url; + if (url.startsWith(self.registration.scope)) + url = url.substring(self.registration.scope.length); + + if (url && url !== "/") // ./ is also implicitly cached so don't bother returning that + { + // If the URL is solely a search string, prefix it with / to ensure it caches correctly. + // e.g. https://example.com/?foo=bar needs to cache as /?foo=bar, not just ?foo=bar. + if (url.startsWith("?")) + url = "/" + url; + + return url; + } + } + + return ""; // no main page URL could be identified +}; + +// Fetch optionally bypassing HTTP cache using fetch cache options +function fetchWithBypass(request, bypassCache) +{ + if (typeof request === "string") + request = new Request(request); + + if (bypassCache) + { + return fetch(request.url, { + method: 'GET', + headers: request.headers, + mode: request.mode == 'navigate' ? 'cors' : request.mode, + credentials: request.credentials, + redirect: request.redirect + }); + } + else + { + // bypass disabled: perform normal fetch which is allowed to return from HTTP cache + return fetch(request); + } +}; + +// Effectively a cache.addAll() that only creates the cache on all requests being successful (as a weak attempt at making it atomic) +// and can optionally cache-bypass with fetchWithBypass in every request +async function CreateCacheFromFileList(cacheName, fileList, bypassCache) +{ + // Kick off all requests and wait for them all to complete + const responses = await Promise.all(fileList.map(url => fetchWithBypass(url, bypassCache))); + + // Check if any request failed. If so don't move on to opening the cache. + // This makes sure we only open a cache if all requests succeeded. + let allOk = true; + + for (const response of responses) + { + if (!response.ok) + { + allOk = false; + console.error(CONSOLE_PREFIX + "Error fetching '" + response.url + "' (" + response.status + " " + response.statusText + ")"); + } + } + + if (!allOk) + throw new Error("not all resources were fetched successfully"); + + // Can now assume all responses are OK. Open a cache and write all responses there. + // TODO: ideally we can do this transactionally to ensure a complete cache is written as one atomic operation. + // This needs either new transactional features in the spec, or at the very least a way to rename a cache + // (so we can write to a temporary name that won't be returned by GetAvailableCacheNames() and then rename it when ready). + const cache = await caches.open(cacheName); + + try { + return await Promise.all(responses.map( + (response, i) => cache.put(fileList[i], response) + )); + } + catch (err) + { + // Not sure why cache.put() would fail (maybe if storage quota exceeded?) but in case it does, + // clean up the cache to try to avoid leaving behind an incomplete cache. + console.error(CONSOLE_PREFIX + "Error writing cache entries: ", err); + caches.delete(cacheName); + throw err; + } +}; + +async function UpdateCheck(isFirst) +{ + try { + // Always bypass cache when requesting offline.js to make sure we find out about new versions. + const response = await fetchWithBypass(OFFLINE_DATA_FILE, true); + + if (!response.ok) + throw new Error(OFFLINE_DATA_FILE + " responded with " + response.status + " " + response.statusText); + + const data = await response.json(); + + const version = data.version; + const fileList = data.fileList; + const lazyLoadList = data.lazyLoad; + const currentCacheName = GetCacheVersionName(version); + + const cacheExists = await caches.has(currentCacheName); + + // Don't recache if there is already a cache that exists for this version. Assume it is complete. + if (cacheExists) + { + // Log whether we are up-to-date or pending an update. + const isUpdatePending = await IsUpdatePending(); + if (isUpdatePending) + { + console.log(CONSOLE_PREFIX + "Update pending"); + Broadcast("update-pending"); + } + else + { + console.log(CONSOLE_PREFIX + "Up to date"); + Broadcast("up-to-date"); + } + return; + } + + // Implicitly add the main page URL to the file list, e.g. "index.html", so we don't have to assume a specific name. + const mainPageUrl = await GetMainPageUrl(); + + // Prepend the main page URL to the file list if we found one and it is not already in the list. + // Also make sure we request the base / which should serve the main page. + fileList.unshift("./"); + + if (mainPageUrl && fileList.indexOf(mainPageUrl) === -1) + fileList.unshift(mainPageUrl); + + console.log(CONSOLE_PREFIX + "Caching " + fileList.length + " files for offline use"); + + if (isFirst) + Broadcast("downloading"); + else + BroadcastDownloadingUpdate(version); + + // Note we don't bypass the cache on the first update check. This is because SW installation and the following + // update check caching will race with the normal page load requests. For any normal loading fetches that have already + // completed or are in-flight, it is pointless and wasteful to cache-bust the request for offline caching, since that + // forces a second network request to be issued when a response from the browser HTTP cache would be fine. + if (lazyLoadList) + await WriteLazyLoadListToStorage(lazyLoadList); // dump lazy load list to local storage# + + await CreateCacheFromFileList(currentCacheName, fileList, !isFirst); + const isUpdatePending = await IsUpdatePending(); + + if (isUpdatePending) + { + console.log(CONSOLE_PREFIX + "All resources saved, update ready"); + BroadcastUpdateReady(version); + } + else + { + console.log(CONSOLE_PREFIX + "All resources saved, offline support ready"); + Broadcast("offline-ready"); + } + } + catch (err) + { + // Update check fetches fail when we're offline, but in case there's any other kind of problem with it, log a warning. + console.warn(CONSOLE_PREFIX + "Update check failed: ", err); + } +}; + +self.addEventListener("install", event => +{ + // On install kick off an update check to cache files on first use. + // If it fails we can still complete the install event and leave the SW running, we'll just + // retry on the next navigate. + event.waitUntil( + UpdateCheck(true) // first update + .catch(() => null) + ); +}); + +async function GetCacheNameToUse(availableCacheNames, doUpdateCheck) +{ + // Prefer the oldest cache available. This avoids mixed-version responses by ensuring that if a new cache + // is created and filled due to an update check while the page is running, we keep returning resources + // from the original (oldest) cache only. + if (availableCacheNames.length === 1 || !doUpdateCheck) + return availableCacheNames[0]; + + // We are making a navigate request with more than one cache available. Check if we can expire any old ones. + const allClients = await clients.matchAll(); + + // If there are other clients open, don't expire anything yet. We don't want to delete any caches they + // might be using, which could cause mixed-version responses. + if (allClients.length > 1) + return availableCacheNames[0]; + + // Identify newest cache to use. Delete all the others. + const latestCacheName = availableCacheNames[availableCacheNames.length - 1]; + console.log(CONSOLE_PREFIX + "Updating to new version"); + + await Promise.all( + availableCacheNames.slice(0, -1) + .map(c => caches.delete(c)) + ); + + return latestCacheName; +}; + +async function HandleFetch(event, doUpdateCheck) +{ + const availableCacheNames = await GetAvailableCacheNames(); + + // No caches available: go to network + if (!availableCacheNames.length) + return fetch(event.request); + + const useCacheName = await GetCacheNameToUse(availableCacheNames, doUpdateCheck); + const cache = await caches.open(useCacheName); + const cachedResponse = await cache.match(event.request); + + if (cachedResponse) + return cachedResponse; // use cached response + + // We need to check if this request is to be lazy-cached. Send the request and load the lazy-load list + // from storage simultaneously. + const result = await Promise.all([fetch(event.request), ReadLazyLoadListFromStorage()]); + const fetchResponse = result[0]; + const lazyLoadList = result[1]; + + if (IsUrlInLazyLoadList(event.request.url, lazyLoadList)) + { + // Handle failure writing to the cache. This can happen if the storage quota is exceeded, which is particularly + // likely in Safari 11.1, which appears to have very tight storage limits. Make sure even in the event of an error + // we continue to return the response from the fetch. + try { + // Note clone response since we also respond with it + await cache.put(event.request, fetchResponse.clone()); + } + catch (err) + { + console.warn(CONSOLE_PREFIX + "Error caching '" + event.request.url + "': ", err); + } + } + + return fetchResponse; +}; + +self.addEventListener("fetch", event => +{ + /** NOTE (iain) + * This check is to prevent a bug with XMLHttpRequest where if its + * proxied with "FetchEvent.prototype.respondWith" no upload progress + * events are triggered. By returning we allow the default action to + * occur instead. Currently all cross-origin requests fall back to default. + */ + if (new URL(event.request.url).origin !== location.origin) + return; + + // Check for an update on navigate requests + const doUpdateCheck = (event.request.mode === "navigate"); + + const responsePromise = HandleFetch(event, doUpdateCheck); + + if (doUpdateCheck) + { + // allow the main request to complete, then check for updates + event.waitUntil( + responsePromise + .then(() => UpdateCheck(false)) // not first check + ); + } + + event.respondWith(responsePromise); +}); \ No newline at end of file diff --git a/index.html b/index.html index 4bc9f0af..b0263f4f 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + e-gamepass - + @@ -21,85 +21,88 @@ -

e-gamepass

-

Click here for bookmarklets that you can use even if we get blocked! - Want a game to be added? Suggest here! - Want to help me develop the website? Click here!

+

e-gamepass

+

Click here for bookmarklets that you can use even if we get blocked! - Want a game to be added? Suggest here! - Want to help me develop the website? Click here!

Download Save - Upload Save


Welcome to the new and updated e-gamepass!


-
ADOFAI Logo

ADOFAI

-
Resent Client Logo

Online Minecraft

-
Minecraft Logo

Offline Minecraft

-
Friday Night FUnkin Logo

Friday Night Funkin

-
Hardest Game Logo

Worlds Hardest Game

-
Mario Logo

Mario

-
Cluster Rush Logo

Cluster Rush

-
Vex 3 Logo

Vex 3

-
BTD3 Logo

Bloons TD 3

-
avalanche Logo

Avalanche

-
Cell Machine Logo

Cell Machine

-
Run 2 Logo

Run 2

-
Vex5 Logo

Vex 5

-
Vex4 Logo

Vex 4

-
Vex2 Logo

Vex 2

-
Wall Smash Logo

Wall Smash

-
Death Run 3d Logo

Death Run 3D

+
ADOFAI Logo

ADOFAI

+
Resent Client Logo

Online Minecraft

+
Minecraft Logo

Offline Minecraft

+
Friday Night FUnkin Logo

Friday Night Funkin

+
Hardest Game Logo

Worlds Hardest Game

+
Mario Logo

Mario

+
Cluster Rush Logo

Cluster Rush

+
Vex 3 Logo

Vex 3

+
BTD3 Logo

Bloons TD 3

+
avalanche Logo

Avalanche

+
Slope Logo

Slope

+
Cell Machine Logo

Cell Machine

+
Run 2 Logo

Run 2

+
Vex5 Logo

Vex 5

+
Vex4 Logo

Vex 4

+
Vex2 Logo

Vex 2

+
Wall Smash Logo

Wall Smash

+
Death Run 3d Logo

Death Run 3D

+
Chrome Dino Logo

Chrome Dino

+
Gun Mayhem 2 Logo

Gun Mayhem 2

-
Gun Mayhem 2 Logo

Gun Mayhem 2

+
circloO Logo

CircloO

+
Vex Logo

Vex

+
Stack Logo

Stack

+
xx142-b2.exe Logo

xx142-b2.exe

+
Paper.IO Logo

Paper.io

+
Chibi Knight Logo

Chibi Knight

+
1v1.lol Logo

1v1.lol

+
Dino Logo

Chrome Dino

+
Turbowarp Packager Logo

Turbowarp Packager

-
circloO Logo

CircloO

-
Vex Logo

Vex

-
Stack Logo

Stack

-
xx142-b2.exe Logo

xx142-b2.exe

-
Paper.IO Logo

Paper.io

-
Chibi Knight Logo

Chibi Knight

-
1v1.lol Logo

1v1.lol

-
Dino Logo

Chrome Dino

-
Turbowarp Packager Logo

Turbowarp Packager

+
Tetris Logo

Tetris

+
GD Logo

Geometry Dash

+
Sand Game Logo

Sand Game

+
Dante Logo

Dante

+
Wordle Logo

Wordle

+
Offline Paradise Logo

Offline Paradise

-
Tetris Logo

Tetris

-
GD Logo

Geometry Dash

-
Sand Game Logo

Sand Game

-
Dante Logo

Dante

-
Wordle Logo

Wordle

-
Offline Paradise Logo

Offline Paradise

+
2048 Logo

2048

+
Doodle Jump Logo

Doodle Jump

-
2048 Logo

2048

-
Doodle Jump Logo

Doodle Jump

- -
Weave Silk Logo

Weave Silk

-
Super Meat Boy Logo

Super Meat Boy

-
Vex 6 Logo

Vex 6

+
Weave Silk Logo

Weave Silk

+
Super Meat Boy Logo

Super Meat Boy

+
Vex 6 Logo

Vex 6

-
WEBGL Fluid Sim Logo

WebGL Fluid Sim

-
Basketball Stars Logo

Basketball Stars

+
WEBGL Fluid Sim Logo

WebGL Fluid Sim

+
Basketball Stars Logo

Basketball Stars

-
osu! Logo

osu!

-
Cookie Clicker Logo

Cookie Clicker

-
Superhot Logo

Superhot

-
SM64 Logo

Super Mario 64

-
Gun Mayhem Redux Logo

Gun Mayhem Redux

-
IWBTC Logo

I Wanna Be Thy Copy

+
osu! Logo

osu!

+
Cookie Clicker Logo

Cookie Clicker

+
Superhot Logo

Superhot

+
SM64 Logo

Super Mario 64

+
Gun Mayhem Redux Logo

Gun Mayhem Redux

+
IWBTC Logo

I Wanna Be Thy Copy

-
HexGL Logo

HexGL

-
Champion Island Logo

Champion Islands

-
Learn To Fly Logo

Learn to Fly

-
Turbowarp Editor Logo

Turbowarp Editor

-
Wordle Bot Logo

Wordle Bot

-
CTR Logo

Cut the Rope

+
HexGL Logo

HexGL

+
Champion Island Logo

Champion Islands

+
Learn To Fly Logo

Learn to Fly

+
Turbowarp Editor Logo

Turbowarp Editor

+
Wordle Bot Logo

Wordle Bot

+
CTR Logo

Cut the Rope

-
Chess Logo

Chess

-
Flappy Bird Logo

Flappy Bird

+
Chess Logo

Chess

+
Flappy Bird Logo

Flappy Bird

-
Lows Adventurs 2 Logo

Lows Adventures 2

-
Drift Boss Logo

Drift Boss

-
Fireboy Watergirl Logo

Fireboy Watergirl

-
Gun Mayhem Logo

Gun Mayhem

-
Duck Life 4 Logo

Duck Life 4

-
achieveunlocked Logo

Achievement Unlocked

-

v. 2023.05.22

+
Lows Adventurs 2 Logo

Lows Adventures 2

+
Drift Boss Logo

Drift Boss

+
Fireboy Watergirl Logo

Fireboy Watergirl

+
Gun Mayhem Logo

Gun Mayhem

+
Duck Life 4 Logo

Duck Life 4

+
achieveunlocked Logo

Achievement Unlocked

+
achieveunlocked2 Logo

Achievement Unlocked 2

+
this is the only level Logo

This Is The Only Level

+

v. 2023.05.24

diff --git a/slope/Build/UnityLoader.js b/slope/Build/UnityLoader.js new file mode 100644 index 00000000..96e59f7b --- /dev/null +++ b/slope/Build/UnityLoader.js @@ -0,0 +1,5027 @@ +var UnityLoader = UnityLoader || { + compatibilityCheck: function (e, t, r) { + UnityLoader.SystemInfo.hasWebGL + ? UnityLoader.SystemInfo.mobile + ? e.popup("Please note that Unity WebGL is not currently supported on mobiles. Press OK if you wish to continue anyway.", [{ text: "OK", callback: t }]) + : ["Edge", "Firefox", "Chrome", "Safari"].indexOf(UnityLoader.SystemInfo.browser) == -1 + ? e.popup("Please note that your browser is not currently supported for this Unity WebGL content. Press OK if you wish to continue anyway.", [{ text: "OK", callback: t }]) + : t() + : e.popup("Your browser does not support WebGL", [{ text: "OK", callback: r }]); + }, + Blobs: {}, + loadCode: function (e, t, r) { + var n = [].slice + .call(UnityLoader.Cryptography.md5(e)) + .map(function (e) { + return ("0" + e.toString(16)).substr(-2); + }) + .join(""), + o = document.createElement("script"), + a = URL.createObjectURL(new Blob(['UnityLoader["' + n + '"]=', e], { type: "text/javascript" })); + (UnityLoader.Blobs[a] = r), + (o.src = a), + (o.onload = function () { + URL.revokeObjectURL(a), t(n); + }), + document.body.appendChild(o); + }, + allocateHeapJob: function (e, t) { + for (var r = e.TOTAL_STACK || 5242880, n = e.TOTAL_MEMORY || (e.buffer ? e.buffer.byteLength : 268435456), o = 65536, a = 16777216, i = o; i < n || i < 2 * r; ) i += i < a ? i : a; + i != n && e.printErr("increasing TOTAL_MEMORY to " + i + " to be compliant with the asm.js spec (and given that TOTAL_STACK=" + r + ")"), + (n = i), + t.parameters.useWasm + ? ((e.wasmMemory = new WebAssembly.Memory({ initial: n / o, maximum: n / o })), (e.buffer = e.wasmMemory.buffer)) + : e.buffer + ? e.buffer.byteLength != n && (e.printErr("provided buffer should be " + n + " bytes, but it is " + e.buffer.byteLength + ", reallocating the buffer"), (e.buffer = new ArrayBuffer(n))) + : (e.buffer = new ArrayBuffer(n)), + (e.TOTAL_MEMORY = e.buffer.byteLength), + t.complete(); + }, + setupIndexedDBJob: function (e, t) { + function r(n) { + r.called || ((r.called = !0), (e.indexedDB = n), t.complete()); + } + try { + var n = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB, + o = n.open("/idbfs-test"); + (o.onerror = function (e) { + e.preventDefault(), r(); + }), + (o.onsuccess = function () { + o.result.close(), r(n); + }), + setTimeout(r, 1e3); + } catch (e) { + r(); + } + }, + processWasmCodeJob: function (e, t) { + (e.wasmBinary = UnityLoader.Job.result(e, "downloadWasmCode")), t.complete(); + }, + processWasmFrameworkJob: function (e, t) { + UnityLoader.loadCode( + UnityLoader.Job.result(e, "downloadWasmFramework"), + function (r) { + UnityLoader[r](e), t.complete(); + }, + { Module: e, url: e.wasmFrameworkUrl } + ); + }, + processAsmCodeJob: function (e, t) { + var r = UnityLoader.Job.result(e, "downloadAsmCode"); + UnityLoader.loadCode( + Math.fround ? r : UnityLoader.Utils.optimizeMathFround(r), + function (r) { + (e.asm = UnityLoader[r]), t.complete(); + }, + { Module: e, url: e.asmCodeUrl } + ); + }, + processAsmFrameworkJob: function (e, t) { + UnityLoader.loadCode( + UnityLoader.Job.result(e, "downloadAsmFramework"), + function (r) { + UnityLoader[r](e), t.complete(); + }, + { Module: e, url: e.asmFrameworkUrl } + ); + }, + processAsmMemoryJob: function (e, t) { + (e.memoryInitializerRequest.status = 200), (e.memoryInitializerRequest.response = UnityLoader.Job.result(e, "downloadAsmMemory")), e.memoryInitializerRequest.callback && e.memoryInitializerRequest.callback(), t.complete(); + }, + processDataJob: function (e, t) { + var r = UnityLoader.Job.result(e, "downloadData"), + n = new DataView(r.buffer, r.byteOffset, r.byteLength), + o = 0, + a = "UnityWebData1.0\0"; + if (!String.fromCharCode.apply(null, r.subarray(o, o + a.length)) == a) throw "unknown data format"; + o += a.length; + var i = n.getUint32(o, !0); + for (o += 4; o < i; ) { + var s = n.getUint32(o, !0); + o += 4; + var d = n.getUint32(o, !0); + o += 4; + var l = n.getUint32(o, !0); + o += 4; + var u = String.fromCharCode.apply(null, r.subarray(o, o + l)); + o += l; + for (var f = 0, c = u.indexOf("/", f) + 1; c > 0; f = c, c = u.indexOf("/", f) + 1) e.FS_createPath(u.substring(0, f), u.substring(f, c - 1), !0, !0); + e.FS_createDataFile(u, null, r.subarray(s, s + d), !0, !0, !0); + } + e.removeRunDependency("processDataJob"), t.complete(); + }, + downloadJob: function (e, t) { + var r = t.parameters.objParameters ? new UnityLoader.XMLHttpRequest(t.parameters.objParameters) : new XMLHttpRequest(); + r.open("GET", t.parameters.url), + (r.responseType = "arraybuffer"), + (r.onload = function () { + UnityLoader.Compression.decompress(new Uint8Array(r.response), function (e) { + t.complete(e); + }); + }), + t.parameters.onprogress && r.addEventListener("progress", t.parameters.onprogress), + t.parameters.onload && r.addEventListener("load", t.parameters.onload), + r.send(); + }, + scheduleBuildDownloadJob: function (e, t, r) { + UnityLoader.Progress.update(e, t), + UnityLoader.Job.schedule(e, t, [], UnityLoader.downloadJob, { + url: e.resolveBuildUrl(e[r]), + onprogress: function (r) { + UnityLoader.Progress.update(e, t, r); + }, + onload: function (r) { + UnityLoader.Progress.update(e, t, r); + }, + objParameters: e.companyName && e.productName && e.cacheControl && e.cacheControl[r] ? { companyName: e.companyName, productName: e.productName, cacheControl: e.cacheControl[r] } : null, + }); + }, + loadModule: function (e) { + if (((e.useWasm = e.wasmCodeUrl && UnityLoader.SystemInfo.hasWasm), e.useWasm)) + UnityLoader.scheduleBuildDownloadJob(e, "downloadWasmCode", "wasmCodeUrl"), + UnityLoader.Job.schedule(e, "processWasmCode", ["downloadWasmCode"], UnityLoader.processWasmCodeJob), + UnityLoader.scheduleBuildDownloadJob(e, "downloadWasmFramework", "wasmFrameworkUrl"), + UnityLoader.Job.schedule(e, "processWasmFramework", ["downloadWasmFramework", "processWasmCode", "setupIndexedDB"], UnityLoader.processWasmFrameworkJob); + else { + if (!e.asmCodeUrl) throw "WebAssembly support is not detected in this browser."; + UnityLoader.scheduleBuildDownloadJob(e, "downloadAsmCode", "asmCodeUrl"), + UnityLoader.Job.schedule(e, "processAsmCode", ["downloadAsmCode"], UnityLoader.processAsmCodeJob), + UnityLoader.scheduleBuildDownloadJob(e, "downloadAsmMemory", "asmMemoryUrl"), + UnityLoader.Job.schedule(e, "processAsmMemory", ["downloadAsmMemory"], UnityLoader.processAsmMemoryJob), + (e.memoryInitializerRequest = { + addEventListener: function (t, r) { + e.memoryInitializerRequest.callback = r; + }, + }), + e.asmLibraryUrl && (e.dynamicLibraries = [e.asmLibraryUrl].map(e.resolveBuildUrl)), + UnityLoader.scheduleBuildDownloadJob(e, "downloadAsmFramework", "asmFrameworkUrl"), + UnityLoader.Job.schedule(e, "processAsmFramework", ["downloadAsmFramework", "processAsmCode", "setupIndexedDB"], UnityLoader.processAsmFrameworkJob); + } + UnityLoader.scheduleBuildDownloadJob(e, "downloadData", "dataUrl"), + UnityLoader.Job.schedule(e, "setupIndexedDB", [], UnityLoader.setupIndexedDBJob), + e.preRun.push(function () { + e.addRunDependency("processDataJob"), UnityLoader.Job.schedule(e, "processData", ["downloadData"], UnityLoader.processDataJob); + }); + }, + instantiate: function (e, t, r) { + function n(e, r) { + if ("string" == typeof e && !(e = document.getElementById(e))) return !1; + (e.innerHTML = ""), + (e.style.border = e.style.margin = e.style.padding = 0), + "static" == getComputedStyle(e).getPropertyValue("position") && (e.style.position = "relative"), + (e.style.width = r.width || e.style.width), + (e.style.height = r.height || e.style.height), + (r.container = e); + var n = r.Module; + return ( + (n.canvas = document.createElement("canvas")), + (n.canvas.style.width = "100%"), + (n.canvas.style.height = "100%"), + n.canvas.addEventListener("contextmenu", function (e) { + e.preventDefault(); + }), + (n.canvas.id = "#canvas"), + e.appendChild(n.canvas), + r.compatibilityCheck( + r, + function () { + var t = new XMLHttpRequest(); + t.open("GET", r.url, !0), + (t.responseType = "text"), + (t.onerror = function () { + n.print("Could not download " + r.url), + 0 == document.URL.indexOf("file:") && alert("It seems your browser does not support running Unity WebGL content from file:// urls. Please upload it to an http server, or try a different browser."); + }), + (t.onload = function () { + var o = JSON.parse(t.responseText); + for (var a in o) "undefined" == typeof n[a] && (n[a] = o[a]); + for (var i = !1, s = 0; s < n.graphicsAPI.length; s++) { + var d = n.graphicsAPI[s]; + "WebGL 2.0" == d && 2 == UnityLoader.SystemInfo.hasWebGL ? (i = !0) : "WebGL 1.0" == d && UnityLoader.SystemInfo.hasWebGL >= 1 ? (i = !0) : n.print("Warning: Unsupported graphics API " + d); + } + return i + ? ((e.style.background = n.backgroundUrl ? "center/cover url('" + n.resolveBuildUrl(n.backgroundUrl) + "')" : n.backgroundColor ? " " + n.backgroundColor : ""), + r.onProgress(r, 0), + void UnityLoader.loadModule(n)) + : void r.popup("Your browser does not support any of the required graphics API for this content: " + n.graphicsAPI, [{ text: "OK" }]); + }), + t.send(); + }, + function () { + n.print("Instantiation of the '" + t + "' terminated due to the failed compatibility check."); + } + ), + !0 + ); + } + var o = { + url: t, + onProgress: UnityLoader.Progress.handler, + compatibilityCheck: UnityLoader.compatibilityCheck, + Module: { + preRun: [], + postRun: [], + print: function (e) { + console.log(e); + }, + printErr: function (e) { + console.error(e); + }, + Jobs: {}, + buildDownloadProgress: {}, + resolveBuildUrl: function (e) { + return e.match(/(http|https|ftp|file):\/\//) ? e : t.substring(0, t.lastIndexOf("/") + 1) + e; + }, + }, + SetFullscreen: function () { + if (o.Module.SetFullscreen) return o.Module.SetFullscreen.apply(o.Module, arguments); + }, + SendMessage: function () { + if (o.Module.SendMessage) return o.Module.SendMessage.apply(o.Module, arguments); + }, + }; + (o.Module.gameInstance = o), + (o.popup = function (e, t) { + return UnityLoader.Error.popup(o, e, t); + }), + o.Module.postRun.push(function () { + o.onProgress(o, 1); + }); + for (var a in r) + if ("Module" == a) for (var i in r[a]) o.Module[i] = r[a][i]; + else o[a] = r[a]; + return ( + n(e, o) || + document.addEventListener("DOMContentLoaded", function () { + n(e, o); + }), + o + ); + }, + SystemInfo: (function () { + var e, + t, + r, + n = "-", + o = navigator.appVersion, + a = navigator.userAgent, + i = navigator.appName, + s = navigator.appVersion, + d = parseInt(navigator.appVersion, 10); + (t = a.indexOf("Opera")) != -1 + ? ((i = "Opera"), (s = a.substring(t + 6)), (t = a.indexOf("Version")) != -1 && (s = a.substring(t + 8))) + : (t = a.indexOf("MSIE")) != -1 + ? ((i = "Microsoft Internet Explorer"), (s = a.substring(t + 5))) + : (t = a.indexOf("Edge")) != -1 + ? ((i = "Edge"), (s = a.substring(t + 5))) + : (t = a.indexOf("Chrome")) != -1 + ? ((i = "Chrome"), (s = a.substring(t + 7))) + : (t = a.indexOf("Safari")) != -1 + ? ((i = "Safari"), (s = a.substring(t + 7)), (t = a.indexOf("Version")) != -1 && (s = a.substring(t + 8))) + : (t = a.indexOf("Firefox")) != -1 + ? ((i = "Firefox"), (s = a.substring(t + 8))) + : a.indexOf("Trident/") != -1 + ? ((i = "Microsoft Internet Explorer"), (s = a.substring(a.indexOf("rv:") + 3))) + : (e = a.lastIndexOf(" ") + 1) < (t = a.lastIndexOf("/")) && ((i = a.substring(e, t)), (s = a.substring(t + 1)), i.toLowerCase() == i.toUpperCase() && (i = navigator.appName)), + (r = s.indexOf(";")) != -1 && (s = s.substring(0, r)), + (r = s.indexOf(" ")) != -1 && (s = s.substring(0, r)), + (r = s.indexOf(")")) != -1 && (s = s.substring(0, r)), + (d = parseInt("" + s, 10)), + isNaN(d) ? ((s = "" + parseFloat(navigator.appVersion)), (d = parseInt(navigator.appVersion, 10))) : (s = "" + parseFloat(s)); + var l = /Mobile|mini|Fennec|Android|iP(ad|od|hone)/.test(o), + u = n, + f = [ + { s: "Windows 3.11", r: /Win16/ }, + { s: "Windows 95", r: /(Windows 95|Win95|Windows_95)/ }, + { s: "Windows ME", r: /(Win 9x 4.90|Windows ME)/ }, + { s: "Windows 98", r: /(Windows 98|Win98)/ }, + { s: "Windows CE", r: /Windows CE/ }, + { s: "Windows 2000", r: /(Windows NT 5.0|Windows 2000)/ }, + { s: "Windows XP", r: /(Windows NT 5.1|Windows XP)/ }, + { s: "Windows Server 2003", r: /Windows NT 5.2/ }, + { s: "Windows Vista", r: /Windows NT 6.0/ }, + { s: "Windows 7", r: /(Windows 7|Windows NT 6.1)/ }, + { s: "Windows 8.1", r: /(Windows 8.1|Windows NT 6.3)/ }, + { s: "Windows 8", r: /(Windows 8|Windows NT 6.2)/ }, + { s: "Windows 10", r: /(Windows 10|Windows NT 10.0)/ }, + { s: "Windows NT 4.0", r: /(Windows NT 4.0|WinNT4.0|WinNT|Windows NT)/ }, + { s: "Windows ME", r: /Windows ME/ }, + { s: "Android", r: /Android/ }, + { s: "Open BSD", r: /OpenBSD/ }, + { s: "Sun OS", r: /SunOS/ }, + { s: "Linux", r: /(Linux|X11)/ }, + { s: "iOS", r: /(iPhone|iPad|iPod)/ }, + { s: "Mac OS X", r: /Mac OS X/ }, + { s: "Mac OS", r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, + { s: "QNX", r: /QNX/ }, + { s: "UNIX", r: /UNIX/ }, + { s: "BeOS", r: /BeOS/ }, + { s: "OS/2", r: /OS\/2/ }, + { s: "Search Bot", r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ }, + ]; + for (var c in f) { + var h = f[c]; + if (h.r.test(a)) { + u = h.s; + break; + } + } + var p = n; + switch ((/Windows/.test(u) && ((p = /Windows (.*)/.exec(u)[1]), (u = "Windows")), u)) { + case "Mac OS X": + p = /Mac OS X (10[\.\_\d]+)/.exec(a)[1]; + break; + case "Android": + p = /Android ([\.\_\d]+)/.exec(a)[1]; + break; + case "iOS": + (p = /OS (\d+)_(\d+)_?(\d+)?/.exec(o)), (p = p[1] + "." + p[2] + "." + (0 | p[3])); + } + return { + width: screen.width ? screen.width : 0, + height: screen.height ? screen.height : 0, + browser: i, + browserVersion: s, + mobile: l, + os: u, + osVersion: p, + gpu: (function () { + var e = document.createElement("canvas"), + t = e.getContext("experimental-webgl"); + if (t) { + var r = t.getExtension("WEBGL_debug_renderer_info"); + if (r) return t.getParameter(r.UNMASKED_RENDERER_WEBGL); + } + return n; + })(), + language: window.navigator.userLanguage || window.navigator.language, + hasWebGL: (function () { + if (!window.WebGLRenderingContext) return 0; + var e = document.createElement("canvas"), + t = e.getContext("webgl2"); + return t ? 2 : ((t = e.getContext("experimental-webgl2")), t ? 2 : ((t = e.getContext("webgl")), t || (t = e.getContext("experimental-webgl")) ? 1 : 0)); + })(), + hasCursorLock: (function () { + var e = document.createElement("canvas"); + return e.requestPointerLock || e.mozRequestPointerLock || e.webkitRequestPointerLock || e.msRequestPointerLock ? 1 : 0; + })(), + hasFullscreen: (function () { + var e = document.createElement("canvas"); + return (e.requestFullScreen || e.mozRequestFullScreen || e.msRequestFullscreen || e.webkitRequestFullScreen) && (i.indexOf("Safari") == -1 || s >= 10.1) ? 1 : 0; + })(), + hasWasm: "object" == typeof WebAssembly && "function" == typeof WebAssembly.validate && "function" == typeof WebAssembly.compile, + }; + })(), + Error: { + init: (function () { + return ( + (Error.stackTraceLimit = 50), + window.addEventListener("error", function (e) { + var t = UnityLoader.Error.getModule(e); + if (!t) return UnityLoader.Error.handler(e); + var r = t.useWasm ? t.wasmSymbolsUrl : t.asmSymbolsUrl; + if (!r) return UnityLoader.Error.handler(e, t); + var n = new XMLHttpRequest(); + n.open("GET", t.resolveBuildUrl(r)), + (n.responseType = "arraybuffer"), + (n.onload = function () { + UnityLoader.loadCode(UnityLoader.Compression.decompress(new Uint8Array(n.response)), function (r) { + (t.demangleSymbol = UnityLoader[r]()), UnityLoader.Error.handler(e, t); + }); + }), + n.send(); + }), + !0 + ); + })(), + stackTraceFormat: + navigator.userAgent.indexOf("Chrome") != -1 + ? "(\\s+at\\s+)(([\\w\\d_\\.]*?)([\\w\\d_$]+)(/[\\w\\d_\\./]+|))(\\s+\\[.*\\]|)\\s*\\((blob:.*)\\)" + : "(\\s*)(([\\w\\d_\\.]*?)([\\w\\d_$]+)(/[\\w\\d_\\./]+|))(\\s+\\[.*\\]|)\\s*@(blob:.*)", + stackTraceFormatWasm: navigator.userAgent.indexOf("Chrome") != -1 ? "((\\s+at\\s*)\\s\\(\\[(\\d+)\\]\\+\\d+\\))()" : "((\\s*)wasm-function\\[(\\d+)\\])@(blob:.*)", + blobParseRegExp: new RegExp("^(blob:.*)(:\\d+:\\d+)$"), + getModule: function (e) { + var t = e.message.match(new RegExp(this.stackTraceFormat, "g")); + for (var r in t) { + var n = t[r].match(new RegExp("^" + this.stackTraceFormat + "$")), + o = n[7].match(this.blobParseRegExp); + if (o && UnityLoader.Blobs[o[1]] && UnityLoader.Blobs[o[1]].Module) return UnityLoader.Blobs[o[1]].Module; + } + }, + demangle: function (e, t) { + var r = e.message; + return t + ? ((r = r.replace( + new RegExp(this.stackTraceFormat, "g"), + function (e) { + var r = e.match(new RegExp("^" + this.stackTraceFormat + "$")), + n = r[7].match(this.blobParseRegExp), + o = t.demangleSymbol ? t.demangleSymbol(r[4]) : r[4], + a = n && UnityLoader.Blobs[n[1]] && UnityLoader.Blobs[n[1]].url ? UnityLoader.Blobs[n[1]].url : "blob"; + return r[1] + o + (r[2] != o ? " [" + r[2] + "]" : "") + " (" + (n ? a.substr(a.lastIndexOf("/") + 1) + n[2] : r[7]) + ")"; + }.bind(this) + )), + t.useWasm && + (r = r.replace( + new RegExp(this.stackTraceFormatWasm, "g"), + function (e) { + var r = e.match(new RegExp("^" + this.stackTraceFormatWasm + "$")), + n = t.demangleSymbol ? t.demangleSymbol(r[3]) : r[3], + o = r[4].match(this.blobParseRegExp), + a = o && UnityLoader.Blobs[o[1]] && UnityLoader.Blobs[o[1]].url ? UnityLoader.Blobs[o[1]].url : "blob"; + return (n == r[3] ? r[1] : r[2] + n + " [wasm:" + r[3] + "]") + (r[4] ? " (" + (o ? a.substr(a.lastIndexOf("/") + 1) + o[2] : r[4]) + ")" : ""); + }.bind(this) + )), + r) + : r; + }, + handler: function (e, t) { + var r = t ? this.demangle(e, t) : e.message; + if ( + !( + (t && t.errorhandler && t.errorhandler(r, e.filename, e.lineno)) || + (console.log("Invoking error handler due to\n" + r), + "function" == typeof dump && dump("Invoking error handler due to\n" + r), + r.indexOf("UnknownError") != -1 || r.indexOf("Program terminated with exit(0)") != -1 || this.didShowErrorMessage) + ) + ) { + var r = "An error occurred running the Unity content on this page. See your browser JavaScript console for more info. The error was:\n" + r; + r.indexOf("DISABLE_EXCEPTION_CATCHING") != -1 + ? (r = + "An exception has occurred, but exception handling has been disabled in this build. If you are the developer of this content, enable exceptions in your project WebGL player settings to be able to catch the exception or see the stack trace.") + : r.indexOf("Cannot enlarge memory arrays") != -1 + ? (r = "Out of memory. If you are the developer of this content, try allocating more memory to your WebGL build in the WebGL player settings.") + : (r.indexOf("Invalid array buffer length") == -1 && r.indexOf("Invalid typed array length") == -1 && r.indexOf("out of memory") == -1) || + (r = "The browser could not allocate enough memory for the WebGL content. If you are the developer of this content, try allocating less memory to your WebGL build in the WebGL player settings."), + alert(r), + (this.didShowErrorMessage = !0); + } + }, + popup: function (e, t, r) { + r = r || [{ text: "OK" }]; + var n = document.createElement("div"); + n.style.cssText = "position: absolute; top: 50%; left: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%); text-align: center; border: 1px solid black; padding: 5px; background: #E8E8E8"; + var o = document.createElement("span"); + (o.textContent = t), n.appendChild(o), n.appendChild(document.createElement("br")); + for (var a = 0; a < r.length; a++) { + var i = document.createElement("button"); + r[a].text && (i.textContent = r[a].text), + r[a].callback && (i.onclick = r[a].callback), + (i.style.margin = "5px"), + i.addEventListener("click", function () { + e.container.removeChild(n); + }), + n.appendChild(i); + } + e.container.appendChild(n); + }, + }, + Job: { + schedule: function (e, t, r, n, o) { + o = o || {}; + var a = e.Jobs[t]; + if ((a || (a = e.Jobs[t] = { dependencies: {}, dependants: {} }), a.callback)) throw "[UnityLoader.Job.schedule] job '" + t + "' has been already scheduled"; + if ("function" != typeof n) throw "[UnityLoader.Job.schedule] job '" + t + "' has invalid callback"; + if ("object" != typeof o) throw "[UnityLoader.Job.schedule] job '" + t + "' has invalid parameters"; + (a.callback = function (e, t) { + (a.starttime = performance.now()), n(e, t); + }), + (a.parameters = o), + (a.complete = function (r) { + (a.endtime = performance.now()), (a.result = { value: r }); + for (var n in a.dependants) { + var o = e.Jobs[n]; + o.dependencies[t] = a.dependants[n] = !1; + var i = "function" != typeof o.callback; + for (var s in o.dependencies) i = i || o.dependencies[s]; + if (!i) { + if (o.executed) throw "[UnityLoader.Job.schedule] job '" + t + "' has already been executed"; + (o.executed = !0), setTimeout(o.callback.bind(null, e, o), 0); + } + } + }); + var i = !1; + r.forEach(function (r) { + var n = e.Jobs[r]; + n || (n = e.Jobs[r] = { dependencies: {}, dependants: {} }), (a.dependencies[r] = n.dependants[t] = !n.result) && (i = !0); + }), + i || ((a.executed = !0), setTimeout(a.callback.bind(null, e, a), 0)); + }, + result: function (e, t) { + var r = e.Jobs[t]; + if (!r) throw "[UnityLoader.Job.result] job '" + t + "' does not exist"; + if ("object" != typeof r.result) throw "[UnityLoader.Job.result] job '" + t + "' has invalid result"; + return r.result.value; + }, + }, + XMLHttpRequest: (function () { + function e(e) { + console.log("[UnityCache] " + e); + } + function t(e) { + return (t.link = t.link || document.createElement("a")), (t.link.href = e), t.link.href; + } + function r(e) { + var t = window.location.href.match(/^[a-z]+:\/\/[^\/]+/); + return !t || e.lastIndexOf(t[0], 0); + } + function n() { + function t(t) { + if ("undefined" == typeof r.database) + for (r.database = t, r.database || e("indexedDB database could not be opened"); r.queue.length; ) { + var n = r.queue.shift(); + r.database ? r.execute.apply(r, n) : "function" == typeof n.onerror && n.onerror(new Error("operation cancelled")); + } + } + var r = this; + r.queue = []; + try { + var n = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB, + o = n.open(i); + (o.onupgradeneeded = function (e) { + var t = e.target.result.createObjectStore(s, { keyPath: "url" }); + ["version", "company", "product", "updated", "revalidated", "accessed"].forEach(function (e) { + t.createIndex(e, e); + }); + }), + (o.onsuccess = function (e) { + t(e.target.result); + }), + (o.onerror = function () { + t(null); + }), + setTimeout(o.onerror, 1e3); + } catch (e) { + t(null); + } + } + function o(e, t, r, n, o) { + var a = { url: e, version: d, company: t, product: r, updated: n, revalidated: n, accessed: n, responseHeaders: {}, xhr: {} }; + return ( + o && + (["Last-Modified", "ETag"].forEach(function (e) { + a.responseHeaders[e] = o.getResponseHeader(e); + }), + ["responseURL", "status", "statusText", "response"].forEach(function (e) { + a.xhr[e] = o[e]; + })), + a + ); + } + function a(t) { + (this.cache = { enabled: !1 }), + t && ((this.cache.control = t.cacheControl), (this.cache.company = t.companyName), (this.cache.product = t.productName)), + (this.xhr = new XMLHttpRequest(t)), + this.xhr.addEventListener( + "load", + function () { + var t = this.xhr, + r = this.cache; + r.enabled && + !r.revalidated && + (304 == t.status + ? ((r.result.revalidated = r.result.accessed), (r.revalidated = !0), l.execute("put", [r.result]), e("'" + r.result.url + "' successfully revalidated and served from the indexedDB cache")) + : 200 == t.status + ? ((r.result = o(r.result.url, r.company, r.product, r.result.accessed, t)), + (r.revalidated = !0), + l.execute( + "put", + [r.result], + function (t) { + e("'" + r.result.url + "' successfully downloaded and stored in the indexedDB cache"); + }, + function (t) { + e("'" + r.result.url + "' successfully downloaded but not stored in the indexedDB cache due to the error: " + t); + } + )) + : e("'" + r.result.url + "' request failed with status: " + t.status + " " + t.statusText)); + }.bind(this) + ); + } + var i = "UnityCache", + s = "XMLHttpRequest", + d = 1; + n.prototype.execute = function (e, t, r, n) { + if (this.database) + try { + var o = this.database.transaction([s], ["put", "delete", "clear"].indexOf(e) != -1 ? "readwrite" : "readonly").objectStore(s); + "openKeyCursor" == e && ((o = o.index(t[0])), (t = t.slice(1))); + var a = o[e].apply(o, t); + "function" == typeof r && + (a.onsuccess = function (e) { + r(e.target.result); + }), + (a.onerror = n); + } catch (e) { + "function" == typeof n && n(e); + } + else "undefined" == typeof this.database ? this.queue.push(arguments) : "function" == typeof n && n(new Error("indexedDB access denied")); + }; + var l = new n(); + (a.prototype.send = function (t) { + var n = this.xhr, + o = this.cache, + a = arguments; + return ( + (o.enabled = o.enabled && "arraybuffer" == n.responseType && !t), + o.enabled + ? void l.execute( + "get", + [o.result.url], + function (t) { + if (!t || t.version != d) return void n.send.apply(n, a); + if (((o.result = t), (o.result.accessed = Date.now()), "immutable" == o.control)) + (o.revalidated = !0), l.execute("put", [o.result]), n.dispatchEvent(new Event("load")), e("'" + o.result.url + "' served from the indexedDB cache without revalidation"); + else if (r(o.result.url) && (o.result.responseHeaders["Last-Modified"] || o.result.responseHeaders.ETag)) { + var i = new XMLHttpRequest(); + i.open("HEAD", o.result.url), + (i.onload = function () { + (o.revalidated = ["Last-Modified", "ETag"].every(function (e) { + return !o.result.responseHeaders[e] || o.result.responseHeaders[e] == i.getResponseHeader(e); + })), + o.revalidated + ? ((o.result.revalidated = o.result.accessed), + l.execute("put", [o.result]), + n.dispatchEvent(new Event("load")), + e("'" + o.result.url + "' successfully revalidated and served from the indexedDB cache")) + : n.send.apply(n, a); + }), + i.send(); + } else + o.result.responseHeaders["Last-Modified"] + ? (n.setRequestHeader("If-Modified-Since", o.result.responseHeaders["Last-Modified"]), n.setRequestHeader("Cache-Control", "no-cache")) + : o.result.responseHeaders.ETag && (n.setRequestHeader("If-None-Match", o.result.responseHeaders.ETag), n.setRequestHeader("Cache-Control", "no-cache")), + n.send.apply(n, a); + }, + function (e) { + n.send.apply(n, a); + } + ) + : n.send.apply(n, a) + ); + }), + (a.prototype.open = function (e, r, n, a, i) { + return ( + (this.cache.result = o(t(r), this.cache.company, this.cache.product, Date.now())), + (this.cache.enabled = + ["must-revalidate", "immutable"].indexOf(this.cache.control) != -1 && "GET" == e && this.cache.result.url.match("^https?://") && ("undefined" == typeof n || n) && "undefined" == typeof a && "undefined" == typeof i), + (this.cache.revalidated = !1), + this.xhr.open.apply(this.xhr, arguments) + ); + }), + (a.prototype.setRequestHeader = function (e, t) { + return (this.cache.enabled = !1), this.xhr.setRequestHeader.apply(this.xhr, arguments); + }); + var u = new XMLHttpRequest(); + for (var f in u) + a.prototype.hasOwnProperty(f) || + !(function (e) { + Object.defineProperty( + a.prototype, + e, + "function" == typeof u[e] + ? { + value: function () { + return this.xhr[e].apply(this.xhr, arguments); + }, + } + : { + get: function () { + return this.cache.revalidated && this.cache.result.xhr.hasOwnProperty(e) ? this.cache.result.xhr[e] : this.xhr[e]; + }, + set: function (t) { + this.xhr[e] = t; + }, + } + ); + })(f); + return a; + })(), + Utils: { + assert: function (e, t) { + e || abort("Assertion failed: " + t); + }, + optimizeMathFround: function (e, t) { + console.log("optimizing out Math.fround calls"); + for ( + var r = { LOOKING_FOR_MODULE: 0, SCANNING_MODULE_VARIABLES: 1, SCANNING_MODULE_FUNCTIONS: 2 }, + n = ["EMSCRIPTEN_START_ASM", "EMSCRIPTEN_START_FUNCS", "EMSCRIPTEN_END_FUNCS"], + o = "var", + a = "global.Math.fround;", + i = 0, + s = t ? r.LOOKING_FOR_MODULE : r.SCANNING_MODULE_VARIABLES, + d = 0, + l = 0; + s <= r.SCANNING_MODULE_FUNCTIONS && i < e.length; + i++ + ) + if (47 == e[i] && 47 == e[i + 1] && 32 == e[i + 2] && String.fromCharCode.apply(null, e.subarray(i + 3, i + 3 + n[s].length)) === n[s]) s++; + else if (s != r.SCANNING_MODULE_VARIABLES || l || 61 != e[i] || String.fromCharCode.apply(null, e.subarray(i + 1, i + 1 + a.length)) !== a) { + if (l && 40 == e[i]) { + for (var u = 0; u < l && e[i - 1 - u] == e[d - u]; ) u++; + if (u == l) { + var f = e[i - 1 - u]; + if (f < 36 || (36 < f && f < 48) || (57 < f && f < 65) || (90 < f && f < 95) || (95 < f && f < 97) || 122 < f) for (; u; u--) e[i - u] = 32; + } + } + } else { + for (d = i - 1; 32 != e[d - l]; ) l++; + (l && String.fromCharCode.apply(null, e.subarray(d - l - o.length, d - l)) === o) || (d = l = 0); + } + return e; + }, + }, + Cryptography: { + crc32: function (e) { + var t = UnityLoader.Cryptography.crc32.module; + if (!t) { + var r = new ArrayBuffer(16777216), + n = (function (e, t, r) { + "use asm"; + var n = new e.Uint8Array(r); + var o = new e.Uint32Array(r); + function a(e, t) { + e = e | 0; + t = t | 0; + var r = 0; + for (r = o[1024 >> 2] | 0; t; e = (e + 1) | 0, t = (t - 1) | 0) r = o[(((r & 255) ^ n[e]) << 2) >> 2] ^ (r >>> 8) ^ 4278190080; + o[1024 >> 2] = r; + } + return { process: a }; + })({ Uint8Array: Uint8Array, Uint32Array: Uint32Array }, null, r); + t = UnityLoader.Cryptography.crc32.module = { buffer: r, HEAPU8: new Uint8Array(r), HEAPU32: new Uint32Array(r), process: n.process, crc32: 1024, data: 1028 }; + for (var o = 0; o < 256; o++) { + for (var a = 255 ^ o, i = 0; i < 8; i++) a = (a >>> 1) ^ (1 & a ? 3988292384 : 0); + t.HEAPU32[o] = a; + } + } + t.HEAPU32[t.crc32 >> 2] = 0; + for (var s = 0; s < e.length; ) { + var d = Math.min(t.HEAPU8.length - t.data, e.length - s); + t.HEAPU8.set(e.subarray(s, s + d), t.data), (crc = t.process(t.data, d)), (s += d); + } + var l = t.HEAPU32[t.crc32 >> 2]; + return new Uint8Array([l >> 24, l >> 16, l >> 8, l]); + }, + md5: function (e) { + var t = UnityLoader.Cryptography.md5.module; + if (!t) { + var r = new ArrayBuffer(16777216), + n = (function (e, t, r) { + "use asm"; + var n = new e.Uint32Array(r); + function o(e, t) { + e = e | 0; + t = t | 0; + var r = 0, + o = 0, + a = 0, + i = 0, + s = 0, + d = 0, + l = 0, + u = 0, + f = 0, + c = 0, + h = 0, + p = 0; + (r = n[128] | 0), (o = n[129] | 0), (a = n[130] | 0), (i = n[131] | 0); + for (; t; e = (e + 64) | 0, t = (t - 1) | 0) { + s = r; + d = o; + l = a; + u = i; + for (c = 0; (c | 0) < 512; c = (c + 8) | 0) { + p = n[c >> 2] | 0; + r = (r + (n[(c + 4) >> 2] | 0) + (n[(e + (p >>> 14)) >> 2] | 0) + ((c | 0) < 128 ? i ^ (o & (a ^ i)) : (c | 0) < 256 ? a ^ (i & (o ^ a)) : (c | 0) < 384 ? o ^ a ^ i : a ^ (o | ~i))) | 0; + h = (((r << (p & 31)) | (r >>> (32 - (p & 31)))) + o) | 0; + r = i; + i = a; + a = o; + o = h; + } + r = (r + s) | 0; + o = (o + d) | 0; + a = (a + l) | 0; + i = (i + u) | 0; + } + n[128] = r; + n[129] = o; + n[130] = a; + n[131] = i; + } + return { process: o }; + })({ Uint32Array: Uint32Array }, null, r); + (t = UnityLoader.Cryptography.md5.module = { buffer: r, HEAPU8: new Uint8Array(r), HEAPU32: new Uint32Array(r), process: n.process, md5: 512, data: 576 }), + t.HEAPU32.set( + new Uint32Array([ + 7, + 3614090360, + 65548, + 3905402710, + 131089, + 606105819, + 196630, + 3250441966, + 262151, + 4118548399, + 327692, + 1200080426, + 393233, + 2821735955, + 458774, + 4249261313, + 524295, + 1770035416, + 589836, + 2336552879, + 655377, + 4294925233, + 720918, + 2304563134, + 786439, + 1804603682, + 851980, + 4254626195, + 917521, + 2792965006, + 983062, + 1236535329, + 65541, + 4129170786, + 393225, + 3225465664, + 720910, + 643717713, + 20, + 3921069994, + 327685, + 3593408605, + 655369, + 38016083, + 983054, + 3634488961, + 262164, + 3889429448, + 589829, + 568446438, + 917513, + 3275163606, + 196622, + 4107603335, + 524308, + 1163531501, + 851973, + 2850285829, + 131081, + 4243563512, + 458766, + 1735328473, + 786452, + 2368359562, + 327684, + 4294588738, + 524299, + 2272392833, + 720912, + 1839030562, + 917527, + 4259657740, + 65540, + 2763975236, + 262155, + 1272893353, + 458768, + 4139469664, + 655383, + 3200236656, + 851972, + 681279174, + 11, + 3936430074, + 196624, + 3572445317, + 393239, + 76029189, + 589828, + 3654602809, + 786443, + 3873151461, + 983056, + 530742520, + 131095, + 3299628645, + 6, + 4096336452, + 458762, + 1126891415, + 917519, + 2878612391, + 327701, + 4237533241, + 786438, + 1700485571, + 196618, + 2399980690, + 655375, + 4293915773, + 65557, + 2240044497, + 524294, + 1873313359, + 983050, + 4264355552, + 393231, + 2734768916, + 851989, + 1309151649, + 262150, + 4149444226, + 720906, + 3174756917, + 131087, + 718787259, + 589845, + 3951481745, + ]) + ); + } + t.HEAPU32.set(new Uint32Array([1732584193, 4023233417, 2562383102, 271733878]), t.md5 >> 2); + for (var o = 0; o < e.length; ) { + var a = Math.min(t.HEAPU8.length - t.data, e.length - o) & -64; + if ((t.HEAPU8.set(e.subarray(o, o + a), t.data), (o += a), t.process(t.data, a >> 6), e.length - o < 64)) { + if (((a = e.length - o), t.HEAPU8.set(e.subarray(e.length - a, e.length), t.data), (o += a), (t.HEAPU8[t.data + a++] = 128), a > 56)) { + for (var i = a; i < 64; i++) t.HEAPU8[t.data + i] = 0; + t.process(t.data, 1), (a = 0); + } + for (var i = a; i < 64; i++) t.HEAPU8[t.data + i] = 0; + for (var s = e.length, d = 0, i = 56; i < 64; i++, d = (224 & s) >> 5, s /= 256) t.HEAPU8[t.data + i] = ((31 & s) << 3) + d; + t.process(t.data, 1); + } + } + return new Uint8Array(t.HEAPU8.subarray(t.md5, t.md5 + 16)); + }, + sha1: function (e) { + var t = UnityLoader.Cryptography.sha1.module; + if (!t) { + var r = new ArrayBuffer(16777216), + n = (function (e, t, r) { + "use asm"; + var n = new e.Uint32Array(r); + function o(e, t) { + e = e | 0; + t = t | 0; + var r = 0, + o = 0, + a = 0, + i = 0, + s = 0, + d = 0, + l = 0, + u = 0, + f = 0, + c = 0, + h = 0, + p = 0; + (r = n[80] | 0), (o = n[81] | 0), (a = n[82] | 0), (i = n[83] | 0), (s = n[84] | 0); + for (; t; e = (e + 64) | 0, t = (t - 1) | 0) { + d = r; + l = o; + u = a; + f = i; + c = s; + for (p = 0; (p | 0) < 320; p = (p + 4) | 0, s = i, i = a, a = (o << 30) | (o >>> 2), o = r, r = h) { + if ((p | 0) < 64) { + h = n[(e + p) >> 2] | 0; + h = ((h << 24) & 4278190080) | ((h << 8) & 16711680) | ((h >>> 8) & 65280) | ((h >>> 24) & 255); + } else { + h = n[(p - 12) >> 2] ^ n[(p - 32) >> 2] ^ n[(p - 56) >> 2] ^ n[(p - 64) >> 2]; + h = (h << 1) | (h >>> 31); + } + n[p >> 2] = h; + h = + (h + + (((r << 5) | (r >>> 27)) + s) + + ((p | 0) < 80 + ? (((o & a) | (~o & i) | 0) + 1518500249) | 0 + : (p | 0) < 160 + ? ((o ^ a ^ i) + 1859775393) | 0 + : (p | 0) < 240 + ? (((o & a) | (o & i) | (a & i)) + 2400959708) | 0 + : ((o ^ a ^ i) + 3395469782) | 0)) | + 0; + } + r = (r + d) | 0; + o = (o + l) | 0; + a = (a + u) | 0; + i = (i + f) | 0; + s = (s + c) | 0; + } + n[80] = r; + n[81] = o; + n[82] = a; + n[83] = i; + n[84] = s; + } + return { process: o }; + })({ Uint32Array: Uint32Array }, null, r); + t = UnityLoader.Cryptography.sha1.module = { buffer: r, HEAPU8: new Uint8Array(r), HEAPU32: new Uint32Array(r), process: n.process, sha1: 320, data: 384 }; + } + t.HEAPU32.set(new Uint32Array([1732584193, 4023233417, 2562383102, 271733878, 3285377520]), t.sha1 >> 2); + for (var o = 0; o < e.length; ) { + var a = Math.min(t.HEAPU8.length - t.data, e.length - o) & -64; + if ((t.HEAPU8.set(e.subarray(o, o + a), t.data), (o += a), t.process(t.data, a >> 6), e.length - o < 64)) { + if (((a = e.length - o), t.HEAPU8.set(e.subarray(e.length - a, e.length), t.data), (o += a), (t.HEAPU8[t.data + a++] = 128), a > 56)) { + for (var i = a; i < 64; i++) t.HEAPU8[t.data + i] = 0; + t.process(t.data, 1), (a = 0); + } + for (var i = a; i < 64; i++) t.HEAPU8[t.data + i] = 0; + for (var s = e.length, d = 0, i = 63; i >= 56; i--, d = (224 & s) >> 5, s /= 256) t.HEAPU8[t.data + i] = ((31 & s) << 3) + d; + t.process(t.data, 1); + } + } + for (var l = new Uint8Array(20), i = 0; i < l.length; i++) l[i] = t.HEAPU8[t.sha1 + (i & -4) + 3 - (3 & i)]; + return l; + }, + }, + Progress: { + Styles: { + Dark: { + progressLogoUrl: + "data:image/png;base64,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", + progressEmptyUrl: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI0AAAASCAYAAABmbl0zAAAACXBIWXMAAAsSAAALEgHS3X78AAAATUlEQVRo3u3aIQ4AIAwEQUr4/5cPiyMVBDOj0M2mCKgkGdAwjYCudZzLOLiITYPrCdEgGkSDaEA0iAbRIBpEA6JBNHx1vnL7V4NNwxsbCNMGI3YImu0AAAAASUVORK5CYII=", + progressFullUrl: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI0AAAASCAYAAABmbl0zAAAACXBIWXMAAAsSAAALEgHS3X78AAAAO0lEQVRo3u3SQREAAAjDMMC/56EB3omEXjtJCg5GAkyDaTANpsE0YBpMg2kwDaYB02AaTINpMA2Yhr8FO18EIBpZMeQAAAAASUVORK5CYII=", + }, + Light: { + progressLogoUrl: + "data:image/png;base64,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", + progressEmptyUrl: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI0AAAASCAYAAABmbl0zAAAACXBIWXMAAAsSAAALEgHS3X78AAAAUUlEQVRo3u3aMQ4AEAxAUcRJzGb3v1mt3cQglvcmc/NTA3XMFQUuNCPgVk/nahwchE2D6wnRIBpEg2hANIgG0SAaRAOiQTR8lV+5/avBpuGNDcz6A6oq1CgNAAAAAElFTkSuQmCC", + progressFullUrl: + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAI0AAAASCAYAAABmbl0zAAAACXBIWXMAAAsSAAALEgHS3X78AAAAQElEQVRo3u3SMREAMAgAsVIpnTvj3xlogDmR8PfxftaBgSsBpsE0mAbTYBowDabBNJgG04BpMA2mwTSYBkzDXgP/hgGnr4PpeAAAAABJRU5ErkJggg==", + }, + }, + handler: function (e, t) { + if (e.Module) { + var r = UnityLoader.Progress.Styles[e.Module.splashScreenStyle], + n = e.Module.progressLogoUrl ? e.Module.resolveBuildUrl(e.Module.progressLogoUrl) : r.progressLogoUrl, + o = e.Module.progressEmptyUrl ? e.Module.resolveBuildUrl(e.Module.progressEmptyUrl) : r.progressEmptyUrl, + a = e.Module.progressFullUrl ? e.Module.resolveBuildUrl(e.Module.progressFullUrl) : r.progressFullUrl, + i = "position: absolute; left: 50%; top: 50%; -webkit-transform: translate(-50%, -50%); transform: translate(-50%, -50%);"; + e.logo || ((e.logo = document.createElement("div")), (e.logo.style.cssText = i + "background: url('" + n + "') no-repeat center / contain; width: 154px; height: 130px;"), e.container.appendChild(e.logo)), + e.progress || + ((e.progress = document.createElement("div")), + (e.progress.style.cssText = i + " height: 18px; width: 141px; margin-top: 90px;"), + (e.progress.empty = document.createElement("div")), + (e.progress.empty.style.cssText = "background: url('" + o + "') no-repeat right / cover; float: right; width: 100%; height: 100%; display: inline-block;"), + e.progress.appendChild(e.progress.empty), + (e.progress.full = document.createElement("div")), + (e.progress.full.style.cssText = "background: url('" + a + "') no-repeat left / cover; float: left; width: 0%; height: 100%; display: inline-block;"), + e.progress.appendChild(e.progress.full), + e.container.appendChild(e.progress)), + (e.progress.full.style.width = 100 * t + "%"), + (e.progress.empty.style.width = 100 * (1 - t) + "%"), + 1 == t && (e.logo.style.display = e.progress.style.display = "none"); + } + }, + update: function (e, t, r) { + var n = e.buildDownloadProgress[t]; + n || (n = e.buildDownloadProgress[t] = { started: !1, finished: !1, lengthComputable: !1, total: 0, loaded: 0 }), + "object" != typeof r || + ("progress" != r.type && "load" != r.type) || + (n.started || ((n.started = !0), (n.lengthComputable = r.lengthComputable), (n.total = r.total)), (n.loaded = r.loaded), "load" == r.type && (n.finished = !0)); + var o = 0, + a = 0, + i = 0, + s = 0, + d = 0; + for (var t in e.buildDownloadProgress) { + var n = e.buildDownloadProgress[t]; + if (!n.started) return 0; + i++, n.lengthComputable ? ((o += n.loaded), (a += n.total), s++) : n.finished || d++; + } + var l = i ? (i - d - (a ? (s * (a - o)) / a : 0)) / i : 0; + e.gameInstance.onProgress(e.gameInstance, 0.9 * l); + }, + }, + Compression: { + identity: { + require: function () { + return {}; + }, + decompress: function (e) { + return e; + }, + }, + gzip: { + require: function (e) { + var t = { + "inflate.js": function (e, t, r) { + "use strict"; + function n(e) { + if (!(this instanceof n)) return new n(e); + this.options = s.assign({ chunkSize: 16384, windowBits: 0, to: "" }, e || {}); + var t = this.options; + t.raw && t.windowBits >= 0 && t.windowBits < 16 && ((t.windowBits = -t.windowBits), 0 === t.windowBits && (t.windowBits = -15)), + !(t.windowBits >= 0 && t.windowBits < 16) || (e && e.windowBits) || (t.windowBits += 32), + t.windowBits > 15 && t.windowBits < 48 && 0 === (15 & t.windowBits) && (t.windowBits |= 15), + (this.err = 0), + (this.msg = ""), + (this.ended = !1), + (this.chunks = []), + (this.strm = new f()), + (this.strm.avail_out = 0); + var r = i.inflateInit2(this.strm, t.windowBits); + if (r !== l.Z_OK) throw new Error(u[r]); + (this.header = new c()), i.inflateGetHeader(this.strm, this.header); + } + function o(e, t) { + var r = new n(t); + if ((r.push(e, !0), r.err)) throw r.msg || u[r.err]; + return r.result; + } + function a(e, t) { + return (t = t || {}), (t.raw = !0), o(e, t); + } + var i = e("./zlib/inflate"), + s = e("./utils/common"), + d = e("./utils/strings"), + l = e("./zlib/constants"), + u = e("./zlib/messages"), + f = e("./zlib/zstream"), + c = e("./zlib/gzheader"), + h = Object.prototype.toString; + (n.prototype.push = function (e, t) { + var r, + n, + o, + a, + u, + f, + c = this.strm, + p = this.options.chunkSize, + w = this.options.dictionary, + m = !1; + if (this.ended) return !1; + (n = t === ~~t ? t : t === !0 ? l.Z_FINISH : l.Z_NO_FLUSH), + "string" == typeof e ? (c.input = d.binstring2buf(e)) : "[object ArrayBuffer]" === h.call(e) ? (c.input = new Uint8Array(e)) : (c.input = e), + (c.next_in = 0), + (c.avail_in = c.input.length); + do { + if ( + (0 === c.avail_out && ((c.output = new s.Buf8(p)), (c.next_out = 0), (c.avail_out = p)), + (r = i.inflate(c, l.Z_NO_FLUSH)), + r === l.Z_NEED_DICT && w && ((f = "string" == typeof w ? d.string2buf(w) : "[object ArrayBuffer]" === h.call(w) ? new Uint8Array(w) : w), (r = i.inflateSetDictionary(this.strm, f))), + r === l.Z_BUF_ERROR && m === !0 && ((r = l.Z_OK), (m = !1)), + r !== l.Z_STREAM_END && r !== l.Z_OK) + ) + return this.onEnd(r), (this.ended = !0), !1; + c.next_out && + ((0 !== c.avail_out && r !== l.Z_STREAM_END && (0 !== c.avail_in || (n !== l.Z_FINISH && n !== l.Z_SYNC_FLUSH))) || + ("string" === this.options.to + ? ((o = d.utf8border(c.output, c.next_out)), + (a = c.next_out - o), + (u = d.buf2string(c.output, o)), + (c.next_out = a), + (c.avail_out = p - a), + a && s.arraySet(c.output, c.output, o, a, 0), + this.onData(u)) + : this.onData(s.shrinkBuf(c.output, c.next_out)))), + 0 === c.avail_in && 0 === c.avail_out && (m = !0); + } while ((c.avail_in > 0 || 0 === c.avail_out) && r !== l.Z_STREAM_END); + return ( + r === l.Z_STREAM_END && (n = l.Z_FINISH), + n === l.Z_FINISH ? ((r = i.inflateEnd(this.strm)), this.onEnd(r), (this.ended = !0), r === l.Z_OK) : n !== l.Z_SYNC_FLUSH || (this.onEnd(l.Z_OK), (c.avail_out = 0), !0) + ); + }), + (n.prototype.onData = function (e) { + this.chunks.push(e); + }), + (n.prototype.onEnd = function (e) { + e === l.Z_OK && ("string" === this.options.to ? (this.result = this.chunks.join("")) : (this.result = s.flattenChunks(this.chunks))), (this.chunks = []), (this.err = e), (this.msg = this.strm.msg); + }), + (r.Inflate = n), + (r.inflate = o), + (r.inflateRaw = a), + (r.ungzip = o); + }, + "utils/common.js": function (e, t, r) { + "use strict"; + var n = "undefined" != typeof Uint8Array && "undefined" != typeof Uint16Array && "undefined" != typeof Int32Array; + (r.assign = function (e) { + for (var t = Array.prototype.slice.call(arguments, 1); t.length; ) { + var r = t.shift(); + if (r) { + if ("object" != typeof r) throw new TypeError(r + "must be non-object"); + for (var n in r) r.hasOwnProperty(n) && (e[n] = r[n]); + } + } + return e; + }), + (r.shrinkBuf = function (e, t) { + return e.length === t ? e : e.subarray ? e.subarray(0, t) : ((e.length = t), e); + }); + var o = { + arraySet: function (e, t, r, n, o) { + if (t.subarray && e.subarray) return void e.set(t.subarray(r, r + n), o); + for (var a = 0; a < n; a++) e[o + a] = t[r + a]; + }, + flattenChunks: function (e) { + var t, r, n, o, a, i; + for (n = 0, t = 0, r = e.length; t < r; t++) n += e[t].length; + for (i = new Uint8Array(n), o = 0, t = 0, r = e.length; t < r; t++) (a = e[t]), i.set(a, o), (o += a.length); + return i; + }, + }, + a = { + arraySet: function (e, t, r, n, o) { + for (var a = 0; a < n; a++) e[o + a] = t[r + a]; + }, + flattenChunks: function (e) { + return [].concat.apply([], e); + }, + }; + (r.setTyped = function (e) { + e ? ((r.Buf8 = Uint8Array), (r.Buf16 = Uint16Array), (r.Buf32 = Int32Array), r.assign(r, o)) : ((r.Buf8 = Array), (r.Buf16 = Array), (r.Buf32 = Array), r.assign(r, a)); + }), + r.setTyped(n); + }, + "utils/strings.js": function (e, t, r) { + "use strict"; + function n(e, t) { + if (t < 65537 && ((e.subarray && i) || (!e.subarray && a))) return String.fromCharCode.apply(null, o.shrinkBuf(e, t)); + for (var r = "", n = 0; n < t; n++) r += String.fromCharCode(e[n]); + return r; + } + var o = e("./common"), + a = !0, + i = !0; + try { + String.fromCharCode.apply(null, [0]); + } catch (e) { + a = !1; + } + try { + String.fromCharCode.apply(null, new Uint8Array(1)); + } catch (e) { + i = !1; + } + for (var s = new o.Buf8(256), d = 0; d < 256; d++) s[d] = d >= 252 ? 6 : d >= 248 ? 5 : d >= 240 ? 4 : d >= 224 ? 3 : d >= 192 ? 2 : 1; + (s[254] = s[254] = 1), + (r.string2buf = function (e) { + var t, + r, + n, + a, + i, + s = e.length, + d = 0; + for (a = 0; a < s; a++) + (r = e.charCodeAt(a)), + 55296 === (64512 & r) && a + 1 < s && ((n = e.charCodeAt(a + 1)), 56320 === (64512 & n) && ((r = 65536 + ((r - 55296) << 10) + (n - 56320)), a++)), + (d += r < 128 ? 1 : r < 2048 ? 2 : r < 65536 ? 3 : 4); + for (t = new o.Buf8(d), i = 0, a = 0; i < d; a++) + (r = e.charCodeAt(a)), + 55296 === (64512 & r) && a + 1 < s && ((n = e.charCodeAt(a + 1)), 56320 === (64512 & n) && ((r = 65536 + ((r - 55296) << 10) + (n - 56320)), a++)), + r < 128 + ? (t[i++] = r) + : r < 2048 + ? ((t[i++] = 192 | (r >>> 6)), (t[i++] = 128 | (63 & r))) + : r < 65536 + ? ((t[i++] = 224 | (r >>> 12)), (t[i++] = 128 | ((r >>> 6) & 63)), (t[i++] = 128 | (63 & r))) + : ((t[i++] = 240 | (r >>> 18)), (t[i++] = 128 | ((r >>> 12) & 63)), (t[i++] = 128 | ((r >>> 6) & 63)), (t[i++] = 128 | (63 & r))); + return t; + }), + (r.buf2binstring = function (e) { + return n(e, e.length); + }), + (r.binstring2buf = function (e) { + for (var t = new o.Buf8(e.length), r = 0, n = t.length; r < n; r++) t[r] = e.charCodeAt(r); + return t; + }), + (r.buf2string = function (e, t) { + var r, + o, + a, + i, + d = t || e.length, + l = new Array(2 * d); + for (o = 0, r = 0; r < d; ) + if (((a = e[r++]), a < 128)) l[o++] = a; + else if (((i = s[a]), i > 4)) (l[o++] = 65533), (r += i - 1); + else { + for (a &= 2 === i ? 31 : 3 === i ? 15 : 7; i > 1 && r < d; ) (a = (a << 6) | (63 & e[r++])), i--; + i > 1 ? (l[o++] = 65533) : a < 65536 ? (l[o++] = a) : ((a -= 65536), (l[o++] = 55296 | ((a >> 10) & 1023)), (l[o++] = 56320 | (1023 & a))); + } + return n(l, o); + }), + (r.utf8border = function (e, t) { + var r; + for (t = t || e.length, t > e.length && (t = e.length), r = t - 1; r >= 0 && 128 === (192 & e[r]); ) r--; + return r < 0 ? t : 0 === r ? t : r + s[e[r]] > t ? r : t; + }); + }, + "zlib/inflate.js": function (e, t, r) { + "use strict"; + function n(e) { + return ((e >>> 24) & 255) + ((e >>> 8) & 65280) + ((65280 & e) << 8) + ((255 & e) << 24); + } + function o() { + (this.mode = 0), + (this.last = !1), + (this.wrap = 0), + (this.havedict = !1), + (this.flags = 0), + (this.dmax = 0), + (this.check = 0), + (this.total = 0), + (this.head = null), + (this.wbits = 0), + (this.wsize = 0), + (this.whave = 0), + (this.wnext = 0), + (this.window = null), + (this.hold = 0), + (this.bits = 0), + (this.length = 0), + (this.offset = 0), + (this.extra = 0), + (this.lencode = null), + (this.distcode = null), + (this.lenbits = 0), + (this.distbits = 0), + (this.ncode = 0), + (this.nlen = 0), + (this.ndist = 0), + (this.have = 0), + (this.next = null), + (this.lens = new y.Buf16(320)), + (this.work = new y.Buf16(288)), + (this.lendyn = null), + (this.distdyn = null), + (this.sane = 0), + (this.back = 0), + (this.was = 0); + } + function a(e) { + var t; + return e && e.state + ? ((t = e.state), + (e.total_in = e.total_out = t.total = 0), + (e.msg = ""), + t.wrap && (e.adler = 1 & t.wrap), + (t.mode = I), + (t.last = 0), + (t.havedict = 0), + (t.dmax = 32768), + (t.head = null), + (t.hold = 0), + (t.bits = 0), + (t.lencode = t.lendyn = new y.Buf32(we)), + (t.distcode = t.distdyn = new y.Buf32(me)), + (t.sane = 1), + (t.back = -1), + O) + : R; + } + function i(e) { + var t; + return e && e.state ? ((t = e.state), (t.wsize = 0), (t.whave = 0), (t.wnext = 0), a(e)) : R; + } + function s(e, t) { + var r, n; + return e && e.state + ? ((n = e.state), + t < 0 ? ((r = 0), (t = -t)) : ((r = (t >> 4) + 1), t < 48 && (t &= 15)), + t && (t < 8 || t > 15) ? R : (null !== n.window && n.wbits !== t && (n.window = null), (n.wrap = r), (n.wbits = t), i(e))) + : R; + } + function d(e, t) { + var r, n; + return e ? ((n = new o()), (e.state = n), (n.window = null), (r = s(e, t)), r !== O && (e.state = null), r) : R; + } + function l(e) { + return d(e, ye); + } + function u(e) { + if (ge) { + var t; + for (m = new y.Buf32(512), b = new y.Buf32(32), t = 0; t < 144; ) e.lens[t++] = 8; + for (; t < 256; ) e.lens[t++] = 9; + for (; t < 280; ) e.lens[t++] = 7; + for (; t < 288; ) e.lens[t++] = 8; + for (U(E, e.lens, 0, 288, m, 0, e.work, { bits: 9 }), t = 0; t < 32; ) e.lens[t++] = 5; + U(k, e.lens, 0, 32, b, 0, e.work, { bits: 5 }), (ge = !1); + } + (e.lencode = m), (e.lenbits = 9), (e.distcode = b), (e.distbits = 5); + } + function f(e, t, r, n) { + var o, + a = e.state; + return ( + null === a.window && ((a.wsize = 1 << a.wbits), (a.wnext = 0), (a.whave = 0), (a.window = new y.Buf8(a.wsize))), + n >= a.wsize + ? (y.arraySet(a.window, t, r - a.wsize, a.wsize, 0), (a.wnext = 0), (a.whave = a.wsize)) + : ((o = a.wsize - a.wnext), + o > n && (o = n), + y.arraySet(a.window, t, r - n, o, a.wnext), + (n -= o), + n ? (y.arraySet(a.window, t, r - n, n, 0), (a.wnext = n), (a.whave = a.wsize)) : ((a.wnext += o), a.wnext === a.wsize && (a.wnext = 0), a.whave < a.wsize && (a.whave += o))), + 0 + ); + } + function c(e, t) { + var r, + o, + a, + i, + s, + d, + l, + c, + h, + p, + w, + m, + b, + we, + me, + be, + ye, + ge, + ve, + Ae, + Ue, + xe, + Ee, + ke, + Be = 0, + Le = new y.Buf8(4), + We = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]; + if (!e || !e.state || !e.output || (!e.input && 0 !== e.avail_in)) return R; + (r = e.state), r.mode === j && (r.mode = X), (s = e.next_out), (a = e.output), (l = e.avail_out), (i = e.next_in), (o = e.input), (d = e.avail_in), (c = r.hold), (h = r.bits), (p = d), (w = l), (xe = O); + e: for (;;) + switch (r.mode) { + case I: + if (0 === r.wrap) { + r.mode = X; + break; + } + for (; h < 16; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (2 & r.wrap && 35615 === c) { + (r.check = 0), (Le[0] = 255 & c), (Le[1] = (c >>> 8) & 255), (r.check = v(r.check, Le, 2, 0)), (c = 0), (h = 0), (r.mode = P); + break; + } + if (((r.flags = 0), r.head && (r.head.done = !1), !(1 & r.wrap) || (((255 & c) << 8) + (c >> 8)) % 31)) { + (e.msg = "incorrect header check"), (r.mode = ce); + break; + } + if ((15 & c) !== T) { + (e.msg = "unknown compression method"), (r.mode = ce); + break; + } + if (((c >>>= 4), (h -= 4), (Ue = (15 & c) + 8), 0 === r.wbits)) r.wbits = Ue; + else if (Ue > r.wbits) { + (e.msg = "invalid window size"), (r.mode = ce); + break; + } + (r.dmax = 1 << Ue), (e.adler = r.check = 1), (r.mode = 512 & c ? G : j), (c = 0), (h = 0); + break; + case P: + for (; h < 16; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (((r.flags = c), (255 & r.flags) !== T)) { + (e.msg = "unknown compression method"), (r.mode = ce); + break; + } + if (57344 & r.flags) { + (e.msg = "unknown header flags set"), (r.mode = ce); + break; + } + r.head && (r.head.text = (c >> 8) & 1), 512 & r.flags && ((Le[0] = 255 & c), (Le[1] = (c >>> 8) & 255), (r.check = v(r.check, Le, 2, 0))), (c = 0), (h = 0), (r.mode = D); + case D: + for (; h < 32; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + r.head && (r.head.time = c), + 512 & r.flags && ((Le[0] = 255 & c), (Le[1] = (c >>> 8) & 255), (Le[2] = (c >>> 16) & 255), (Le[3] = (c >>> 24) & 255), (r.check = v(r.check, Le, 4, 0))), + (c = 0), + (h = 0), + (r.mode = F); + case F: + for (; h < 16; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + r.head && ((r.head.xflags = 255 & c), (r.head.os = c >> 8)), 512 & r.flags && ((Le[0] = 255 & c), (Le[1] = (c >>> 8) & 255), (r.check = v(r.check, Le, 2, 0))), (c = 0), (h = 0), (r.mode = q); + case q: + if (1024 & r.flags) { + for (; h < 16; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (r.length = c), r.head && (r.head.extra_len = c), 512 & r.flags && ((Le[0] = 255 & c), (Le[1] = (c >>> 8) & 255), (r.check = v(r.check, Le, 2, 0))), (c = 0), (h = 0); + } else r.head && (r.head.extra = null); + r.mode = V; + case V: + if ( + 1024 & r.flags && + ((m = r.length), + m > d && (m = d), + m && + (r.head && ((Ue = r.head.extra_len - r.length), r.head.extra || (r.head.extra = new Array(r.head.extra_len)), y.arraySet(r.head.extra, o, i, m, Ue)), + 512 & r.flags && (r.check = v(r.check, o, m, i)), + (d -= m), + (i += m), + (r.length -= m)), + r.length) + ) + break e; + (r.length = 0), (r.mode = Z); + case Z: + if (2048 & r.flags) { + if (0 === d) break e; + m = 0; + do (Ue = o[i + m++]), r.head && Ue && r.length < 65536 && (r.head.name += String.fromCharCode(Ue)); + while (Ue && m < d); + if ((512 & r.flags && (r.check = v(r.check, o, m, i)), (d -= m), (i += m), Ue)) break e; + } else r.head && (r.head.name = null); + (r.length = 0), (r.mode = Y); + case Y: + if (4096 & r.flags) { + if (0 === d) break e; + m = 0; + do (Ue = o[i + m++]), r.head && Ue && r.length < 65536 && (r.head.comment += String.fromCharCode(Ue)); + while (Ue && m < d); + if ((512 & r.flags && (r.check = v(r.check, o, m, i)), (d -= m), (i += m), Ue)) break e; + } else r.head && (r.head.comment = null); + r.mode = z; + case z: + if (512 & r.flags) { + for (; h < 16; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (c !== (65535 & r.check)) { + (e.msg = "header crc mismatch"), (r.mode = ce); + break; + } + (c = 0), (h = 0); + } + r.head && ((r.head.hcrc = (r.flags >> 9) & 1), (r.head.done = !0)), (e.adler = r.check = 0), (r.mode = j); + break; + case G: + for (; h < 32; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (e.adler = r.check = n(c)), (c = 0), (h = 0), (r.mode = J); + case J: + if (0 === r.havedict) return (e.next_out = s), (e.avail_out = l), (e.next_in = i), (e.avail_in = d), (r.hold = c), (r.bits = h), N; + (e.adler = r.check = 1), (r.mode = j); + case j: + if (t === L || t === W) break e; + case X: + if (r.last) { + (c >>>= 7 & h), (h -= 7 & h), (r.mode = le); + break; + } + for (; h < 3; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + switch (((r.last = 1 & c), (c >>>= 1), (h -= 1), 3 & c)) { + case 0: + r.mode = K; + break; + case 1: + if ((u(r), (r.mode = re), t === W)) { + (c >>>= 2), (h -= 2); + break e; + } + break; + case 2: + r.mode = $; + break; + case 3: + (e.msg = "invalid block type"), (r.mode = ce); + } + (c >>>= 2), (h -= 2); + break; + case K: + for (c >>>= 7 & h, h -= 7 & h; h < 32; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if ((65535 & c) !== ((c >>> 16) ^ 65535)) { + (e.msg = "invalid stored block lengths"), (r.mode = ce); + break; + } + if (((r.length = 65535 & c), (c = 0), (h = 0), (r.mode = Q), t === W)) break e; + case Q: + r.mode = _; + case _: + if ((m = r.length)) { + if ((m > d && (m = d), m > l && (m = l), 0 === m)) break e; + y.arraySet(a, o, i, m, s), (d -= m), (i += m), (l -= m), (s += m), (r.length -= m); + break; + } + r.mode = j; + break; + case $: + for (; h < 14; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (((r.nlen = (31 & c) + 257), (c >>>= 5), (h -= 5), (r.ndist = (31 & c) + 1), (c >>>= 5), (h -= 5), (r.ncode = (15 & c) + 4), (c >>>= 4), (h -= 4), r.nlen > 286 || r.ndist > 30)) { + (e.msg = "too many length or distance symbols"), (r.mode = ce); + break; + } + (r.have = 0), (r.mode = ee); + case ee: + for (; r.have < r.ncode; ) { + for (; h < 3; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (r.lens[We[r.have++]] = 7 & c), (c >>>= 3), (h -= 3); + } + for (; r.have < 19; ) r.lens[We[r.have++]] = 0; + if (((r.lencode = r.lendyn), (r.lenbits = 7), (Ee = { bits: r.lenbits }), (xe = U(x, r.lens, 0, 19, r.lencode, 0, r.work, Ee)), (r.lenbits = Ee.bits), xe)) { + (e.msg = "invalid code lengths set"), (r.mode = ce); + break; + } + (r.have = 0), (r.mode = te); + case te: + for (; r.have < r.nlen + r.ndist; ) { + for (; (Be = r.lencode[c & ((1 << r.lenbits) - 1)]), (me = Be >>> 24), (be = (Be >>> 16) & 255), (ye = 65535 & Be), !(me <= h); ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (ye < 16) (c >>>= me), (h -= me), (r.lens[r.have++] = ye); + else { + if (16 === ye) { + for (ke = me + 2; h < ke; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (((c >>>= me), (h -= me), 0 === r.have)) { + (e.msg = "invalid bit length repeat"), (r.mode = ce); + break; + } + (Ue = r.lens[r.have - 1]), (m = 3 + (3 & c)), (c >>>= 2), (h -= 2); + } else if (17 === ye) { + for (ke = me + 3; h < ke; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (c >>>= me), (h -= me), (Ue = 0), (m = 3 + (7 & c)), (c >>>= 3), (h -= 3); + } else { + for (ke = me + 7; h < ke; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (c >>>= me), (h -= me), (Ue = 0), (m = 11 + (127 & c)), (c >>>= 7), (h -= 7); + } + if (r.have + m > r.nlen + r.ndist) { + (e.msg = "invalid bit length repeat"), (r.mode = ce); + break; + } + for (; m--; ) r.lens[r.have++] = Ue; + } + } + if (r.mode === ce) break; + if (0 === r.lens[256]) { + (e.msg = "invalid code -- missing end-of-block"), (r.mode = ce); + break; + } + if (((r.lenbits = 9), (Ee = { bits: r.lenbits }), (xe = U(E, r.lens, 0, r.nlen, r.lencode, 0, r.work, Ee)), (r.lenbits = Ee.bits), xe)) { + (e.msg = "invalid literal/lengths set"), (r.mode = ce); + break; + } + if (((r.distbits = 6), (r.distcode = r.distdyn), (Ee = { bits: r.distbits }), (xe = U(k, r.lens, r.nlen, r.ndist, r.distcode, 0, r.work, Ee)), (r.distbits = Ee.bits), xe)) { + (e.msg = "invalid distances set"), (r.mode = ce); + break; + } + if (((r.mode = re), t === W)) break e; + case re: + r.mode = ne; + case ne: + if (d >= 6 && l >= 258) { + (e.next_out = s), + (e.avail_out = l), + (e.next_in = i), + (e.avail_in = d), + (r.hold = c), + (r.bits = h), + A(e, w), + (s = e.next_out), + (a = e.output), + (l = e.avail_out), + (i = e.next_in), + (o = e.input), + (d = e.avail_in), + (c = r.hold), + (h = r.bits), + r.mode === j && (r.back = -1); + break; + } + for (r.back = 0; (Be = r.lencode[c & ((1 << r.lenbits) - 1)]), (me = Be >>> 24), (be = (Be >>> 16) & 255), (ye = 65535 & Be), !(me <= h); ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (be && 0 === (240 & be)) { + for (ge = me, ve = be, Ae = ye; (Be = r.lencode[Ae + ((c & ((1 << (ge + ve)) - 1)) >> ge)]), (me = Be >>> 24), (be = (Be >>> 16) & 255), (ye = 65535 & Be), !(ge + me <= h); ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (c >>>= ge), (h -= ge), (r.back += ge); + } + if (((c >>>= me), (h -= me), (r.back += me), (r.length = ye), 0 === be)) { + r.mode = de; + break; + } + if (32 & be) { + (r.back = -1), (r.mode = j); + break; + } + if (64 & be) { + (e.msg = "invalid literal/length code"), (r.mode = ce); + break; + } + (r.extra = 15 & be), (r.mode = oe); + case oe: + if (r.extra) { + for (ke = r.extra; h < ke; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (r.length += c & ((1 << r.extra) - 1)), (c >>>= r.extra), (h -= r.extra), (r.back += r.extra); + } + (r.was = r.length), (r.mode = ae); + case ae: + for (; (Be = r.distcode[c & ((1 << r.distbits) - 1)]), (me = Be >>> 24), (be = (Be >>> 16) & 255), (ye = 65535 & Be), !(me <= h); ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (0 === (240 & be)) { + for (ge = me, ve = be, Ae = ye; (Be = r.distcode[Ae + ((c & ((1 << (ge + ve)) - 1)) >> ge)]), (me = Be >>> 24), (be = (Be >>> 16) & 255), (ye = 65535 & Be), !(ge + me <= h); ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (c >>>= ge), (h -= ge), (r.back += ge); + } + if (((c >>>= me), (h -= me), (r.back += me), 64 & be)) { + (e.msg = "invalid distance code"), (r.mode = ce); + break; + } + (r.offset = ye), (r.extra = 15 & be), (r.mode = ie); + case ie: + if (r.extra) { + for (ke = r.extra; h < ke; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + (r.offset += c & ((1 << r.extra) - 1)), (c >>>= r.extra), (h -= r.extra), (r.back += r.extra); + } + if (r.offset > r.dmax) { + (e.msg = "invalid distance too far back"), (r.mode = ce); + break; + } + r.mode = se; + case se: + if (0 === l) break e; + if (((m = w - l), r.offset > m)) { + if (((m = r.offset - m), m > r.whave && r.sane)) { + (e.msg = "invalid distance too far back"), (r.mode = ce); + break; + } + m > r.wnext ? ((m -= r.wnext), (b = r.wsize - m)) : (b = r.wnext - m), m > r.length && (m = r.length), (we = r.window); + } else (we = a), (b = s - r.offset), (m = r.length); + m > l && (m = l), (l -= m), (r.length -= m); + do a[s++] = we[b++]; + while (--m); + 0 === r.length && (r.mode = ne); + break; + case de: + if (0 === l) break e; + (a[s++] = r.length), l--, (r.mode = ne); + break; + case le: + if (r.wrap) { + for (; h < 32; ) { + if (0 === d) break e; + d--, (c |= o[i++] << h), (h += 8); + } + if (((w -= l), (e.total_out += w), (r.total += w), w && (e.adler = r.check = r.flags ? v(r.check, a, w, s - w) : g(r.check, a, w, s - w)), (w = l), (r.flags ? c : n(c)) !== r.check)) { + (e.msg = "incorrect data check"), (r.mode = ce); + break; + } + (c = 0), (h = 0); + } + r.mode = ue; + case ue: + if (r.wrap && r.flags) { + for (; h < 32; ) { + if (0 === d) break e; + d--, (c += o[i++] << h), (h += 8); + } + if (c !== (4294967295 & r.total)) { + (e.msg = "incorrect length check"), (r.mode = ce); + break; + } + (c = 0), (h = 0); + } + r.mode = fe; + case fe: + xe = M; + break e; + case ce: + xe = C; + break e; + case he: + return H; + case pe: + default: + return R; + } + return ( + (e.next_out = s), + (e.avail_out = l), + (e.next_in = i), + (e.avail_in = d), + (r.hold = c), + (r.bits = h), + (r.wsize || (w !== e.avail_out && r.mode < ce && (r.mode < le || t !== B))) && f(e, e.output, e.next_out, w - e.avail_out) + ? ((r.mode = he), H) + : ((p -= e.avail_in), + (w -= e.avail_out), + (e.total_in += p), + (e.total_out += w), + (r.total += w), + r.wrap && w && (e.adler = r.check = r.flags ? v(r.check, a, w, e.next_out - w) : g(r.check, a, w, e.next_out - w)), + (e.data_type = r.bits + (r.last ? 64 : 0) + (r.mode === j ? 128 : 0) + (r.mode === re || r.mode === Q ? 256 : 0)), + ((0 === p && 0 === w) || t === B) && xe === O && (xe = S), + xe) + ); + } + function h(e) { + if (!e || !e.state) return R; + var t = e.state; + return t.window && (t.window = null), (e.state = null), O; + } + function p(e, t) { + var r; + return e && e.state ? ((r = e.state), 0 === (2 & r.wrap) ? R : ((r.head = t), (t.done = !1), O)) : R; + } + function w(e, t) { + var r, + n, + o, + a = t.length; + return e && e.state ? ((r = e.state), 0 !== r.wrap && r.mode !== J ? R : r.mode === J && ((n = 1), (n = g(n, t, a, 0)), n !== r.check) ? C : (o = f(e, t, a, a)) ? ((r.mode = he), H) : ((r.havedict = 1), O)) : R; + } + var m, + b, + y = e("../utils/common"), + g = e("./adler32"), + v = e("./crc32"), + A = e("./inffast"), + U = e("./inftrees"), + x = 0, + E = 1, + k = 2, + B = 4, + L = 5, + W = 6, + O = 0, + M = 1, + N = 2, + R = -2, + C = -3, + H = -4, + S = -5, + T = 8, + I = 1, + P = 2, + D = 3, + F = 4, + q = 5, + V = 6, + Z = 7, + Y = 8, + z = 9, + G = 10, + J = 11, + j = 12, + X = 13, + K = 14, + Q = 15, + _ = 16, + $ = 17, + ee = 18, + te = 19, + re = 20, + ne = 21, + oe = 22, + ae = 23, + ie = 24, + se = 25, + de = 26, + le = 27, + ue = 28, + fe = 29, + ce = 30, + he = 31, + pe = 32, + we = 852, + me = 592, + be = 15, + ye = be, + ge = !0; + (r.inflateReset = i), + (r.inflateReset2 = s), + (r.inflateResetKeep = a), + (r.inflateInit = l), + (r.inflateInit2 = d), + (r.inflate = c), + (r.inflateEnd = h), + (r.inflateGetHeader = p), + (r.inflateSetDictionary = w), + (r.inflateInfo = "pako inflate (from Nodeca project)"); + }, + "zlib/constants.js": function (e, t, r) { + "use strict"; + t.exports = { + Z_NO_FLUSH: 0, + Z_PARTIAL_FLUSH: 1, + Z_SYNC_FLUSH: 2, + Z_FULL_FLUSH: 3, + Z_FINISH: 4, + Z_BLOCK: 5, + Z_TREES: 6, + Z_OK: 0, + Z_STREAM_END: 1, + Z_NEED_DICT: 2, + Z_ERRNO: -1, + Z_STREAM_ERROR: -2, + Z_DATA_ERROR: -3, + Z_BUF_ERROR: -5, + Z_NO_COMPRESSION: 0, + Z_BEST_SPEED: 1, + Z_BEST_COMPRESSION: 9, + Z_DEFAULT_COMPRESSION: -1, + Z_FILTERED: 1, + Z_HUFFMAN_ONLY: 2, + Z_RLE: 3, + Z_FIXED: 4, + Z_DEFAULT_STRATEGY: 0, + Z_BINARY: 0, + Z_TEXT: 1, + Z_UNKNOWN: 2, + Z_DEFLATED: 8, + }; + }, + "zlib/messages.js": function (e, t, r) { + "use strict"; + t.exports = { 2: "need dictionary", 1: "stream end", 0: "", "-1": "file error", "-2": "stream error", "-3": "data error", "-4": "insufficient memory", "-5": "buffer error", "-6": "incompatible version" }; + }, + "zlib/zstream.js": function (e, t, r) { + "use strict"; + function n() { + (this.input = null), + (this.next_in = 0), + (this.avail_in = 0), + (this.total_in = 0), + (this.output = null), + (this.next_out = 0), + (this.avail_out = 0), + (this.total_out = 0), + (this.msg = ""), + (this.state = null), + (this.data_type = 2), + (this.adler = 0); + } + t.exports = n; + }, + "zlib/gzheader.js": function (e, t, r) { + "use strict"; + function n() { + (this.text = 0), (this.time = 0), (this.xflags = 0), (this.os = 0), (this.extra = null), (this.extra_len = 0), (this.name = ""), (this.comment = ""), (this.hcrc = 0), (this.done = !1); + } + t.exports = n; + }, + "zlib/adler32.js": function (e, t, r) { + "use strict"; + function n(e, t, r, n) { + for (var o = (65535 & e) | 0, a = ((e >>> 16) & 65535) | 0, i = 0; 0 !== r; ) { + (i = r > 2e3 ? 2e3 : r), (r -= i); + do (o = (o + t[n++]) | 0), (a = (a + o) | 0); + while (--i); + (o %= 65521), (a %= 65521); + } + return o | (a << 16) | 0; + } + t.exports = n; + }, + "zlib/crc32.js": function (e, t, r) { + "use strict"; + function n() { + for (var e, t = [], r = 0; r < 256; r++) { + e = r; + for (var n = 0; n < 8; n++) e = 1 & e ? 3988292384 ^ (e >>> 1) : e >>> 1; + t[r] = e; + } + return t; + } + function o(e, t, r, n) { + var o = a, + i = n + r; + e ^= -1; + for (var s = n; s < i; s++) e = (e >>> 8) ^ o[255 & (e ^ t[s])]; + return e ^ -1; + } + var a = n(); + t.exports = o; + }, + "zlib/inffast.js": function (e, t, r) { + "use strict"; + var n = 30, + o = 12; + t.exports = function (e, t) { + var r, a, i, s, d, l, u, f, c, h, p, w, m, b, y, g, v, A, U, x, E, k, B, L, W; + (r = e.state), + (a = e.next_in), + (L = e.input), + (i = a + (e.avail_in - 5)), + (s = e.next_out), + (W = e.output), + (d = s - (t - e.avail_out)), + (l = s + (e.avail_out - 257)), + (u = r.dmax), + (f = r.wsize), + (c = r.whave), + (h = r.wnext), + (p = r.window), + (w = r.hold), + (m = r.bits), + (b = r.lencode), + (y = r.distcode), + (g = (1 << r.lenbits) - 1), + (v = (1 << r.distbits) - 1); + e: do { + m < 15 && ((w += L[a++] << m), (m += 8), (w += L[a++] << m), (m += 8)), (A = b[w & g]); + t: for (;;) { + if (((U = A >>> 24), (w >>>= U), (m -= U), (U = (A >>> 16) & 255), 0 === U)) W[s++] = 65535 & A; + else { + if (!(16 & U)) { + if (0 === (64 & U)) { + A = b[(65535 & A) + (w & ((1 << U) - 1))]; + continue t; + } + if (32 & U) { + r.mode = o; + break e; + } + (e.msg = "invalid literal/length code"), (r.mode = n); + break e; + } + (x = 65535 & A), + (U &= 15), + U && (m < U && ((w += L[a++] << m), (m += 8)), (x += w & ((1 << U) - 1)), (w >>>= U), (m -= U)), + m < 15 && ((w += L[a++] << m), (m += 8), (w += L[a++] << m), (m += 8)), + (A = y[w & v]); + r: for (;;) { + if (((U = A >>> 24), (w >>>= U), (m -= U), (U = (A >>> 16) & 255), !(16 & U))) { + if (0 === (64 & U)) { + A = y[(65535 & A) + (w & ((1 << U) - 1))]; + continue r; + } + (e.msg = "invalid distance code"), (r.mode = n); + break e; + } + if (((E = 65535 & A), (U &= 15), m < U && ((w += L[a++] << m), (m += 8), m < U && ((w += L[a++] << m), (m += 8))), (E += w & ((1 << U) - 1)), E > u)) { + (e.msg = "invalid distance too far back"), (r.mode = n); + break e; + } + if (((w >>>= U), (m -= U), (U = s - d), E > U)) { + if (((U = E - U), U > c && r.sane)) { + (e.msg = "invalid distance too far back"), (r.mode = n); + break e; + } + if (((k = 0), (B = p), 0 === h)) { + if (((k += f - U), U < x)) { + x -= U; + do W[s++] = p[k++]; + while (--U); + (k = s - E), (B = W); + } + } else if (h < U) { + if (((k += f + h - U), (U -= h), U < x)) { + x -= U; + do W[s++] = p[k++]; + while (--U); + if (((k = 0), h < x)) { + (U = h), (x -= U); + do W[s++] = p[k++]; + while (--U); + (k = s - E), (B = W); + } + } + } else if (((k += h - U), U < x)) { + x -= U; + do W[s++] = p[k++]; + while (--U); + (k = s - E), (B = W); + } + for (; x > 2; ) (W[s++] = B[k++]), (W[s++] = B[k++]), (W[s++] = B[k++]), (x -= 3); + x && ((W[s++] = B[k++]), x > 1 && (W[s++] = B[k++])); + } else { + k = s - E; + do (W[s++] = W[k++]), (W[s++] = W[k++]), (W[s++] = W[k++]), (x -= 3); + while (x > 2); + x && ((W[s++] = W[k++]), x > 1 && (W[s++] = W[k++])); + } + break; + } + } + break; + } + } while (a < i && s < l); + (x = m >> 3), + (a -= x), + (m -= x << 3), + (w &= (1 << m) - 1), + (e.next_in = a), + (e.next_out = s), + (e.avail_in = a < i ? 5 + (i - a) : 5 - (a - i)), + (e.avail_out = s < l ? 257 + (l - s) : 257 - (s - l)), + (r.hold = w), + (r.bits = m); + }; + }, + "zlib/inftrees.js": function (e, t, r) { + "use strict"; + var n = e("../utils/common"), + o = 15, + a = 852, + i = 592, + s = 0, + d = 1, + l = 2, + u = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0], + f = [16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78], + c = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 0, 0], + h = [16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, 28, 28, 29, 29, 64, 64]; + t.exports = function (e, t, r, p, w, m, b, y) { + var g, + v, + A, + U, + x, + E, + k, + B, + L, + W = y.bits, + O = 0, + M = 0, + N = 0, + R = 0, + C = 0, + H = 0, + S = 0, + T = 0, + I = 0, + P = 0, + D = null, + F = 0, + q = new n.Buf16(o + 1), + V = new n.Buf16(o + 1), + Z = null, + Y = 0; + for (O = 0; O <= o; O++) q[O] = 0; + for (M = 0; M < p; M++) q[t[r + M]]++; + for (C = W, R = o; R >= 1 && 0 === q[R]; R--); + if ((C > R && (C = R), 0 === R)) return (w[m++] = 20971520), (w[m++] = 20971520), (y.bits = 1), 0; + for (N = 1; N < R && 0 === q[N]; N++); + for (C < N && (C = N), T = 1, O = 1; O <= o; O++) if (((T <<= 1), (T -= q[O]), T < 0)) return -1; + if (T > 0 && (e === s || 1 !== R)) return -1; + for (V[1] = 0, O = 1; O < o; O++) V[O + 1] = V[O] + q[O]; + for (M = 0; M < p; M++) 0 !== t[r + M] && (b[V[t[r + M]]++] = M); + if ( + (e === s ? ((D = Z = b), (E = 19)) : e === d ? ((D = u), (F -= 257), (Z = f), (Y -= 257), (E = 256)) : ((D = c), (Z = h), (E = -1)), + (P = 0), + (M = 0), + (O = N), + (x = m), + (H = C), + (S = 0), + (A = -1), + (I = 1 << C), + (U = I - 1), + (e === d && I > a) || (e === l && I > i)) + ) + return 1; + for (;;) { + (k = O - S), b[M] < E ? ((B = 0), (L = b[M])) : b[M] > E ? ((B = Z[Y + b[M]]), (L = D[F + b[M]])) : ((B = 96), (L = 0)), (g = 1 << (O - S)), (v = 1 << H), (N = v); + do (v -= g), (w[x + (P >> S) + v] = (k << 24) | (B << 16) | L | 0); + while (0 !== v); + for (g = 1 << (O - 1); P & g; ) g >>= 1; + if ((0 !== g ? ((P &= g - 1), (P += g)) : (P = 0), M++, 0 === --q[O])) { + if (O === R) break; + O = t[r + b[M]]; + } + if (O > C && (P & U) !== A) { + for (0 === S && (S = C), x += N, H = O - S, T = 1 << H; H + S < R && ((T -= q[H + S]), !(T <= 0)); ) H++, (T <<= 1); + if (((I += 1 << H), (e === d && I > a) || (e === l && I > i))) return 1; + (A = P & U), (w[A] = (C << 24) | (H << 16) | (x - m) | 0); + } + } + return 0 !== P && (w[x + P] = ((O - S) << 24) | (64 << 16) | 0), (y.bits = C), 0; + }; + }, + }; + for (var r in t) t[r].folder = r.substring(0, r.lastIndexOf("/") + 1); + var n = function (e) { + var r = []; + return ( + (e = e.split("/").every(function (e) { + return ".." == e ? r.pop() : "." == e || "" == e || r.push(e); + }) + ? r.join("/") + : null), + e ? t[e] || t[e + ".js"] || t[e + "/index.js"] : null + ); + }, + o = function (e, t) { + return e ? n(e.folder + "node_modules/" + t) || o(e.parent, t) : null; + }, + a = function (e, t) { + var r = t.match(/^\//) ? null : e ? (t.match(/^\.\.?\//) ? n(e.folder + t) : o(e, t)) : n(t); + if (!r) throw "module not found: " + t; + return r.exports || ((r.parent = e), r(a.bind(null, r), r, (r.exports = {}))), r.exports; + }; + return a(null, e); + }, + decompress: function (e) { + this.exports || (this.exports = this.require("inflate.js")); + try { + return this.exports.inflate(e); + } catch (e) {} + }, + hasUnityMarker: function (e) { + var t = 10, + r = "UnityWeb Compressed Content (gzip)"; + if (t > e.length || 31 != e[0] || 139 != e[1]) return !1; + var n = e[3]; + if (4 & n) { + if (t + 2 > e.length) return !1; + if (((t += 2 + e[t] + (e[t + 1] << 8)), t > e.length)) return !1; + } + if (8 & n) { + for (; t < e.length && e[t]; ) t++; + if (t + 1 > e.length) return !1; + t++; + } + return 16 & n && String.fromCharCode.apply(null, e.subarray(t, t + r.length + 1)) == r + "\0"; + }, + }, + brotli: { + require: function (e) { + var t = { + "decompress.js": function (e, t, r) { + t.exports = e("./dec/decode").BrotliDecompressBuffer; + }, + "dec/bit_reader.js": function (e, t, r) { + function n(e) { + (this.buf_ = new Uint8Array(a)), (this.input_ = e), this.reset(); + } + const o = 4096, + a = 8224, + i = 8191, + s = new Uint32Array([0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, 131071, 262143, 524287, 1048575, 2097151, 4194303, 8388607, 16777215]); + (n.READ_SIZE = o), + (n.IBUF_MASK = i), + (n.prototype.reset = function () { + (this.buf_ptr_ = 0), (this.val_ = 0), (this.pos_ = 0), (this.bit_pos_ = 0), (this.bit_end_pos_ = 0), (this.eos_ = 0), this.readMoreInput(); + for (var e = 0; e < 4; e++) (this.val_ |= this.buf_[this.pos_] << (8 * e)), ++this.pos_; + return this.bit_end_pos_ > 0; + }), + (n.prototype.readMoreInput = function () { + if (!(this.bit_end_pos_ > 256)) + if (this.eos_) { + if (this.bit_pos_ > this.bit_end_pos_) throw new Error("Unexpected end of input " + this.bit_pos_ + " " + this.bit_end_pos_); + } else { + var e = this.buf_ptr_, + t = this.input_.read(this.buf_, e, o); + if (t < 0) throw new Error("Unexpected end of input"); + if (t < o) { + this.eos_ = 1; + for (var r = 0; r < 32; r++) this.buf_[e + t + r] = 0; + } + if (0 === e) { + for (var r = 0; r < 32; r++) this.buf_[8192 + r] = this.buf_[r]; + this.buf_ptr_ = o; + } else this.buf_ptr_ = 0; + this.bit_end_pos_ += t << 3; + } + }), + (n.prototype.fillBitWindow = function () { + for (; this.bit_pos_ >= 8; ) (this.val_ >>>= 8), (this.val_ |= this.buf_[this.pos_ & i] << 24), ++this.pos_, (this.bit_pos_ = (this.bit_pos_ - 8) >>> 0), (this.bit_end_pos_ = (this.bit_end_pos_ - 8) >>> 0); + }), + (n.prototype.readBits = function (e) { + 32 - this.bit_pos_ < e && this.fillBitWindow(); + var t = (this.val_ >>> this.bit_pos_) & s[e]; + return (this.bit_pos_ += e), t; + }), + (t.exports = n); + }, + "dec/context.js": function (e, t, r) { + (r.lookup = new Uint8Array([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4, + 4, + 0, + 0, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8, + 12, + 16, + 12, + 12, + 20, + 12, + 16, + 24, + 28, + 12, + 12, + 32, + 12, + 36, + 12, + 44, + 44, + 44, + 44, + 44, + 44, + 44, + 44, + 44, + 44, + 32, + 32, + 24, + 40, + 28, + 12, + 12, + 48, + 52, + 52, + 52, + 48, + 52, + 52, + 52, + 48, + 52, + 52, + 52, + 52, + 52, + 48, + 52, + 52, + 52, + 52, + 52, + 48, + 52, + 52, + 52, + 52, + 52, + 24, + 12, + 28, + 12, + 12, + 12, + 56, + 60, + 60, + 60, + 56, + 60, + 60, + 60, + 56, + 60, + 60, + 60, + 60, + 60, + 56, + 60, + 60, + 60, + 60, + 60, + 56, + 60, + 60, + 60, + 60, + 60, + 24, + 12, + 28, + 12, + 0, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 2, + 3, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 0, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 8, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 16, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 24, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 32, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 40, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 48, + 56, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 62, + 63, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 6, + 6, + 6, + 6, + 7, + 7, + 7, + 7, + 8, + 8, + 8, + 8, + 9, + 9, + 9, + 9, + 10, + 10, + 10, + 10, + 11, + 11, + 11, + 11, + 12, + 12, + 12, + 12, + 13, + 13, + 13, + 13, + 14, + 14, + 14, + 14, + 15, + 15, + 15, + 15, + 16, + 16, + 16, + 16, + 17, + 17, + 17, + 17, + 18, + 18, + 18, + 18, + 19, + 19, + 19, + 19, + 20, + 20, + 20, + 20, + 21, + 21, + 21, + 21, + 22, + 22, + 22, + 22, + 23, + 23, + 23, + 23, + 24, + 24, + 24, + 24, + 25, + 25, + 25, + 25, + 26, + 26, + 26, + 26, + 27, + 27, + 27, + 27, + 28, + 28, + 28, + 28, + 29, + 29, + 29, + 29, + 30, + 30, + 30, + 30, + 31, + 31, + 31, + 31, + 32, + 32, + 32, + 32, + 33, + 33, + 33, + 33, + 34, + 34, + 34, + 34, + 35, + 35, + 35, + 35, + 36, + 36, + 36, + 36, + 37, + 37, + 37, + 37, + 38, + 38, + 38, + 38, + 39, + 39, + 39, + 39, + 40, + 40, + 40, + 40, + 41, + 41, + 41, + 41, + 42, + 42, + 42, + 42, + 43, + 43, + 43, + 43, + 44, + 44, + 44, + 44, + 45, + 45, + 45, + 45, + 46, + 46, + 46, + 46, + 47, + 47, + 47, + 47, + 48, + 48, + 48, + 48, + 49, + 49, + 49, + 49, + 50, + 50, + 50, + 50, + 51, + 51, + 51, + 51, + 52, + 52, + 52, + 52, + 53, + 53, + 53, + 53, + 54, + 54, + 54, + 54, + 55, + 55, + 55, + 55, + 56, + 56, + 56, + 56, + 57, + 57, + 57, + 57, + 58, + 58, + 58, + 58, + 59, + 59, + 59, + 59, + 60, + 60, + 60, + 60, + 61, + 61, + 61, + 61, + 62, + 62, + 62, + 62, + 63, + 63, + 63, + 63, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ])), + (r.lookupOffsets = new Uint16Array([1024, 1536, 1280, 1536, 0, 256, 768, 512])); + }, + "dec/decode.js": function (e, t, r) { + function n(e) { + var t; + return 0 === e.readBits(1) ? 16 : ((t = e.readBits(3)), t > 0 ? 17 + t : ((t = e.readBits(3)), t > 0 ? 8 + t : 17)); + } + function o(e) { + if (e.readBits(1)) { + var t = e.readBits(3); + return 0 === t ? 1 : e.readBits(t) + (1 << t); + } + return 0; + } + function a() { + (this.meta_block_length = 0), (this.input_end = 0), (this.is_uncompressed = 0), (this.is_metadata = !1); + } + function i(e) { + var t, + r, + n, + o = new a(); + if (((o.input_end = e.readBits(1)), o.input_end && e.readBits(1))) return o; + if (((t = e.readBits(2) + 4), 7 === t)) { + if (((o.is_metadata = !0), 0 !== e.readBits(1))) throw new Error("Invalid reserved bit"); + if (((r = e.readBits(2)), 0 === r)) return o; + for (n = 0; n < r; n++) { + var i = e.readBits(8); + if (n + 1 === r && r > 1 && 0 === i) throw new Error("Invalid size byte"); + o.meta_block_length |= i << (8 * n); + } + } else + for (n = 0; n < t; ++n) { + var s = e.readBits(4); + if (n + 1 === t && t > 4 && 0 === s) throw new Error("Invalid size nibble"); + o.meta_block_length |= s << (4 * n); + } + return ++o.meta_block_length, o.input_end || o.is_metadata || (o.is_uncompressed = e.readBits(1)), o; + } + function s(e, t, r) { + var n; + return ( + r.fillBitWindow(), + (t += (r.val_ >>> r.bit_pos_) & D), + (n = e[t].bits - P), + n > 0 && ((r.bit_pos_ += P), (t += e[t].value), (t += (r.val_ >>> r.bit_pos_) & ((1 << n) - 1))), + (r.bit_pos_ += e[t].bits), + e[t].value + ); + } + function d(e, t, r, n) { + for (var o = 0, a = N, i = 0, s = 0, d = 32768, l = [], u = 0; u < 32; u++) l.push(new B(0, 0)); + for (L(l, 0, 5, e, q); o < t && d > 0; ) { + var f, + c = 0; + if ((n.readMoreInput(), n.fillBitWindow(), (c += (n.val_ >>> n.bit_pos_) & 31), (n.bit_pos_ += l[c].bits), (f = 255 & l[c].value), f < R)) (i = 0), (r[o++] = f), 0 !== f && ((a = f), (d -= 32768 >> f)); + else { + var h, + p, + w = f - 14, + m = 0; + if ((f === R && (m = a), s !== m && ((i = 0), (s = m)), (h = i), i > 0 && ((i -= 2), (i <<= w)), (i += n.readBits(w) + 3), (p = i - h), o + p > t)) + throw new Error("[ReadHuffmanCodeLengths] symbol + repeat_delta > num_symbols"); + for (var b = 0; b < p; b++) r[o + b] = s; + (o += p), 0 !== s && (d -= p << (15 - s)); + } + } + if (0 !== d) throw new Error("[ReadHuffmanCodeLengths] space = " + d); + for (; o < t; o++) r[o] = 0; + } + function l(e, t, r, n) { + var o, + a = 0, + i = new Uint8Array(e); + if ((n.readMoreInput(), (o = n.readBits(2)), 1 === o)) { + for (var s, l = e - 1, u = 0, f = new Int32Array(4), c = n.readBits(2) + 1; l; ) (l >>= 1), ++u; + for (s = 0; s < c; ++s) (f[s] = n.readBits(u) % e), (i[f[s]] = 2); + switch (((i[f[0]] = 1), c)) { + case 1: + break; + case 3: + if (f[0] === f[1] || f[0] === f[2] || f[1] === f[2]) throw new Error("[ReadHuffmanCode] invalid symbols"); + break; + case 2: + if (f[0] === f[1]) throw new Error("[ReadHuffmanCode] invalid symbols"); + i[f[1]] = 1; + break; + case 4: + if (f[0] === f[1] || f[0] === f[2] || f[0] === f[3] || f[1] === f[2] || f[1] === f[3] || f[2] === f[3]) throw new Error("[ReadHuffmanCode] invalid symbols"); + n.readBits(1) ? ((i[f[2]] = 3), (i[f[3]] = 3)) : (i[f[0]] = 2); + } + } else { + var s, + h = new Uint8Array(q), + p = 32, + w = 0, + m = [ + new B(2, 0), + new B(2, 4), + new B(2, 3), + new B(3, 2), + new B(2, 0), + new B(2, 4), + new B(2, 3), + new B(4, 1), + new B(2, 0), + new B(2, 4), + new B(2, 3), + new B(3, 2), + new B(2, 0), + new B(2, 4), + new B(2, 3), + new B(4, 5), + ]; + for (s = o; s < q && p > 0; ++s) { + var b, + y = V[s], + g = 0; + n.fillBitWindow(), (g += (n.val_ >>> n.bit_pos_) & 15), (n.bit_pos_ += m[g].bits), (b = m[g].value), (h[y] = b), 0 !== b && ((p -= 32 >> b), ++w); + } + if (1 !== w && 0 !== p) throw new Error("[ReadHuffmanCode] invalid num_codes or space"); + d(h, e, i, n); + } + if (((a = L(t, r, P, i, e)), 0 === a)) throw new Error("[ReadHuffmanCode] BuildHuffmanTable failed: "); + return a; + } + function u(e, t, r) { + var n, o; + return (n = s(e, t, r)), (o = O.kBlockLengthPrefixCode[n].nbits), O.kBlockLengthPrefixCode[n].offset + r.readBits(o); + } + function f(e, t, r) { + var n; + return e < Z ? ((r += Y[e]), (r &= 3), (n = t[r] + z[e])) : (n = e - Z + 1), n; + } + function c(e, t) { + for (var r = e[t], n = t; n; --n) e[n] = e[n - 1]; + e[0] = r; + } + function h(e, t) { + var r, + n = new Uint8Array(256); + for (r = 0; r < 256; ++r) n[r] = r; + for (r = 0; r < t; ++r) { + var o = e[r]; + (e[r] = n[o]), o && c(n, o); + } + } + function p(e, t) { + (this.alphabet_size = e), (this.num_htrees = t), (this.codes = new Array(t + t * G[(e + 31) >>> 5])), (this.htrees = new Uint32Array(t)); + } + function w(e, t) { + var r, + n, + a, + i = { num_htrees: null, context_map: null }, + d = 0; + t.readMoreInput(); + var u = (i.num_htrees = o(t) + 1), + f = (i.context_map = new Uint8Array(e)); + if (u <= 1) return i; + for (r = t.readBits(1), r && (d = t.readBits(4) + 1), n = [], a = 0; a < F; a++) n[a] = new B(0, 0); + for (l(u + d, n, 0, t), a = 0; a < e; ) { + var c; + if ((t.readMoreInput(), (c = s(n, 0, t)), 0 === c)) (f[a] = 0), ++a; + else if (c <= d) + for (var p = 1 + (1 << c) + t.readBits(c); --p; ) { + if (a >= e) throw new Error("[DecodeContextMap] i >= context_map_size"); + (f[a] = 0), ++a; + } + else (f[a] = c - d), ++a; + } + return t.readBits(1) && h(f, e), i; + } + function m(e, t, r, n, o, a, i) { + var d, + l = 2 * r, + u = r, + f = s(t, r * F, i); + (d = 0 === f ? o[l + (1 & a[u])] : 1 === f ? o[l + ((a[u] - 1) & 1)] + 1 : f - 2), d >= e && (d -= e), (n[r] = d), (o[l + (1 & a[u])] = d), ++a[u]; + } + function b(e, t, r, n, o, a) { + var i, + s = o + 1, + d = r & o, + l = a.pos_ & E.IBUF_MASK; + if (t < 8 || a.bit_pos_ + (t << 3) < a.bit_end_pos_) for (; t-- > 0; ) a.readMoreInput(), (n[d++] = a.readBits(8)), d === s && (e.write(n, s), (d = 0)); + else { + if (a.bit_end_pos_ < 32) throw new Error("[CopyUncompressedBlockToOutput] br.bit_end_pos_ < 32"); + for (; a.bit_pos_ < 32; ) (n[d] = a.val_ >>> a.bit_pos_), (a.bit_pos_ += 8), ++d, --t; + if (((i = (a.bit_end_pos_ - a.bit_pos_) >> 3), l + i > E.IBUF_MASK)) { + for (var u = E.IBUF_MASK + 1 - l, f = 0; f < u; f++) n[d + f] = a.buf_[l + f]; + (i -= u), (d += u), (t -= u), (l = 0); + } + for (var f = 0; f < i; f++) n[d + f] = a.buf_[l + f]; + if (((d += i), (t -= i), d >= s)) { + e.write(n, s), (d -= s); + for (var f = 0; f < d; f++) n[f] = n[s + f]; + } + for (; d + t >= s; ) { + if (((i = s - d), a.input_.read(n, d, i) < i)) throw new Error("[CopyUncompressedBlockToOutput] not enough bytes"); + e.write(n, s), (t -= i), (d = 0); + } + if (a.input_.read(n, d, t) < t) throw new Error("[CopyUncompressedBlockToOutput] not enough bytes"); + a.reset(); + } + } + function y(e) { + var t = (e.bit_pos_ + 7) & -8, + r = e.readBits(t - e.bit_pos_); + return 0 == r; + } + function g(e) { + var t = new U(e), + r = new E(t); + n(r); + var o = i(r); + return o.meta_block_length; + } + function v(e, t) { + var r = new U(e); + null == t && (t = g(e)); + var n = new Uint8Array(t), + o = new x(n); + return A(r, o), o.pos < o.buffer.length && (o.buffer = o.buffer.subarray(0, o.pos)), o.buffer; + } + function A(e, t) { + var r, + a, + d, + c, + h, + g, + v, + A, + U, + x = 0, + L = 0, + N = 0, + R = 0, + P = [16, 15, 11, 4], + D = 0, + q = 0, + V = 0, + Y = [new p(0, 0), new p(0, 0), new p(0, 0)]; + const z = 128 + E.READ_SIZE; + (U = new E(e)), (N = n(U)), (a = (1 << N) - 16), (d = 1 << N), (c = d - 1), (h = new Uint8Array(d + z + k.maxDictionaryWordLength)), (g = d), (v = []), (A = []); + for (var G = 0; G < 3240; G++) (v[G] = new B(0, 0)), (A[G] = new B(0, 0)); + for (; !L; ) { + var J, + j, + X, + K, + Q, + _, + $, + ee, + te, + re = 0, + ne = [1 << 28, 1 << 28, 1 << 28], + oe = [0], + ae = [1, 1, 1], + ie = [0, 1, 0, 1, 0, 1], + se = [0], + de = null, + le = null, + ue = null, + fe = 0, + ce = null, + he = 0, + pe = 0, + we = null, + me = 0, + be = 0, + ye = 0; + for (r = 0; r < 3; ++r) (Y[r].codes = null), (Y[r].htrees = null); + U.readMoreInput(); + var ge = i(U); + if (((re = ge.meta_block_length), x + re > t.buffer.length)) { + var ve = new Uint8Array(x + re); + ve.set(t.buffer), (t.buffer = ve); + } + if (((L = ge.input_end), (J = ge.is_uncompressed), ge.is_metadata)) for (y(U); re > 0; --re) U.readMoreInput(), U.readBits(8); + else if (0 !== re) + if (J) (U.bit_pos_ = (U.bit_pos_ + 7) & -8), b(t, re, x, h, c, U), (x += re); + else { + for (r = 0; r < 3; ++r) (ae[r] = o(U) + 1), ae[r] >= 2 && (l(ae[r] + 2, v, r * F, U), l(S, A, r * F, U), (ne[r] = u(A, r * F, U)), (se[r] = 1)); + for (U.readMoreInput(), j = U.readBits(2), X = Z + (U.readBits(4) << j), K = (1 << j) - 1, Q = X + (48 << j), le = new Uint8Array(ae[0]), r = 0; r < ae[0]; ++r) + U.readMoreInput(), (le[r] = U.readBits(2) << 1); + var Ae = w(ae[0] << T, U); + (_ = Ae.num_htrees), (de = Ae.context_map); + var Ue = w(ae[2] << I, U); + for ($ = Ue.num_htrees, ue = Ue.context_map, Y[0] = new p(C, _), Y[1] = new p(H, ae[1]), Y[2] = new p(Q, $), r = 0; r < 3; ++r) Y[r].decode(U); + for (ce = 0, we = 0, ee = le[oe[0]], be = W.lookupOffsets[ee], ye = W.lookupOffsets[ee + 1], te = Y[1].htrees[0]; re > 0; ) { + var xe, Ee, ke, Be, Le, We, Oe, Me, Ne, Re, Ce; + for ( + U.readMoreInput(), + 0 === ne[1] && (m(ae[1], v, 1, oe, ie, se, U), (ne[1] = u(A, F, U)), (te = Y[1].htrees[oe[1]])), + --ne[1], + xe = s(Y[1].codes, te, U), + Ee = xe >> 6, + Ee >= 2 ? ((Ee -= 2), (Oe = -1)) : (Oe = 0), + ke = O.kInsertRangeLut[Ee] + ((xe >> 3) & 7), + Be = O.kCopyRangeLut[Ee] + (7 & xe), + Le = O.kInsertLengthPrefixCode[ke].offset + U.readBits(O.kInsertLengthPrefixCode[ke].nbits), + We = O.kCopyLengthPrefixCode[Be].offset + U.readBits(O.kCopyLengthPrefixCode[Be].nbits), + q = h[(x - 1) & c], + V = h[(x - 2) & c], + Re = 0; + Re < Le; + ++Re + ) + U.readMoreInput(), + 0 === ne[0] && (m(ae[0], v, 0, oe, ie, se, U), (ne[0] = u(A, 0, U)), (fe = oe[0] << T), (ce = fe), (ee = le[oe[0]]), (be = W.lookupOffsets[ee]), (ye = W.lookupOffsets[ee + 1])), + (Ne = W.lookup[be + q] | W.lookup[ye + V]), + (he = de[ce + Ne]), + --ne[0], + (V = q), + (q = s(Y[0].codes, Y[0].htrees[he], U)), + (h[x & c] = q), + (x & c) === c && t.write(h, d), + ++x; + if (((re -= Le), re <= 0)) break; + if (Oe < 0) { + var Ne; + if ( + (U.readMoreInput(), + 0 === ne[2] && (m(ae[2], v, 2, oe, ie, se, U), (ne[2] = u(A, 2160, U)), (pe = oe[2] << I), (we = pe)), + --ne[2], + (Ne = 255 & (We > 4 ? 3 : We - 2)), + (me = ue[we + Ne]), + (Oe = s(Y[2].codes, Y[2].htrees[me], U)), + Oe >= X) + ) { + var He, Se, Te; + (Oe -= X), (Se = Oe & K), (Oe >>= j), (He = (Oe >> 1) + 1), (Te = ((2 + (1 & Oe)) << He) - 4), (Oe = X + ((Te + U.readBits(He)) << j) + Se); + } + } + if (((Me = f(Oe, P, D)), Me < 0)) throw new Error("[BrotliDecompress] invalid distance"); + if (((R = x < a && R !== a ? x : a), (Ce = x & c), Me > R)) { + if (!(We >= k.minDictionaryWordLength && We <= k.maxDictionaryWordLength)) throw new Error("Invalid backward reference. pos: " + x + " distance: " + Me + " len: " + We + " bytes left: " + re); + var Te = k.offsetsByLength[We], + Ie = Me - R - 1, + Pe = k.sizeBitsByLength[We], + De = (1 << Pe) - 1, + Fe = Ie & De, + qe = Ie >> Pe; + if (((Te += Fe * We), !(qe < M.kNumTransforms))) throw new Error("Invalid backward reference. pos: " + x + " distance: " + Me + " len: " + We + " bytes left: " + re); + var Ve = M.transformDictionaryWord(h, Ce, Te, We, qe); + if (((Ce += Ve), (x += Ve), (re -= Ve), Ce >= g)) { + t.write(h, d); + for (var Ze = 0; Ze < Ce - g; Ze++) h[Ze] = h[g + Ze]; + } + } else { + if ((Oe > 0 && ((P[3 & D] = Me), ++D), We > re)) throw new Error("Invalid backward reference. pos: " + x + " distance: " + Me + " len: " + We + " bytes left: " + re); + for (Re = 0; Re < We; ++Re) (h[x & c] = h[(x - Me) & c]), (x & c) === c && t.write(h, d), ++x, --re; + } + (q = h[(x - 1) & c]), (V = h[(x - 2) & c]); + } + x &= 1073741823; + } + } + t.write(h, x & c); + } + var U = e("./streams").BrotliInput, + x = e("./streams").BrotliOutput, + E = e("./bit_reader"), + k = e("./dictionary"), + B = e("./huffman").HuffmanCode, + L = e("./huffman").BrotliBuildHuffmanTable, + W = e("./context"), + O = e("./prefix"), + M = e("./transform"); + const N = 8, + R = 16, + C = 256, + H = 704, + S = 26, + T = 6, + I = 2, + P = 8, + D = 255, + F = 1080, + q = 18, + V = new Uint8Array([1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15]), + Z = 16, + Y = new Uint8Array([3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2]), + z = new Int8Array([0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3]), + G = new Uint16Array([256, 402, 436, 468, 500, 534, 566, 598, 630, 662, 694, 726, 758, 790, 822, 854, 886, 920, 952, 984, 1016, 1048, 1080]); + (p.prototype.decode = function (e) { + var t, + r, + n = 0; + for (t = 0; t < this.num_htrees; ++t) (this.htrees[t] = n), (r = l(this.alphabet_size, this.codes, n, e)), (n += r); + }), + (r.BrotliDecompressedSize = g), + (r.BrotliDecompressBuffer = v), + (r.BrotliDecompress = A), + k.init(); + }, + "dec/dictionary.js": function (e, t, r) { + var n = e("./dictionary-browser"); + (r.init = function () { + r.dictionary = n.init(); + }), + (r.offsetsByLength = new Uint32Array([0, 0, 0, 0, 0, 4096, 9216, 21504, 35840, 44032, 53248, 63488, 74752, 87040, 93696, 100864, 104704, 106752, 108928, 113536, 115968, 118528, 119872, 121280, 122016])), + (r.sizeBitsByLength = new Uint8Array([0, 0, 0, 0, 10, 10, 11, 11, 10, 10, 10, 10, 10, 9, 9, 8, 7, 7, 8, 7, 7, 6, 6, 5, 5])), + (r.minDictionaryWordLength = 4), + (r.maxDictionaryWordLength = 24); + }, + "dec/dictionary.bin.js": function (e, t, r) { + t.exports = + "W5/fcQLn5gKf2XUbAiQ1XULX+TZz6ADToDsgqk6qVfeC0e4m6OO2wcQ1J76ZBVRV1fRkEsdu//62zQsFEZWSTCnMhcsQKlS2qOhuVYYMGCkV0fXWEoMFbESXrKEZ9wdUEsyw9g4bJlEt1Y6oVMxMRTEVbCIwZzJzboK5j8m4YH02qgXYhv1V+PM435sLVxyHJihaJREEhZGqL03txGFQLm76caGO/ovxKvzCby/3vMTtX/459f0igi7WutnKiMQ6wODSoRh/8Lx1V3Q99MvKtwB6bHdERYRY0hStJoMjNeTsNX7bn+Y7e4EQ3bf8xBc7L0BsyfFPK43dGSXpL6clYC/I328h54/VYrQ5i0648FgbGtl837svJ35L3Mot/+nPlNpWgKx1gGXQYqX6n+bbZ7wuyCHKcUok12Xjqub7NXZGzqBx0SD+uziNf87t7ve42jxSKQoW3nyxVrWIGlFShhCKxjpZZ5MeGna0+lBkk+kaN8F9qFBAFgEogyMBdcX/T1W/WnMOi/7ycWUQloEBKGeC48MkiwqJkJO+12eQiOFHMmck6q/IjWW3RZlany23TBm+cNr/84/oi5GGmGBZWrZ6j+zykVozz5fT/QH/Da6WTbZYYPynVNO7kxzuNN2kxKKWche5WveitPKAecB8YcAHz/+zXLjcLzkdDSktNIDwZE9J9X+tto43oJy65wApM3mDzYtCwX9lM+N5VR3kXYo0Z3t0TtXfgBFg7gU8oN0Dgl7fZlUbhNll+0uuohRVKjrEd8egrSndy5/Tgd2gqjA4CAVuC7ESUmL3DZoGnfhQV8uwnpi8EGvAVVsowNRxPudck7+oqAUDkwZopWqFnW1riss0t1z6iCISVKreYGNvQcXv+1L9+jbP8cd/dPUiqBso2q+7ZyFBvENCkkVr44iyPbtOoOoCecWsiuqMSML5lv+vN5MzUr+Dnh73G7Q1YnRYJVYXHRJaNAOByiaK6CusgFdBPE40r0rvqXV7tksKO2DrHYXBTv8P5ysqxEx8VDXUDDqkPH6NNOV/a2WH8zlkXRELSa8P+heNyJBBP7PgsG1EtWtNef6/i+lcayzQwQCsduidpbKfhWUDgAEmyhGu/zVTacI6RS0zTABrOYueemnVa19u9fT23N/Ta6RvTpof5DWygqreCqrDAgM4LID1+1T/taU6yTFVLqXOv+/MuQOFnaF8vLMKD7tKWDoBdALgxF33zQccCcdHx8fKIVdW69O7qHtXpeGr9jbbpFA+qRMWr5hp0s67FPc7HAiLV0g0/peZlW7hJPYEhZyhpSwahnf93/tZgfqZWXFdmdXBzqxGHLrQKxoAY6fRoBhgCRPmmGueYZ5JexTVDKUIXzkG/fqp/0U3hAgQdJ9zumutK6nqWbaqvm1pgu03IYR+G+8s0jDBBz8cApZFSBeuWasyqo2OMDKAZCozS+GWSvL/HsE9rHxooe17U3s/lTE+VZAk4j3dp6uIGaC0JMiqR5CUsabPyM0dOYDR7Ea7ip4USZlya38YfPtvrX/tBlhHilj55nZ1nfN24AOAi9BVtz/Mbn8AEDJCqJgsVUa6nQnSxv2Fs7l/NlCzpfYEjmPrNyib/+t0ei2eEMjvNhLkHCZlci4WhBe7ePZTmzYqlY9+1pxtS4GB+5lM1BHT9tS270EWUDYFq1I0yY/fNiAk4bk9yBgmef/f2k6AlYQZHsNFnW8wBQxCd68iWv7/35bXfz3JZmfGligWAKRjIs3IpzxQ27vAglHSiOzCYzJ9L9A1CdiyFvyR66ucA4jKifu5ehwER26yV7HjKqn5Mfozo7Coxxt8LWWPT47BeMxX8p0Pjb7hZn+6bw7z3Lw+7653j5sI8CLu5kThpMlj1m4c2ch3jGcP1FsT13vuK3qjecKTZk2kHcOZY40UX+qdaxstZqsqQqgXz+QGF99ZJLqr3VYu4aecl1Ab5GmqS8k/GV5b95zxQ5d4EfXUJ6kTS/CXF/aiqKDOT1T7Jz5z0PwDUcwr9clLN1OJGCiKfqvah+h3XzrBOiLOW8wvn8gW6qE8vPxi+Efv+UH55T7PQFVMh6cZ1pZQlzJpKZ7P7uWvwPGJ6DTlR6wbyj3Iv2HyefnRo/dv7dNx+qaa0N38iBsR++Uil7Wd4afwDNsrzDAK4fXZwvEY/jdKuIKXlfrQd2C39dW7ntnRbIp9OtGy9pPBn/V2ASoi/2UJZfS+xuGLH8bnLuPlzdTNS6zdyk8Dt/h6sfOW5myxh1f+zf3zZ3MX/mO9cQPp5pOx967ZA6/pqHvclNfnUFF+rq+Vd7alKr6KWPcIDhpn6v2K6NlUu6LrKo8b/pYpU/Gazfvtwhn7tEOUuXht5rUJdSf6sLjYf0VTYDgwJ81yaqKTUYej/tbHckSRb/HZicwGJqh1mAHB/IuNs9dc9yuvF3D5Xocm3elWFdq5oEy70dYFit79yaLiNjPj5UUcVmZUVhQEhW5V2Z6Cm4HVH/R8qlamRYwBileuh07CbEce3TXa2JmXWBf+ozt319psboobeZhVnwhMZzOeQJzhpTDbP71Tv8HuZxxUI/+ma3XW6DFDDs4+qmpERwHGBd2edxwUKlODRdUWZ/g0GOezrbzOZauFMai4QU6GVHV6aPNBiBndHSsV4IzpvUiiYyg6OyyrL4Dj5q/Lw3N5kAwftEVl9rNd7Jk5PDij2hTH6wIXnsyXkKePxbmHYgC8A6an5Fob/KH5GtC0l4eFso+VpxedtJHdHpNm+Bvy4C79yVOkrZsLrQ3OHCeB0Ra+kBIRldUGlDCEmq2RwXnfyh6Dz+alk6eftI2n6sastRrGwbwszBeDRS/Fa/KwRJkCzTsLr/JCs5hOPE/MPLYdZ1F1fv7D+VmysX6NpOC8aU9F4Qs6HvDyUy9PvFGDKZ/P5101TYHFl8pjj6wm/qyS75etZhhfg0UEL4OYmHk6m6dO192AzoIyPSV9QedDA4Ml23rRbqxMPMxf7FJnDc5FTElVS/PyqgePzmwVZ26NWhRDQ+oaT7ly7ell4s3DypS1s0g+tOr7XHrrkZj9+x/mJBttrLx98lFIaRZzHz4aC7r52/JQ4VjHahY2/YVXZn/QC2ztQb/sY3uRlyc5vQS8nLPGT/n27495i8HPA152z7Fh5aFpyn1GPJKHuPL8Iw94DuW3KjkURAWZXn4EQy89xiKEHN1mk/tkM4gYDBxwNoYvRfE6LFqsxWJtPrDGbsnLMap3Ka3MUoytW0cvieozOmdERmhcqzG+3HmZv2yZeiIeQTKGdRT4HHNxekm1tY+/n06rGmFleqLscSERzctTKM6G9P0Pc1RmVvrascIxaO1CQCiYPE15bD7c3xSeW7gXxYjgxcrUlcbIvO0r+Yplhx0kTt3qafDOmFyMjgGxXu73rddMHpV1wMubyAGcf/v5dLr5P72Ta9lBF+fzMJrMycwv+9vnU3ANIl1cH9tfW7af8u0/HG0vV47jNFXzFTtaha1xvze/s8KMtCYucXc1nzfd/MQydUXn/b72RBt5wO/3jRcMH9BdhC/yctKBIveRYPrNpDWqBsO8VMmP+WvRaOcA4zRMR1PvSoO92rS7pYEv+fZfEfTMzEdM+6X5tLlyxExhqLRkms5EuLovLfx66de5fL2/yX02H52FPVwahrPqmN/E0oVXnsCKhbi/yRxX83nRbUKWhzYceXOntfuXn51NszJ6MO73pQf5Pl4in3ec4JU8hF7ppV34+mm9r1LY0ee/i1O1wpd8+zfLztE0cqBxggiBi5Bu95v9l3r9r/U5hweLn+TbfxowrWDqdJauKd8+q/dH8sbPkc9ttuyO94f7/XK/nHX46MPFLEb5qQlNPvhJ50/59t9ft3LXu7uVaWaO2bDrDCnRSzZyWvFKxO1+vT8MwwunR3bX0CkfPjqb4K9O19tn5X50PvmYpEwHtiW9WtzuV/s76B1zvLLNkViNd8ySxIl/3orfqP90TyTGaf7/rx8jQzeHJXdmh/N6YDvbvmTBwCdxfEQ1NcL6wNMdSIXNq7b1EUzRy1/Axsyk5p22GMG1b+GxFgbHErZh92wuvco0AuOLXct9hvw2nw/LqIcDRRmJmmZzcgUa7JpM/WV/S9IUfbF56TL2orzqwebdRD8nIYNJ41D/hz37Fo11p2Y21wzPcn713qVGhqtevStYfGH4n69OEJtPvbbLYWvscDqc3Hgnu166+tAyLnxrX0Y5zoYjV++1sI7t5kMr02KT/+uwtkc+rZLOf/qn/s3nYCf13Dg8/sB2diJgjGqjQ+TLhxbzyue2Ob7X6/9lUwW7a+lbznHzOYy8LKW1C/uRPbQY3KW/0gO9LXunHLvPL97afba9bFtc9hmz7GAttjVYlCvQAiOwAk/gC5+hkLEs6tr3AZKxLJtOEwk2dLxTYWsIB/j/ToWtIWzo906FrSG8iaqqqqqqiIiIiAgzMzMzNz+AyK+01/zi8n8S+Y1MjoRaQ80WU/G8MBlO+53VPXANrWm4wzGUVZUjjBJZVdhpcfkjsmcWaO+UEldXi1e+zq+HOsCpknYshuh8pOLISJun7TN0EIGW2xTnlOImeecnoGW4raxe2G1T3HEvfYUYMhG+gAFOAwh5nK8mZhwJMmN7r224QVsNFvZ87Z0qatvknklyPDK3Hy45PgVKXji52Wen4d4PlFVVYGnNap+fSpFbK90rYnhUc6n91Q3AY9E0tJOFrcfZtm/491XbcG/jsViUPPX76qmeuiz+qY1Hk7/1VPM405zWVuoheLUimpWYdVzCmUdKHebMdzgrYrb8mL2eeLSnRWHdonfZa8RsOU9F37w+591l5FLYHiOqWeHtE/lWrBHcRKp3uhtr8yXm8LU/5ms+NM6ZKsqu90cFZ4o58+k4rdrtB97NADFbwmEG7lXqvirhOTOqU14xuUF2myIjURcPHrPOQ4lmM3PeMg7bUuk0nnZi67bXsU6H8lhqIo8TaOrEafCO1ARK9PjC0QOoq2BxmMdgYB9G/lIb9++fqNJ2s7BHGFyBNmZAR8J3KCo012ikaSP8BCrf6VI0X5xdnbhHIO+B5rbOyB54zXkzfObyJ4ecwxfqBJMLFc7m59rNcw7hoHnFZ0b00zee+gTqvjm61Pb4xn0kcDX4jvHM0rBXZypG3DCKnD/Waa/ZtHmtFPgO5eETx+k7RrVg3aSwm2YoNXnCs3XPQDhNn+Fia6IlOOuIG6VJH7TP6ava26ehKHQa2T4N0tcZ9dPCGo3ZdnNltsHQbeYt5vPnJezV/cAeNypdml1vCHI8M81nSRP5Qi2+mI8v/sxiZru9187nRtp3f/42NemcONa+4eVC3PCZzc88aZh851CqSsshe70uPxeN/dmYwlwb3trwMrN1Gq8jbnApcVDx/yDPeYs5/7r62tsQ6lLg+DiFXTEhzR9dHqv0iT4tgj825W+H3XiRUNUZT2kR9Ri0+lp+UM3iQtS8uOE23Ly4KYtvqH13jghUntJRAewuzNLDXp8RxdcaA3cMY6TO2IeSFRXezeWIjCqyhsUdMYuCgYTZSKpBype1zRfq8FshvfBPc6BAQWl7/QxIDp3VGo1J3vn42OEs3qznws+YLRXbymyB19a9XBx6n/owcyxlEYyFWCi+kG9F+EyD/4yn80+agaZ9P7ay2Dny99aK2o91FkfEOY8hBwyfi5uwx2y5SaHmG+oq/zl1FX/8irOf8Y3vAcX/6uLP6A6nvMO24edSGPjQc827Rw2atX+z2bKq0CmW9mOtYnr5/AfDa1ZfPaXnKtlWborup7QYx+Or2uWb+N3N//2+yDcXMqIJdf55xl7/vsj4WoPPlxLxtVrkJ4w/tTe3mLdATOOYwxcq52w5Wxz5MbPdVs5O8/lhfE7dPj0bIiPQ3QV0iqm4m3YX8hRfc6jQ3fWepevMqUDJd86Z4vwM40CWHnn+WphsGHfieF02D3tmZvpWD+kBpNCFcLnZhcmmrhpGzzbdA+sQ1ar18OJD87IOKOFoRNznaHPNHUfUNhvY1iU+uhvEvpKHaUn3qK3exVVyX4joipp3um7FmYJWmA+WbIDshRpbVRx5/nqstCgy87FGbfVB8yDGCqS+2qCsnRwnSAN6zgzxfdB2nBT/vZ4/6uxb6oH8b4VBRxiIB93wLa47hG3w2SL/2Z27yOXJFwZpSJaBYyvajA7vRRYNKqljXKpt/CFD/tSMr18DKKbwB0xggBePatl1nki0yvqW5zchlyZmJ0OTxJ3D+fsYJs/mxYN5+Le5oagtcl+YsVvy8kSjI2YGvGjvmpkRS9W2dtXqWnVuxUhURm1lKtou/hdEq19VBp9OjGvHEQSmrpuf2R24mXGheil8KeiANY8fW1VERUfBImb64j12caBZmRViZHbeVMjCrPDg9A90IXrtnsYCuZtRQ0PyrKDjBNOsPfKsg1pA02gHlVr0OXiFhtp6nJqXVzcbfM0KnzC3ggOENPE9VBdmHKN6LYaijb4wXxJn5A0FSDF5j+h1ooZx885Jt3ZKzO5n7Z5WfNEOtyyPqQEnn7WLv5Fis3PdgMshjF1FRydbNyeBbyKI1oN1TRVrVK7kgsb/zjX4NDPIRMctVeaxVB38Vh1x5KbeJbU138AM5KzmZu3uny0ErygxiJF7GVXUrPzFxrlx1uFdAaZFDN9cvIb74qD9tzBMo7L7WIEYK+sla1DVMHpF0F7b3+Y6S+zjvLeDMCpapmJo1weBWuxKF3rOocih1gun4BoJh1kWnV/Jmiq6uOhK3VfKxEHEkafjLgK3oujaPzY6SXg8phhL4TNR1xvJd1Wa0aYFfPUMLrNBDCh4AuGRTbtKMc6Z1Udj8evY/ZpCuMAUefdo69DZUngoqE1P9A3PJfOf7WixCEj+Y6t7fYeHbbxUAoFV3M89cCKfma3fc1+jKRe7MFWEbQqEfyzO2x/wrO2VYH7iYdQ9BkPyI8/3kXBpLaCpU7eC0Yv/am/tEDu7HZpqg0EvHo0nf/R/gRzUWy33/HXMJQeu1GylKmOkXzlCfGFruAcPPhaGqZOtu19zsJ1SO2Jz4Ztth5cBX6mRQwWmDwryG9FUMlZzNckMdK+IoMJv1rOWnBamS2w2KHiaPMPLC15hCZm4KTpoZyj4E2TqC/P6r7/EhnDMhKicZZ1ZwxuC7DPzDGs53q8gXaI9kFTK+2LTq7bhwsTbrMV8Rsfua5lMS0FwbTitUVnVa1yTb5IX51mmYnUcP9wPr8Ji1tiYJeJV9GZTrQhF7vvdU2OTU42ogJ9FDwhmycI2LIg++03C6scYhUyUuMV5tkw6kGUoL+mjNC38+wMdWNljn6tGPpRES7veqrSn5TRuv+dh6JVL/iDHU1db4c9WK3++OrH3PqziF916UMUKn8G67nN60GfWiHrXYhUG3yVWmyYak59NHj8t1smG4UDiWz2rPHNrKnN4Zo1LBbr2/eF9YZ0n0blx2nG4X+EKFxvS3W28JESD+FWk61VCD3z/URGHiJl++7TdBwkCj6tGOH3qDb0QqcOF9Kzpj0HUb/KyFW3Yhj2VMKJqGZleFBH7vqvf7WqLC3XMuHV8q8a4sTFuxUtkD/6JIBvKaVjv96ndgruKZ1k/BHzqf2K9fLk7HGXANyLDd1vxkK/i055pnzl+zw6zLnwXlVYVtfmacJgEpRP1hbGgrYPVN6v2lG+idQNGmwcKXu/8xEj/P6qe/sB2WmwNp6pp8jaISMkwdleFXYK55NHWLTTbutSUqjBfDGWo/Yg918qQ+8BRZSAHZbfuNZz2O0sov1Ue4CWlVg3rFhM3Kljj9ksGd/NUhk4nH+a5UN2+1i8+NM3vRNp7uQ6sqexSCukEVlVZriHNqFi5rLm9TMWa4qm3idJqppQACol2l4VSuvWLfta4JcXy3bROPNbXOgdOhG47LC0CwW/dMlSx4Jf17aEU3yA1x9p+Yc0jupXgcMuYNku64iYOkGToVDuJvlbEKlJqsmiHbvNrIVZEH+yFdF8DbleZ6iNiWwMqvtMp/mSpwx5KxRrT9p3MAPTHGtMbfvdFhyj9vhaKcn3At8Lc16Ai+vBcSp1ztXi7rCJZx/ql7TXcclq6Q76UeKWDy9boS0WHIjUuWhPG8LBmW5y2rhuTpM5vsLt+HOLh1Yf0DqXa9tsfC+kaKt2htA0ai/L2i7RKoNjEwztkmRU0GfgW1TxUvPFhg0V7DdfWJk5gfrccpYv+MA9M0dkGTLECeYwUixRzjRFdmjG7zdZIl3XKB9YliNKI31lfa7i2JG5C8Ss+rHe0D7Z696/V3DEAOWHnQ9yNahMUl5kENWS6pHKKp2D1BaSrrHdE1w2qNxIztpXgUIrF0bm15YML4b6V1k+GpNysTahKMVrrS85lTVo9OGJ96I47eAy5rYWpRf/mIzeoYU1DKaQCTUVwrhHeyNoDqHel+lLxr9WKzhSYw7vrR6+V5q0pfi2k3L1zqkubY6rrd9ZLvSuWNf0uqnkY+FpTvFzSW9Fp0b9l8JA7THV9eCi/PY/SCZIUYx3BU2alj7Cm3VV6eYpios4b6WuNOJdYXUK3zTqj5CVG2FqYM4Z7CuIU0qO05XR0d71FHM0YhZmJmTRfLlXEumN82BGtzdX0S19t1e+bUieK8zRmqpa4Qc5TSjifmaQsY2ETLjhI36gMR1+7qpjdXXHiceUekfBaucHShAOiFXmv3sNmGQyU5iVgnoocuonQXEPTFwslHtS8R+A47StI9wj0iSrtbi5rMysczFiImsQ+bdFClnFjjpXXwMy6O7qfjOr8Fb0a7ODItisjnn3EQO16+ypd1cwyaAW5Yzxz5QknfMO7643fXW/I9y3U2xH27Oapqr56Z/tEzglj6IbT6HEHjopiXqeRbe5mQQvxtcbDOVverN0ZgMdzqRYRjaXtMRd56Q4cZSmdPvZJdSrhJ1D9zNXPqAEqPIavPdfubt5oke2kmv0dztIszSv2VYuoyf1UuopbsYb+uX9h6WpwjpgtZ6fNNawNJ4q8O3CFoSbioAaOSZMx2GYaPYB+rEb6qjQiNRFQ76TvwNFVKD+BhH9VhcKGsXzmMI7BptU/CNWolM7YzROvpFAntsiWJp6eR2d3GarcYShVYSUqhmYOWj5E96NK2WvmYNTeY7Zs4RUEdv9h9QT4EseKt6LzLrqEOs3hxAY1MaNWpSa6zZx8F3YOVeCYMS88W+CYHDuWe4yoc6YK+djDuEOrBR5lvh0r+Q9uM88lrjx9x9AtgpQVNE8r+3O6Gvw59D+kBF/UMXyhliYUtPjmvXGY6Dk3x+kEOW+GtdMVC4EZTqoS/jmR0P0LS75DOc/w2vnri97M4SdbZ8qeU7gg8DVbERkU5geaMQO3mYrSYyAngeUQqrN0C0/vsFmcgWNXNeidsTAj7/4MncJR0caaBUpbLK1yBCBNRjEv6KvuVSdpPnEMJdsRRtqJ+U8tN1gXA4ePHc6ZT0eviI73UOJF0fEZ8YaneAQqQdGphNvwM4nIqPnXxV0xA0fnCT+oAhJuyw/q8jO0y8CjSteZExwBpIN6SvNp6A5G/abi6egeND/1GTguhuNjaUbbnSbGd4L8937Ezm34Eyi6n1maeOBxh3PI0jzJDf5mh/BsLD7F2GOKvlA/5gtvxI3/eV4sLfKW5Wy+oio+es/u6T8UU+nsofy57Icb/JlZHPFtCgd/x+bwt3ZT+xXTtTtTrGAb4QehC6X9G+8YT+ozcLxDsdCjsuOqwPFnrdLYaFc92Ui0m4fr39lYmlCaqTit7G6O/3kWDkgtXjNH4BiEm/+jegQnihOtfffn33WxsFjhfMd48HT+f6o6X65j7XR8WLSHMFkxbvOYsrRsF1bowDuSQ18Mkxk4qz2zoGPL5fu9h2Hqmt1asl3Q3Yu3szOc+spiCmX4AETBM3pLoTYSp3sVxahyhL8eC4mPN9k2x3o0xkiixIzM3CZFzf5oR4mecQ5+ax2wCah3/crmnHoqR0+KMaOPxRif1oEFRFOO/kTPPmtww+NfMXxEK6gn6iU32U6fFruIz8Q4WgljtnaCVTBgWx7diUdshC9ZEa5yKpRBBeW12r/iNc/+EgNqmhswNB8SBoihHXeDF7rrWDLcmt3V8GYYN7pXRy4DZjj4DJuUBL5iC3DQAaoo4vkftqVTYRGLS3mHZ7gdmdTTqbgNN/PTdTCOTgXolc88MhXAEUMdX0iy1JMuk5wLsgeu0QUYlz2S4skTWwJz6pOm/8ihrmgGfFgri+ZWUK2gAPHgbWa8jaocdSuM4FJYoKicYX/ZSENkg9Q1ZzJfwScfVnR2DegOGwCvmogaWJCLQepv9WNlU6QgsmOwICquU28Mlk3d9W5E81lU/5Ez0LcX6lwKMWDNluNKfBDUy/phJgBcMnfkh9iRxrdOzgs08JdPB85Lwo+GUSb4t3nC+0byqMZtO2fQJ4U2zGIr49t/28qmmGv2RanDD7a3FEcdtutkW8twwwlUSpb8QalodddbBfNHKDQ828BdE7OBgFdiKYohLawFYqpybQoxATZrheLhdI7+0Zlu9Q1myRcd15r9UIm8K2LGJxqTegntqNVMKnf1a8zQiyUR1rxoqjiFxeHxqFcYUTHfDu7rhbWng6qOxOsI+5A1p9mRyEPdVkTlE24vY54W7bWc6jMgZvNXdfC9/9q7408KDsbdL7Utz7QFSDetz2picArzrdpL8OaCHC9V26RroemtDZ5yNM/KGkWMyTmfnInEvwtSD23UcFcjhaE3VKzkoaEMKGBft4XbIO6forTY1lmGQwVmKicBCiArDzE+1oIxE08fWeviIOD5TznqH+OoHadvoOP20drMPe5Irg3XBQziW2XDuHYzjqQQ4wySssjXUs5H+t3FWYMHppUnBHMx/nYIT5d7OmjDbgD9F6na3m4l7KdkeSO3kTEPXafiWinogag7b52taiZhL1TSvBFmEZafFq2H8khQaZXuitCewT5FBgVtPK0j4xUHPfUz3Q28eac1Z139DAP23dgki94EC8vbDPTQC97HPPSWjUNG5tWKMsaxAEMKC0665Xvo1Ntd07wCLNf8Q56mrEPVpCxlIMVlQlWRxM3oAfpgIc+8KC3rEXUog5g06vt7zgXY8grH7hhwVSaeuvC06YYRAwpbyk/Unzj9hLEZNs2oxPQB9yc+GnL6zTgq7rI++KDJwX2SP8Sd6YzTuw5lV/kU6eQxRD12omfQAW6caTR4LikYkBB1CMOrvgRr/VY75+NSB40Cni6bADAtaK+vyxVWpf9NeKJxN2KYQ8Q2xPB3K1s7fuhvWbr2XpgW044VD6DRs0qXoqKf1NFsaGvKJc47leUV3pppP/5VTKFhaGuol4Esfjf5zyCyUHmHthChcYh4hYLQF+AFWsuq4t0wJyWgdwQVOZiV0efRHPoK5+E1vjz9wTJmVkITC9oEstAsyZSgE/dbicwKr89YUxKZI+owD205Tm5lnnmDRuP/JnzxX3gMtlrcX0UesZdxyQqYQuEW4R51vmQ5xOZteUd8SJruMlTUzhtVw/Nq7eUBcqN2/HVotgfngif60yKEtoUx3WYOZlVJuJOh8u59fzSDPFYtQgqDUAGyGhQOAvKroXMcOYY0qjnStJR/G3aP+Jt1sLVlGV8POwr/6OGsqetnyF3TmTqZjENfnXh51oxe9qVUw2M78EzAJ+IM8lZ1MBPQ9ZWSVc4J3mWSrLKrMHReA5qdGoz0ODRsaA+vwxXA2cAM4qlfzBJA6581m4hzxItQw5dxrrBL3Y6kCbUcFxo1S8jyV44q//+7ASNNudZ6xeaNOSIUffqMn4A9lIjFctYn2gpEPAb3f7p3iIBN8H14FUGQ9ct2hPsL+cEsTgUrR47uJVN4n4wt/wgfwwHuOnLd4yobkofy8JvxSQTA7rMpDIc608SlZFJfZYcmbT0tAHpPE8MrtQ42siTUNWxqvWZOmvu9f0JPoQmg+6l7sZWwyfi6PXkxJnwBraUG0MYG4zYHQz3igy/XsFkx5tNQxw43qvI9dU3f0DdhOUlHKjmi1VAr2Kiy0HZwD8VeEbhh0OiDdMYspolQsYdSwjCcjeowIXNZVUPmL2wwIkYhmXKhGozdCJ4lRKbsf4NBh/XnQoS92NJEWOVOFs2YhN8c5QZFeK0pRdAG40hqvLbmoSA8xQmzOOEc7wLcme9JOsjPCEgpCwUs9E2DohMHRhUeyGIN6TFvrbny8nDuilsDpzrH5mS76APoIEJmItS67sQJ+nfwddzmjPxcBEBBCw0kWDwd0EZCkNeOD7NNQhtBm7KHL9mRxj6U1yWU2puzlIDtpYxdH4ZPeXBJkTGAJfUr/oTCz/iypY6uXaR2V1doPxJYlrw2ghH0D5gbrhFcIxzYwi4a/4hqVdf2DdxBp6vGYDjavxMAAoy+1+3aiO6S3W/QAKNVXagDtvsNtx7Ks+HKgo6U21B+QSZgIogV5Bt+BnXisdVfy9VyXV+2P5fMuvdpAjM1o/K9Z+XnE4EOCrue+kcdYHqAQ0/Y/OmNlQ6OI33jH/uD1RalPaHpJAm2av0/xtpqdXVKNDrc9F2izo23Wu7firgbURFDNX9eGGeYBhiypyXZft2j3hTvzE6PMWKsod//rEILDkzBXfi7xh0eFkfb3/1zzPK/PI5Nk3FbZyTl4mq5BfBoVoqiPHO4Q4QKZAlrQ3MdNfi3oxIjvsM3kAFv3fdufurqYR3PSwX/mpGy/GFI/B2MNPiNdOppWVbs/gjF3YH+QA9jMhlAbhvasAHstB0IJew09iAkmXHl1/TEj+jvHOpOGrPRQXbPADM+Ig2/OEcUcpgPTItMtW4DdqgfYVI/+4hAFWYjUGpOP/UwNuB7+BbKOcALbjobdgzeBQfjgNSp2GOpxzGLj70Vvq5cw2AoYENwKLUtJUX8sGRox4dVa/TN4xKwaKcl9XawQR/uNus700Hf17pyNnezrUgaY9e4MADhEDBpsJT6y1gDJs1q6wlwGhuUzGR7C8kgpjPyHWwsvrf3yn1zJEIRa5eSxoLAZOCR9xbuztxFRJW9ZmMYfCFJ0evm9F2fVnuje92Rc4Pl6A8bluN8MZyyJGZ0+sNSb//DvAFxC2BqlEsFwccWeAl6CyBcQV1bx4mQMBP1Jxqk1EUADNLeieS2dUFbQ/c/kvwItbZ7tx0st16viqd53WsRmPTKv2AD8CUnhtPWg5aUegNpsYgasaw2+EVooeNKmrW3MFtj76bYHJm5K9gpAXZXsE5U8DM8XmVOSJ1F1WnLy6nQup+jx52bAb+rCq6y9WXl2B2oZDhfDkW7H3oYfT/4xx5VncBuxMXP2lNfhUVQjSSzSRbuZFE4vFawlzveXxaYKVs8LpvAb8IRYF3ZHiRnm0ADeNPWocwxSzNseG7NrSEVZoHdKWqaGEBz1N8Pt7kFbqh3LYmAbm9i1IChIpLpM5AS6mr6OAPHMwwznVy61YpBYX8xZDN/a+lt7n+x5j4bNOVteZ8lj3hpAHSx1VR8vZHec4AHO9XFCdjZ9eRkSV65ljMmZVzaej2qFn/qt1lvWzNZEfHxK3qOJrHL6crr0CRzMox5f2e8ALBB4UGFZKA3tN6F6IXd32GTJXGQ7DTi9j/dNcLF9jCbDcWGKxoKTYblIwbLDReL00LRcDPMcQuXLMh5YzgtfjkFK1DP1iDzzYYVZz5M/kWYRlRpig1htVRjVCknm+h1M5LiEDXOyHREhvzCGpFZjHS0RsK27o2avgdilrJkalWqPW3D9gmwV37HKmfM3F8YZj2ar+vHFvf3B8CRoH4kDHIK9mrAg+owiEwNjjd9V+FsQKYR8czJrUkf7Qoi2YaW6EVDZp5zYlqiYtuXOTHk4fAcZ7qBbdLDiJq0WNV1l2+Hntk1mMWvxrYmc8kIx8G3rW36J6Ra4lLrTOCgiOihmow+YnzUT19jbV2B3RWqSHyxkhmgsBqMYWvOcUom1jDQ436+fcbu3xf2bbeqU/ca+C4DOKE+e3qvmeMqW3AxejfzBRFVcwVYPq4L0APSWWoJu+5UYX4qg5U6YTioqQGPG9XrnuZ/BkxuYpe6Li87+18EskyQW/uA+uk2rpHpr6hut2TlVbKgWkFpx+AZffweiw2+VittkEyf/ifinS/0ItRL2Jq3tQOcxPaWO2xrG68GdFoUpZgFXaP2wYVtRc6xYCfI1CaBqyWpg4bx8OHBQwsV4XWMibZZ0LYjWEy2IxQ1mZrf1/UNbYCJplWu3nZ4WpodIGVA05d+RWSS+ET9tH3RfGGmNI1cIY7evZZq7o+a0bjjygpmR3mVfalkT/SZGT27Q8QGalwGlDOS9VHCyFAIL0a1Q7JiW3saz9gqY8lqKynFrPCzxkU4SIfLc9VfCI5edgRhDXs0edO992nhTKHriREP1NJC6SROMgQ0xO5kNNZOhMOIT99AUElbxqeZF8A3xrfDJsWtDnUenAHdYWSwAbYjFqQZ+D5gi3hNK8CSxU9i6f6ClL9IGlj1OPMQAsr84YG6ijsJpCaGWj75c3yOZKBB9mNpQNPUKkK0D6wgLH8MGoyRxTX6Y05Q4AnYNXMZwXM4eij/9WpsM/9CoRnFQXGR6MEaY+FXvXEO3RO0JaStk6OXuHVATHJE+1W+TU3bSZ2ksMtqjO0zfSJCdBv7y2d8DMx6TfVme3q0ZpTKMMu4YL/t7ciTNtdDkwPogh3Cnjx7qk08SHwf+dksZ7M2vCOlfsF0hQ6J4ehPCaHTNrM/zBSOqD83dBEBCW/F/LEmeh0nOHd7oVl3/Qo/9GUDkkbj7yz+9cvvu+dDAtx8NzCDTP4iKdZvk9MWiizvtILLepysflSvTLFBZ37RLwiriqyRxYv/zrgFd/9XVHh/OmzBvDX4mitMR/lUavs2Vx6cR94lzAkplm3IRNy4TFfu47tuYs9EQPIPVta4P64tV+sZ7n3ued3cgEx2YK+QL5+xms6osk8qQbTyuKVGdaX9FQqk6qfDnT5ykxk0VK7KZ62b6DNDUfQlqGHxSMKv1P0XN5BqMeKG1P4Wp5QfZDUCEldppoX0U6ss2jIko2XpURKCIhfaOqLPfShdtS37ZrT+jFRSH2xYVV1rmT/MBtRQhxiO4MQ3iAGlaZi+9PWBEIXOVnu9jN1f921lWLZky9bqbM3J2MAAI9jmuAx3gyoEUa6P2ivs0EeNv/OR+AX6q5SW6l5HaoFuS6jr6yg9limu+P0KYKzfMXWcQSfTXzpOzKEKpwI3YGXZpSSy2LTlMgfmFA3CF6R5c9xWEtRuCg2ZPUQ2Nb6dRFTNd4TfGHrnEWSKHPuRyiJSDAZ+KX0VxmSHjGPbQTLVpqixia2uyhQ394gBMt7C3ZAmxn/DJS+l1fBsAo2Eir/C0jG9csd4+/tp12pPc/BVJGaK9mfvr7M/CeztrmCO5qY06Edi4xAGtiEhnWAbzLy2VEyazE1J5nPmgU4RpW4Sa0TnOT6w5lgt3/tMpROigHHmexBGAMY0mdcDbDxWIz41NgdD6oxgHsJRgr5RnT6wZAkTOcStU4NMOQNemSO7gxGahdEsC+NRVGxMUhQmmM0llWRbbmFGHzEqLM4Iw0H7577Kyo+Zf+2cUFIOw93gEY171vQaM0HLwpjpdRR6Jz7V0ckE7XzYJ0TmY9znLdzkva0vNrAGGT5SUZ5uaHDkcGvI0ySpwkasEgZPMseYcu85w8HPdSNi+4T6A83iAwDbxgeFcB1ZM2iGXzFcEOUlYVrEckaOyodfvaYSQ7GuB4ISE0nYJc15X/1ciDTPbPCgYJK55VkEor4LvzL9S2WDy4xj+6FOqVyTAC2ZNowheeeSI5hA/02l8UYkv4nk9iaVn+kCVEUstgk5Hyq+gJm6R9vG3rhuM904he/hFmNQaUIATB1y3vw+OmxP4X5Yi6A5I5jJufHCjF9+AGNwnEllZjUco6XhsO5T5+R3yxz5yLVOnAn0zuS+6zdj0nTJbEZCbXJdtpfYZfCeCOqJHoE2vPPFS6eRLjIJlG69X93nfR0mxSFXzp1Zc0lt/VafDaImhUMtbnqWVb9M4nGNQLN68BHP7AR8Il9dkcxzmBv8PCZlw9guY0lurbBsmNYlwJZsA/B15/HfkbjbwPddaVecls/elmDHNW2r4crAx43feNkfRwsaNq/yyJ0d/p5hZ6AZajz7DBfUok0ZU62gCzz7x8eVfJTKA8IWn45vINLSM1q+HF9CV9qF3zP6Ml21kPPL3CXzkuYUlnSqT+Ij4tI/od5KwIs+tDajDs64owN7tOAd6eucGz+KfO26iNcBFpbWA5732bBNWO4kHNpr9D955L61bvHCF/mwSrz6eQaDjfDEANqGMkFc+NGxpKZzCD2sj/JrHd+zlPQ8Iz7Q+2JVIiVCuCKoK/hlAEHzvk/Piq3mRL1rT/fEh9hoT5GJmeYswg1otiKydizJ/fS2SeKHVu6Z3JEHjiW8NaTQgP5xdBli8nC57XiN9hrquBu99hn9zqwo92+PM2JXtpeVZS0PdqR5mDyDreMMtEws+CpwaRyyzoYtfcvt9PJIW0fJVNNi/FFyRsea7peLvJrL+5b4GOXJ8tAr+ATk9f8KmiIsRhqRy0vFzwRV3Z5dZ3QqIU8JQ/uQpkJbjMUMFj2F9sCFeaBjI4+fL/oN3+LQgjI4zuAfQ+3IPIPFQBccf0clJpsfpnBxD84atwtupkGqKvrH7cGNl/QcWcSi6wcVDML6ljOgYbo+2BOAWNNjlUBPiyitUAwbnhFvLbnqw42kR3Yp2kv2dMeDdcGOX5kT4S6M44KHEB/SpCfl7xgsUvs+JNY9G3O2X/6FEt9FyAn57lrbiu+tl83sCymSvq9eZbe9mchL7MTf/Ta78e80zSf0hYY5eUU7+ff14jv7Xy8qjzfzzzvaJnrIdvFb5BLWKcWGy5/w7+vV2cvIfwHqdTB+RuJK5oj9mbt0Hy94AmjMjjwYNZlNS6uiyxNnwNyt3gdreLb64p/3+08nXkb92LTkkRgFOwk1oGEVllcOj5lv1hfAZywDows0944U8vUFw+A/nuVq/UCygsrmWIBnHyU01d0XJPwriEOvx/ISK6Pk4y2w0gmojZs7lU8TtakBAdne4v/aNxmMpK4VcGMp7si0yqsiolXRuOi1Z1P7SqD3Zmp0CWcyK4Ubmp2SXiXuI5nGLCieFHKHNRIlcY3Pys2dwMTYCaqlyWSITwr2oGXvyU3h1Pf8eQ3w1bnD7ilocVjYDkcXR3Oo1BXgMLTUjNw2xMVwjtp99NhSVc5aIWrDQT5DHPKtCtheBP4zHcw4dz2eRdTMamhlHhtfgqJJHI7NGDUw1XL8vsSeSHyKqDtqoAmrQqsYwvwi7HW3ojWyhIa5oz5xJTaq14NAzFLjVLR12rRNUQ6xohDnrWFb5bG9yf8aCD8d5phoackcNJp+Dw3Due3RM+5Rid7EuIgsnwgpX0rUWh/nqPtByMhMZZ69NpgvRTKZ62ViZ+Q7Dp5r4K0d7EfJuiy06KuIYauRh5Ecrhdt2QpTS1k1AscEHvapNbU3HL1F2TFyR33Wxb5MvH5iZsrn3SDcsxlnnshO8PLwmdGN+paWnQuORtZGX37uhFT64SeuPsx8UOokY6ON85WdQ1dki5zErsJGazcBOddWJEKqNPiJpsMD1GrVLrVY+AOdPWQneTyyP1hRX/lMM4ZogGGOhYuAdr7F/DOiAoc++cn5vlf0zkMUJ40Z1rlgv9BelPqVOpxKeOpzKdF8maK+1Vv23MO9k/8+qpLoxrIGH2EDQlnGmH8CD31G8QqlyQIcpmR5bwmSVw9/Ns6IHgulCRehvZ/+VrM60Cu/r3AontFfrljew74skYe2uyn7JKQtFQBQRJ9ryGic/zQOsbS4scUBctA8cPToQ3x6ZBQu6DPu5m1bnCtP8TllLYA0UTQNVqza5nfew3Mopy1GPUwG5jsl0OVXniPmAcmLqO5HG8Hv3nSLecE9oOjPDXcsTxoCBxYyzBdj4wmnyEV4kvFDunipS8SSkvdaMnTBN9brHUR8xdmmEAp/Pdqk9uextp1t+JrtXwpN/MG2w/qhRMpSNxQ1uhg/kKO30eQ/FyHUDkWHT8V6gGRU4DhDMxZu7xXij9Ui6jlpWmQCqJg3FkOTq3WKneCRYZxBXMNAVLQgHXSCGSqNdjebY94oyIpVjMYehAiFx/tqzBXFHZaL5PeeD74rW5OysFoUXY8sebUZleFTUa/+zBKVTFDopTReXNuZq47QjkWnxjirCommO4L/GrFtVV21EpMyw8wyThL5Y59d88xtlx1g1ttSICDwnof6lt/6zliPzgVUL8jWBjC0o2D6Kg+jNuThkAlaDJsq/AG2aKA//A76avw2KNqtv223P+Wq3StRDDNKFFgtsFukYt1GFDWooFVXitaNhb3RCyJi4cMeNjROiPEDb4k+G3+hD8tsg+5hhmSc/8t2JTSwYoCzAI75doq8QTHe+E/Tw0RQSUDlU+6uBeNN3h6jJGX/mH8oj0i3caCNsjvTnoh73BtyZpsflHLq6AfwJNCDX4S98h4+pCOhGKDhV3rtkKHMa3EG4J9y8zFWI4UsfNzC/Rl5midNn7gwoN9j23HGCQQ+OAZpTTPMdiVow740gIyuEtd0qVxMyNXhHcnuXRKdw5wDUSL358ktjMXmAkvIB73BLa1vfF9BAUZInPYJiwxqFWQQBVk7gQH4ojfUQ/KEjn+A/WR6EEe4CtbpoLe1mzHkajgTIoE0SLDHVauKhrq12zrAXBGbPPWKCt4DGedq3JyGRbmPFW32bE7T20+73BatV/qQhhBWfWBFHfhYWXjALts38FemnoT+9bn1jDBMcUMmYgSc0e7GQjv2MUBwLU8ionCpgV+Qrhg7iUIfUY6JFxR0Y+ZTCPM+rVuq0GNLyJXX6nrUTt8HzFBRY1E/FIm2EeVA9NcXrj7S6YYIChVQCWr/m2fYUjC4j0XLkzZ8GCSLfmkW3PB/xq+nlXsKVBOj7vTvqKCOMq7Ztqr3cQ+N8gBnPaAps+oGwWOkbuxnRYj/x/WjiDclVrs22xMK4qArE1Ztk1456kiJriw6abkNeRHogaPRBgbgF9Z8i/tbzWELN4CvbqtrqV9TtGSnmPS2F9kqOIBaazHYaJ9bi3AoDBvlZasMluxt0BDXfhp02Jn411aVt6S4TUB8ZgFDkI6TP6gwPY85w+oUQSsjIeXVminrwIdK2ZAawb8Se6XOJbOaliQxHSrnAeONDLuCnFejIbp4YDtBcQCwMsYiRZfHefuEJqJcwKTTJ8sx5hjHmJI1sPFHOr6W9AhZ2NAod38mnLQk1gOz2LCAohoQbgMbUK9RMEA3LkiF7Sr9tLZp6lkciIGhE2V546w3Mam53VtVkGbB9w0Yk2XiRnCmbpxmHr2k4eSC0RuNbjNsUfDIfc8DZvRvgUDe1IlKdZTzcT4ZGEb53dp8VtsoZlyXzLHOdAbsp1LPTVaHvLA0GYDFMbAW/WUBfUAdHwqLFAV+3uHvYWrCfhUOR2i89qvCBoOb48usAGdcF2M4aKn79k/43WzBZ+xR1L0uZfia70XP9soQReeuhZiUnXFDG1T8/OXNmssTSnYO+3kVLAgeiY719uDwL9FQycgLPessNihMZbAKG7qwPZyG11G1+ZA3jAX2yddpYfmaKBlmfcK/V0mwIRUDC0nJSOPUl2KB8h13F4dlVZiRhdGY5farwN+f9hEb1cRi41ZcGDn6Xe9MMSTOY81ULJyXIHSWFIQHstVYLiJEiUjktlHiGjntN5/btB8Fu+vp28zl2fZXN+dJDyN6EXhS+0yzqpl/LSJNEUVxmu7BsNdjAY0jVsAhkNuuY0E1G48ej25mSt+00yPbQ4SRCVkIwb6ISvYtmJRPz9Zt5dk76blf+lJwAPH5KDF+vHAmACLoCdG2Adii6dOHnNJnTmZtoOGO8Q1jy1veMw6gbLFToQmfJa7nT7Al89mRbRkZZQxJTKgK5Kc9INzmTJFp0tpAPzNmyL/F08bX3nhCumM/cR/2RPn9emZ3VljokttZD1zVWXlUIqEU7SLk5I0lFRU0AcENXBYazNaVzsVHA/sD3o9hm42wbHIRb/BBQTKzAi8s3+bMtpOOZgLdQzCYPfX3UUxKd1WYVkGH7lh/RBBgMZZwXzU9+GYxdBqlGs0LP+DZ5g2BWNh6FAcR944B+K/JTWI3t9YyVyRhlP4CCoUk/mmF7+r2pilVBjxXBHFaBfBtr9hbVn2zDuI0kEOG3kBx8CGdPOjX1ph1POOZJUO1JEGG0jzUy2tK4X0CgVNYhmkqqQysRNtKuPdCJqK3WW57kaV17vXgiyPrl4KEEWgiGF1euI4QkSFHFf0TDroQiLNKJiLbdhH0YBhriRNCHPxSqJmNNoketaioohqMglh6wLtEGWSM1EZbQg72h0UJAIPVFCAJOThpQGGdKfFovcwEeiBuZHN2Ob4uVM7+gwZLz1D9E7ta4RmMZ24OBBAg7Eh6dLXGofZ4U2TFOCQMKjwhVckjrydRS+YaqCw1kYt6UexuzbNEDyYLTZnrY1PzsHZJT4U+awO2xlqTSYu6n/U29O2wPXgGOEKDMSq+zTUtyc8+6iLp0ivav4FKx+xxVy4FxhIF/pucVDqpsVe2jFOfdZhTzLz2QjtzvsTCvDPU7bzDH2eXVKUV9TZ+qFtaSSxnYgYdXKwVreIgvWhT9eGDB2OvnWyPLfIIIfNnfIxU8nW7MbcH05nhlsYtaW9EZRsxWcKdEqInq1DiZPKCz7iGmAU9/ccnnQud2pNgIGFYOTAWjhIrd63aPDgfj8/sdlD4l+UTlcxTI9jbaMqqN0gQxSHs60IAcW3cH4p3V1aSciTKB29L1tz2eUQhRiTgTvmqc+sGtBNh4ky0mQJGsdycBREP+fAaSs1EREDVo5gvgi5+aCN7NECw30owbCc1mSpjiahyNVwJd1jiGgzSwfTpzf2c5XJvG/g1n0fH88KHNnf+u7ZiRMlXueSIsloJBUtW9ezvsx9grfsX/FNxnbxU1Lvg0hLxixypHKGFAaPu0xCD8oDTeFSyfRT6s8109GMUZL8m2xXp8X2dpPCWWdX84iga4BrTlOfqox4shqEgh/Ht4qRst52cA1xOIUuOxgfUivp6v5f8IVyaryEdpVk72ERAwdT4aoY1usBgmP+0m06Q216H/nubtNYxHaOIYjcach3A8Ez/zc0KcShhel0HCYjFsA0FjYqyJ5ZUH1aZw3+zWC0hLpM6GDfcAdn9fq2orPmZbW6XXrf+Krc9RtvII5jeD3dFoT1KwZJwxfUMvc5KLfn8rROW23Jw89sJ2a5dpB3qWDUBWF2iX8OCuKprHosJ2mflBR+Wqs86VvgI/XMnsqb97+VlKdPVysczPj8Jhzf+WCvGBHijAqYlavbF60soMWlHbvKT+ScvhprgeTln51xX0sF+Eadc/l2s2a5BgkVbHYyz0E85p0LstqH+gEGiR84nBRRFIn8hLSZrGwqjZ3E29cuGi+5Z5bp7EM8MWFa9ssS/vy4VrDfECSv7DSU84DaP0sXI3Ap4lWznQ65nQoTKRWU30gd7Nn8ZowUvGIx4aqyXGwmA/PB4qN8msJUODezUHEl0VP9uo+cZ8vPFodSIB4C7lQYjEFj8yu49C2KIV3qxMFYTevG8KqAr0TPlkbzHHnTpDpvpzziAiNFh8xiT7C/TiyH0EguUw4vxAgpnE27WIypV+uFN2zW7xniF/n75trs9IJ5amB1zXXZ1LFkJ6GbS/dFokzl4cc2mamVwhL4XU0Av5gDWAl+aEWhAP7t2VIwU+EpvfOPDcLASX7H7lZpXA2XQfbSlD4qU18NffNPoAKMNSccBfO9YVVgmlW4RydBqfHAV7+hrZ84WJGho6bNT0YMhxxLdOx/dwGj0oyak9aAkNJ8lRJzUuA8sR+fPyiyTgUHio5+Pp+YaKlHrhR41jY5NESPS3x+zTMe0S2HnLOKCOQPpdxKyviBvdHrCDRqO+l96HhhNBLXWv4yEMuEUYo8kXnYJM8oIgVM4XJ+xXOev4YbWeqsvgq0lmw4/PiYr9sYLt+W5EAuYSFnJEan8CwJwbtASBfLBBpJZiRPor/aCJBZsM+MhvS7ZepyHvU8m5WSmaZnxuLts8ojl6KkS8oSAHkq5GWlCB/NgJ5W3rO2Cj1MK7ahxsCrbTT3a0V/QQH+sErxV4XUWDHx0kkFy25bPmBMBQ6BU3HoHhhYcJB9JhP6NXUWKxnE0raXHB6U9KHpWdQCQI72qevp5fMzcm+AvC85rsynVQhruDA9fp9COe7N56cg1UKGSas89vrN+WlGLYTwi5W+0xYdKEGtGCeNJwXKDU0XqU5uQYnWsMwTENLGtbQMvoGjIFIEMzCRal4rnBAg7D/CSn8MsCvS+FDJJAzoiioJEhZJgAp9n2+1Yznr7H+6eT4YkJ9Mpj60ImcW4i4iHDLn9RydB8dx3QYm3rsX6n4VRrZDsYK6DCGwkwd5n3/INFEpk16fYpP6JtMQpqEMzcOfQGAHXBTEGzuLJ03GYQL9bmV2/7ExDlRf+Uvf1sM2frRtCWmal12pMgtonvSCtR4n1CLUZRdTHDHP1Otwqd+rcdlavnKjUB/OYXQHUJzpNyFoKpQK+2OgrEKpGyIgIBgn2y9QHnTJihZOpEvOKIoHAMGAXHmj21Lym39Mbiow4IF+77xNuewziNVBxr6KD5e+9HzZSBIlUa/AmsDFJFXeyrQakR3FwowTGcADJHcEfhGkXYNGSYo4dh4bxwLM+28xjiqkdn0/3R4UEkvcBrBfn/SzBc1XhKM2VPlJgKSorjDac96V2UnQYXl1/yZPT4DVelgO+soMjexXwYO58VLl5xInQUZI8jc3H2CPnCNb9X05nOxIy4MlecasTqGK6s2az4RjpF2cQP2G28R+7wDPsZDZC/kWtjdoHC7SpdPmqQrUAhMwKVuxCmYTiD9q/O7GHtZvPSN0CAUQN/rymXZNniYLlJDE70bsk6Xxsh4kDOdxe7A2wo7P9F5YvqqRDI6brf79yPCSp4I0jVoO4YnLYtX5nzspR5WB4AKOYtR1ujXbOQpPyYDvfRE3FN5zw0i7reehdi7yV0YDRKRllGCGRk5Yz+Uv1fYl2ZwrnGsqsjgAVo0xEUba8ohjaNMJNwTwZA/wBDWFSCpg1eUH8MYL2zdioxRTqgGQrDZxQyNzyBJPXZF0+oxITJAbj7oNC5JwgDMUJaM5GqlGCWc//KCIrI+aclEe4IA0uzv7cuj6GCdaJONpi13O544vbtIHBF+A+JeDFUQNy61Gki3rtyQ4aUywn6ru314/dkGiP8Iwjo0J/2Txs49ZkwEl4mx+iYUUO55I6pJzU4P+7RRs+DXZkyKUYZqVWrPF4I94m4Wx1tXeE74o9GuX977yvJ/jkdak8+AmoHVjI15V+WwBdARFV2IPirJgVMdsg1Pez2VNHqa7EHWdTkl3XTcyjG9BiueWFvQfXI8aWSkuuRmqi/HUuzqyvLJfNfs0txMqldYYflWB1BS31WkuPJGGwXUCpjiQSktkuBMWwHjSkQxeehqw1Kgz0Trzm7QbtgxiEPDVmWCNCAeCfROTphd1ZNOhzLy6XfJyG6Xgd5MCAZw4xie0Sj5AnY1/akDgNS9YFl3Y06vd6FAsg2gVQJtzG7LVq1OH2frbXNHWH/NY89NNZ4QUSJqL2yEcGADbT38X0bGdukqYlSoliKOcsSTuqhcaemUeYLLoI8+MZor2RxXTRThF1LrHfqf/5LcLAjdl4EERgUysYS2geE+yFdasU91UgUDsc2cSQ1ZoT9+uLOwdgAmifwQqF028INc2IQEDfTmUw3eZxvz7Ud1z3xc1PQfeCvfKsB9jOhRj7rFyb9XcDWLcYj0bByosychMezMLVkFiYcdBBQtvI6K0KRuOZQH2kBsYHJaXTkup8F0eIhO1/GcIwWKpr2mouB7g5TUDJNvORXPXa/mU8bh27TAZYBe2sKx4NSv5OjnHIWD2RuysCzBlUfeNXhDd2jxnHoUlheJ3jBApzURy0fwm2FwwsSU0caQGl0Kv8hopRQE211NnvtLRsmCNrhhpEDoNiZEzD2QdJWKbRRWnaFedXHAELSN0t0bfsCsMf0ktfBoXBoNA+nZN9+pSlmuzspFevmsqqcMllzzvkyXrzoA+Ryo1ePXpdGOoJvhyru+EBRsmOp7MXZ0vNUMUqHLUoKglg1p73sWeZmPc+KAw0pE2zIsFFE5H4192KwDvDxdxEYoDBDNZjbg2bmADTeUKK57IPD4fTYF4c6EnXx/teYMORBDtIhPJneiZny7Nv/zG+YmekIKCoxr6kauE2bZtBLufetNG0BtBY7f+/ImUypMBvdWu/Q7vTMRzw5aQGZWuc1V0HEsItFYMIBnoKGZ0xcarba/TYZq50kCaflFysYjA4EDKHqGdpYWdKYmm+a7TADmW35yfnOYpZYrkpVEtiqF0EujI00aeplNs2k+qyFZNeE3CDPL9P6b4PQ/kataHkVpLSEVGK7EX6rAa7IVNrvZtFvOA6okKvBgMtFDAGZOx88MeBcJ8AR3AgUUeIznAN6tjCUipGDZONm1FjWJp4A3QIzSaIOmZ7DvF/ysYYbM/fFDOV0jntAjRdapxJxL0eThpEhKOjCDDq2ks+3GrwxqIFKLe1WdOzII8XIOPGnwy6LKXVfpSDOTEfaRsGujhpS4hBIsMOqHbl16PJxc4EkaVu9wpEYlF/84NSv5Zum4drMfp9yXbzzAOJqqS4YkI4cBrFrC7bMPiCfgI3nNZAqkk3QOZqR+yyqx+nDQKBBBZ7QKrfGMCL+XpqFaBJU0wpkBdAhbR4hJsmT5aynlvkouoxm/NjD5oe6BzVIO9uktM+/5dEC5P7vZvarmuO/lKXz4sBabVPIATuKTrwbJP8XUkdM6uEctHKXICUJGjaZIWRbZp8czquQYfY6ynBUCfIU+gG6wqSIBmYIm9pZpXdaL121V7q0VjDjmQnXvMe7ysoEZnZL15B0SpxS1jjd83uNIOKZwu5MPzg2NhOx3xMOPYwEn2CUzbSrwAs5OAtrz3GAaUkJOU74XwjaYUmGJdZBS1NJVkGYrToINLKDjxcuIlyfVsKQSG/G4DyiO2SlQvJ0d0Ot1uOG5IFSAkq+PRVMgVMDvOIJMdqjeCFKUGRWBW9wigYvcbU7CQL/7meF2KZAaWl+4y9uhowAX7elogAvItAAxo2+SFxGRsHGEW9BnhlTuWigYxRcnVUBRQHV41LV+Fr5CJYV7sHfeywswx4XMtUx6EkBhR+q8AXXUA8uPJ73Pb49i9KG9fOljvXeyFj9ixgbo6CcbAJ7WHWqKHy/h+YjBwp6VcN7M89FGzQ04qbrQtgrOFybg3gQRTYG5xn73ArkfQWjCJROwy3J38Dx/D7jOa6BBNsitEw1wGq780EEioOeD+ZGp2J66ADiVGMayiHYucMk8nTK2zzT9CnEraAk95kQjy4k0GRElLL5YAKLQErJ5rp1eay9O4Fb6yJGm9U4FaMwPGxtKD6odIIHKoWnhKo1U8KIpFC+MVn59ZXmc7ZTBZfsg6FQ8W10YfTr4u0nYrpHZbZ1jXiLmooF0cOm0+mPnJBXQtepc7n0BqOipNCqI6yyloTeRShNKH04FIo0gcMk0H/xThyN4pPAWjDDkEp3lNNPRNVfpMI44CWRlRgViP64eK0JSRp0WUvCWYumlW/c58Vcz/yMwVcW5oYb9+26TEhwvbxiNg48hl1VI1UXTU//Eta+BMKnGUivctfL5wINDD0giQL1ipt6U7C9cd4+lgqY2lMUZ02Uv6Prs+ZEZer7ZfWBXVghlfOOrClwsoOFKzWEfz6RZu1eCs+K8fLvkts5+BX0gyrFYve0C3qHrn5U/Oh6D/CihmWIrY7HUZRhJaxde+tldu6adYJ+LeXupQw0XExC36RETdNFxcq9glMu4cNQSX9cqR/GQYp+IxUkIcNGWVU7ZtGa6P3XAyodRt0XeS3Tp01AnCh0ZbUh4VrSZeV9RWfSoWyxnY3hzcZ30G/InDq4wxRrEejreBxnhIQbkxenxkaxl+k7eLUQkUR6vKJ2iDFNGX3WmVA1yaOH+mvhBd+sE6vacQzFobwY5BqEAFmejwW5ne7HtVNolOUgJc8CsUxmc/LBi8N5mu9VsIA5HyErnS6zeCz7VLI9+n/hbT6hTokMXTVyXJRKSG2hd2labXTbtmK4fNH3IZBPreSA4FMeVouVN3zG5x9CiGpLw/3pceo4qGqp+rVp+z+7yQ98oEf+nyH4F3+J9IheDBa94Wi63zJbLBCIZm7P0asHGpIJt3PzE3m0S4YIWyXBCVXGikj8MudDPB/6Nm2v4IxJ5gU0ii0guy5SUHqGUYzTP0jIJU5E82RHUXtX4lDdrihBLdP1YaG1AGUC12rQKuIaGvCpMjZC9bWSCYnjDlvpWbkdXMTNeBHLKiuoozMGIvkczmP0aRJSJ8PYnLCVNhKHXBNckH79e8Z8Kc2wUej4sQZoH8qDRGkg86maW/ZQWGNnLcXmq3FlXM6ssR/3P6E/bHMvm6HLrv1yRixit25JsH3/IOr2UV4BWJhxXW5BJ6Xdr07n9kF3ZNAk6/Xpc5MSFmYJ2R7bdL8Kk7q1OU9Elg/tCxJ8giT27wSTySF0GOxg4PbYJdi/Nyia9Nn89CGDulfJemm1aiEr/eleGSN+5MRrVJ4K6lgyTTIW3i9cQ0dAi6FHt0YMbH3wDSAtGLSAccezzxHitt1QdhW36CQgPcA8vIIBh3/JNjf/Obmc2yzpk8edSlS4lVdwgW5vzbYEyFoF4GCBBby1keVNueHAH+evi+H7oOVfS3XuPQSNTXOONAbzJeSb5stwdQHl1ZjrGoE49I8+A9j3t+ahhQj74FCSWpZrj7wRSFJJnnwi1T9HL5qrCFW/JZq6P62XkMWTb+u4lGpKfmmwiJWx178GOG7KbrZGqyWwmuyKWPkNswkZ1q8uptUlviIi+AXh2bOOTOLsrtNkfqbQJeh24reebkINLkjut5r4d9GR/r8CBa9SU0UQhsnZp5cP+RqWCixRm7i4YRFbtZ4EAkhtNa6jHb6gPYQv7MKqkPLRmX3dFsK8XsRLVZ6IEVrCbmNDc8o5mqsogjAQfoC9Bc7R6gfw03m+lQpv6kTfhxscDIX6s0w+fBxtkhjXAXr10UouWCx3C/p/FYwJRS/AXRKkjOb5CLmK4XRe0+xeDDwVkJPZau52bzLEDHCqV0f44pPgKOkYKgTZJ33fmk3Tu8SdxJ02SHM8Fem5SMsWqRyi2F1ynfRJszcFKykdWlNqgDA/L9lKYBmc7Zu/q9ii1FPF47VJkqhirUob53zoiJtVVRVwMR34gV9iqcBaHbRu9kkvqk3yMpfRFG49pKKjIiq7h/VpRwPGTHoY4cg05X5028iHsLvUW/uz+kjPyIEhhcKUwCkJAwbR9pIEGOn8z6svAO8i89sJ3dL5qDWFYbS+HGPRMxYwJItFQN86YESeJQhn2urGiLRffQeLptDl8dAgb+Tp47UQPxWOw17OeChLN1WnzlkPL1T5O+O3Menpn4C3IY5LEepHpnPeZHbvuWfeVtPlkH4LZjPbBrkJT3NoRJzBt86CO0Xq59oQ+8dsm0ymRcmQyn8w71mhmcuEI5byuF+C88VPYly2sEzjlzAQ3vdn/1+Hzguw6qFNNbqenhZGbdiG6RwZaTG7jTA2X9RdXjDN9yj1uQpyO4Lx8KRAcZcbZMafp4wPOd5MdXoFY52V1A8M9hi3sso93+uprE0qYNMjkE22CvK4HuUxqN7oIz5pWuETq1lQAjqlSlqdD2Rnr/ggp/TVkQYjn9lMfYelk2sH5HPdopYo7MHwlV1or9Bxf+QCyLzm92vzG2wjiIjC/ZHEJzeroJl6bdFPTpZho5MV2U86fLQqxNlGIMqCGy+9WYhJ8ob1r0+Whxde9L2PdysETv97O+xVw+VNN1TZSQN5I6l9m5Ip6pLIqLm4a1B1ffH6gHyqT9p82NOjntRWGIofO3bJz5GhkvSWbsXueTAMaJDou99kGLqDlhwBZNEQ4mKPuDvVwSK4WmLluHyhA97pZiVe8g+JxmnJF8IkV/tCs4Jq/HgOoAEGR9tCDsDbDmi3OviUQpG5D8XmKcSAUaFLRXb2lmJTNYdhtYyfjBYZQmN5qT5CNuaD3BVnlkCk7bsMW3AtXkNMMTuW4HjUERSJnVQ0vsBGa1wo3Qh7115XGeTF3NTz8w0440AgU7c3bSXO/KMINaIWXd0oLpoq/0/QJxCQSJ9XnYy1W7TYLBJpHsVWD1ahsA7FjNvRd6mxCiHsm8g6Z0pnzqIpF1dHUtP2ITU5Z1hZHbu+L3BEEStBbL9XYvGfEakv1bmf+bOZGnoiuHEdlBnaChxYKNzB23b8sw8YyT7Ajxfk49eJIAvdbVkdFCe2J0gMefhQ0bIZxhx3fzMIysQNiN8PgOUKxOMur10LduigREDRMZyP4oGWrP1GFY4t6groASsZ421os48wAdnrbovNhLt7ScNULkwZ5AIZJTrbaKYTLjA1oJ3sIuN/aYocm/9uoQHEIlacF1s/TM1fLcPTL38O9fOsjMEIwoPKfvt7opuI9G2Hf/PR4aCLDQ7wNmIdEuXJ/QNL72k5q4NejAldPfe3UVVqzkys8YZ/jYOGOp6c+YzRCrCuq0M11y7TiN6qk7YXRMn/gukxrEimbMQjr3jwRM6dKVZ4RUfWQr8noPXLJq6yh5R3EH1IVOHESst/LItbG2D2vRsZRkAObzvQAAD3mb3/G4NzopI0FAiHfbpq0X72adg6SRj+8OHMShtFxxLZlf/nLgRLbClwl5WmaYSs+yEjkq48tY7Z2bE0N91mJwt+ua0NlRJIDh0HikF4UvSVorFj2YVu9YeS5tfvlVjPSoNu/Zu6dEUfBOT555hahBdN3Sa5Xuj2Rvau1lQNIaC944y0RWj9UiNDskAK1WoL+EfXcC6IbBXFRyVfX/WKXxPAwUyIAGW8ggZ08hcijKTt1YKnUO6QPvcrmDVAb0FCLIXn5id4fD/Jx4tw/gbXs7WF9b2RgXtPhLBG9vF5FEkdHAKrQHZAJC/HWvk7nvzzDzIXZlfFTJoC3JpGgLPBY7SQTjGlUvG577yNutZ1hTfs9/1nkSXK9zzKLRZ3VODeKUovJe0WCq1zVMYxCJMenmNzPIU2S8TA4E7wWmbNkxq9rI2dd6v0VpcAPVMxnDsvWTWFayyqvKZO7Z08a62i/oH2/jxf8rpmfO64in3FLiL1GX8IGtVE9M23yGsIqJbxDTy+LtaMWDaPqkymb5VrQdzOvqldeU0SUi6IirG8UZ3jcpRbwHa1C0Dww9G/SFX3gPvTJQE+kyz+g1BeMILKKO+olcHzctOWgzxYHnOD7dpCRtuZEXACjgqesZMasoPgnuDC4nUviAAxDc5pngjoAITIkvhKwg5d608pdrZcA+qn5TMT6Uo/QzBaOxBCLTJX3Mgk85rMfsnWx86oLxf7p2PX5ONqieTa/qM3tPw4ZXvlAp83NSD8F7+ZgctK1TpoYwtiU2h02HCGioH5tkVCqNVTMH5p00sRy2JU1qyDBP2CII/Dg4WDsIl+zgeX7589srx6YORRQMBfKbodbB743Tl4WLKOEnwWUVBsm94SOlCracU72MSyj068wdpYjyz1FwC2bjQnxnB6Mp/pZ+yyZXtguEaYB+kqhjQ6UUmwSFazOb+rhYjLaoiM+aN9/8KKn0zaCTFpN9eKwWy7/u4EHzO46TdFSNjMfn2iPSJwDPCFHc0I1+vjdAZw5ZjqR/uzi9Zn20oAa5JnLEk/EA3VRWE7J/XrupfFJPtCUuqHPpnlL7ISJtRpSVcB8qsZCm2QEkWoROtCKKxUh3yEcMbWYJwk6DlEBG0bZP6eg06FL3v6RPb7odGuwm7FN8fG4woqtB8e7M5klPpo97GoObNwt+ludTAmxyC5hmcFx+dIvEZKI6igFKHqLH01iY1o7903VzG9QGetyVx5RNmBYUU+zIuSva/yIcECUi4pRmE3VkF2avqulQEUY4yZ/wmNboBzPmAPey3+dSYtBZUjeWWT0pPwCz4Vozxp9xeClIU60qvEFMQCaPvPaA70WlOP9f/ey39macvpGCVa+zfa8gO44wbxpJUlC8GN/pRMTQtzY8Z8/hiNrU+Zq64ZfFGIkdj7m7abcK1EBtws1X4J/hnqvasPvvDSDYWN+QcQVGMqXalkDtTad5rYY0TIR1Eqox3czwPMjKPvF5sFv17Thujr1IZ1Ytl4VX1J0vjXKmLY4lmXipRAro0qVGEcXxEVMMEl54jQMd4J7RjgomU0j1ptjyxY+cLiSyXPfiEcIS2lWDK3ISAy6UZ3Hb5vnPncA94411jcy75ay6B6DSTzK6UTCZR9uDANtPBrvIDgjsfarMiwoax2OlLxaSoYn4iRgkpEGqEkwox5tyI8aKkLlfZ12lO11TxsqRMY89j5JaO55XfPJPDL1LGSnC88Re9Ai+Nu5bZjtwRrvFITUFHPR4ZmxGslQMecgbZO7nHk32qHxYkdvWpup07ojcMCaVrpFAyFZJJbNvBpZfdf39Hdo2kPtT7v0/f8R/B5Nz4f1t9/3zNM/7n6SUHfcWk5dfQFJvcJMgPolGCpOFb/WC0FGWU2asuQyT+rm88ZKZ78Cei/CAh939CH0JYbpZIPtxc2ufXqjS3pHH9lnWK4iJ7OjR/EESpCo2R3MYKyE7rHfhTvWho4cL1QdN4jFTyR6syMwFm124TVDDRXMNveI1Dp/ntwdz8k8kxw7iFSx6+Yx6O+1LzMVrN0BBzziZi9kneZSzgollBnVwBh6oSOPHXrglrOj+QmR/AESrhDpKrWT+8/AiMDxS/5wwRNuGQPLlJ9ovomhJWn8sMLVItQ8N/7IXvtD8kdOoHaw+vBSbFImQsv/OCAIui99E+YSIOMlMvBXkAt+NAZK8wB9Jf8CPtB+TOUOR+z71d/AFXpPBT6+A5FLjxMjLIEoJzrQfquvxEIi+WoUzGR1IzQFNvbYOnxb2PyQ0kGdyXKzW2axQL8lNAXPk6NEjqrRD1oZtKLlFoofrXw0dCNWASHzy+7PSzOUJ3XtaPZsxLDjr+o41fKuKWNmjiZtfkOzItvlV2MDGSheGF0ma04qE3TUEfqJMrXFm7DpK+27DSvCUVf7rbNoljPhha5W7KBqVq0ShUSTbRmuqPtQreVWH4JET5yMhuqMoSd4r/N8sDmeQiQQvi1tcZv7Moc7dT5X5AtCD6kNEGZOzVcNYlpX4AbTsLgSYYliiPyVoniuYYySxsBy5cgb3pD+EK0Gpb0wJg031dPgaL8JZt6sIvzNPEHfVPOjXmaXj4bd4voXzpZ5GApMhILgMbCEWZ2zwgdeQgjNHLbPIt+KqxRwWPLTN6HwZ0Ouijj4UF+Sg0Au8XuIKW0WxlexdrFrDcZJ8Shauat3X0XmHygqgL1nAu2hrJFb4wZXkcS+i36KMyU1yFvYv23bQUJi/3yQpqr/naUOoiEWOxckyq/gq43dFou1DVDaYMZK9tho7+IXXokBCs5GRfOcBK7g3A+jXQ39K4YA8PBRW4m5+yR0ZAxWJncjRVbITvIAPHYRt1EJ3YLiUbqIvoKHtzHKtUy1ddRUQ0AUO41vonZDUOW+mrszw+SW/6Q/IUgNpcXFjkM7F4CSSQ2ExZg85otsMs7kqsQD4OxYeBNDcSpifjMoLb7GEbGWTwasVObmB/bfPcUlq0wYhXCYEDWRW02TP5bBrYsKTGWjnWDDJ1F7zWai0zW/2XsCuvBQjPFcTYaQX3tSXRSm8hsAoDdjArK/OFp6vcWYOE7lizP0Yc+8p16i7/NiXIiiQTp7c7Xus925VEtlKAjUdFhyaiLT7VxDagprMFwix4wZ05u0qj7cDWFd0W9OYHIu3JbJKMXRJ1aYNovugg+QqRN7fNHSi26VSgBpn+JfMuPo3aeqPWik/wI5Rz3BWarPQX4i5+dM0npwVOsX+KsOhC7vDg+OJsz4Q5zlnIeflUWL6QYMbf9WDfLmosLF4Qev3mJiOuHjoor/dMeBpA9iKDkMjYBNbRo414HCxjsHrB4EXNbHzNMDHCLuNBG6Sf+J4MZ/ElVsDSLxjIiGsTPhw8BPjxbfQtskj+dyNMKOOcUYIRBEIqbazz3lmjlRQhplxq673VklMMY6597vu+d89ec/zq7Mi4gQvh87ehYbpOuZEXj5g/Q7S7BFDAAB9DzG35SC853xtWVcnZQoH54jeOqYLR9NDuwxsVthTV7V99n/B7HSbAytbEyVTz/5NhJ8gGIjG0E5j3griULUd5Rg7tQR+90hJgNQKQH2btbSfPcaTOfIexc1db1BxUOhM1vWCpLaYuKr3FdNTt/T3PWCpEUWDKEtzYrjpzlL/wri3MITKsFvtF8QVV/NhVo97aKIBgdliNc10dWdXVDpVtsNn+2UIolrgqdWA4EY8so0YvB4a+aLzMXiMAuOHQrXY0tr+CL10JbvZzgjJJuB1cRkdT7DUqTvnswVUp5kkUSFVtIIFYK05+tQxT6992HHNWVhWxUsD1PkceIrlXuUVRogwmfdhyrf6zzaL8+c0L7GXMZOteAhAVQVwdJh+7nrX7x4LaIIfz2F2v7Dg/uDfz2Fa+4gFm2zHAor8UqimJG3VTJtZEoFXhnDYXvxMJFc6ku2bhbCxzij2z5UNuK0jmp1mnvkVNUfR+SEmj1Lr94Lym75PO7Fs0MIr3GdsWXRXSfgLTVY0FLqba97u1In8NAcY7IC6TjWLigwKEIm43NxTdaVTv9mcKkzuzBkKd8x/xt1p/9BbP7Wyb4bpo1K1gnOpbLvKz58pWl3B55RJ/Z5mRDLPtNQg14jdOEs9+h/V5UVpwrAI8kGbX8KPVPDIMfIqKDjJD9UyDOPhjZ3vFAyecwyq4akUE9mDOtJEK1hpDyi6Ae87sWAClXGTiwPwN7PXWwjxaR79ArHRIPeYKTunVW24sPr/3HPz2IwH8oKH4OlWEmt4BLM6W5g4kMcYbLwj2usodD1088stZA7VOsUSpEVl4w7NMb1EUHMRxAxLF0CIV+0L3iZb+ekB1vSDSFjAZ3hfLJf7gFaXrOKn+mhR+rWw/eTXIcAgl4HvFuBg1LOmOAwJH3eoVEjjwheKA4icbrQCmvAtpQ0mXG0agYp5mj4Rb6mdQ+RV4QBPbxMqh9C7o8nP0Wko2ocnCHeRGhN1XVyT2b9ACsL+6ylUy+yC3QEnaKRIJK91YtaoSrcWZMMwxuM0E9J68Z+YyjA0g8p1PfHAAIROy6Sa04VXOuT6A351FOWhKfTGsFJ3RTJGWYPoLk5FVK4OaYR9hkJvezwF9vQN1126r6isMGXWTqFW+3HL3I/jurlIdDWIVvYY+s6yq7lrFSPAGRdnU7PVwY/SvWbZGpXzy3BQ2LmAJlrONUsZs4oGkly0V267xbD5KMY8woNNsmWG1VVgLCra8aQBBcI4DP2BlNwxhiCtHlaz6OWFoCW0vMR3ErrG7JyMjTSCnvRcsEHgmPnwA6iNpJ2DrFb4gLlhKJyZGaWkA97H6FFdwEcLT6DRQQL++fOkVC4cYGW1TG/3iK5dShRSuiBulmihqgjR45Vi03o2RbQbP3sxt90VxQ6vzdlGfkXmmKmjOi080JSHkLntjvsBJnv7gKscOaTOkEaRQqAnCA4HWtB4XnMtOhpRmH2FH8tTXrIjAGNWEmudQLCkcVlGTQ965Kh0H6ixXbgImQP6b42B49sO5C8pc7iRlgyvSYvcnH9FgQ3azLbQG2cUW96SDojTQStxkOJyOuDGTHAnnWkz29aEwN9FT8EJ4yhXOg+jLTrCPKeEoJ9a7lDXOjEr8AgX4BmnMQ668oW0zYPyQiVMPxKRHtpfnEEyaKhdzNVThlxxDQNdrHeZiUFb6NoY2KwvSb7BnRcpJy+/g/zAYx3fYSN5QEaVD2Y1VsNWxB0BSO12MRsRY8JLfAezRMz5lURuLUnG1ToKk6Q30FughqWN6gBNcFxP/nY/iv+iaUQOa+2Nuym46wtI/DvSfzSp1jEi4SdYBE7YhTiVV5cX9gwboVDMVgZp5YBQlHOQvaDNfcCoCJuYhf5kz5kwiIKPjzgpcRJHPbOhJajeoeRL53cuMahhV8Z7IRr6M4hW0JzT7mzaMUzQpm866zwM7Cs07fJYXuWvjAMkbe5O6V4bu71sOG6JQ4oL8zIeXHheFVavzxmlIyBkgc9IZlEDplMPr8xlcyss4pVUdwK1e7CK2kTsSdq7g5SHRAl3pYUB9Ko4fsh4qleOyJv1z3KFSTSvwEcRO/Ew8ozEDYZSqpfoVW9uhJfYrNAXR0Z3VmeoAD+rVWtwP/13sE/3ICX3HhDG3CMc476dEEC0K3umSAD4j+ZQLVdFOsWL2C1TH5+4KiSWH+lMibo+B55hR3Gq40G1n25sGcN0mEcoU2wN9FCVyQLBhYOu9aHVLWjEKx2JIUZi5ySoHUAI9b8hGzaLMxCZDMLhv8MkcpTqEwz9KFDpCpqQhVmsGQN8m24wyB82FAKNmjgfKRsXRmsSESovAwXjBIoMKSG51p6Um8b3i7GISs7kjTq/PZoioCfJzfKdJTN0Q45kQEQuh9H88M3yEs3DbtRTKALraM0YC8laiMiOOe6ADmTcCiREeAWZelBaEXRaSuj2lx0xHaRYqF65O0Lo5OCFU18A8cMDE4MLYm9w2QSr9NgQAIcRxZsNpA7UJR0e71JL+VU+ISWFk5I97lra8uGg7GlQYhGd4Gc6rxsLFRiIeGO4abP4S4ekQ1fiqDCy87GZHd52fn5aaDGuvOmIofrzpVwMvtbreZ/855OaXTRcNiNE0wzGZSxbjg26v8ko8L537v/XCCWP2MFaArJpvnkep0pA+O86MWjRAZPQRfznZiSIaTppy6m3p6HrNSsY7fDtz7Cl4V/DJAjQDoyiL2uwf1UHVd2AIrzBUSlJaTj4k6NL97a/GqhWKU9RUmjnYKpm2r+JYUcrkCuZKvcYvrg8pDoUKQywY9GDWg03DUFSirlUXBS5SWn/KAntnf0IdHGL/7mwXqDG+LZYjbEdQmqUqq4y54TNmWUP7IgcAw5816YBzwiNIJiE9M4lPCzeI/FGBeYy3p6IAmH4AjXXmvQ4Iy0Y82NTobcAggT2Cdqz6Mx4TdGoq9fn2etrWKUNFyatAHydQTVUQ2S5OWVUlugcNvoUrlA8cJJz9MqOa/W3iVno4zDHfE7zhoY5f5lRTVZDhrQbR8LS4eRLz8iPMyBL6o4PiLlp89FjdokQLaSBmKHUwWp0na5fE3v9zny2YcDXG/jfI9sctulHRbdkI5a4GOPJx4oAJQzVZ/yYAado8KNZUdEFs9ZPiBsausotXMNebEgr0dyopuqfScFJ3ODNPHgclACPdccwv0YJGQdsN2lhoV4HVGBxcEUeUX/alr4nqpcc1CCR3vR7g40zteQg/JvWmFlUE4mAiTpHlYGrB7w+U2KdSwQz2QJKBe/5eiixWipmfP15AFWrK8Sh1GBBYLgzki1wTMhGQmagXqJ2+FuqJ8f0XzXCVJFHQdMAw8xco11HhM347alrAu+wmX3pDFABOvkC+WPX0Uhg1Z5MVHKNROxaR84YV3s12UcM+70cJ460SzEaKLyh472vOMD3XnaK7zxZcXlWqenEvcjmgGNR2OKbI1s8U+iwiW+HotHalp3e1MGDy6BMVIvajnAzkFHbeVsgjmJUkrP9OAwnEHYXVBqYx3q7LvXjoVR0mY8h+ZaOnh053pdsGkmbqhyryN01eVHySr+CkDYkSMeZ1xjPNVM+gVLTDKu2VGsMUJqWO4TwPDP0VOg2/8ITbAUaMGb4LjL7L+Pi11lEVMXTYIlAZ/QHmTENjyx3kDkBdfcvvQt6tKk6jYFM4EG5UXDTaF5+1ZjRz6W7MdJPC+wTkbDUim4p5QQH3b9kGk2Bkilyeur8Bc20wm5uJSBO95GfYDI1EZipoRaH7uVveneqz43tlTZGRQ4a7CNmMHgXyOQQOL6WQkgMUTQDT8vh21aSdz7ERiZT1jK9F+v6wgFvuEmGngSvIUR2CJkc5tx1QygfZnAruONobB1idCLB1FCfO7N1ZdRocT8/Wye+EnDiO9pzqIpnLDl4bkaRKW+ekBVwHn46Shw1X0tclt/0ROijuUB4kIInrVJU4buWf4YITJtjOJ6iKdr1u+flgQeFH70GxKjhdgt/MrwfB4K/sXczQ+9zYcrD4dhY6qZhZ010rrxggWA8JaZyg2pYij8ieYEg1aZJkZK9O1Re7sB0iouf60rK0Gd+AYlp7soqCBCDGwfKeUQhCBn0E0o0GS6PdmjLi0TtCYZeqazqwN+yNINIA8Lk3iPDnWUiIPLGNcHmZDxfeK0iAdxm/T7LnN+gemRL61hHIc0NCAZaiYJR+OHnLWSe8sLrK905B5eEJHNlWq4RmEXIaFTmo49f8w61+NwfEUyuJAwVqZCLFcyHBKAcIVj3sNzfEOXzVKIndxHw+AR93owhbCxUZf6Gs8cz6/1VdrFEPrv330+9s6BtMVPJ3zl/Uf9rUi0Z/opexfdL3ykF76e999GPfVv8fJv/Y/+/5hEMon1tqNFyVRevV9y9/uIvsG3dbB8GRRrgaEXfhx+2xeOFt+cEn3RZanNxdEe2+B6MHpNbrRE53PlDifPvFcp4kO78ILR0T4xyW/WGPyBsqGdoA7zJJCu1TKbGfhnqgnRbxbB2B3UZoeQ2bz2sTVnUwokTcTU21RxN1PYPS3Sar7T0eRIsyCNowr9amwoMU/od9s2APtiKNL6ENOlyKADstAEWKA+sdKDhrJ6BOhRJmZ+QJbAaZ3/5Fq0/lumCgEzGEbu3yi0Y4I4EgVAjqxh4HbuQn0GrRhOWyAfsglQJAVL1y/6yezS2k8RE2MstJLh92NOB3GCYgFXznF4d25qiP4ZCyI4RYGesut6FXK6GwPpKK8WHEkhYui0AyEmr5Ml3uBFtPFdnioI8RiCooa7Z1G1WuyIi3nSNglutc+xY8BkeW3JJXPK6jd2VIMpaSxpVtFq+R+ySK9J6WG5Qvt+C+QH1hyYUOVK7857nFmyDBYgZ/o+AnibzNVqyYCJQvyDXDTK+iXdkA71bY7TL3bvuLxLBQ8kbTvTEY9aqkQ3+MiLWbEgjLzOH+lXgco1ERgzd80rDCymlpaRQbOYnKG/ODoFl46lzT0cjM5FYVvv0qLUbD5lyJtMUaC1pFlTkNONx6lliaX9o0i/1vws5bNKn5OuENQEKmLlcP4o2ZmJjD4zzd3Fk32uQ4uRWkPSUqb4LBe3EXHdORNB2BWsws5daRnMfNVX7isPSb1hMQdAJi1/qmDMfRUlCU74pmnzjbXfL8PVG8NsW6IQM2Ne23iCPIpryJjYbVnm5hCvKpMa7HLViNiNc+xTfDIaKm3jctViD8A1M9YPJNk003VVr4Zo2MuGW8vil8SLaGpPXqG7I4DLdtl8a4Rbx1Lt4w5Huqaa1XzZBtj208EJVGcmKYEuaeN27zT9EE6a09JerXdEbpaNgNqYJdhP1NdqiPKsbDRUi86XvvNC7rME5mrSQtrzAZVndtSjCMqd8BmaeGR4l4YFULGRBeXIV9Y4yxLFdyoUNpiy2IhePSWzBofYPP0eIa2q5JP4j9G8at/AqoSsLAUuRXtvgsqX/zYwsE+of6oSDbUOo4RMJw+DOUTJq+hnqwKim9Yy/napyZNTc2rCq6V9jHtJbxGPDwlzWj/Sk3zF/BHOlT/fSjSq7FqlPI1q6J+ru8Aku008SFINXZfOfnZNOvGPMtEmn2gLPt+H4QLA+/SYe4j398auzhKIp2Pok3mPC5q1IN1HgR+mnEfc4NeeHYwd2/kpszR3cBn7ni9NbIqhtSWFW8xbUJuUPVOeeXu3j0IGZmFNiwaNZ6rH4/zQ2ODz6tFxRLsUYZu1bfd1uIvfQDt4YD/efKYv8VF8bHGDgK22w2Wqwpi43vNCOXFJZCGMqWiPbL8mil6tsmOTXAWCyMCw73e2rADZj2IK6rqksM3EXF2cbLb4vjB14wa/yXK5vwU+05MzERJ5nXsXsW21o7M+gO0js2OyKciP5uF2iXyb2DiptwQeHeqygkrNsqVCSlldxBMpwHi1vfc8RKpP/4L3Lmpq6DZcvhDDfxTCE3splacTcOtXdK2g303dIWBVe2wD/Gvja1cClFQ67gw0t1ZUttsUgQ1Veky8oOpS6ksYEc4bqseCbZy766SvL3FodmnahlWJRgVCNjPxhL/fk2wyvlKhITH/VQCipOI0dNcRa5B1M5HmOBjTLeZQJy237e2mobwmDyJNHePhdDmiknvLKaDbShL+Is1XTCJuLQd2wmdJL7+mKvs294whXQD+vtd88KKk0DXP8B1Xu9J+xo69VOuFgexgTrcvI6SyltuLix9OPuE6/iRJYoBMEXxU4shQMf4Fjqwf1PtnJ/wWSZd29rhZjRmTGgiGTAUQqRz+nCdjeMfYhsBD5Lv60KILWEvNEHfmsDs2L0A252351eUoYxAysVaCJVLdH9QFWAmqJDCODUcdoo12+gd6bW2boY0pBVHWL6LQDK5bYWh1V8vFvi0cRpfwv7cJiMX3AZNJuTddHehTIdU0YQ/sQ1dLoF2xQPcCuHKiuCWOY30DHe1OwcClLAhqAKyqlnIbH/8u9ScJpcS4kgp6HKDUdiOgRaRGSiUCRBjzI5gSksMZKqy7Sd51aeg0tgJ+x0TH9YH2Mgsap9N7ENZdEB0bey2DMTrBA1hn56SErNHf3tKtqyL9b6yXEP97/rc+jgD2N1LNUH6RM9AzP3kSipr06RkKOolR7HO768jjWiH1X92jA7dkg7gcNcjqsZCgfqWw0tPXdLg20cF6vnQypg7gLtkazrHAodyYfENPQZsdfnjMZiNu4nJO97D1/sQE+3vNFzrSDOKw+keLECYf7RJwVHeP/j79833oZ0egonYB2FlFE5qj02B/LVOMJQlsB8uNg3Leg4qtZwntsOSNidR0abbZmAK4sCzvt8Yiuz2yrNCJoH5O8XvX/vLeR/BBYTWj0sOPYM/jyxRd5+/JziKAABaPcw/34UA3aj/gLZxZgRCWN6m4m3demanNgsx0P237/Q+Ew5VYnJPkyCY0cIVHoFn2Ay/e7U4P19APbPFXEHX94N6KhEMPG7iwB3+I+O1jd5n6VSgHegxgaSawO6iQCYFgDsPSMsNOcUj4q3sF6KzGaH/0u5PQoAj/8zq6Uc9MoNrGqhYeb2jQo0WlGlXjxtanZLS24/OIN5Gx/2g684BPDQpwlqnkFcxpmP/osnOXrFuu4PqifouQH0eF5qCkvITQbJw/Zvy5mAHWC9oU+cTiYhJmSfKsCyt1cGVxisKu+NymEQIAyaCgud/V09qT3nk/9s/SWsYtha7yNpzBIMM40rCSGaJ9u6lEkl00vXBiEt7p9P5IBCiavynEOv7FgLqPdeqxRiCwuFVMolSIUBcoyfUC2e2FJSAUgYdVGFf0b0Kn2EZlK97yyxrT2MVgvtRikfdaAW8RwEEfN+B7/eK8bBdp7URpbqn1xcrC6d2UjdsKbzCjBFqkKkoZt7Mrhg6YagE7spkqj0jOrWM+UGQ0MUlG2evP1uE1p2xSv4dMK0dna6ENcNUF+xkaJ7B764NdxLCpuvhblltVRAf7vK5qPttJ/9RYFUUSGcLdibnz6mf7WkPO3MkUUhR2mAOuGv8IWw5XG1ZvoVMnjSAZe6T7WYA99GENxoHkMiKxHlCuK5Gd0INrISImHQrQmv6F4mqU/TTQ8nHMDzCRivKySQ8dqkpQgnUMnwIkaAuc6/FGq1hw3b2Sba398BhUwUZSAIO8XZvnuLdY2n6hOXws+gq9BHUKcKFA6kz6FDnpxLPICa3qGhnc97bo1FT/XJk48LrkHJ2CAtBv0RtN97N21plfpXHvZ8gMJb7Zc4cfI6MbPwsW7AilCSXMFIEUEmir8XLEklA0ztYbGpTTGqttp5hpFTTIqUyaAIqvMT9A/x+Ji5ejA4Bhxb/cl1pUdOD6epd3yilIdO6j297xInoiBPuEDW2/UfslDyhGkQs7Wy253bVnlT+SWg89zYIK/9KXFl5fe+jow2rd5FXv8zDPrmfMXiUPt9QBO/iK4QGbX5j/7Rx1c1vzsY8ONbP3lVIaPrhL4+1QrECTN3nyKavGG0gBBtHvTKhGoBHgMXHStFowN+HKrPriYu+OZ05Frn8okQrPaaxoKP1ULCS/cmKFN3gcH7HQlVjraCeQmtjg1pSQxeuqXiSKgLpxc/1OiZsU4+n4lz4hpahGyWBURLi4642n1gn9qz9bIsaCeEPJ0uJmenMWp2tJmIwLQ6VSgDYErOeBCfSj9P4G/vI7oIF+l/n5fp956QgxGvur77ynawAu3G9MdFbJbu49NZnWnnFcQHjxRuhUYvg1U/e84N4JTecciDAKb/KYIFXzloyuE1eYXf54MmhjTq7B/yBToDzzpx3tJCTo3HCmVPYfmtBRe3mPYEE/6RlTIxbf4fSOcaKFGk4gbaUWe44hVk9SZzhW80yfW5QWBHxmtUzvMhfVQli4gZTktIOZd9mjJ5hsbmzttaHQB29Am3dZkmx3g/qvYocyhZ2PXAWsNQiIaf+Q8W/MWPIK7/TjvCx5q2XRp4lVWydMc2wIQkhadDB0xsnw/kSEyGjLKjI4coVIwtubTF3E7MJ6LS6UOsJKj82XVAVPJJcepfewbzE91ivXZvOvYfsmMevwtPpfMzGmC7WJlyW2j0jh7AF1JLmwEJSKYwIvu6DHc3YnyLH9ZdIBnQ+nOVDRiP+REpqv++typYHIvoJyICGA40d8bR7HR2k7do6UQTHF4oriYeIQbxKe4Th6+/l1BjUtS9hqORh3MbgvYrStXTfSwaBOmAVQZzpYNqsAmQyjY56MUqty3c/xH6GuhNvNaG9vGbG6cPtBM8UA3e8r51D0AR9kozKuGGSMgLz3nAHxDNnc7GTwpLj7/6HeWp1iksDeTjwCLpxejuMtpMnGJgsiku1sOACwQ9ukzESiDRN77YNESxR5LphOlcASXA5uIts1LnBIcn1J7BLWs49DMALSnuz95gdOrTZr0u1SeYHinno/pE58xYoXbVO/S+FEMMs5qyWkMnp8Q3ClyTlZP52Y9nq7b8fITPuVXUk9ohG5EFHw4gAEcjFxfKb3xuAsEjx2z1wxNbSZMcgS9GKyW3R6KwJONgtA64LTyxWm8Bvudp0M1FdJPEGopM4Fvg7G/hsptkhCfHFegv4ENwxPeXmYhxwZy7js+BeM27t9ODBMynVCLJ7RWcBMteZJtvjOYHb5lOnCLYWNEMKC59BA7covu1cANa2PXL05iGdufOzkgFqqHBOrgQVUmLEc+Mkz4Rq8O6WkNr7atNkH4M8d+SD1t/tSzt3oFql+neVs+AwEI5JaBJaxARtY2Z4mKoUqxds4UpZ0sv3zIbNoo0J4fihldQTX3XNcuNcZmcrB5LTWMdzeRuAtBk3cZHYQF6gTi3PNuDJ0nmR+4LPLoHvxQIxRgJ9iNNXqf2SYJhcvCtJiVWo85TsyFOuq7EyBPJrAdhEgE0cTq16FQXhYPJFqSfiVn0IQnPOy0LbU4BeG94QjdYNB0CiQ3QaxQqD2ebSMiNjaVaw8WaM4Z5WnzcVDsr4eGweSLa2DE3BWViaxhZFIcSTjgxNCAfelg+hznVOYoe5VqTYs1g7WtfTm3e4/WduC6p+qqAM8H4ZyrJCGpewThTDPe6H7CzX/zQ8Tm+r65HeZn+MsmxUciEWPlAVaK/VBaQBWfoG/aRL/jSZIQfep/89GjasWmbaWzeEZ2R1FOjvyJT37O9B8046SRSKVEnXWlBqbkb5XCS3qFeuE9xb9+frEknxWB5h1D/hruz2iVDEAS7+qkEz5Ot5agHJc7WCdY94Ws61sURcX5nG8UELGBAHZ3i+3VulAyT0nKNNz4K2LBHBWJcTBX1wzf+//u/j/9+//v87+9/l9Lbh/L/uyNYiTsWV2LwsjaA6MxTuzFMqmxW8Jw/+IppdX8t/Clgi1rI1SN0UC/r6tX/4lUc2VV1OQReSeCsjUpKZchw4XUcjHfw6ryCV3R8s6VXm67vp4n+lcPV9gJwmbKQEsmrJi9c2vkwrm8HFbVYNTaRGq8D91t9n5+U+aD/hNtN3HjC/nC/vUoGFSCkXP+NlRcmLUqLbiUBl4LYf1U/CCvwtd3ryCH8gUmGITAxiH1O5rnGTz7y1LuFjmnFGQ1UWuM7HwfXtWl2fPFKklYwNUpF2IL/TmaRETjQiM5SJacI+3Gv5MBU8lP5Io6gWkawpyzNEVGqOdx4YlO1dCvjbWFZWbCmeiFKPSlMKtKcMFLs/KQxtgAHi7NZNCQ32bBAW2mbHflVZ8wXKi1JKVHkW20bnYnl3dKWJeWJOiX3oKPBD6Zbi0ZvSIuWktUHB8qDR8DMMh1ZfkBL9FS9x5r0hBGLJ8pUCJv3NYH+Ae8p40mZWd5m5fhobFjQeQvqTT4VKWIYfRL0tfaXKiVl75hHReuTJEcqVlug+eOIIc4bdIydtn2K0iNZPsYWQvQio2qbO3OqAlPHDDOB7DfjGEfVF51FqqNacd6QmgFKJpMfLp5DHTv4wXlONKVXF9zTJpDV4m1sYZqJPhotcsliZM8yksKkCkzpiXt+EcRQvSQqmBS9WdWkxMTJXPSw94jqI3varCjQxTazjlMH8jTS8ilaW8014/vwA/LNa+YiFoyyx3s/KswP3O8QW1jtq45yTM/DX9a8M4voTVaO2ebvw1EooDw/yg6Y1faY+WwrdVs5Yt0hQ5EwRfYXSFxray1YvSM+kYmlpLG2/9mm1MfmbKHXr44Ih8nVKb1M537ZANUkCtdsPZ80JVKVKabVHCadaLXg+IV8i5GSwpZti0h6diTaKs9sdpUKEpd7jDUpYmHtiX33SKiO3tuydkaxA7pEc9XIQEOfWJlszj5YpL5bKeQyT7aZSBOamvSHl8xsWvgo26IP/bqk+0EJUz+gkkcvlUlyPp2kdKFtt7y5aCdks9ZJJcFp5ZWeaWKgtnXMN3ORwGLBE0PtkEIek5FY2aVssUZHtsWIvnljMVJtuVIjpZup/5VL1yPOHWWHkOMc6YySWMckczD5jUj2mlLVquFaMU8leGVaqeXis+aRRL8zm4WuBk6cyWfGMxgtr8useQEx7k/PvRoZyd9nde1GUCV84gMX8Ogu/BWezYPSR27llzQnA97oo0pYyxobYUJfsj+ysTm9zJ+S4pk0TGo9VTG0KjqYhTmALfoDZVKla2b5yhv241PxFaLJs3i05K0AAIdcGxCJZmT3ZdT7CliR7q+kur7WdQjygYtOWRL9B8E4s4LI8KpAj7bE0dg7DLOaX+MGeAi0hMMSSWZEz+RudXbZCsGYS0QqiXjH9XQbd8sCB+nIVTq7/T/FDS+zWY9q7Z2fdq1tdLb6v3hKKVDAw5gjj6o9r1wHFROdHc18MJp4SJ2Ucvu+iQ9EgkekW8VCM+psM6y+/2SBy8tNN4a3L1MzP+OLsyvESo5gS7IQOnIqMmviJBVc6zbVG1n8eXiA3j46kmvvtJlewwNDrxk4SbJOtP/TV/lIVK9ueShNbbMHfwnLTLLhbZuO79ec5XvfgRwLFK+w1r5ZWW15rVFZrE+wKqNRv5KqsLNfpGgnoUU6Y71NxEmN7MyqwqAQqoIULOw/LbuUB2+uE75gJt+kq1qY4LoxV+qR/zalupea3D5+WMeaRIn0sAI6DDWDh158fqUb4YhAxhREbUN0qyyJYkBU4V2KARXDT65gW3gRsiv7xSPYEKLwzgriWcWgPr0sbZnv7m1XHNFW6xPdGNZUdxFiUYlmXNjDVWuu7LCkX/nVkrXaJhiYktBISC2xgBXQnNEP+cptWl1eG62a7CPXrnrkTQ5BQASbEqUZWMDiZUisKyHDeLFOaJILUo5f6iDt4ZO8MlqaKLto0AmTHVVbkGuyPa1R/ywZsWRoRDoRdNMMHwYTsklMVnlAd2S0282bgMI8fiJpDh69OSL6K3qbo20KfpNMurnYGQSr/stFqZ7hYsxKlLnKAKhsmB8AIpEQ4bd/NrTLTXefsE6ChRmKWjXKVgpGoPs8GAicgKVw4K0qgDgy1A6hFq1WRat3fHF+FkU+b6H4NWpOU3KXTxrIb2qSHAb+qhm8hiSROi/9ofapjxhyKxxntPpge6KL5Z4+WBMYkAcE6+0Hd3Yh2zBsK2MV3iW0Y6cvOCroXlRb2MMJtdWx+3dkFzGh2Pe3DZ9QpSqpaR/rE1ImOrHqYYyccpiLC22amJIjRWVAherTfpQLmo6/K2pna85GrDuQPlH1Tsar8isAJbXLafSwOof4gg9RkAGm/oYpBQQiPUoyDk2BCQ1k+KILq48ErFo4WSRhHLq/y7mgw3+L85PpP6xWr6cgp9sOjYjKagOrxF148uhuaWtjet953fh1IQiEzgC+d2IgBCcUZqgTAICm2bR8oCjDLBsmg+ThyhfD+zBalsKBY1Ce54Y/t9cwfbLu9SFwEgphfopNA3yNxgyDafUM3mYTovZNgPGdd4ZFFOj1vtfFW3u7N+iHEN1HkeesDMXKPyoCDCGVMo4GCCD6PBhQ3dRZIHy0Y/3MaE5zU9mTCrwwnZojtE+qNpMSkJSpmGe0EzLyFelMJqhfFQ7a50uXxZ8pCc2wxtAKWgHoeamR2O7R+bq7IbPYItO0esdRgoTaY38hZLJ5y02oIVwoPokGIzxAMDuanQ1vn2WDQ00Rh6o5QOaCRu99fwDbQcN0XAuqkFpxT/cfz3slGRVokrNU0iqiMAJFEbKScZdmSkTUznC0U+MfwFOGdLgsewRyPKwBZYSmy6U325iUhBQNxbAC3FLKDV9VSOuQpOOukJ/GAmu/tyEbX9DgEp6dv1zoU0IqzpG6gssSjIYRVPGgU1QAQYRgIT8gEV0EXr1sqeh2I6rXjtmoCYyEDCe/PkFEi/Q48FuT29p557iN+LCwk5CK/CZ2WdAdfQZh2Z9QGrzPLSNRj5igUWzl9Vi0rCqH8G1Kp4QMLkuwMCAypdviDXyOIk0AHTM8HBYKh3b0/F+DxoNj4ZdoZfCpQVdnZarqoMaHWnMLNVcyevytGsrXQEoIbubqWYNo7NRHzdc0zvT21fWVirj7g36iy6pxogfvgHp1xH1Turbz8QyyHnXeBJicpYUctbzApwzZ1HT+FPEXMAgUZetgeGMwt4G+DHiDT2Lu+PT21fjJCAfV16a/Wu1PqOkUHSTKYhWW6PhhHUlNtWzFnA7MbY+r64vkwdpfNB2JfWgWXAvkzd42K4lN9x7Wrg4kIKgXCb4mcW595MCPJ/cTfPAMQMFWwnqwde4w8HZYJFpQwcSMhjVz4B8p6ncSCN1X4klxoIH4BN2J6taBMj6lHkAOs8JJAmXq5xsQtrPIPIIp/HG6i21xMGcFgqDXSRF0xQg14d2uy6HgKE13LSvQe52oShF5Jx1R6avyL4thhXQZHfC94oZzuPUBKFYf1VvDaxIrtV6dNGSx7DO0i1p6CzBkuAmEqyWceQY7F9+U0ObYDzoa1iKao/cOD/v6Q9gHrrr1uCeOk8fST9MG23Ul0KmM3r+Wn6Hi6WAcL7gEeaykicvgjzkjSwFsAXIR81Zx4QJ6oosVyJkCcT+4xAldCcihqvTf94HHUPXYp3REIaR4dhpQF6+FK1H0i9i7Pvh8owu3lO4PT1iuqu+DkL2Bj9+kdfGAg2TXw03iNHyobxofLE2ibjsYDPgeEQlRMR7afXbSGQcnPjI2D+sdtmuQ771dbASUsDndU7t58jrrNGRzISvwioAlHs5FA+cBE5Ccznkd8NMV6BR6ksnKLPZnMUawRDU1MZ/ib3xCdkTblHKu4blNiylH5n213yM0zubEie0o4JhzcfAy3H5qh2l17uLooBNLaO+gzonTH2uF8PQu9EyH+pjGsACTMy4cHzsPdymUSXYJOMP3yTkXqvO/lpvt0cX5ekDEu9PUfBeZODkFuAjXCaGdi6ew4qxJ8PmFfwmPpkgQjQlWqomFY6UkjmcnAtJG75EVR+NpzGpP1Ef5qUUbfowrC3zcSLX3BxgWEgEx/v9cP8H8u1Mvt9/rMDYf6sjwU1xSOPBgzFEeJLMRVFtKo5QHsUYT8ZRLCah27599EuqoC9PYjYO6aoAMHB8X1OHwEAYouHfHB3nyb2B+SnZxM/vw/bCtORjLMSy5aZoEpvgdGvlJfNPFUu/p7Z4VVK1hiI0/UTuB3ZPq4ohEbm7Mntgc1evEtknaosgZSwnDC2BdMmibpeg48X8Ixl+/8+xXdbshQXUPPvx8jT3fkELivHSmqbhblfNFShWAyQnJ3WBU6SMYSIpTDmHjdLVAdlADdz9gCplZw6mTiHqDwIsxbm9ErGusiVpg2w8Q3khKV/R9Oj8PFeF43hmW/nSd99nZzhyjCX3QOZkkB6BsH4H866WGyv9E0hVAzPYah2tkRfQZMmP2rinfOeQalge0ovhduBjJs9a1GBwReerceify49ctOh5/65ATYuMsAkVltmvTLBk4oHpdl6i+p8DoNj4Fb2vhdFYer2JSEilEwPd5n5zNoGBXEjreg/wh2NFnNRaIUHSOXa4eJRwygZoX6vnWnqVdCRT1ARxeFrNBJ+tsdooMwqnYhE7zIxnD8pZH+P0Nu1wWxCPTADfNWmqx626IBJJq6NeapcGeOmbtXvl0TeWG0Y7OGGV4+EHTtNBIT5Wd0Bujl7inXgZgfXTM5efD3qDTJ54O9v3Bkv+tdIRlq1kXcVD0BEMirmFxglNPt5pedb1AnxuCYMChUykwsTIWqT23XDpvTiKEru1cTcEMeniB+HQDehxPXNmkotFdwUPnilB/u4Nx5Xc6l8J9jH1EgKZUUt8t8cyoZleDBEt8oibDmJRAoMKJ5Oe9CSWS5ZMEJvacsGVdXDWjp/Ype5x0p9PXB2PAwt2LRD3d+ftNgpuyvxlP8pB84oB1i73vAVpwyrmXW72hfW6Dzn9Jkj4++0VQ4d0KSx1AsDA4OtXXDo63/w+GD+zC7w5SJaxsmnlYRQ4dgdjA7tTl2KNLnpJ+mvkoDxtt1a4oPaX3EVqj96o9sRKBQqU7ZOiupeAIyLMD+Y3YwHx30XWHB5CQiw7q3mj1EDlP2eBsZbz79ayUMbyHQ7s8gu4Lgip1LiGJj7NQj905/+rgUYKAA5qdrlHKIknWmqfuR+PB8RdBkDg/NgnlT89G72h2NvySnj7UyBwD+mi/IWs1xWbxuVwUIVXun5cMqBtFbrccI+DILjsVQg6eeq0itiRfedn89CvyFtpkxaauEvSANuZmB1p8FGPbU94J9medwsZ9HkUYjmI7OH5HuxendLbxTaYrPuIfE2ffXFKhoNBUp33HsFAXmCV/Vxpq5AYgFoRr5Ay93ZLRlgaIPjhZjXZZChT+aE5iWAXMX0oSFQEtwjiuhQQItTQX5IYrKfKB+queTNplR1Hoflo5/I6aPPmACwQCE2jTOYo5Dz1cs7Sod0KTG/3kEDGk3kUaUCON19xSJCab3kNpWZhSWkO8l+SpW70Wn3g0ciOIJO5JXma6dbos6jyisuxXwUUhj2+1uGhcvuliKtWwsUTw4gi1c/diEEpZHoKoxTBeMDmhPhKTx7TXWRakV8imJR355DcIHkR9IREHxohP4TbyR5LtFU24umRPRmEYHbpe1LghyxPx7YgUHjNbbQFRQhh4KeU1EabXx8FS3JAxp2rwRDoeWkJgWRUSKw6gGP5U2PuO9V4ZuiKXGGzFQuRuf+tkSSsbBtRJKhCi3ENuLlXhPbjTKD4djXVnfXFds6Zb+1XiUrRfyayGxJq1+SYBEfbKlgjiSmk0orgTqzSS+DZ5rTqsJbttiNtp+KMqGE2AHGFw6jQqM5vD6vMptmXV9OAjq49Uf/Lx9Opam+Hn5O9p8qoBBAQixzQZ4eNVkO9sPzJAMyR1y4/RCQQ1s0pV5KAU5sKLw3tkcFbI/JqrjCsK4Mw+W8aod4lioYuawUiCyVWBE/qPaFi5bnkgpfu/ae47174rI1fqQoTbW0HrU6FAejq7ByM0V4zkZTg02/YJK2N7hUQRCeZ4BIgSEqgD8XsjzG6LIsSbuHoIdz/LhFzbNn1clci1NHWJ0/6/O8HJMdIpEZbqi1RrrFfoo/rI/7ufm2MPG5lUI0IYJ4MAiHRTSOFJ2oTverFHYXThkYFIoyFx6rMYFgaOKM4xNWdlOnIcKb/suptptgTOTdVIf4YgdaAjJnIAm4qNNHNQqqAzvi53GkyRCEoseUBrHohZsjUbkR8gfKtc/+Oa72lwxJ8Mq6HDfDATbfbJhzeIuFQJSiw1uZprHlzUf90WgqG76zO0eCB1WdPv1IT6sNxxh91GEL2YpgC97ikFHyoaH92ndwduqZ6IYjkg20DX33MWdoZk7QkcKUCgisIYslOaaLyvIIqRKWQj16jE1DlQWJJaPopWTJjXfixEjRJJo8g4++wuQjbq+WVYjsqCuNIQW3YjnxKe2M5ZKEqq+cX7ZVgnkbsU3RWIyXA1rxv4kGersYJjD//auldXGmcEbcfTeF16Y1708FB1HIfmWv6dSFi6oD4E+RIjCsEZ+kY7dKnwReJJw3xCjKvi3kGN42rvyhUlIz0Bp+fNSV5xwFiuBzG296e5s/oHoFtUyUplmPulIPl+e1CQIQVtjlzLzzzbV+D/OVQtYzo5ixtMi5BmHuG4N/uKfJk5UIREp7+12oZlKtPBomXSzAY0KgtbPzzZoHQxujnREUgBU+O/jKKhgxVhRPtbqyHiUaRwRpHv7pgRPyUrnE7fYkVblGmfTY28tFCvlILC04Tz3ivkNWVazA+OsYrxvRM/hiNn8Fc4bQBeUZABGx5S/xFf9Lbbmk298X7iFg2yeimvsQqqJ+hYbt6uq+Zf9jC+Jcwiccd61NKQtFvGWrgJiHB5lwi6fR8KzYS7EaEHf/ka9EC7H8D+WEa3TEACHBkNSj/cXxFeq4RllC+fUFm2xtstYLL2nos1DfzsC9vqDDdRVcPA3Ho95aEQHvExVThXPqym65llkKlfRXbPTRiDepdylHjmV9YTWAEjlD9DdQnCem7Aj/ml58On366392214B5zrmQz/9ySG2mFqEwjq5sFl5tYJPw5hNz8lyZPUTsr5E0F2C9VMPnZckWP7+mbwp/BiN7f4kf7vtGnZF2JGvjK/sDX1RtcFY5oPQnE4lIAYV49U3C9SP0LCY/9i/WIFK9ORjzM9kG/KGrAuwFmgdEpdLaiqQNpCTGZVuAO65afkY1h33hrqyLjZy92JK3/twdj9pafFcwfXONmPQWldPlMe7jlP24Js0v9m8bIJ9TgS2IuRvE9ZVRaCwSJYOtAfL5H/YS4FfzKWKbek+GFulheyKtDNlBtrdmr+KU+ibHTdalzFUmMfxw3f36x+3cQbJLItSilW9cuvZEMjKw987jykZRlsH/UI+HlKfo2tLwemBEeBFtmxF2xmItA/dAIfQ+rXnm88dqvXa+GapOYVt/2waFimXFx3TC2MUiOi5/Ml+3rj/YU6Ihx2hXgiDXFsUeQkRAD6wF3SCPi2flk7XwKAA4zboqynuELD312EJ88lmDEVOMa1W/K/a8tGylZRMrMoILyoMQzzbDJHNZrhH77L9qSC42HVmKiZ5S0016UTp83gOhCwz9XItK9fgXfK3F5d7nZCBUekoLxrutQaPHa16Rjsa0gTrzyjqTnmcIcrxg6X6dkKiucudc0DD5W4pJPf0vuDW8r5/uw24YfMuxFRpD2ovT2mFX79xH6Jf+MVdv2TYqR6/955QgVPe3JCD/WjAYcLA9tpXgFiEjge2J5ljeI/iUzg91KQuHkII4mmHZxC3XQORLAC6G7uFn5LOmlnXkjFdoO976moNTxElS8HdxWoPAkjjocDR136m2l+f5t6xaaNgdodOvTu0rievnhNAB79WNrVs6EsPgkgfahF9gSFzzAd+rJSraw5Mllit7vUP5YxA843lUpu6/5jAR0RvH4rRXkSg3nE+O5GFyfe+L0s5r3k05FyghSFnKo4TTgs07qj4nTLqOYj6qaW9knJTDkF5OFMYbmCP+8H16Ty482OjvERV6OFyw043L9w3hoJi408sR+SGo1WviXUu8d7qS+ehKjpKwxeCthsm2LBFSFeetx0x4AaKPxtp3CxdWqCsLrB1s/j5TAhc1jNZsXWl6tjo/WDoewxzg8T8NnhZ1niUwL/nhfygLanCnRwaFGDyLw+sfZhyZ1UtYTp8TYB6dE7R3VsKKH95CUxJ8u8N+9u2/9HUNKHW3x3w5GQrfOPafk2w5qZq8MaHT0ebeY3wIsp3rN9lrpIsW9c1ws3VNV+JwNz0Lo9+V7zZr6GD56We6gWVIvtmam5GPPkVAbr74r6SwhuL+TRXtW/0pgyX16VNl4/EAD50TnUPuwrW6OcUO2VlWXS0inq872kk7GUlW6o/ozFKq+Sip6LcTtSDfDrPTcCHhx75H8BeRon+KG2wRwzfDgWhALmiWOMO6h3pm1UCZEPEjScyk7tdLx6WrdA2N1QTPENvNnhCQjW6kl057/qv7IwRryHrZBCwVSbLLnFRiHdTwk8mlYixFt1slEcPD7FVht13HyqVeyD55HOXrh2ElAxJyinGeoFzwKA91zfrdLvDxJSjzmImfvTisreI25EDcVfGsmxLVbfU8PGe/7NmWWKjXcdTJ11jAlVIY/Bv/mcxg/Q10vCHwKG1GW/XbJq5nxDhyLqiorn7Wd7VEVL8UgVzpHMjQ+Z8DUgSukiVwWAKkeTlVVeZ7t1DGnCgJVIdBPZAEK5f8CDyDNo7tK4/5DBjdD5MPV86TaEhGsLVFPQSI68KlBYy84FievdU9gWh6XZrugvtCZmi9vfd6db6V7FmoEcRHnG36VZH8N4aZaldq9zZawt1uBFgxYYx+Gs/qW1jwANeFy+LCoymyM6zgG7j8bGzUyLhvrbJkTYAEdICEb4kMKusKT9V3eIwMLsjdUdgijMc+7iKrr+TxrVWG0U+W95SGrxnxGrE4eaJFfgvAjUM4SAy8UaRwE9j6ZQH5qYAWGtXByvDiLSDfOD0yFA3UCMKSyQ30fyy1mIRg4ZcgZHLNHWl+c9SeijOvbOJxoQy7lTN2r3Y8p6ovxvUY74aOYbuVezryqXA6U+fcp6wSV9X5/OZKP18tB56Ua0gMyxJI7XyNT7IrqN8GsB9rL/kP5KMrjXxgqKLDa+V5OCH6a5hmOWemMUsea9vQl9t5Oce76PrTyTv50ExOqngE3PHPfSL//AItPdB7kGnyTRhVUUFNdJJ2z7RtktZwgmQzhBG/G7QsjZmJfCE7k75EmdIKH7xlnmDrNM/XbTT6FzldcH/rcRGxlPrv4qDScqE7JSmQABJWqRT/TUcJSwoQM+1jvDigvrjjH8oeK2in1S+/yO1j8xAws/T5u0VnIvAPqaE1atNuN0cuRliLcH2j0nTL4JpcR7w9Qya0JoaHgsOiALLCCzRkl1UUESz+ze/gIXHGtDwgYrK6pCFKJ1webSDog4zTlPkgXZqxlQDiYMjhDpwTtBW2WxthWbov9dt2X9XFLFmcF+eEc1UaQ74gqZiZsdj63pH1qcv3Vy8JYciogIVKsJ8Yy3J9w/GhjWVSQAmrS0BPOWK+RKV+0lWqXgYMnIFwpcZVD7zPSp547i9HlflB8gVnSTGmmq1ClO081OW/UH11pEQMfkEdDFzjLC1Cdo/BdL3s7cXb8J++Hzz1rhOUVZFIPehRiZ8VYu6+7Er7j5PSZu9g/GBdmNzJmyCD9wiswj9BZw+T3iBrg81re36ihMLjoVLoWc+62a1U/7qVX5CpvTVF7rocSAKwv4cBVqZm7lLDS/qoXs4fMs/VQi6BtVbNA3uSzKpQfjH1o3x4LrvkOn40zhm6hjduDglzJUwA0POabgdXIndp9fzhOo23Pe+Rk9GSLX0d71Poqry8NQDTzNlsa+JTNG9+UrEf+ngxCjGEsDCc0bz+udVRyHQI1jmEO3S+IOQycEq7XwB6z3wfMfa73m8PVRp+iOgtZfeSBl01xn03vMaQJkyj7vnhGCklsCWVRUl4y+5oNUzQ63B2dbjDF3vikd/3RUMifPYnX5Glfuk2FsV/7RqjI9yKTbE8wJY+74p7qXO8+dIYgjtLD/N8TJtRh04N9tXJA4H59IkMmLElgvr0Q5OCeVfdAt+5hkh4pQgfRMHpL74XatLQpPiOyHRs/OdmHtBf8nOZcxVKzdGclIN16lE7kJ+pVMjspOI+5+TqLRO6m0ZpNXJoZRv9MPDRcAfJUtNZHyig/s2wwReakFgPPJwCQmu1I30/tcBbji+Na53i1W1N+BqoY7Zxo+U/M9XyJ4Ok2SSkBtoOrwuhAY3a03Eu6l8wFdIG1cN+e8hopTkiKF093KuH/BcB39rMiGDLn6XVhGKEaaT/vqb/lufuAdpGExevF1+J9itkFhCfymWr9vGb3BTK4j598zRH7+e+MU9maruZqb0pkGxRDRE1CD4Z8LV4vhgPidk5w2Bq816g3nHw1//j3JStz7NR9HIWELO8TMn3QrP/zZp//+Dv9p429/ogv+GATR+n/UdF+ns9xNkXZQJXY4t9jMkJNUFygAtzndXwjss+yWH9HAnLQQfhAskdZS2l01HLWv7L7us5uTH409pqitvfSOQg/c+Zt7k879P3K9+WV68n7+3cZfuRd/dDPP/03rn+d+/nBvWfgDlt8+LzjqJ/vx3CnNOwiXhho778C96iD+1TBvRZYeP+EH81LE0vVwOOrmCLB3iKzI1x+vJEsrPH4uF0UB4TJ4X3uDfOCo3PYpYe0MF4bouh0DQ/l43fxUF7Y+dpWuvTSffB0yO2UQUETI/LwCZE3BvnevJ7c9zUlY3H58xzke6DNFDQG8n0WtDN4LAYN4nogKav1ezOfK/z+t6tsCTp+dhx4ymjWuCJk1dEUifDP+HyS4iP/Vg9B2jTo9L4NbiBuDS4nuuHW6H+JDQn2JtqRKGkEQPEYE7uzazXIkcxIAqUq1esasZBETlEZY7y7Jo+RoV/IsjY9eIMkUvr42Hc0xqtsavZvhz1OLwSxMOTuqzlhb0WbdOwBH9EYiyBjatz40bUxTHbiWxqJ0uma19qhPruvcWJlbiSSH48OLDDpaHPszvyct41ZfTu10+vjox6kOqK6v0K/gEPphEvMl/vwSv+A4Hhm36JSP9IXTyCZDm4kKsqD5ay8b1Sad/vaiyO5N/sDfEV6Z4q95E+yfjxpqBoBETW2C7xl4pIO2bDODDFurUPwE7EWC2Uplq+AHmBHvir2PSgkR12/Ry65O0aZtQPeXi9mTlF/Wj5GQ+vFkYyhXsLTjrBSP9hwk4GPqDP5rBn5/l8b0mLRAvRSzXHc293bs3s8EsdE3m2exxidWVB4joHR+S+dz5/W+v00K3TqN14CDBth8eWcsTbiwXPsygHdGid0PEdy6HHm2v/IUuV5RVapYmzGsX90mpnIdNGcOOq64Dbc5GUbYpD9M7S+6cLY//QmjxFLP5cuTFRm3vA5rkFZroFnO3bjHF35uU3s8mvL7Tp9nyTc4mymTJ5sLIp7umSnGkO23faehtz3mmTS7fbVx5rP7x3HXIjRNeq/A3xCs9JNB08c9S9BF2O3bOur0ItslFxXgRPdaapBIi4dRpKGxVz7ir69t/bc9qTxjvtOyGOfiLGDhR4fYywHv1WdOplxIV87TpLBy3Wc0QP0P9s4G7FBNOdITS/tep3o3h1TEa5XDDii7fWtqRzUEReP2fbxz7bHWWJdbIOxOUJZtItNZpTFRfj6vm9sYjRxQVO+WTdiOhdPeTJ+8YirPvoeL88l5iLYOHd3b/Imkq+1ZN1El3UikhftuteEYxf1Wujof8Pr4ICTu5ezZyZ4tHQMxlzUHLYO2VMOoNMGL/20S5i2o2obfk+8qqdR7xzbRDbgU0lnuIgz4LelQ5XS7xbLuSQtNS95v3ZUOdaUx/Qd8qxCt6xf2E62yb/HukLO6RyorV8KgYl5YNc75y+KvefrxY+lc/64y9kvWP0a0bDz/rojq+RWjO06WeruWqNFU7r3HPIcLWRql8ICZsz2Ls/qOm/CLn6++X+Qf7mGspYCrZod/lpl6Rw4xN/yuq8gqV4B6aHk1hVE1SfILxWu5gvXqbfARYQpspcxKp1F/c8XOPzkZvmoSw+vEqBLdrq1fr3wAPv5NnM9i8F+jdAuxkP5Z71c6uhK3enlnGymr7UsWZKC12qgUiG8XXGQ9mxnqz4GSIlybF9eXmbqj2sHX+a1jf0gRoONHRdRSrIq03Ty89eQ1GbV/Bk+du4+V15zls+vvERvZ4E7ZbnxWTVjDjb4o/k8jlw44pTIrUGxxuJvBeO+heuhOjpFsO6lVJ/aXnJDa/bM0Ql1cLbXE/Pbv3EZ3vj3iVrB5irjupZTzlnv677NrI9UNYNqbPgp/HZXS+lJmk87wec+7YOxTDo2aw2l3NfDr34VNlvqWJBknuK7oSlZ6/T10zuOoPZOeoIk81N+sL843WJ2Q4Z0fZ3scsqC/JV2fuhWi1jGURSKZV637lf53Xnnx16/vKEXY89aVJ0fv91jGdfG+G4+sniwHes4hS+udOr4RfhFhG/F5gUG35QaU+McuLmclb5ZWmR+sG5V6nf+PxYzlrnFGxpZaK8eqqVo0NfmAWoGfXDiT/FnUbWvzGDOTr8aktOZWg4BYvz5YH12ZbfCcGtNk+dDAZNGWvHov+PIOnY9Prjg8h/wLRrT69suaMVZ5bNuK00lSVpnqSX1NON/81FoP92rYndionwgOiA8WMf4vc8l15KqEEG4yAm2+WAN5Brfu1sq9suWYqgoajgOYt/JCk1gC8wPkK+XKCtRX6TAtgvrnuBgNRmn6I8lVDipOVB9kX6Oxkp4ZKyd1M6Gj8/v2U7k+YQBL95Kb9PQENucJb0JlW3b5tObN7m/Z1j1ev388d7o15zgXsI9CikAGAViR6lkJv7nb4Ak40M2G8TJ447kN+pvfHiOFjSUSP6PM+QfbAywKJCBaxSVxpizHseZUyUBhq59vFwrkyGoRiHbo0apweEZeSLuNiQ+HAekOnarFg00dZNXaPeoHPTRR0FmEyqYExOVaaaO8c0uFUh7U4e/UxdBmthlBDgg257Q33j1hA7HTxSeTTSuVnPZbgW1nodwmG16aKBDKxEetv7D9OjO0JhrbJTnoe+kcGoDJazFSO8/fUN9Jy/g4XK5PUkw2dgPDGpJqBfhe7GA+cjzfE/EGsMM+FV9nj9IAhrSfT/J3QE5TEIYyk5UjsI6ZZcCPr6A8FZUF4g9nnpVmjX90MLSQysIPD0nFzqwCcSJmIb5mYv2Cmk+C1MDFkZQyCBq4c/Yai9LJ6xYkGS/x2s5/frIW2vmG2Wrv0APpCdgCA9snFvfpe8uc0OwdRs4G9973PGEBnQB5qKrCQ6m6X/H7NInZ7y/1674/ZXOVp7OeuCRk8JFS516VHrnH1HkIUIlTIljjHaQtEtkJtosYul77cVwjk3gW1Ajaa6zWeyHGLlpk3VHE2VFzT2yI/EvlGUSz2H9zYE1s4nsKMtMqNyKNtL/59CpFJki5Fou6VXGm8vWATEPwrUVOLvoA8jLuwOzVBCgHB2Cr5V6OwEWtJEKokJkfc87h+sNHTvMb0KVTp5284QTPupoWvQVUwUeogZR3kBMESYo0mfukewRVPKh5+rzLQb7HKjFFIgWhj1w3yN/qCNoPI8XFiUgBNT1hCHBsAz8L7Oyt8wQWUFj92ONn/APyJFg8hzueqoJdNj57ROrFbffuS/XxrSXLTRgj5uxZjpgQYceeMc2wJrahReSKpm3QjHfqExTLAB2ipVumE8pqcZv8LYXQiPHHsgb5BMW8zM5pvQit+mQx8XGaVDcfVbLyMTlY8xcfmm/RSAT/H09UQol5gIz7rESDmnrQ4bURIB4iRXMDQwxgex1GgtDxKp2HayIkR+E/aDmCttNm2C6lytWdfOVzD6X2SpDWjQDlMRvAp1symWv4my1bPCD+E1EmGnMGWhNwmycJnDV2WrQNxO45ukEb08AAffizYKVULp15I4vbNK5DzWwCSUADfmKhfGSUqii1L2UsE8rB7mLuHuUJZOx4+WiizHBJ/hwboaBzhpNOVvgFTf5cJsHef7L1HCI9dOUUbb+YxUJWn6dYOLz+THi91kzY5dtO5c+grX7v0jEbsuoOGnoIreDIg/sFMyG+TyCLIcAWd1IZ1UNFxE8Uie13ucm40U2fcxC0u3WLvLOxwu+F7MWUsHsdtFQZ7W+nlfCASiAKyh8rnP3EyDByvtJb6Kax6/HkLzT9SyEyTMVM1zPtM0MJY14DmsWh4MgD15Ea9Hd00AdkTZ0EiG5NAGuIBzQJJ0JR0na+OB7lQA6UKxMfihIQ7GCCnVz694QvykWXTxpS2soDu+smru1UdIxSvAszBFD1c8c6ZOobA8bJiJIvuycgIXBQIXWwhyTgZDQxJTRXgEwRNAawGSXO0a1DKjdihLVNp/taE/xYhsgwe+VpKEEB4LlraQyE84gEihxCnbfoyOuJIEXy2FIYw+JjRusybKlU2g/vhTSGTydvCvXhYBdtAXtS2v7LkHtmXh/8fly1do8FI/D0f8UbzVb5h+KRhMGSAmR2mhi0YG/uj7wgxcfzCrMvdjitUIpXDX8ae2JcF/36qUWIMwN6JsjaRGNj+jEteGDcFyTUb8X/NHSucKMJp7pduxtD6KuxVlyxxwaeiC1FbGBESO84lbyrAugYxdl+2N8/6AgWpo/IeoAOcsG35IA/b3AuSyoa55L7llBLlaWlEWvuCFd8f8NfcTUgzJv6CbB+6ohWwodlk9nGWFpBAOaz5uEW5xBvmjnHFeDsb0mXwayj3mdYq5gxxNf3H3/tnCgHwjSrpSgVxLmiTtuszdRUFIsn6LiMPjL808vL1uQhDbM7aA43mISXReqjSskynIRcHCJ9qeFopJfx9tqyUoGbSwJex/0aDE3plBPGtNBYgWbdLom3+Q/bjdizR2/AS/c/dH/d3G7pyl1qDXgtOFtEqidwLqxPYtrNEveasWq3vPUUtqTeu8gpov4bdOQRI2kneFvRNMrShyVeEupK1PoLDPMSfWMIJcs267mGB8X9CehQCF0gIyhpP10mbyM7lwW1e6TGvHBV1sg/UyTghHPGRqMyaebC6pbB1WKNCQtlai1GGvmq9zUKaUzLaXsXEBYtHxmFbEZ2kJhR164LhWW2Tlp1dhsGE7ZgIWRBOx3Zcu2DxgH+G83WTPceKG0TgQKKiiNNOlWgvqNEbnrk6fVD+AqRam2OguZb0YWSTX88N+i/ELSxbaUUpPx4vJUzYg/WonSeA8xUK6u7DPHgpqWpEe6D4cXg5uK9FIYVba47V/nb+wyOtk+zG8RrS4EA0ouwa04iByRLSvoJA2FzaobbZtXnq8GdbfqEp5I2dpfpj59TCVif6+E75p665faiX8gS213RqBxTZqfHP46nF6NSenOneuT+vgbLUbdTH2/t0REFXZJOEB6DHvx6N6g9956CYrY/AYcm9gELJXYkrSi+0F0geKDZgOCIYkLU/+GOW5aGj8mvLFgtFH5+XC8hvAE3CvHRfl4ofM/Qwk4x2A+R+nyc9gNu/9Tem7XW4XRnyRymf52z09cTOdr+PG6+P/Vb4QiXlwauc5WB1z3o+IJjlbxI8MyWtSzT+k4sKVbhF3xa+vDts3NxXa87iiu+xRH9cAprnOL2h6vV54iQRXuOAj1s8nLFK8gZ70ThIQcWdF19/2xaJmT0efrkNDkWbpAQPdo92Z8+Hn/aLjbOzB9AI/k12fPs9HhUNDJ1u6ax2VxD3R6PywN7BrLJ26z6s3QoMp76qzzwetrDABKSGkfW5PwS1GvYNUbK6uRqxfyVGNyFB0E+OugMM8kKwmJmupuRWO8XkXXXQECyRVw9UyIrtCtcc4oNqXqr7AURBmKn6Khz3eBN96LwIJrAGP9mr/59uTOSx631suyT+QujDd4beUFpZ0kJEEnjlP+X/Kr2kCKhnENTg4BsMTOmMqlj2WMFLRUlVG0fzdCBgUta9odrJfpVdFomTi6ak0tFjXTcdqqvWBAzjY6hVrH9sbt3Z9gn+AVDpTcQImefbB4edirjzrsNievve4ZT4EUZWV3TxEsIW+9MT/RJoKfZZYSRGfC1CwPG/9rdMOM8qR/LUYvw5f/emUSoD7YSFuOoqchdUg2UePd1eCtFSKgxLSZ764oy4lvRCIH6bowPxZWwxNFctksLeil47pfevcBipkkBIc4ngZG+kxGZ71a72KQ7VaZ6MZOZkQJZXM6kb/Ac0/XkJx8dvyfJcWbI3zONEaEPIW8GbkYjsZcwy+eMoKrYjDmvEEixHzkCSCRPRzhOfJZuLdcbx19EL23MA8rnjTZZ787FGMnkqnpuzB5/90w1gtUSRaWcb0eta8198VEeZMUSfIhyuc4/nywFQ9uqn7jdqXh+5wwv+RK9XouNPbYdoEelNGo34KyySwigsrfCe0v/PlWPvQvQg8R0KgHO18mTVThhQrlbEQ0Kp/JxPdjHyR7E1QPw/ut0r+HDDG7BwZFm9IqEUZRpv2WpzlMkOemeLcAt5CsrzskLGaVOAxyySzZV/D2EY7ydNZMf8e8VhHcKGHAWNszf1EOq8fNstijMY4JXyATwTdncFFqcNDfDo+mWFvxJJpc4sEZtjXyBdoFcxbUmniCoKq5jydUHNjYJxMqN1KzYV62MugcELVhS3Bnd+TLLOh7dws/zSXWzxEb4Nj4aFun5x4kDWLK5TUF/yCXB/cZYvI9kPgVsG2jShtXkxfgT+xzjJofXqPEnIXIQ1lnIdmVzBOM90EXvJUW6a0nZ/7XjJGl8ToO3H/fdxnxmTNKBZxnkpXLVgLXCZywGT3YyS75w/PAH5I/jMuRspej8xZObU9kREbRA+kqjmKRFaKGWAmFQspC+QLbKPf0RaK3OXvBSWqo46p70ws/eZpu6jCtZUgQy6r4tHMPUdAgWGGUYNbuv/1a6K+MVFsd3T183+T8capSo6m0+Sh57fEeG/95dykGJBQMj09DSW2bY0mUonDy9a8trLnnL5B5LW3Nl8rJZNysO8Zb+80zXxqUGFpud3Qzwb7bf+8mq6x0TAnJU9pDQR9YQmZhlna2xuxJt0aCO/f1SU8gblOrbIyMsxTlVUW69VJPzYU2HlRXcqE2lLLxnObZuz2tT9CivfTAUYfmzJlt/lOPgsR6VN64/xQd4Jlk/RV7UKVv2Gx/AWsmTAuCWKhdwC+4HmKEKYZh2Xis4KsUR1BeObs1c13wqFRnocdmuheaTV30gvVXZcouzHKK5zwrN52jXJEuX6dGx3BCpV/++4f3hyaW/cQJLFKqasjsMuO3B3WlMq2gyYfdK1e7L2pO/tRye2mwzwZPfdUMrl5wdLqdd2Kv/wVtnpyWYhd49L6rsOV+8HXPrWH2Kup89l2tz6bf80iYSd+V4LROSOHeamvexR524q4r43rTmtFzQvArpvWfLYFZrbFspBsXNUqqenjxNNsFXatZvlIhk7teUPfK+YL32F8McTnjv0BZNppb+vshoCrtLXjIWq3EJXpVXIlG6ZNL0dh6qEm2WMwDjD3LfOfkGh1/czYc/0qhiD2ozNnH4882MVVt3JbVFkbwowNCO3KL5IoYW5wlVeGCViOuv1svZx7FbzxKzA4zGqBlRRaRWCobXaVq4yYCWbZf8eiJwt3OY+MFiSJengcFP2t0JMfzOiJ7cECvpx7neg1Rc5x+7myPJOXt2FohVRyXtD+/rDoTOyGYInJelZMjolecVHUhUNqvdZWg2J2t0jPmiLFeRD/8fOT4o+NGILb+TufCo9ceBBm3JLVn+MO2675n7qiEX/6W+188cYg3Zn5NSTjgOKfWFSAANa6raCxSoVU851oJLY11WIoYK0du0ec5E4tCnAPoKh71riTsjVIp3gKvBbEYQiNYrmH22oLQWA2AdwMnID6PX9b58dR2QKo4qag1D1Z+L/FwEKTR7osOZPWECPJIHQqPUsM5i/CH5YupVPfFA5pHUBcsesh8eO5YhyWnaVRPZn/BmdXVumZWPxMP5e28zm2uqHgFoT9CymHYNNrzrrjlXZM06HnzDxYNlI5b/QosxLmmrqDFqmogQdqk0WLkUceoAvQxHgkIyvWU69BPFr24VB6+lx75Rna6dGtrmOxDnvBojvi1/4dHjVeg8owofPe1cOnxU1ioh016s/Vudv9mhV9f35At+Sh28h1bpp8xhr09+vf47Elx3Ms6hyp6QvB3t0vnLbOhwo660cp7K0vvepabK7YJfxEWWfrC2YzJfYOjygPwfwd/1amTqa0hZ5ueebhWYVMubRTwIjj+0Oq0ohU3zfRfuL8gt59XsHdwKtxTQQ4Y2qz6gisxnm2UdlmpEkgOsZz7iEk6QOt8BuPwr+NR01LTqXmJo1C76o1N274twJvl+I069TiLpenK/miRxhyY8jvYV6W1WuSwhH9q7kuwnJMtm7IWcqs7HsnyHSqWXLSpYtZGaR1V3t0gauninFPZGtWskF65rtti48UV9uV9KM8kfDYs0pgB00S+TlzTXV6P8mxq15b9En8sz3jWSszcifZa/NuufPNnNTb031pptt0+sRSH/7UG8pzbsgtt3OG3ut7B9JzDMt2mTZuyRNIV8D54TuTrpNcHtgmMlYJeiY9XS83NYJicjRjtJSf9BZLsQv629QdDsKQhTK5CnXhpk7vMNkHzPhm0ExW/VCGApHfPyBagtZQTQmPHx7g5IXXsrQDPzIVhv2LB6Ih138iSDww1JNHrDvzUxvp73MsQBVhW8EbrReaVUcLB1R3PUXyaYG4HpJUcLVxMgDxcPkVRQpL7VTAGabDzbKcvg12t5P8TSGQkrj/gOrpnbiDHwluA73xbXts/L7u468cRWSWRtgTwlQnA47EKg0OiZDgFxAKQQUcsbGomITgeXUAAyKe03eA7Mp4gnyKQmm0LXJtEk6ddksMJCuxDmmHzmVhO+XaN2A54MIh3niw5CF7PwiXFZrnA8wOdeHLvvhdoqIDG9PDI7UnWWHq526T8y6ixJPhkuVKZnoUruOpUgOOp3iIKBjk+yi1vHo5cItHXb1PIKzGaZlRS0g5d3MV2pD8FQdGYLZ73aae/eEIUePMc4NFz8pIUfLCrrF4jVWH5gQneN3S8vANBmUXrEcKGn6hIUN95y1vpsvLwbGpzV9L0ZKTan6TDXM05236uLJcIEMKVAxKNT0K8WljuwNny3BNQRfzovA85beI9zr1AGNYnYCVkR1aGngWURUrgqR+gRrQhxW81l3CHevjvGEPzPMTxdsIfB9dfGRbZU0cg/1mcubtECX4tvaedmNAvTxCJtc2QaoUalGfENCGK7IS/O8CRpdOVca8EWCRwv2sSWE8CJPW5PCugjCXPd3h6U60cPD+bdhtXZuYB6stcoveE7Sm5MM2yvfUHXFSW7KzLmi7/EeEWL0wqcOH9MOSKjhCHHmw+JGLcYE/7SBZQCRggox0ZZTAxrlzNNXYXL5fNIjkdT4YMqVUz6p8YDt049v4OXGdg3qTrtLBUXOZf7ahPlZAY/O+7Sp0bvGSHdyQ8B1LOsplqMb9Se8VAE7gIdSZvxbRSrfl+Lk5Qaqi5QJceqjitdErcHXg/3MryljPSIAMaaloFm1cVwBJ8DNmkDqoGROSHFetrgjQ5CahuKkdH5pRPigMrgTtlFI8ufJPJSUlGgTjbBSvpRc0zypiUn6U5KZqcRoyrtzhmJ7/caeZkmVRwJQeLOG8LY6vP5ChpKhc8Js0El+n6FXqbx9ItdtLtYP92kKfaTLtCi8StLZdENJa9Ex1nOoz1kQ7qxoiZFKRyLf4O4CHRT0T/0W9F8epNKVoeyxUXhy3sQMMsJjQJEyMOjmOhMFgOmmlscV4eFi1CldU92yjwleirEKPW3bPAuEhRZV7JsKV3Lr5cETAiFuX5Nw5UlF7d2HZ96Bh0sgFIL5KGaKSoVYVlvdKpZJVP5+NZ7xDEkQhmDgsDKciazJCXJ6ZN2B3FY2f6VZyGl/t4aunGIAk/BHaS+i+SpdRfnB/OktOvyjinWNfM9Ksr6WwtCa1hCmeRI6icpFM4o8quCLsikU0tMoZI/9EqXRMpKGaWzofl4nQuVQm17d5fU5qXCQeCDqVaL9XJ9qJ08n3G3EFZS28SHEb3cdRBdtO0YcTzil3QknNKEe/smQ1fTb0XbpyNB5xAeuIlf+5KWlEY0DqJbsnzJlQxJPOVyHiKMx5Xu9FcEv1Fbg6Fhm4t+Jyy5JC1W3YO8dYLsO0PXPbxodBgttTbH3rt9Cp1lJIk2r3O1Zqu94eRbnIz2f50lWolYzuKsj4PMok4abHLO8NAC884hiXx5Fy5pWKO0bWL7uEGXaJCtznhP67SlQ4xjWIfgq6EpZ28QMtuZK7JC0RGbl9nA4XtFLug/NLMoH1pGt9IonAJqcEDLyH6TDROcbsmGPaGIxMo41IUAnQVPMPGByp4mOmh9ZQMkBAcksUK55LsZj7E5z5XuZoyWCKu6nHmDq22xI/9Z8YdxJy4kWpD16jLVrpwGLWfyOD0Wd+cBzFBxVaGv7S5k9qwh/5t/LQEXsRqI3Q9Rm3QIoaZW9GlsDaKOUyykyWuhNOprSEi0s1G4rgoiX1V743EELti+pJu5og6X0g6oTynUqlhH9k6ezyRi05NGZHz0nvp3HOJr7ebrAUFrDjbkFBObEvdQWkkUbL0pEvMU46X58vF9j9F3j6kpyetNUBItrEubW9ZvMPM4qNqLlsSBJqOH3XbNwv/cXDXNxN8iFLzUhteisYY+RlHYOuP29/Cb+L+xv+35Rv7xudnZ6ohK4cMPfCG8KI7dNmjNk/H4e84pOxn/sZHK9psfvj8ncA8qJz7O8xqbxESDivGJOZzF7o5PJLQ7g34qAWoyuA+x3btU98LT6ZyGyceIXjrqob2CAVql4VOTQPUQYvHV/g4zAuCZGvYQBtf0wmd5lilrvuEn1BXLny01B4h4SMDlYsnNpm9d7m9h578ufpef9Z4WplqWQvqo52fyUA7J24eZD5av6SyGIV9kpmHNqyvdfzcpEMw97BvknV2fq+MFHun9BT3Lsf8pbzvisWiIQvYkng+8Vxk1V+dli1u56kY50LRjaPdotvT5BwqtwyF+emo/z9J3yVUVGfKrxQtJMOAQWoQii/4dp9wgybSa5mkucmRLtEQZ/pz0tL/NVcgWAd95nEQ3Tg6tNbuyn3Iepz65L3huMUUBntllWuu4DbtOFSMSbpILV4fy6wlM0SOvi6CpLh81c1LreIvKd61uEWBcDw1lUBUW1I0Z+m/PaRlX+PQ/oxg0Ye6KUiIiTF4ADNk59Ydpt5/rkxmq9tV5Kcp/eQLUVVmBzQNVuytQCP6Ezd0G8eLxWyHpmZWJ3bAzkWTtg4lZlw42SQezEmiUPaJUuR/qklVA/87S4ArFCpALdY3QRdUw3G3XbWUp6aq9z0zUizcPa7351p9JXOZyfdZBFnqt90VzQndXB/mwf8LC9STj5kenVpNuqOQQP3mIRJj7eV21FxG8VAxKrEn3c+XfmZ800EPb9/5lIlijscUbB6da0RQaMook0zug1G0tKi/JBC4rw7/D3m4ARzAkzMcVrDcT2SyFtUdWAsFlsPDFqV3N+EjyXaoEePwroaZCiLqEzb8MW+PNE9TmTC01EzWli51PzZvUqkmyuROU+V6ik+Le/9qT6nwzUzf9tP68tYei0YaDGx6kAd7jn1cKqOCuYbiELH9zYqcc4MnRJjkeGiqaGwLImhyeKs+xKJMBlOJ05ow9gGCKZ1VpnMKoSCTbMS+X+23y042zOb5MtcY/6oBeAo1Vy89OTyhpavFP78jXCcFH0t7Gx24hMEOm2gsEfGabVpQgvFqbQKMsknFRRmuPHcZu0Su/WMFphZvB2r/EGbG72rpGGho3h+Msz0uGzJ7hNK2uqQiE1qmn0zgacKYYZBCqsxV+sjbpoVdSilW/b94n2xNb648VmNIoizqEWhBnsen+d0kbCPmRItfWqSBeOd9Wne3c6bcd6uvXOJ6WdiSsuXq0ndhqrQ4QoWUjCjYtZ0EAhnSOP1m44xkf0O7jXghrzSJWxP4a/t72jU29Vu2rvu4n7HfHkkmQOMGSS+NPeLGO5I73mC2B7+lMiBQQZRM9/9liLIfowupUFAbPBbR+lxDM6M8Ptgh1paJq5Rvs7yEuLQv/7d1oU2woFSb3FMPWQOKMuCuJ7pDDjpIclus5TeEoMBy2YdVB4fxmesaCeMNsEgTHKS5WDSGyNUOoEpcC2OFWtIRf0w27ck34/DjxRTVIcc9+kqZE6iMSiVDsiKdP/Xz5XfEhm/sBhO50p1rvJDlkyyxuJ9SPgs7YeUJBjXdeAkE+P9OQJm6SZnn1svcduI78dYmbkE2mtziPrcjVisXG78spLvbZaSFx/Rks9zP4LKn0Cdz/3JsetkT06A8f/yCgMO6Mb1Hme0JJ7b2wZz1qleqTuKBGokhPVUZ0dVu+tnQYNEY1fmkZSz6+EGZ5EzL7657mreZGR3jUfaEk458PDniBzsSmBKhDRzfXameryJv9/D5m6HIqZ0R+ouCE54Dzp4IJuuD1e4Dc5i+PpSORJfG23uVgqixAMDvchMR0nZdH5brclYwRoJRWv/rlxGRI5ffD5NPGmIDt7vDE1434pYdVZIFh89Bs94HGGJbTwrN8T6lh1HZFTOB4lWzWj6EVqxSMvC0/ljWBQ3F2kc/mO2b6tWonT2JEqEwFts8rz2h+oWNds9ceR2cb7zZvJTDppHaEhK5avWqsseWa2Dt5BBhabdWSktS80oMQrL4TvAM9b5HMmyDnO+OkkbMXfUJG7eXqTIG6lqSOEbqVR+qYdP7uWb57WEJqzyh411GAVsDinPs7KvUeXItlcMdOUWzXBH6zscymV1LLVCtc8IePojzXHF9m5b5zGwBRdzcyUJkiu938ApmAayRdJrX1PmVguWUvt2ThQ62czItTyWJMW2An/hdDfMK7SiFQlGIdAbltHz3ycoh7j9V7GxNWBpbtcSdqm4XxRwTawc3cbZ+xfSv9qQfEkDKfZTwCkqWGI/ur250ItXlMlh6vUNWEYIg9A3GzbgmbqvTN8js2YMo87CU5y6nZ4dbJLDQJj9fc7yM7tZzJDZFtqOcU8+mZjYlq4VmifI23iHb1ZoT9E+kT2dolnP1AfiOkt7PQCSykBiXy5mv637IegWSKj9IKrYZf4Lu9+I7ub+mkRdlvYzehh/jaJ9n7HUH5b2IbgeNdkY7wx1yVzxS7pbvky6+nmVUtRllEFfweUQ0/nG017WoUYSxs+j2B4FV/F62EtHlMWZXYrjGHpthnNb1x66LKZ0Qe92INWHdfR/vqp02wMS8r1G4dJqHok8KmQ7947G13a4YXbsGgHcBvRuVu1eAi4/A5+ZixmdSXM73LupB/LH7O9yxLTVXJTyBbI1S49TIROrfVCOb/czZ9pM4JsZx8kUz8dQGv7gUWKxXvTH7QM/3J2OuXXgciUhqY+cgtaOliQQVOYthBLV3xpESZT3rmfEYNZxmpBbb24CRao86prn+i9TNOh8VxRJGXJfXHATJHs1T5txgc/opYrY8XjlGQQbRcoxIBcnVsMjmU1ymmIUL4dviJXndMAJ0Yet+c7O52/p98ytlmAsGBaTAmMhimAnvp1TWNGM9BpuitGj+t810CU2UhorrjPKGtThVC8WaXw04WFnT5fTjqmPyrQ0tN3CkLsctVy2xr0ZWgiWVZ1OrlFjjxJYsOiZv2cAoOvE+7sY0I/TwWcZqMoyIKNOftwP7w++Rfg67ljfovKYa50if3fzE/8aPYVey/Nq35+nH2sLPh/fP5TsylSKGOZ4k69d2PnH43+kq++sRXHQqGArWdwhx+hpwQC6JgT2uxehYU4Zbw7oNb6/HLikPyJROGK2ouyr+vzseESp9G50T4AyFrSqOQ0rroCYP4sMDFBrHn342EyZTMlSyk47rHSq89Y9/nI3zG5lX16Z5lxphguLOcZUndL8wNcrkyjH82jqg8Bo8OYkynrxZvbFno5lUS3OPr8Ko3mX9NoRPdYOKKjD07bvgFgpZ/RF+YzkWvJ/Hs/tUbfeGzGWLxNAjfDzHHMVSDwB5SabQLsIZHiBp43FjGkaienYoDd18hu2BGwOK7U3o70K/WY/kuuKdmdrykIBUdG2mvE91L1JtTbh20mOLbk1vCAamu7utlXeGU2ooVikbU/actcgmsC1FKk2qmj3GWeIWbj4tGIxE7BLcBWUvvcnd/lYxsMV4F917fWeFB/XbINN3qGvIyTpCalz1lVewdIGqeAS/gB8Mi+sA+BqDiX3VGD2eUunTRbSY+AuDy4E3Qx3hAhwnSXX+B0zuj3eQ1miS8Vux2z/l6/BkWtjKGU72aJkOCWhGcSf3+kFkkB15vGOsQrSdFr6qTj0gBYiOlnBO41170gOWHSUoBVRU2JjwppYdhIFDfu7tIRHccSNM5KZOFDPz0TGMAjzzEpeLwTWp+kn201kU6NjbiMQJx83+LX1e1tZ10kuChJZ/XBUQ1dwaBHjTDJDqOympEk8X2M3VtVw21JksChA8w1tTefO3RJ1FMbqZ01bHHkudDB/OhLfe7P5GOHaI28ZXKTMuqo0hLWQ4HabBsGG7NbP1RiXtETz074er6w/OerJWEqjmkq2y51q1BVI+JUudnVa3ogBpzdhFE7fC7kybrAt2Z6RqDjATAUEYeYK45WMupBKQRtQlU+uNsjnzj6ZmGrezA+ASrWxQ6LMkHRXqXwNq7ftv28dUx/ZSJciDXP2SWJsWaN0FjPX9Yko6LobZ7aYW/IdUktI9apTLyHS8DyWPyuoZyxN1TK/vtfxk3HwWh6JczZC8Ftn0bIJay2g+n5wd7lm9rEsKO+svqVmi+c1j88hSCxbzrg4+HEP0Nt1/B6YW1XVm09T1CpAKjc9n18hjqsaFGdfyva1ZG0Xu3ip6N6JGpyTSqY5h4BOlpLPaOnyw45PdXTN+DtAKg7DLrLFTnWusoSBHk3s0d7YouJHq85/R09Tfc37ENXZF48eAYLnq9GLioNcwDZrC6FW6godB8JnqYUPvn0pWLfQz0lM0Yy8Mybgn84Ds3Q9bDP10bLyOV+qzxa4Rd9Dhu7cju8mMaONXK3UqmBQ9qIg7etIwEqM/kECk/Dzja4Bs1xR+Q/tCbc8IKrSGsTdJJ0vge7IG20W687uVmK6icWQ6cD3lwFzgNMGtFvO5qyJeKflGLAAcQZOrkxVwy3cWvqlGpvjmf9Qe6Ap20MPbV92DPV0OhFM4kz8Yr0ffC2zLWSQ1kqY6QdQrttR3kh1YLtQd1kCEv5hVoPIRWl5ERcUTttBIrWp6Xs5Ehh5OUUwI5aEBvuiDmUoENmnVw1FohCrbRp1A1E+XSlWVOTi7ADW+5Ohb9z1vK4qx5R5lPdGCPBJZ00mC+Ssp8VUbgpGAvXWMuWQQRbCqI6Rr2jtxZxtfP7W/8onz+yz0Gs76LaT5HX9ecyiZCB/ZR/gFtMxPsDwohoeCRtiuLxE1GM1vUEUgBv86+eehL58/P56QFGQ/MqOe/vC76L63jzmeax4exd/OKTUvkXg+fOJUHych9xt/9goJMrapSgvXrj8+8vk/N80f22Sewj6cyGqt1B6mztoeklVHHraouhvHJaG/OuBz6DHKMpFmQULU1bRWlyYE0RPXYYkUycIemN7TLtgNCJX6BqdyxDKkegO7nJK5xQ7OVYDZTMf9bVHidtk6DQX9Et+V9M7esgbsYBdEeUpsB0Xvw2kd9+rI7V+m47u+O/tq7mw7262HU1WlS9uFzsV6JxIHNmUCy0QS9e077JGRFbG65z3/dOKB/Zk+yDdKpUmdXjn/aS3N5nv4fK7bMHHmPlHd4E2+iTbV5rpzScRnxk6KARuDTJ8Q1LpK2mP8gj1EbuJ9RIyY+EWK4hCiIDBAS1Tm2IEXAFfgKPgdL9O6mAa06wjCcUAL6EsxPQWO9VNegBPm/0GgkZbDxCynxujX/92vmGcjZRMAY45puak2sFLCLSwXpEsyy5fnF0jGJBhm+fNSHKKUUfy+276A7/feLOFxxUuHRNJI2Osenxyvf8DAGObT60pfTTlhEg9u/KKkhJqm5U1/+BEcSkpFDA5XeCqxwXmPac1jcuZ3JWQ+p0NdWzb/5v1ZvF8GtMTFFEdQjpLO0bwPb0BHNWnip3liDXI2fXf05jjvfJ0NpjLCUgfTh9CMFYVFKEd4Z/OG/2C+N435mnK+9t1gvCiVcaaH7rK4+PjCvpVNiz+t2QyqH1O8x3JKZVl6Q+Lp/XK8wMjVMslOq9FdSw5FtUs/CptXH9PW+wbWHgrV17R5jTVOtGtKFu3nb80T+E0tv9QkzW3J2dbaw/8ddAKZ0pxIaEqLjlPrji3VgJ3GvdFvlqD8075woxh4fVt0JZE0KVFsAvqhe0dqN9b35jtSpnYMXkU+vZq+IAHad3IHc2s/LYrnD1anfG46IFiMIr9oNbZDWvwthqYNqOigaKd/XlLU4XHfk/PXIjPsLy/9/kAtQ+/wKH+hI/IROWj5FPvTZAT9f7j4ZXQyG4M0TujMAFXYkKvEHv1xhySekgXGGqNxWeWKlf8dDAlLuB1cb/qOD+rk7cmwt+1yKpk9cudqBanTi6zTbXRtV8qylNtjyOVKy1HTz0GW9rjt6sSjAZcT5R+KdtyYb0zyqG9pSLuCw5WBwAn7fjBjKLLoxLXMI+52L9cLwIR2B6OllJZLHJ8vDxmWdtF+QJnmt1rsHPIWY20lftk8fYePkAIg6Hgn532QoIpegMxiWgAOfe5/U44APR8Ac0NeZrVh3gEhs12W+tVSiWiUQekf/YBECUy5fdYbA08dd7VzPAP9aiVcIB9k6tY7WdJ1wNV+bHeydNtmC6G5ICtFC1ZwmJU/j8hf0I8TRVKSiz5oYIa93EpUI78X8GYIAZabx47/n8LDAAJ0nNtP1rpROprqKMBRecShca6qXuTSI3jZBLOB3Vp381B5rCGhjSvh/NSVkYp2qIdP/Bg="; + }, + "dec/dictionary-browser.js": function (e, t, r) { + var n = e("base64-js"); + r.init = function () { + var t = e("./decode").BrotliDecompressBuffer, + r = n.toByteArray(e("./dictionary.bin.js")); + return t(r); + }; + }, + "dec/huffman.js": function (e, t, r) { + function n(e, t) { + (this.bits = e), (this.value = t); + } + function o(e, t) { + for (var r = 1 << (t - 1); e & r; ) r >>= 1; + return (e & (r - 1)) + r; + } + function a(e, t, r, o, a) { + do (o -= r), (e[t + o] = new n(a.bits, a.value)); + while (o > 0); + } + function i(e, t, r) { + for (var n = 1 << (t - r); t < s && ((n -= e[t]), !(n <= 0)); ) ++t, (n <<= 1); + return t - r; + } + r.HuffmanCode = n; + const s = 15; + r.BrotliBuildHuffmanTable = function (e, t, r, d, l) { + var u, + f, + c, + h, + p, + w, + m, + b, + y, + g, + v, + A = t, + U = new Int32Array(16), + x = new Int32Array(16); + for (v = new Int32Array(l), c = 0; c < l; c++) U[d[c]]++; + for (x[1] = 0, f = 1; f < s; f++) x[f + 1] = x[f] + U[f]; + for (c = 0; c < l; c++) 0 !== d[c] && (v[x[d[c]]++] = c); + if (((b = r), (y = 1 << b), (g = y), 1 === x[s])) { + for (h = 0; h < g; ++h) e[t + h] = new n(0, 65535 & v[0]); + return g; + } + for (h = 0, c = 0, f = 1, p = 2; f <= r; ++f, p <<= 1) for (; U[f] > 0; --U[f]) (u = new n(255 & f, 65535 & v[c++])), a(e, t + h, p, y, u), (h = o(h, f)); + for (m = g - 1, w = -1, f = r + 1, p = 2; f <= s; ++f, p <<= 1) + for (; U[f] > 0; --U[f]) + (h & m) !== w && ((t += y), (b = i(U, f, r)), (y = 1 << b), (g += y), (w = h & m), (e[A + w] = new n((b + r) & 255, (t - A - w) & 65535))), + (u = new n((f - r) & 255, 65535 & v[c++])), + a(e, t + (h >> r), p, y, u), + (h = o(h, f)); + return g; + }; + }, + "dec/prefix.js": function (e, t, r) { + function n(e, t) { + (this.offset = e), (this.nbits = t); + } + (r.kBlockLengthPrefixCode = [ + new n(1, 2), + new n(5, 2), + new n(9, 2), + new n(13, 2), + new n(17, 3), + new n(25, 3), + new n(33, 3), + new n(41, 3), + new n(49, 4), + new n(65, 4), + new n(81, 4), + new n(97, 4), + new n(113, 5), + new n(145, 5), + new n(177, 5), + new n(209, 5), + new n(241, 6), + new n(305, 6), + new n(369, 7), + new n(497, 8), + new n(753, 9), + new n(1265, 10), + new n(2289, 11), + new n(4337, 12), + new n(8433, 13), + new n(16625, 24), + ]), + (r.kInsertLengthPrefixCode = [ + new n(0, 0), + new n(1, 0), + new n(2, 0), + new n(3, 0), + new n(4, 0), + new n(5, 0), + new n(6, 1), + new n(8, 1), + new n(10, 2), + new n(14, 2), + new n(18, 3), + new n(26, 3), + new n(34, 4), + new n(50, 4), + new n(66, 5), + new n(98, 5), + new n(130, 6), + new n(194, 7), + new n(322, 8), + new n(578, 9), + new n(1090, 10), + new n(2114, 12), + new n(6210, 14), + new n(22594, 24), + ]), + (r.kCopyLengthPrefixCode = [ + new n(2, 0), + new n(3, 0), + new n(4, 0), + new n(5, 0), + new n(6, 0), + new n(7, 0), + new n(8, 0), + new n(9, 0), + new n(10, 1), + new n(12, 1), + new n(14, 2), + new n(18, 2), + new n(22, 3), + new n(30, 3), + new n(38, 4), + new n(54, 4), + new n(70, 5), + new n(102, 5), + new n(134, 6), + new n(198, 7), + new n(326, 8), + new n(582, 9), + new n(1094, 10), + new n(2118, 24), + ]), + (r.kInsertRangeLut = [0, 0, 8, 8, 0, 16, 8, 16, 16]), + (r.kCopyRangeLut = [0, 8, 0, 8, 16, 0, 16, 8, 16]); + }, + "dec/streams.js": function (e, t, r) { + function n(e) { + (this.buffer = e), (this.pos = 0); + } + function o(e) { + (this.buffer = e), (this.pos = 0); + } + (n.prototype.read = function (e, t, r) { + this.pos + r > this.buffer.length && (r = this.buffer.length - this.pos); + for (var n = 0; n < r; n++) e[t + n] = this.buffer[this.pos + n]; + return (this.pos += r), r; + }), + (r.BrotliInput = n), + (o.prototype.write = function (e, t) { + if (this.pos + t > this.buffer.length) throw new Error("Output buffer is not large enough"); + return this.buffer.set(e.subarray(0, t), this.pos), (this.pos += t), t; + }), + (r.BrotliOutput = o); + }, + "dec/transform.js": function (e, t, r) { + function n(e, t, r) { + (this.prefix = new Uint8Array(e.length)), (this.transform = t), (this.suffix = new Uint8Array(r.length)); + for (var n = 0; n < e.length; n++) this.prefix[n] = e.charCodeAt(n); + for (var n = 0; n < r.length; n++) this.suffix[n] = r.charCodeAt(n); + } + function o(e, t) { + return e[t] < 192 ? (e[t] >= 97 && e[t] <= 122 && (e[t] ^= 32), 1) : e[t] < 224 ? ((e[t + 1] ^= 32), 2) : ((e[t + 2] ^= 5), 3); + } + var a = e("./dictionary"); + const i = 0, + s = 1, + d = 2, + l = 3, + u = 4, + f = 5, + c = 6, + h = 7, + p = 8, + w = 9, + m = 10, + b = 11, + y = 12, + g = 13, + v = 14, + A = 15, + U = 16, + x = 17, + E = 18, + k = 20; + var B = [ + new n("", i, ""), + new n("", i, " "), + new n(" ", i, " "), + new n("", y, ""), + new n("", m, " "), + new n("", i, " the "), + new n(" ", i, ""), + new n("s ", i, " "), + new n("", i, " of "), + new n("", m, ""), + new n("", i, " and "), + new n("", g, ""), + new n("", s, ""), + new n(", ", i, " "), + new n("", i, ", "), + new n(" ", m, " "), + new n("", i, " in "), + new n("", i, " to "), + new n("e ", i, " "), + new n("", i, '"'), + new n("", i, "."), + new n("", i, '">'), + new n("", i, "\n"), + new n("", l, ""), + new n("", i, "]"), + new n("", i, " for "), + new n("", v, ""), + new n("", d, ""), + new n("", i, " a "), + new n("", i, " that "), + new n(" ", m, ""), + new n("", i, ". "), + new n(".", i, ""), + new n(" ", i, ", "), + new n("", A, ""), + new n("", i, " with "), + new n("", i, "'"), + new n("", i, " from "), + new n("", i, " by "), + new n("", U, ""), + new n("", x, ""), + new n(" the ", i, ""), + new n("", u, ""), + new n("", i, ". The "), + new n("", b, ""), + new n("", i, " on "), + new n("", i, " as "), + new n("", i, " is "), + new n("", h, ""), + new n("", s, "ing "), + new n("", i, "\n\t"), + new n("", i, ":"), + new n(" ", i, ". "), + new n("", i, "ed "), + new n("", k, ""), + new n("", E, ""), + new n("", c, ""), + new n("", i, "("), + new n("", m, ", "), + new n("", p, ""), + new n("", i, " at "), + new n("", i, "ly "), + new n(" the ", i, " of "), + new n("", f, ""), + new n("", w, ""), + new n(" ", m, ", "), + new n("", m, '"'), + new n(".", i, "("), + new n("", b, " "), + new n("", m, '">'), + new n("", i, '="'), + new n(" ", i, "."), + new n(".com/", i, ""), + new n(" the ", i, " of the "), + new n("", m, "'"), + new n("", i, ". This "), + new n("", i, ","), + new n(".", i, " "), + new n("", m, "("), + new n("", m, "."), + new n("", i, " not "), + new n(" ", i, '="'), + new n("", i, "er "), + new n(" ", b, " "), + new n("", i, "al "), + new n(" ", b, ""), + new n("", i, "='"), + new n("", b, '"'), + new n("", m, ". "), + new n(" ", i, "("), + new n("", i, "ful "), + new n(" ", m, ". "), + new n("", i, "ive "), + new n("", i, "less "), + new n("", b, "'"), + new n("", i, "est "), + new n(" ", m, "."), + new n("", b, '">'), + new n(" ", i, "='"), + new n("", m, ","), + new n("", i, "ize "), + new n("", b, "."), + new n("\xc2\xa0", i, ""), + new n(" ", i, ","), + new n("", m, '="'), + new n("", b, '="'), + new n("", i, "ous "), + new n("", b, ", "), + new n("", m, "='"), + new n(" ", m, ","), + new n(" ", b, '="'), + new n(" ", b, ", "), + new n("", b, ","), + new n("", b, "("), + new n("", b, ". "), + new n(" ", b, "."), + new n("", b, "='"), + new n(" ", b, ". "), + new n(" ", m, '="'), + new n(" ", b, "='"), + new n(" ", m, "='"), + ]; + (r.kTransforms = B), + (r.kNumTransforms = B.length), + (r.transformDictionaryWord = function (e, t, r, n, i) { + var s, + d = B[i].prefix, + l = B[i].suffix, + u = B[i].transform, + f = u < y ? 0 : u - 11, + c = 0, + h = t; + f > n && (f = n); + for (var p = 0; p < d.length; ) e[t++] = d[p++]; + for (r += f, n -= f, u <= w && (n -= u), c = 0; c < n; c++) e[t++] = a.dictionary[r + c]; + if (((s = t - n), u === m)) o(e, s); + else if (u === b) + for (; n > 0; ) { + var g = o(e, s); + (s += g), (n -= g); + } + for (var v = 0; v < l.length; ) e[t++] = l[v++]; + return t - h; + }); + }, + "node_modules/base64-js/index.js": function (e, t, r) { + "use strict"; + function n(e) { + var t = e.length; + if (t % 4 > 0) throw new Error("Invalid string. Length must be a multiple of 4"); + return "=" === e[t - 2] ? 2 : "=" === e[t - 1] ? 1 : 0; + } + function o(e) { + return (3 * e.length) / 4 - n(e); + } + function a(e) { + var t, + r, + o, + a, + i, + s, + d = e.length; + (i = n(e)), (s = new f((3 * d) / 4 - i)), (o = i > 0 ? d - 4 : d); + var l = 0; + for (t = 0, r = 0; t < o; t += 4, r += 3) + (a = (u[e.charCodeAt(t)] << 18) | (u[e.charCodeAt(t + 1)] << 12) | (u[e.charCodeAt(t + 2)] << 6) | u[e.charCodeAt(t + 3)]), (s[l++] = (a >> 16) & 255), (s[l++] = (a >> 8) & 255), (s[l++] = 255 & a); + return ( + 2 === i + ? ((a = (u[e.charCodeAt(t)] << 2) | (u[e.charCodeAt(t + 1)] >> 4)), (s[l++] = 255 & a)) + : 1 === i && ((a = (u[e.charCodeAt(t)] << 10) | (u[e.charCodeAt(t + 1)] << 4) | (u[e.charCodeAt(t + 2)] >> 2)), (s[l++] = (a >> 8) & 255), (s[l++] = 255 & a)), + s + ); + } + function i(e) { + return l[(e >> 18) & 63] + l[(e >> 12) & 63] + l[(e >> 6) & 63] + l[63 & e]; + } + function s(e, t, r) { + for (var n, o = [], a = t; a < r; a += 3) (n = (e[a] << 16) + (e[a + 1] << 8) + e[a + 2]), o.push(i(n)); + return o.join(""); + } + function d(e) { + for (var t, r = e.length, n = r % 3, o = "", a = [], i = 16383, d = 0, u = r - n; d < u; d += i) a.push(s(e, d, d + i > u ? u : d + i)); + return ( + 1 === n + ? ((t = e[r - 1]), (o += l[t >> 2]), (o += l[(t << 4) & 63]), (o += "==")) + : 2 === n && ((t = (e[r - 2] << 8) + e[r - 1]), (o += l[t >> 10]), (o += l[(t >> 4) & 63]), (o += l[(t << 2) & 63]), (o += "=")), + a.push(o), + a.join("") + ); + } + (r.byteLength = o), (r.toByteArray = a), (r.fromByteArray = d); + for (var l = [], u = [], f = "undefined" != typeof Uint8Array ? Uint8Array : Array, c = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", h = 0, p = c.length; h < p; ++h) + (l[h] = c[h]), (u[c.charCodeAt(h)] = h); + (u["-".charCodeAt(0)] = 62), (u["_".charCodeAt(0)] = 63); + }, + }; + for (var r in t) t[r].folder = r.substring(0, r.lastIndexOf("/") + 1); + var n = function (e) { + var r = []; + return ( + (e = e.split("/").every(function (e) { + return ".." == e ? r.pop() : "." == e || "" == e || r.push(e); + }) + ? r.join("/") + : null), + e ? t[e] || t[e + ".js"] || t[e + "/index.js"] : null + ); + }, + o = function (e, t) { + return e ? n(e.folder + "node_modules/" + t) || o(e.parent, t) : null; + }, + a = function (e, t) { + var r = t.match(/^\//) ? null : e ? (t.match(/^\.\.?\//) ? n(e.folder + t) : o(e, t)) : n(t); + if (!r) throw "module not found: " + t; + return r.exports || ((r.parent = e), r(a.bind(null, r), r, (r.exports = {}))), r.exports; + }; + return a(null, e); + }, + decompress: function (e) { + this.exports || (this.exports = this.require("decompress.js")); + try { + return this.exports(e); + } catch (e) {} + }, + hasUnityMarker: function (e) { + var t = "UnityWeb Compressed Content (brotli)"; + if (!e.length) return !1; + var r = 1 & e[0] ? (14 & e[0] ? 4 : 7) : 1, + n = e[0] & ((1 << r) - 1), + o = 1 + ((Math.log(t.length - 1) / Math.log(2)) >> 3); + if (((commentOffset = (r + 1 + 2 + 1 + 2 + (o << 3) + 7) >> 3), 17 == n || commentOffset > e.length)) return !1; + for (var a = n + ((6 + (o << 4) + ((t.length - 1) << 6)) << r), i = 0; i < commentOffset; i++, a >>>= 8) if (e[i] != (255 & a)) return !1; + return String.fromCharCode.apply(null, e.subarray(commentOffset, commentOffset + t.length)) == t; + }, + }, + decompress: function (e, t) { + var r = this.gzip.hasUnityMarker(e) ? this.gzip : this.brotli.hasUnityMarker(e) ? this.brotli : this.identity; + if ( + (this.serverSetupWarningEnabled && + r != this.identity && + (console.log("You can reduce your startup time if you configure your web server to host .unityweb files using " + (r == this.gzip ? "gzip" : "brotli") + " compression."), (this.serverSetupWarningEnabled = !1)), + "function" != typeof t) + ) + return r.decompress(e); + if (!r.worker) { + var n = URL.createObjectURL( + new Blob( + [ + "this.require = ", + r.require.toString(), + "; this.decompress = ", + r.decompress.toString(), + "; this.onmessage = ", + function (e) { + var t = { id: e.data.id, decompressed: this.decompress(e.data.compressed) }; + postMessage(t, t.decompressed ? [t.decompressed.buffer] : []); + }.toString(), + "; postMessage({ ready: true });", + ], + { type: "text/javascript" } + ) + ); + (r.worker = new Worker(n)), + (r.worker.onmessage = function (e) { + return e.data.ready ? void URL.revokeObjectURL(n) : (this.callbacks[e.data.id](e.data.decompressed), void delete this.callbacks[e.data.id]); + }), + (r.worker.callbacks = {}), + (r.worker.nextCallbackId = 0); + } + var o = r.worker.nextCallbackId++; + (r.worker.callbacks[o] = t), r.worker.postMessage({ id: o, compressed: e }, [e.buffer]); + }, + serverSetupWarningEnabled: !0, + }, +}; diff --git a/slope/Build/slope_27Sept.data.unityweb b/slope/Build/slope_27Sept.data.unityweb new file mode 100644 index 00000000..cf884251 Binary files /dev/null and b/slope/Build/slope_27Sept.data.unityweb differ diff --git a/slope/Build/slope_27Sept.json b/slope/Build/slope_27Sept.json new file mode 100644 index 00000000..7aa09efe --- /dev/null +++ b/slope/Build/slope_27Sept.json @@ -0,0 +1,15 @@ +{ + "companyName": "Slope", + "productName": "Slope", + "dataUrl": "slope_27Sept.data.unityweb", + "wasmCodeUrl": "slope_27Sept.wasm.code.unityweb", + "wasmFrameworkUrl": "slope_27Sept.wasm.framework.unityweb", + "asmCodeUrl": "slope_27Sept.asm.code.unityweb", + "asmMemoryUrl": "slope_27Sept.asm.memory.unityweb", + "asmFrameworkUrl": "slope_27Sept.asm.framework.unityweb", + "TOTAL_MEMORY": 268435456, + "graphicsAPI": ["WebGL 2.0", "WebGL 1.0"], + "webglContextAttributes": {"preserveDrawingBuffer": false}, + "splashScreenStyle": "Dark", + "backgroundColor": "#231F20" + } \ No newline at end of file diff --git a/slope/Build/slope_27Sept.wasm.code.unityweb b/slope/Build/slope_27Sept.wasm.code.unityweb new file mode 100644 index 00000000..79f7b3a3 Binary files /dev/null and b/slope/Build/slope_27Sept.wasm.code.unityweb differ diff --git a/slope/Build/slope_27Sept.wasm.framework.unityweb b/slope/Build/slope_27Sept.wasm.framework.unityweb new file mode 100644 index 00000000..f8cff4d2 Binary files /dev/null and b/slope/Build/slope_27Sept.wasm.framework.unityweb differ diff --git a/slope/TemplateData/y8-logo.png b/slope/TemplateData/y8-logo.png new file mode 100644 index 00000000..253aa312 Binary files /dev/null and b/slope/TemplateData/y8-logo.png differ diff --git a/slope/TemplateData56/UnityProgress.js b/slope/TemplateData56/UnityProgress.js new file mode 100644 index 00000000..de38bcc0 --- /dev/null +++ b/slope/TemplateData56/UnityProgress.js @@ -0,0 +1,126 @@ +const rootPath = "TemplateData56"; +function UnityProgress(gameInstance, progress) { + if (!gameInstance.Module) { + return; + } + if (!gameInstance.logo) { + gameInstance.logo = document.createElement("div"); + gameInstance.logo.className = "logo " + gameInstance.Module.splashScreenStyle; + gameInstance.container.appendChild(gameInstance.logo); + } + if (!gameInstance.progress) { + gameInstance.progress = document.createElement("div"); + gameInstance.progress.className = "progress " + gameInstance.Module.splashScreenStyle; + gameInstance.progress.empty = document.createElement("div"); + gameInstance.progress.empty.className = "empty"; + gameInstance.progress.appendChild(gameInstance.progress.empty); + gameInstance.progress.full = document.createElement("div"); + gameInstance.progress.full.className = "full"; + gameInstance.progress.appendChild(gameInstance.progress.full); + gameInstance.container.appendChild(gameInstance.progress); + gameInstance.textProgress = document.createElement("div"); + gameInstance.textProgress.className = "text"; + gameInstance.container.appendChild(gameInstance.textProgress); + } + gameInstance.progress.full.style.width = 100 * progress + "%"; + gameInstance.progress.empty.style.width = 100 * (1 - progress) + "%"; + gameInstance.textProgress.innerHTML = "Loading: " + Math.floor(progress * 100) + "%"; + if (progress == 1) { + gameInstance.textProgress.innerHTML = 'Running... '; + } + if (progress == "complete") { + gameInstance.logo.style.display = "none"; + gameInstance.progress.style.display = "none"; + gameInstance.textProgress.style.display = "none"; + const event = new Event("removeSoundOverlay"); + document.dispatchEvent(event); + } +} +window.Game = (function () { + var Game = function () { + this.registerEvents(); + }; + Game.prototype.registerEvents = function () { + var _this = this; + window.addEventListener( + "keydown", + function (e) { + if ([8, 37, 38, 39, 40].indexOf(e.keyCode) > -1) { + e.preventDefault(); + } + }, + false + ); + document.onmousedown = function () { + window.focus(); + }; + document.addEventListener( + "DOMContentLoaded", + function () { + _this.resize(); + }, + false + ); + window.addEventListener( + "resize", + function () { + setTimeout(function () { + _this.resize(); + }, 1000); + }, + false + ); + }; + Game.prototype.getQueryVariable = function (variable) { + var query = window.location.search.substring(1); + var vars = query.split("&"); + for (var i = 0; i < vars.length; i++) { + var pair = vars[i].split("="); + if (pair[0] == variable) { + return pair[1]; + } + } + return false; + }; + Game.prototype.resize = function () { + var ratioTolerant = this.getQueryVariable("ratio_tolerant"); + if (ratioTolerant == false || this.fullscreen()) { + return; + } + document.getElementsByTagName("body")[0].style.overflow = "hidden"; + var gameContainer = document.getElementById("gameContainer") || document.getElementById("unityContainer"); + var canvas = document.getElementById("#canvas"); + var gameSizeRatio = gameContainer.offsetWidth / gameContainer.offsetHeight; + var maxHeight = this.maxHeight(); + var maxWidth = window.innerWidth; + var windowSizeRatio = maxWidth / maxHeight; + var newStyle = { width: gameContainer.offsetWidth, height: gameContainer.offsetHeight }; + if (ratioTolerant == "true") { + newStyle = { width: maxWidth, height: maxHeight }; + } else if (ratioTolerant == "false") { + if (gameSizeRatio > windowSizeRatio) { + newStyle = { width: maxWidth, height: maxWidth / gameSizeRatio }; + } else { + newStyle = { width: maxHeight * gameSizeRatio, height: maxHeight }; + } + } + this.updateStyle(gameContainer, newStyle); + if (canvas) { + this.updateStyle(canvas, newStyle); + } + }; + Game.prototype.maxHeight = function () { + return window.innerHeight - 43; + }; + Game.prototype.updateStyle = function (element, size) { + element.setAttribute("width", size.width); + element.setAttribute("height", size.height); + element.style.width = size.width + "px"; + element.style.height = size.height + "px"; + }; + Game.prototype.fullscreen = function () { + return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement; + }; + return Game; +})(); +new Game(); \ No newline at end of file diff --git a/slope/TemplateData56/gears.gif b/slope/TemplateData56/gears.gif new file mode 100644 index 00000000..18743478 Binary files /dev/null and b/slope/TemplateData56/gears.gif differ diff --git a/slope/TemplateData56/style.css b/slope/TemplateData56/style.css new file mode 100644 index 00000000..b3a3f982 --- /dev/null +++ b/slope/TemplateData56/style.css @@ -0,0 +1 @@ +.webgl-content *{border:0;margin:0;padding:0}.webgl-content{position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.webgl-content .logo,.progress,.text{position:absolute;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.webgl-content .logo{background:url('logo.png') no-repeat center/contain;width:154px;height:130px}.webgl-content .progress{height:18px;width:141px;margin-top:80px}.webgl-content .progress .empty{background:url('progressEmpty.Light.png') no-repeat right/cover;float:right;width:100%;height:100%;display:inline-block}.webgl-content .progress .full{background:url('progressFull.Light.png') no-repeat left/cover;float:left;width:0%;height:100%;display:inline-block}.webgl-content .text{margin-top:110px;color:#fff;font-weight:700}.webgl-content .spinner{vertical-align:middle}.webgl-content .logo.Dark{background-image:url(../TemplateData/y8-logo.png);width:206px;height:130px;margin:0 auto;background-size:206px 130px}.webgl-content .progress.Dark .empty{background:#000}.webgl-content .progress.Dark .full{background:red}.webgl-content .footer{margin-top:5px;height:38px;line-height:38px;font-family:Helvetica,Verdana,Arial,sans-serif;font-size:18px;background:#fff}.webgl-content .footer .webgl-logo,.title,.fullscreen{height:100%;display:inline-block;background:transparent center no-repeat}.webgl-content .footer .webgl-logo{background-image:url(webgl-logo.png);width:204px;float:left}.webgl-content .footer .title{margin-right:10px;float:right}.webgl-content .footer .fullscreen{background-image:url(fullscreen.png);width:38px;float:right}.webgl-content #gameContainer{background:#4d4d4d!important}.webgl-content .footer{background:#222;font-family:Helvetica,Verdana,Arial,sans-serif;height:40px;line-height:40px;margin:0}.webgl-content .footer .webgl-logo,.title,.fullscreen{background:transparent center no-repeat;display:inline-block;height:100%}.webgl-content .footer .unity,.webgl-content .footer .webgl{box-sizing:border-box;background-position:10px center;background-repeat:no-repeat;background-size:46px 18px;height:40px;float:left;padding:0 10px;width:66px}.webgl-content .footer .unity.enable{background-image:url(unity-enable.png)}.webgl-content .footer .unity.disable{background-image:url(unity-disable.png)}.webgl-content .footer .webgl.enable{background-image:url(webgl-enable.png)}.webgl-content .footer .webgl.disable{background-image:url(webgl-disable.png)}.webgl-content .footer .fullscreen{background-image:url(maximize-icon.png);background-position:10px center;background-repeat:no-repeat;background-size:16px;box-sizing:border-box;color:#fff;float:right;font-size:12px;padding:0 10px 0 36px;width:auto}.webgl-content .footer .unity.disable:hover,.webgl-content .footer .webgl.disable:hover,.webgl-content .footer .fullscreen:hover{background-color:#3e3e3e;cursor:pointer} diff --git a/slope/index.html b/slope/index.html new file mode 100644 index 00000000..c71e9d6b --- /dev/null +++ b/slope/index.html @@ -0,0 +1,74 @@ + + + + + + + Slope | 3kh0 + + + + + + + + + + + + + +
+
+
+ + + diff --git a/slope/shared/gamebreak/gamebreak.js b/slope/shared/gamebreak/gamebreak.js new file mode 100644 index 00000000..5b1abe16 --- /dev/null +++ b/slope/shared/gamebreak/gamebreak.js @@ -0,0 +1 @@ +function InitExternEval(){} \ No newline at end of file diff --git a/slope/shared/lib.js b/slope/shared/lib.js new file mode 100644 index 00000000..deb4c9e8 --- /dev/null +++ b/slope/shared/lib.js @@ -0,0 +1,89 @@ +window.BrowserDetector = (function () { + function detect() { + var ua = navigator.userAgent; + var tem; + var M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+\.\d+)/i) || []; + if (/trident/i.test(M[1])) { + tem = /\brv[ :]+(\d+\.\d+)/g.exec(ua) || []; + return { name: "Internet Explorer", version: tem[1] || "" }; + } + if (/(coc_coc_browser|edge(?=\/))\/?\s*(\d+\.\d+)/i.test(ua)) { + tem = ua.match(/(coc_coc_browser|edge(?=\/))\/?\s*(\d+)\.\d+/i); + if (tem != null) { + return { name: tem[1] == "Edge" ? tem[1] : "CocCoc", version: tem[2] }; + } + } + if (M[1] === "Chrome") { + tem = ua.match(/\bOPR\/(\d+\.\d+)/); + if (tem != null) { + return { name: "Opera", version: tem[1] }; + } + } + M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, "-?"]; + if ((tem = ua.match(/version\/(\d+\.\d+)/i)) != null) { + M.splice(1, 1, tem[1]); + } + if (M[0] == "MSIE") { + M[0] = "Internet Explorer"; + } + return { name: M[0], version: M[1] }; + } + var detect = detect(); + return { + name: detect.name, + version: detect.version, + msie: detect.name == "Internet Explorer", + msedge: detect.name == "Edge", + coccoc: detect.name == "CocCoc", + firefox: detect.name == "Firefox", + safari: detect.name == "Safari", + chrome: detect.name == "Chrome", + opera: detect.name == "Opera", + }; +})(); +function enableSound() { + return; +} +!(function () { + if (window.UnityLoader) { + if (window.UnityLoader.compatibilityCheck) { + window.UnityLoader.compatibilityCheck = function (element, callback, errCallback) { + callback(); + }; + } + } + if (!(BrowserDetector.chrome && BrowserDetector.version >= 66)) { + return; + } + function buildSoundOverlay() { + const overlay = document.createElement("div"); + overlay.classList.add("sound-overlay"); + overlay.setAttribute("id", "sound-overlay"); + return overlay; + } + function buildSoundText() { + const textNode = document.createTextNode("Click here to enable sound!"); + const textSpan = document.createElement("span"); + textSpan.classList.add("sound-text"); + textSpan.appendChild(textNode); + return textSpan; + } + const soundOverlay = buildSoundOverlay(); + soundOverlay.appendChild(buildSoundText()); + document.addEventListener("DOMContentLoaded", function () { + var root = document.getElementsByClassName("webgl-content")[0]; + if (!root) { + root = document.getElementsByClassName("template-wrap")[0]; + } + root.appendChild(soundOverlay); + }); + "click removeSoundOverlay".split(" ").forEach((e) => + document.addEventListener( + e, + function () { + soundOverlay.style.display = "none"; + }, + false + ) + ); +})(); \ No newline at end of file diff --git a/slope/shared/style.css b/slope/shared/style.css new file mode 100644 index 00000000..d886bd56 --- /dev/null +++ b/slope/shared/style.css @@ -0,0 +1 @@ +.sound-overlay{z-index:99999;position:absolute;top:0;left:0;width:100%;height:100%;text-align:center;opacity:.9;background:#000}.sound-text{position:relative;top:50%;transform:translateY(-50%);color:#fff;font-size:20px;text-shadow:1px 0 0 #000,0 -1px 0 #000,0 1px 0 #000,-1px 0 0 #000}.redirect-overlay{z-index:99999;position:absolute;top:0;left:0;width:100%;height:100%;text-align:center;background:#000}.redirect-text{position:relative;top:50%;transform:translateY(-50%);color:#fff;font-size:20px;text-shadow:1px 0 0 #000,0 -1px 0 #000,0 1px 0 #000,-1px 0 0 #000}.redirect-text a{color:red}.link-to-y8-url{height:40px;float:left;padding:0 10px;color:#fff;font-size:18px}.link-to-y8-url a{color:red}.game-name{padding:0 10px;margin:0;color:#fff;font-size:18px;display:inline-block;font-weight:400} \ No newline at end of file diff --git a/slope/slope4.jpeg b/slope/slope4.jpeg new file mode 100644 index 00000000..96d18ffd Binary files /dev/null and b/slope/slope4.jpeg differ diff --git a/thisistheonlylevel/index.html b/thisistheonlylevel/index.html new file mode 100644 index 00000000..a5b10129 --- /dev/null +++ b/thisistheonlylevel/index.html @@ -0,0 +1,24 @@ + + + + This Is The Only Level + + + +
+ + + + diff --git a/thisistheonlylevel/logo.png b/thisistheonlylevel/logo.png new file mode 100644 index 00000000..d1333a60 Binary files /dev/null and b/thisistheonlylevel/logo.png differ diff --git a/thisistheonlylevel/thisistheonlylevel.swf b/thisistheonlylevel/thisistheonlylevel.swf new file mode 100644 index 00000000..1023065b Binary files /dev/null and b/thisistheonlylevel/thisistheonlylevel.swf differ