PageSourceSearch

https://www.ral-logolizenz-warentest.de/fileadmin/Resources/Public/Js/libs/addtohomescreen.js

js ral-logolizenz-warentest.de collected 2026-10-09 17:12:23 UTC 30,805 bytes, 710 lines download raw bytes

1/* Add to Homescreen v3.2.1 ~ (c) 2015 Matteo Spinelli ~ @license: http://cubiq.org/license */
2(function (window, document) {
3/*
4       _   _ _____     _____
5 ___ _| |_| |_   _|___|  |  |___ _____ ___ ___ ___ ___ ___ ___ ___
6| .'| . | . | | | | . |     | . |     | -_|_ -|  _|  _| -_| -_|   |
7|__,|___|___| |_| |___|__|__|___|_|_|_|___|___|___|_| |___|___|_|_|
8                              by Matteo Spinelli ~ http://cubiq.org
9*/
10
11// Check for addEventListener browser support (prevent errors in IE<9)
12var _eventListener = 'addEventListener' in window;
13
14// Check if document is loaded, needed by autostart
15var _DOMReady = false;
16if ( document.readyState === 'complete' ) {
17	_DOMReady = true;
18} else if ( _eventListener ) {
19	window.addEventListener('load', loaded, false);
20}
21
22function loaded () {
23	window.removeEventListener('load', loaded, false);
24	_DOMReady = true;
25}
26
27// regex used to detect if app has been added to the homescreen
28var _reSmartURL = /\/ath(\/)?$/;
29var _reQueryString = /([\?&]ath=[^&]*$|&ath=[^&]*(&))/;
30
31// singleton
32var _instance;
33function ath (options) {
34	_instance = _instance || new ath.Class(options);
35
36	return _instance;
37}
38
39// message in all supported languages
40ath.intl = {
41	de_de: {
42		ios: 'Um diese Web-App zum Home-Bildschirm hinzuzufügen, tippen Sie auf %icon und dann <strong>Zum Home-Bildschirm</strong>.',
43		android: 'Um diese Web-App zum Home-Bildschirm hinzuzufügen, öffnen Sie das Menü und tippen dann auf <strong>Zum Startbildschirm hinzufügen</strong>. <small>Wenn Ihr Gerät eine Menütaste hat, lässt sich das Browsermenü über diese öffnen. Ansonsten tippen Sie auf <span class="ath-action-icon">icon</span>.</small>',
44	},
45
46	da_dk: {
47		ios: 'For at tilføje denne web app til hjemmeskærmen: Tryk %icon og derefter <strong>Føj til hjemmeskærm</strong>.',
48		android: 'For at tilføje denne web app til hjemmeskærmen, åbn browser egenskaber menuen og tryk på <strong>Føj til hjemmeskærm</strong>. <small>Denne menu kan tilgås ved at trykke på menu knappen, hvis din enhed har en, eller ved at trykke på det øverste højre menu ikon <span class="ath-action-icon">icon</span>.</small>',
49	},
50
51	en_us: {
52		ios: 'To add this web app to the home screen: tap %icon and then <strong>Add to Home Screen</strong>.',
53		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
54	},
55
56	da_dk: {
57		ios: 'For at tilføje denne web app til din startskærm: tryk på %icon og vælg <strong>Føj til hjemmeskærm</strong>.',
58		android: 'For at tilføje denne web app til din startskærm, åben browserens værktøjsmenuog tryk på <strong>Tilføj til hjemmeskærm</strong>. <small>Menuen kan aktiveres ved at trykke på den fysiske menuknap hvis din enhed har en, eller ved at trykke på menuikonet i øverste højre hjørne <span class="ath-action-icon">icon</span>.</small>',
59	},
60
61	es_es: {
62		ios: 'Para añadir esta aplicación web a la pantalla de inicio: pulsa %icon y selecciona <strong>Añadir a pantalla de inicio</strong>.',
63		android: 'Para añadir esta aplicación web a la pantalla de inicio, abre las opciones y pulsa <strong>Añadir a pantalla inicio</strong>. <small>El menú se puede acceder pulsando el botón táctil en caso de tenerlo, o bien el icono de la parte superior derecha de la pantalla <span class="ath-action-icon">icon</span>.</small>',
64	},
65
66	fi_fi: {
67		ios: 'Liitä tämä sovellus kotivalikkoon: klikkaa %icon ja tämän jälkeen <strong>Lisää kotivalikkoon</strong>.',
68		android: 'Lisätäksesi tämän sovelluksen aloitusnäytölle, avaa selaimen valikko ja klikkaa tähti -ikonia tai <strong>Lisää aloitusnäytölle tekstiä</strong>. <small>Valikkoon pääsee myös painamalla menuvalikkoa, jos laitteessasi on sellainen tai koskettamalla oikealla yläkulmassa menu ikonia <span class="ath-action-icon">icon</span>.</small>',
69	},
70
71	fr_fr: {
72		ios: 'Pour ajouter cette application web sur l\'écran d\'accueil : Appuyez %icon et sélectionnez <strong>Ajouter sur l\'écran d\'accueil</strong>.',
73		android: 'Pour ajouter cette application web sur l\'écran d\'accueil : Appuyez sur le bouton "menu", puis sur <strong>Ajouter sur l\'écran d\'accueil</strong>. <small>Le menu peut-être accessible en appyant sur le bouton "menu" du téléphone s\'il en possède un <i class="fa fa-bars"></i>. Sinon, il se trouve probablement dans la coin supérieur droit du navigateur %icon.</small>',
74	},
75
76	he_il: {
77		ios: '<span dir="rtl">להוספת האפליקציה למסך הבית: ללחוץ על %icon ואז <strong>הוסף למסך הבית</strong>.</span>',
78		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
79	},
80
81	it_it: {
82		ios: 'Per aggiungere questa web app alla schermata iniziale: premi %icon e poi <strong>Aggiungi a Home</strong>.',
83		android: 'Per aggiungere questa web app alla schermata iniziale, apri il menu opzioni del browser e premi su <strong>Aggiungi alla homescreen</strong>. <small>Puoi accedere al menu premendo il pulsante hardware delle opzioni se la tua device ne ha uno, oppure premendo l\'icona <span class="ath-action-icon">icon</span> in alto a destra.</small>',
84	},
85
86	ja_jp: {
87		ios: 'このウェプアプリをホーム画面に追加するために%iconを押して<strong>ホーム画面に追加</strong>。',
88		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
89 	},
90
91	ko_kr: {
92		ios: '홈 화면에 바로가기 생성: %icon 을 클릭한 후 <strong>홈 화면에 추가</strong>.',
93		android: '브라우저 옵션 메뉴의 <string>홈 화면에 추가</string>를 클릭하여 홈화면에 바로가기를 생성할 수 있습니다. <small>옵션 메뉴는 장치의 메뉴 버튼을 누르거나 오른쪽 상단의 메뉴 아이콘 <span class="ath-action-icon">icon</span>을 클릭하여 접근할 수 있습니다.</small>'
94	},
95
96	nb_no: {
97		ios: 'For å installere denne appen på hjem-skjermen: trykk på %icon og deretter <strong>Legg til på Hjem-skjerm</strong>.',
98		android: 'For å legge til denne webappen på startsiden åpner en nettlesermenyen og velger <strong>Legg til på startsiden</strong>. <small>Menyen åpnes ved å trykke på den fysiske menyknappen hvis enheten har det, eller ved å trykke på menyikonet øverst til høyre <span class="ath-action-icon">icon</span>.</small>',
99	},
100
101	pt_br: {
102		ios: 'Para adicionar este app à tela de início: clique %icon e então <strong>Tela de início</strong>.',
103		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
104	},
105
106	pt_pt: {
107		ios: 'Para adicionar esta app ao ecrã principal: clique %icon e depois <strong>Ecrã principal</strong>.',
108		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
109	},
110
111	nl_nl: {
112		ios: 'Om deze webapp op je telefoon te installeren, klik op %icon en dan <strong>Zet in beginscherm</strong>.',
113		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
114	},
115
116	ru_ru: {
117		ios: 'Чтобы добавить этот сайт на свой домашний экран, нажмите на иконку %icon и затем <strong>На экран "Домой"</strong>.',
118		android: 'Чтобы добавить сайт на свой домашний экран, откройте меню браузера и нажмите на <strong>Добавить на главный экран</strong>. <small>Меню можно вызвать, нажав на кнопку меню вашего телефона, если она есть. Или найдите иконку сверху справа <span class="ath-action-icon">иконка</span>.</small>',
119	},
120
121	sv_se: {
122		ios: 'För att lägga till denna webbapplikation på hemskärmen: tryck på %icon och därefter <strong>Lägg till på hemskärmen</strong>.',
123		android: 'För att lägga till den här webbappen på hemskärmen öppnar du webbläsarens alternativ-meny och väljer <strong>Lägg till på startskärmen</strong>. <small>
123Man hittar menyn genom att trycka på hårdvaruknappen om din enhet har en sådan, eller genom att trycka på menyikonen högst upp till höger <span class="ath-action-icon">icon</span>.</small>',
124	},
125
126	zh_cn: {
127		ios: '如要把应用程式加至主屏幕,请点击%icon, 然后<strong>加至主屏幕</strong>',
128		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
129	},
130
131	zh_tw: {
132		ios: '如要把應用程式加至主屏幕, 請點擊%icon, 然後<strong>加至主屏幕</strong>.',
133		android: 'To add this web app to the home screen open the browser option menu and tap on <strong>Add to homescreen</strong>. <small>The menu can be accessed by pressing the menu hardware button if your device has one, or by tapping the top right menu icon <span class="ath-action-icon">icon</span>.</small>',
134	},
135
136};
137
138// Add 2 characters language support (Android mostly)
139for ( var lang in ath.intl ) {
140	ath.intl[lang.substr(0, 2)] = ath.intl[lang];
141}
142
143// default options
144ath.defaults = {
145	appID: 'org.cubiq.addtohome',		// local storage name (no need to change)
146	fontSize: 15,				// base font size, used to properly resize the popup based on viewport scale factor
147	debug: false,				// override browser checks
148	logging: false,				// log reasons for showing or not showing to js console; defaults to true when debug is true
149	modal: false,				// prevent further actions until the message is closed
150	mandatory: false,			// you can't proceed if you don't add the app to the homescreen
151	autostart: true,			// show the message automatically
152	skipFirstVisit: false,		// show only to returning visitors (ie: skip the first time you visit)
153	startDelay: 1,				// display the message after that many seconds from page load
154	lifespan: 15,				// life of the message in seconds
155	displayPace: 1440,			// minutes before the message is shown again (0: display every time, default 24 hours)
156	maxDisplayCount: 0,			// absolute maximum number of times the message will be shown to the user (0: no limit)
157	icon: true,					// add touch icon to the message
158	message: '',				// the message can be customized
159	validLocation: [],			// list of pages where the message will be shown (array of regexes)
160	onInit: null,				// executed on instance creation
161	onShow: null,				// executed when the message is shown
162	onRemove: null,				// executed when the message is removed
163	onAdd: null,				// when the application is launched the first time from the homescreen (guesstimate)
164	onPrivate: null,			// executed if user is in private mode
165	privateModeOverride: false,	// show the message even in private mode (very rude)
166	detectHomescreen: false		// try to detect if the site has been added to the homescreen (false | true | 'hash' | 'queryString' | 'smartURL')
167};
168
169// browser info and capability
170var _ua = window.navigator.userAgent;
171
172var _nav = window.navigator;
173_extend(ath, {
174	hasToken: document.location.hash == '#ath' || _reSmartURL.test(document.location.href) || _reQueryString.test(document.location.search),
175	isRetina: window.devicePixelRatio && window.devicePixelRatio > 1,
176	isIDevice: (/iphone|ipod|ipad/i).test(_ua),
177	isMobileChrome: _ua.indexOf('Android') > -1 && (/Chrome\/[.0-9]*/).test(_ua) && _ua.indexOf("Version") == -1,
178	isMobileIE: _ua.indexOf('Windows Phone') > -1,
179	language: _nav.language && _nav.language.toLowerCase().replace('-', '_') || ''
180});
181
182// falls back to en_us if language is unsupported
183ath.language = ath.language && ath.language in ath.intl ? ath.language : 'en_us';
184
185ath.isMobileSafari = ath.isIDevice && _ua.indexOf('Safari') > -1 && _ua.indexOf('CriOS') < 0;
186ath.OS = ath.isIDevice ? 'ios' : ath.isMobileChrome ? 'android' : ath.isMobileIE ? 'windows' : 'unsupported';
187
188ath.OSVersion = _ua.match(/(OS|Android) (\d+[_\.]\d+)/);
189ath.OSVersion = ath.OSVersion && ath.OSVersion[2] ? +ath.OSVersion[2].replace('_', '.') : 0;
190
191ath.isStandalone = 'standalone' in window.navigator && window.navigator.standalone;
192ath.isTablet = (ath.isMobileSafari && _ua.indexOf('iPad') > -1) || (ath.isMobileChrome && _ua.indexOf('Mobile') < 0);
193
194ath.isCompatible = (ath.isMobileSafari && ath.OSVersion >= 6) || ath.isMobileChrome;	// TODO: add winphone
195
196var _defaultSession = {
197	lastDisplayTime: 0,			// last time we displayed the message
198	returningVisitor: false,	// is this the first time you visit
199	displayCount: 0,			// number of times the message has been shown
200	optedout: false,			// has the user opted out
201	added: false				// has been actually added to the homescreen
202};
203
204ath.removeSession = function (appID) {
205	try {
206		localStorage.removeItem(appID || ath.defaults.appID);
207	} catch (e) {
208		// we are most likely in private mode
209	}
210};
211
212ath.doLog = function (logStr) {
213	if ( this.options.logging ) {
214		console.log(logStr);
215	}
216};
217
218ath.Class = function (options) {
219	// class methods
220	this.doLog = ath.doLog;
221
222	// merge default options with user config
223	this.options = _extend({}, ath.defaults);
224	_extend(this.options, options);
225	// override defaults that are dependent on each other
226	if ( options.debug && (typeof options.logging === "undefined") ) {
227		this.options.logging = true;
228	}
229
230	// IE<9 so exit (I hate you, really)
231	if ( !_eventListener ) {
232		return;
233	}
234
235	// normalize some options
236	this.options.mandatory = this.options.mandatory && ( 'standalone' in window.navigator || this.options.debug );
237	this.options.modal = this.options.modal || this.options.mandatory;
238	if ( this.options.mandatory ) {
239		this.options.startDelay = -0.5;		// make the popup hasty
240	}
241	this.options.detectHomescreen = this.options.detectHomescreen === true ? 'hash' : this.options.detectHomescreen;
242
243	// setup the debug environment
244	if ( this.options.debug ) {
245		ath.isCompatible = true;
246		ath.OS = typeof this.options.debug == 'string' ? this.options.debug : ath.OS == 'unsupported' ? 'android' : ath.OS;
247		ath.OSVersion = ath.OS == 'ios' ? '8' : '4';
248	}
249
250	// the element the message will be appended to
251	this.container = document.documentElement;
252
253	// load session
254	this.session = this.getItem(this.options.appID);
255	this.session = this.session ? JSON.parse(this.session) : undefined;
256
257	// user most likely came from a direct link containing our token, we don't need it and we remove it
258	if ( ath.hasToken && ( !ath.isCompatible || !this.session ) ) {
259		ath.hasToken = false;
260		_removeToken();
261	}
262
263	// the device is not supported
264	if ( !ath.isCompatible ) {
265 		this.doLog("Add to homescreen: not displaying callout because device not supported");
266		return;
267	}
268
269	this.session = this.session || _defaultSession;
270
271	// check if we can use the local storage
272	try {
273		localStorage.setItem(this.options.appID, JSON.stringify(this.session));
274		ath.hasLocalStorage = true;
275	} catch (e) {
276		// we are most likely in private mode
277		ath.hasLocalStorage = false;
278
279		if ( this.options.onPrivate ) {
280			this.options.onPrivate.call(this);
281		}
282	}
283
284	// check if this is a valid location
285	var isValidLocation = !this.options.validLocation.length;
286	for ( var i = this.options.validLocation.length; i--; ) {
287		if ( this.options.validLocation[i].test(document.location.href) ) {
288			isValidLocation = true;
289			break;
290		}
291	}
292
293	// check compatibility with old versions of add to homescreen. Opt-out if an old session is found
294	if ( this.getItem('addToHome') ) {
295		this.optOut();
296	}
297
298	// critical errors:
299	if ( this.session.optedout ) {
300		this.doLog("Add to homescreen: not displaying callout because user opted out");
301		return;
302	}
303	if ( this.session.added ) {
304		this.doLog("Add to homescreen: not displaying callout because already added to the homescreen");
305		return;
306	}
307	if ( !isValidLocation ) {
308		this.doLog("Add to homescreen: not displaying callout because not a valid location");
309		return;
310	}
311
312	// check if the app is in stand alone mode
313	if ( ath.isStandalone ) {
314		// execute the onAdd event if we haven't already
315		if ( !this.session.added ) {
316			this.session.added = true;
317			this.updateSession();
318
319			if ( this.options.onAdd && ath.hasLocalStorage ) {	// double check on localstorage to avoid multiple calls to the custom event
320				this.options.onAdd.call(this);
321			}
322		}
323
324		this.doLog("Add to homescreen: not displaying callout because in standalone mode");
325		return;
326	}
327
328	// (try to) check if the page has been added to the homescreen
329	if ( this.options.detectHomescreen ) {
330		// the URL has the token, we are likely coming from the homescreen
331		if ( ath.hasToken ) {
332			_removeToken();		// we don't actually need the token anymore, we remove it to prevent redistribution
333
334			// this is called the first time the user opens the app from the homescreen
335			if ( !this.session.added ) {
336				this.session.added = true;
337				this.updateSession();
338
339				if ( this.options.onAdd && ath.hasLocalStorage ) {	// double check on localstorage to avoid multiple calls to the custom event
340					this.options.onAdd.call(this);
341				}
342			}
343
344			this.doLog("Add to homescreen: not displaying callout because URL has token, so we are likely coming from homescreen");
345			return;
346		}
347
348		// URL doesn't have the token, so add it
349		if ( this.options.detectHomescreen == 'hash' ) {
350			history.replaceState('', window.document.title, document.location.href + '#ath');
351		} else if ( this.options.detectHomescreen == 'smartURL' ) {
352			history.replaceState('', window.document.title, document.location.href.replace(/(\/)?$/, '/ath$1'));
353		} else {
354			history.replaceState('', window.document.title, document.location.href + (document.location.search ? '&' : '?' ) + 'ath=');
355		}
356	}
357
358	// check if this is a returning visitor
359	if ( !this.session.returningVisitor ) {
360		this.session.returningVisitor = true;
361		this.updateSession();
362
363		// we do not show the message if this is your first visit
364		if ( this.options.skipFirstVisit ) {
365			this.doLog("Add to homescreen: not displaying callout because skipping first visit");
366			return;
367		}
368	}
369
370	// we do no show the message in private mode
371	if ( !this.options.privateModeOverride && !ath.hasLocalStorage ) {
372		this.doLog("Add to homescreen: not displaying callout because browser is in private mode");
373		return;
374	}
375
376	// all checks passed, ready to display
377	this.ready = true;
378
379	if ( this.options.onInit ) {
380		this.options.onInit.call(this);
381	}
382
383	if ( this.options.autostart ) {
384		this.doLog("Add to homescreen: autostart displaying callout");
385		this.show();
386	}
387};
388
389ath.Class.prototype = {
390	// event type to method conversion
391	events: {
392		load: '_delayedShow',
393		error: '_delayedShow',
394		orientationchange: 'resize',
395		resize: 'resize',
396		scroll: 'resize',
397		click: 'remove',
398		touchmove: '_preventDefault',
399		transitionend: '_removeElements',
400		webkitTransitionEnd: '_removeElements',
401		MSTransitionEnd: '_removeElements'
402	},
403
404	handleEvent: function (e) {
405		var type = this.events[e.type];
406		if ( type ) {
407			this[type](e);
408		}
409	},
410
411	show: function (force) {
412		// in autostart mode wait for the document to be ready
413		if ( this.options.autostart && !_DOMReady ) {
414			setTimeout(this.show.bind(this), 50);
415			// we are not displaying callout because DOM not ready, but don't log that because
416			// it would log too frequently
417			return;
418		}
419
420		// message already on screen
421		if ( this.shown ) {
422			this.doLog("Add to homescreen: not displaying callout because already shown on screen");
423			return;
424		}
425
426		var now = Date.now();
427		var lastDisplayTime = this.session.lastDisplayTime;
428
429		if ( force !== true ) {
430			// this is needed if autostart is disabled and you programmatically call the show() method
431			if ( !this.ready ) {
432				this.doLog("Add to homescreen: not displaying callout because not ready");
433				return;
434			}
435
436			// we obey the display pace (prevent the message to popup too often)
437			if ( now - lastDisplayTime < this.options.displayPace * 60000 ) {
438				this.doLog("Add to homescreen: not displaying callout because displayed recently");
439				return;
440			}
441
442			// obey the maximum number of display count
443			if ( this.options.maxDisplayCount && this.session.displayCount >= this.options.maxDisplayCount ) {
444				this.doLog("Add to homescreen: not displaying callout because displayed too many times already");
445				return;
446			}
447		}
448
449		this.shown = true;
450
451		// increment the display count
452		this.session.lastDisplayTime = now;
453		this.session.displayCount++;
454		this.updateSession();
455
456		// try to get the highest resolution application icon
457		if ( !this.applicationIcon ) {
458			if ( ath.OS == 'ios' ) {
459				this.applicationIcon = document.querySelector('head link[rel^=apple-touch-icon][sizes="152x152"],head link[rel^=apple-touch-icon][sizes="144x144"],head link[rel^=apple-touch-icon][sizes="120x120"],head link[rel^=apple-touch-icon][sizes="114x114"],head link[rel^=apple-touch-icon]');
460			} else {
461				this.applicationIcon = document.querySelector('head link[rel^="shortcut icon"][sizes="196x196"],head link[rel^=apple-touch-icon]');
462			}
463		}
464
465		var message = '';
466
467		if ( typeof this.options.message == 'object' && ath.language in this.options.message ) {		// use custom language message
468			message = this.options.message[ath.language][ath.OS];
469		} else if ( typeof this.options.message == 'object' && ath.OS in this.options.message ) {		// use custom os message
470			message = this.options.message[ath.OS];
471		} else if ( this.options.message in ath.intl ) {				// you can force the locale
472			message = ath.intl[this.options.message][ath.OS];
473		} else if ( this.options.message !== '' ) {						// use a custom message
474			message = this.options.message;
475		} else if ( ath.OS in ath.intl[ath.language] ) {				// otherwise we use our message
476			message = ath.intl[ath.language][ath.OS];
477		}
478
479		// add the action icon
480		message = '<p>' + message.replace('%icon', '<span class="ath-action-icon">icon</span>') + '</p>';
481
482		// create the message container
483		this.viewport = document.createElement('div');
484		this.viewport.className = 'ath-viewport';
485		if ( this.options.modal ) {
486			this.viewport.className += ' ath-modal';
487		}
488		if ( this.options.mandatory ) {
489			this.viewport.className += ' ath-mandatory';
490		}
491		this.viewport.style.position = 'absolute';
492
493		// create the actual message element
494		this.element = document.createElement('div');
495		this.element.className = 'ath-container ath-' + ath.OS + ' ath-' + ath.OS + (ath.OSVersion + '').substr(0,1) + ' ath-' + (ath.isTablet ? 'tablet' : 'phone');
496		this.element.style.cssText = '-webkit-transition-property:-webkit-transform,opacity;-webkit-transition-duration:0s;-webkit-transition-timing-function:ease-out;transition-property:transform,opacity;transition-duration:0s;transition-timing-function:ease-out;';
497		this.element.style.webkitTransform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
498		this.element.style.transform = 'translate3d(0,-' + window.innerHeight + 'px,0)';
499
500		// add the application icon
501		if ( this.options.icon && this.applicationIcon ) {
502			this.element.className += ' ath-icon';
503			this.img = document.createElement('img');
504			this.img.className = 'ath-application-icon';
505			this.img.addEventListener('load', this, false);
506			this.img.addEventListener('error', this, false);
507
508			this.img.src = this.applicationIcon.href;
509			this.element.appendChild(this.img);
510		}
511
512		this.element.innerHTML += message;
513
514		// we are not ready to show, place the message out of sight
515		this.viewport.style.left = '-99999em';
516
517		// attach all elements to the DOM
518		this.viewport.appendChild(this.element);
519		this.container.appendChild(this.viewport);
520
521		// if we don't have to wait for an image to load, show the message right away
522		if ( this.img ) {
523			this.doLog("Add to homescreen: not displaying callout because waiting for img to load");
524		} else {
525			this._delayedShow();
526		}
527	},
528
529	_delayedShow: function (e) {
530		setTimeout(this._show.bind(this), this.options.startDelay * 1000 + 500);
531	},
532
533	_show: function () {
534		var that = this;
535
536		// update the viewport size and orientation
537		this.updateViewport();
538
539		// reposition/resize the message on orientation change
540		window.addEventListener('resize', this, false);
541		window.addEventListener('scroll', this, false);
542		window.addEventListener('orientationchange', this, false);
543
544		if ( this.options.modal ) {
545			// lock any other interaction
546			document.addEventListener('touchmove', this, true);
547		}
548
549		// Enable closing after 1 second
550		if ( !this.options.mandatory ) {
551			setTimeout(function () {
552				that.element.addEventListener('click', that, true);
553			}, 1000);
554		}
555
556		// kick the animation
557		setTimeout(function () {
558			that.element.style.webkitTransitionDuration = '1.2s';
559			that.element.style.transitionDuration = '1.2s';
560			that.element.style.webkitTransform = 'translate3d(0,0,0)';
561			that.element.style.transform = 'translate3d(0,0,0)';
562		}, 0);
563
564		// set the destroy timer
565		if ( this.options.lifespan ) {
566			this.removeTimer = setTimeout(this.remove.bind(this), this.options.lifespan * 1000);
567		}
568
569		// fire the custom onShow event
570		if ( this.options.onShow ) {
571			this.options.onShow.call(this);
572		}
573	},
574
575	remove: function () {
576		clearTimeout(this.removeTimer);
577
578		// clear up the event listeners
579		if ( this.img ) {
580			this.img.removeEventListener('load', this, false);
581			this.img.removeEventListener('error', this, false);
582		}
583
584		window.removeEventListener('resize', this, false);
585		window.removeEventListener('scroll', this, false);
586		window.removeEventListener('orientationchange', this, false);
587		document.removeEventListener('touchmove', this, true);
588		this.element.removeEventListener('click', this, true);
589
590		// remove the message element on transition end
591		this.element.addEventListener('transitionend', this, false);
592		this.element.addEventListener('webkitTransitionEnd', this, false);
593		this.element.addEventListener('MSTransitionEnd', this, false);
594
595		// start the fade out animation
596		this.element.style.webkitTransitionDuration = '0.3s';
597		this.element.style.opacity = '0';
598	},
599
600	_removeElements: function () {
601		this.element.removeEventListener('transitionend', this, false);
602		this.element.removeEventListener('webkitTransitionEnd', this, false);
603		this.element.removeEventListener('MSTransitionEnd', this, false);
604
605		// remove the message from the DOM
606		this.container.removeChild(this.viewport);
607
608		this.shown = false;
609
610		// fire the custom onRemove event
611		if ( this.options.onRemove ) {
612			this.options.onRemove.call(this);
613		}
614	},
615
616	updateViewport: function () {
617		if ( !this.shown ) {
618			return;
619		}
620
621		this.viewport.style.width = window.innerWidth + 'px';
622		this.viewport.style.height = window.innerHeight + 'px';
623		this.viewport.style.left = window.scrollX + 'px';
624		this.viewport.style.top = window.scrollY + 'px';
625
626		var clientWidth = document.documentElement.clientWidth;
627
628		this.orientation = clientWidth > document.documentElement.clientHeight ? 'landscape' : 'portrait';
629
630		var screenWidth = ath.OS == 'ios' ? this.orientation == 'portrait' ? screen.width : screen.height : screen.width;
631		this.scale = screen.width > clientWidth ? 1 : screenWidth / window.innerWidth;
632
633		this.element.style.fontSize = this.options.fontSize / this.scale + 'px';
634	},
635
636	resize: function () {
637		clearTimeout(this.resizeTimer);
638		this.resizeTimer = setTimeout(this.updateViewport.bind(this), 100);
639	},
640
641	updateSession: function () {
642		if ( ath.hasLocalStorage === false ) {
643			return;
644		}
645
646		localStorage.setItem(this.options.appID, JSON.stringify(this.session));
647	},
648
649	clearSession: function () {
650		this.session = _defaultSession;
651		this.updateSession();
652	},
653
654	getItem: function(item) {
655		try {
656			return localStorage.getItem(item);
657		} catch(e) {
658			// Preventing exception for some browsers when fetching localStorage key
659			ath.hasLocalStorage = false;
660		}
661	},
662
663	optOut: function () {
664		this.session.optedout = true;
665		this.updateSession();
666	},
667
668	optIn: function () {
669		this.session.optedout = false;
670		this.updateSession();
671	},
672
673	clearDisplayCount: function () {
674		this.session.displayCount = 0;
675		this.updateSession();
676	},
677
678	_preventDefault: function (e) {
679		e.preventDefault();
680		e.stopPropagation();
681	}
682};
683
684// utility
685function _extend (target, obj) {
686	for ( var i in obj ) {
687		target[i] = obj[i];
688	}
689
690	return target;
691}
692
693function _removeToken () {
694	if ( document.location.hash == '#ath' ) {
695		history.replaceState('', window.document.title, document.location.href.split('#')[0]);
696	}
697
698	if ( _reSmartURL.test(document.location.href) ) {
699		history.replaceState('', window.document.title, document.location.href.replace(_reSmartURL, '$1'));
700	}
701
702	if ( _reQueryString.test(document.location.search) ) {
703		history.replaceState('', window.document.title, document.location.href.replace(_reQueryString, '$2'));
704	}
705}
706
707// expose to the world
708window.addToHomescreen = ath;
709
710})(window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.