<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0,minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Gas South Light Up</title>
<script src="//media.admob.com/api/v1/google_mobile_app_ads.js"></script>
<!-- <script src="mraid.js"></script> -->
<script>
var labAd = {
adCond: null,
mode: null,
meta: {
jiraID: 'tcl7165', //Example: The name of the JIRA ID
projectName: 'DUAL:THOR Gas South Light Up - Q3 24', //CVS Stores Preseason (Week Ahead SPON)
advertiser: 'GAS SOUTH', // Ex: 'mcdonalds'
placement: '', // Ex: 'ros-shopper-moms'
created: '10-18-2024', // Ex: '08-23-2018'
modified: '', // change when base code changes to current date of change
designer: 'eciprian', // Ex: 'coreyd'
dev: 'jjvillanueva' //Ex: 'dks'
},
product: {
productName: 'MAIM - Mobile App Integrated Marquee (Animated)', // Abrev. + Full name of Product
version: 'v1.3.3', //This will reflect the Github Version on the stable branch
type: 'animated', // 'static', 'animated'
interactive: '', // EX: 'locator', 'vertical-video', 'swipe-carousel', etc.
adaptor: 'base-conditions', //EX: 'temp-range', 'temp-comparison', 'countdown', etc.
milliBGImages: true, //EX: true, false - THOR CHANGE - MILLI ASSETS
milliFGImages: false, //EX: true, false - THOR CHANGE - MILLI ASSETS
dimensions: {
ad: {
//default for milli devices
sm: { width: 320, height: 190 }, //THOR CHANGE - AD SIZE
//default for kilo devices
md: { width: 360, height: 190 }, //THOR CHANGE - AD SIZE
lg: { width: 360, height: 190 }, //THOR CHANGE - AD SIZE
xl: { width: 360, height: 190 } //THOR CHANGE - AD SIZE
},
bg: {
//default for milli devices
sm: { width: 500, height: 720 }, //THOR CHANGE - BG SIZE
//default for kilo devices
md: { width: 500, height: 720 }, //THOR CHANGE - BG SIZE
lg: { width: 500, height: 720 }, //THOR CHANGE - BG SIZE
xl: { width: 500, height: 720 } //THOR CHANGE - BG SIZE
}
},
platforms: ['android', 'ios']// 'ios', 'android', 'web', 'mw'
},
appBg: { // object passed to AppEvent - do not add new properties to this object.
baseURL: '',
imgID: ''
},
appBgVid: { // object passed to AppEvent - do not add new properties to this object.
vidURL: '47ab603a-7ecb-42a6-afa6-d26524aea659/500x720-bg-cld-dmezz_HVEC_2mbs.mp4' // Ex:'414x210-clear_day@1x.mp4' - @1x filename only for initial assignment. labAd.dir prepended before sending to app event.
},
deviceSize: 'sm', // sm, md, lg, xl
dir: 'https://v.w-x.co/digital_video/The_Weather_Channel_-_Reach_Engine/',
dpr: window.devicePixelRatio,
//THOR CHANGE - foreground
foregroundElements: {
addFg: false, // Set to true if Foreground element is needed | False if no Foreground element is needed
fimg: '' // using transparent png since everything is baked into BG image
},
default: {
cnd: 'cld',
dynght: 'D',
img: 'https://s.w-x.co/cl/test/1x1_test.png', // using transparent png since everything is baked into BG image
previewMode: 'o',
reason: 'na' // 'na' is default
},
displayed: false, // flag indicating showAd or showDefault has executed successfully.
selected: { // for tracking selected values
adImages: [],
bgEvent: 'adBgVid', // Ex: 'adBg' or 'adBgVid' - set based on ad concept.
scenario: 'na'
},
els: {},
errors: [],
imgs: {},
log: logInfo,
logLevel: 1, // 0 = none, 1 = labAd, 2 = everything
logs: [],
macros: { // macros populated by GAM when ad is served
cnd: "%%PATTERN:cnd%%", // current condition.
dynght: "%%PATTERN:dynght%%", // day or night? Possible values: 'D', 'N'
height: "%%HEIGHT%%", // ad slot height
width: "%%WIDTH%%", // ad slot width
plat: "%%PATTERN:plat%%", // platform. Used for tracking
timeframe: "%%PATTERN:tf%%", // timeframe. Used for tracking
gamClick: "%%CLICK_URL_UNESC%%", // Google Ad Manager click track URL
gamPreview: '%%PREVIEW_MODE%%',
cid: "%ecid!", // creative id
lid: "%eaid!", // line item id
ord: '%%CACHEBUSTER%%'
},
preview: {
//THOR CHANGE - previewMode
css: { id: 'previewCSS', href: 'preview-im-ani-thor.css', dir: 'hostedcss' },
dirs: {
hostedAssets: 'https://s.w-x.co/cl/wxcl/preview-mode/thor/im/',
hostedjs: 'https://s.w-x.co/cl/wxcl/preview-mode/thor/im/',
hostedcss: 'https://s.w-x.co/cl/wxcl/preview-mode/thor/im/',
localAssets: 'preview-mode/assets/',
localjs: 'preview-mode/js/',
localcss: 'preview-mode/css/'
},
images: [
// NOTE: images.src values use split('-'). Extraneous '-' in filenames will cause issues.
// Standard Preview Images & Conditions
{ id: 'logo', type: 'logo', src: 'all-logo-watson-ads.png', dir: 'hostedAssets' },
{ id: 'device', type: 'device', src: 'lg-device.png', dir: 'hostedAssets' },
//for devices uis'
//THOR CHANGE - previewMode
{ id: 'ui-clr+D', type: 'ui', src: 'light-lg-ui-clear-day.png', dir: 'hostedAssets' },
{ id: 'ui-clr+N', type: 'ui', src: 'light-lg-ui-clear-night.png', dir: 'hostedAssets' },
{ id: 'ui-cld+D', type: 'ui', src: 'light-lg-ui-cloudy-day.png', dir: 'hostedAssets' },
{ id: 'ui-cld+N', type: 'ui', src: 'light-lg-ui-cloudy-night.png', dir: 'hostedAssets' },
{ id: 'ui-rain+D', type: 'ui', src: 'light-lg-ui-rainy-day.png', dir: 'hostedAssets' },
{ id: 'ui-rain+N', type: 'ui', src: 'light-lg-ui-rainy-night.png', dir: 'hostedAssets' },
{ id: 'ui-snow+D', type: 'ui', src: 'light-lg-ui-snowy-day.png', dir: 'hostedAssets' },
{ id: 'ui-snow+N', type: 'ui', src: 'light-lg-ui-snowy-night.png', dir: 'hostedAssets' }
],
//THOR CHANGE - previewMode
//textMode should be set to 'dark' for daytime conditions
//and should be set to 'light' for nighttime conditions by default.
//This value can be changed per campaign if needed.
textMode: {
clrD: 'dark',
clrN: 'light',
cldD: 'dark',
cldN: 'light',
rainD: 'dark',
rainN: 'light',
snowD: 'dark',
snowN: 'light'
}
},
promises: {},
test: {},
time: {
log: {},
stamp: 0 // most recent getTime. Updated when logTime is called.
},
tracking: {
clickTag: 'https://arttrk.com/pixel/?ad_log=ua-00040002-91728328435450&tag=fclick&msg=uid=%%CACHEBUSTER%%%7Crev=1%7Cdfp_ctrk=%eaid!!%ecid!!adxcel!%ebuy!!Gas_South_Q4_2024&pixid=8f898adb-7dea-467d-a64b-c46ad27f468a&consumer_id=%%ADVERTISING_IDENTIFIER_PLAIN%%&enc_url=https%3A%2F%2Fservedby.flashtalking.com%2Fclick%2F8%2F251900%3B8782477%3B4930844%3B211%3B0%2F%3Fgdpr%3D%24%7BGDPR%7D%26gdpr_consent%3D%24%7BGDPR_CONSENT_78%7D%26us_privacy%3D%26url%3D39973030', // Ex: 3rd party click tracker
// >>>>>keyword_ INSERT TRACKING HERE<<<<<
imp3rdParty1: '',
imp3rdParty2: '',
imp3rdParty3: ''
}
}
function initAd() {
try {
// init date & time
labAd.time.date = new Date();
logTime('initAd');
// init labAd props and els - especially those needed for tracking and timing operations
labAd.els.labAdDiv = document.getElementById('labAdDiv');
labAd.els.labTracking = document.getElementById('labTracking');
// request overall tracking.
getImpPixel(labAd.tracking.imp3rdParty1, 'imp-3rdParty1', false);
getImpPixel(labAd.tracking.imp3rdParty2, 'imp-3rdParty2', false);
getImpPixel(labAd.tracking.imp3rdParty3, 'imp-3rdParty3', false);
// get ad mode to determine if vars populated by macros or test hash
labAd.mode = getAdMode();
// populate vars from the correct source for mode
initAdMode(labAd.mode);
// determine adCond based on cnd and dynght values
labAd.adCond = labAd.macros.cnd + '-' + labAd.macros.dynght;
// init loader method for scripts, imgs and link els
initLoader();
// if Preview Mode, load resources needed, then init Preview Mode.
if (labAd.mode === 'preview' || labAd.mode === 'gamPreview') {
// group loader calls in array so they share the same callback
var sPathjs = labAd.preview.dirs.hostedjs; // .standard, .customDir or .local
var sPathcss = labAd.preview.dirs.hostedcss; // .standard, .customDir or .local
var aPreviewPromises = [
//THOR CHANGE - previewMode
labAd.loader.js({ src: sPathjs + 'preview-im-ani-thor.js', append: true }),
labAd.loader.css({ src: sPathcss + 'preview-im-ani-thor.css', append: true })
];
// use promise.all to execute callback when all promises are fulfilled
labAd.promises.previewResources = Promise.all(aPreviewPromises).then(function (result) {
// log success result
labAd.log({ label: 'aPreviewPromises fulfilled. ', detail: result, type: 'log' });
// complete init PreviewMode now that resources are loaded
labAd.preview.init();
}).catch(function (err) {
labAd.log({ label: 'aPreviewPromises file load error: ', detail: err, type: 'error', isFatal: true });
});
} else { // else proceed to display using ad served
initDisplay(labAd.adCond);
}
} catch (err) {
labAd.log({ label: 'initAd error: ', detail: err, type: 'error', isFatal: true });
}
}
function initAdMode(sMode) {
try {
var result = '';
// Cases below should define adjustments needed for test modes supported by ad.
switch (sMode) {
case 'preview':
case 'gamPreview':
// clear GAM click macro so clicks will work in test modes
labAd.macros.gamClick = "";
// assign values from HashParts - hash order is adstest + cnd + dynght + deviceSize + DPR + previewMode
labAd.macros.cnd = labAd.test.HashParts[1];
labAd.macros.dynght = labAd.test.HashParts[2].toUpperCase();
labAd.deviceSize = labAd.test.HashParts[3].toLowerCase();
// get dpr from hash if not undefined, otherwise use window property
labAd.dpr = (labAd.test.HashParts[4] !== undefined) ? Number(labAd.test.HashParts[4]) : window.devicePixelRatio;
// adjust meta height
if (labAd.deviceSize !== 'sm') {
labAd.meta.width = 360;
}
break;
default: // covers 'live' and 'none'
labAd.deviceSize = getDeviceSize();
break;
}
// log result
result = labAd.mode + ' activated';
labAd.log({ label: 'initAdMode ', detail: result, type: 'log' });
} catch (err) {
labAd.log({ label: 'initAdMode error: ', detail: err, type: 'error', isFatal: false });
}
}
function initDisplay(sCond) {
logTime('initDisplay');
try {
// start the display routine by firing appEvent, selecting files, loading AD & BG images
// fire adIM event asap so the app knows to push the feed card down
fireAppEvent('adIM', {});
// select files needed for AD & BG display.
labAd.selected = fileSelection(sCond);
// 1: showBg - should be called first since it cannot be preloaded.
// labAd.selected.bg properties will be used to package & send .
showBg(labAd.selected.bg);
// create an array of img promises to be loaded together
labAd.promises.adImages = [];
// create a loader.img instance for each selected img
labAd.selected.adImages.forEach(function (imgObj) {
// add each individual promise to the collection
labAd.promises.adImages.push(labAd.loader.img(imgObj));
});
// use promise.all to execute callback when all promises are fulfilled
labAd.promises.allAdImages = Promise.all(labAd.promises.adImages).then(function (aLoadedImages) {
// log success aLoadedImages
labAd.log({ label: 'labAd.promises.allAdImages fulfilled. ', detail: aLoadedImages, type: 'log' });
// call showAd and pass the successfully loaded Image els that still
// need to be added to the DOM
showAd(aLoadedImages);
}).catch(function (err) {
labAd.log({ label: 'labAd.promises.allAdImages file load error: ', detail: err, type: 'error', isFatal: true });
});
labAd.log({ label: 'initDisplay ', detail: sCond, type: 'log' });
} catch (err) {
labAd.log({ label: 'initDisplay error: ', detail: err, type: 'error', isFatal: false });
}
}
function initLoader() {
try {
// initLoader: creates labAd.loader - which is a method that returns a function for loading resources using a Promise to determine success or failure. Currently supports script, link & img els. All different types can be loaded with the same callback.
logTime('initLoader');
labAd.loader = (function () {
// Function which returns a function: https://davidwalsh.name/javascript-functions
function _load(tag) {
return function (loadObj) {
// This promise will be used by Promise.all to determine success or failure
return new Promise(function (resolve, reject) {
var element;
var parent = 'body';
var attr = 'src';
// Adjust element type & attributes depending on tag type
switch (tag) {
case 'img':
element = new Image();
element.id = loadObj.id;
element.src = loadObj.src;
break;
case 'script':
element = document.createElement(tag);
element.async = true;
break;
case 'link':
element = document.createElement(tag);
element.type = 'text/css';
element.rel = 'stylesheet';
attr = 'href';
parent = 'head';
break;
}
// Important success and error for the promise
element.onload = function () {
labAd.log({ label: tag + ' loaded ', detail: { lO: loadObj, el: element, t: tag }, type: 'log' });
// resolve the promise & return the element
resolve(element);
};
element.onerror = function (err) {
labAd.errors.push(err);
reject(err);
};
// if append is true, element needs to be added to DOM immediately
// doing this & applying src will cause the resource to load but
// the Promise controls what happens next.
if (loadObj.append) {
// apply the source & add to DOM
element[attr] = loadObj.src;
document[parent].appendChild(element);
}
});
};
}
return {
css: _load('link'),
js: _load('script'),
img: _load('img')
}
})();
labAd.log({ label: 'initLoader ', detail: labAd.loader, type: 'log' });
} catch (err) {
labAd.log({ label: 'initLoader error: ', detail: err, type: 'error', isFatal: false });
}
}
function exitClick(event) {
try {
event.preventDefault();
var id = event.target.id;
var url = labAd.tracking.clickTag;
var target = "_blank";
var clickTime = logTime('exitClick');
var btnPos = labAd.els.btnInv.getBoundingClientRect();
labAd.log({ label: 'exitClick from: ' + id, detail: url, type: 'log' });
// launch clickthrough window
window.open(labAd.macros.gamClick + url, target);
} catch (err) {
labAd.log({ label: 'exitClick error: ', detail: err, type: 'error', isFatal: false });
}
}
function fileSelection(sCond) {
try {
// selected is a return object containing all relevant details needed to
// complete the ad display process.
var selected = {
adImages: [], // array of AD images to be loaded
bg: {
eventName: '', // string name AppEvent: 'adBg' or 'adBgVid'
fileName: '', // string filename of the selected BG image or video
logicalWidth: 500, // logical (aka 1x) width of BG asset - THOR CHANGE - BG SIZE
logicalHeight: 720,// logical (aka 1x) height of BG asset - THOR CHANGE - BG SIZE
loopCount: 3, // number of times to loop BG video
textMode: 'dark' //EX: 'light', 'dark' THOR CHANGE - textMode
},
scenario: '' // string name representing the selected scenario
};
/////////////////////////////////////////////////////////
// 1: Assign values used for most or all scenarios //
/////////////////////////////////////////////////////////
// NOTE: since there's only one image in the AD region for this creative,
// its also assigned as the static default - which is why the line below
// references that instead of just using the filename string. Also, the
// id is being set to 'btnInv' so it will inherit the click handler that
// that might be assigned to a transparent png in a more complex ad.
selected.adImages.push(imgObj('btnInv', labAd.default.img));
selected.bg.eventName = 'adBgVid';
//THOR CHANGE - foreground
///TOGGLE FOR ADDING FOREGROUND ELEMENT(S)//
var fgele = labAd.foregroundElements.addFg;
if (fgele === true) {
selected.adImages.push(imgObj('fgGrid', labAd.foregroundElements.fimg, labAd.product.dimensions.ad[labAd.deviceSize].width));
} else if (fgele === false) {
//DONT PUSH ANYTHING TO THE DOM
};
//////////////////////////////////////////////
// 2: Assign BG image/video to appBg or appBgVid //
//////////////////////////////////////////////
sCond = sCond.toLowerCase();
switch (sCond) {
case 'sun-d':
case 'clr-d':
selected.scenario = 'Clear Day';
selected.bg.fileName = '9eb42665-d188-49de-a518-90ef9e06c26f/500x720-bg-clr-dmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.clrD; //THOR CHANGE - textMode
break;
case 'sun-n':
case 'clr-n':
selected.scenario = 'Clear Night';
selected.bg.fileName = 'b66d9f38-55b7-4720-a3cf-2fd6e9272004/500x720-bg-clr-nmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.clrN; //THOR CHANGE - textMode
break;
case 'cld-n':
case 'pcld-n':
selected.scenario = 'Cloudy Night';
selected.bg.fileName = '733b1d7b-8283-485a-bf20-d83fda15326e/500x720-bg-cld-nmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.cldN; //THOR CHANGE - textMode
break;
case 'rain-d':
case 'thdr-d':
selected.scenario = 'Rainy Day';
selected.bg.fileName = '371efb78-1545-40d8-a09f-08787d09f494/500x720-bg-rain-dmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.rainD; //THOR CHANGE - textMode
break;
case 'rain-n':
case 'thdr-n':
selected.scenario = 'Rainy Night';
selected.bg.fileName = '73a6a270-55dd-4a81-9e1a-1db410e129d9/500x720-bg-rain-nmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.rainN; //THOR CHANGE - textMode
break;
case 'snow-d'://Wintery Day
case 'ice-d': //Ice Day
selected.scenario = 'Wintry/Icy Day';
selected.bg.fileName = 'd936b416-f74d-4156-ac5c-6c5459ed0a9e/500x720-bg-snow-dmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.snowD; //THOR CHANGE - textMode
break;
case 'snow-n'://Wintery Night
case 'ice-n': //Ice Night
selected.scenario = 'Wintry/Icy Night';
selected.bg.fileName = '42dfb55c-dd3f-4289-8c47-3db9f1b71d21/500x720-bg-snow-nmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.snowN; //THOR CHANGE - textMode
break;
default:
//Kylie Prevatt - 03/31/22
//Added conditional logic to display correct D/N version of default creative
if (labAd.macros.dynght === 'n' || labAd.macros.dynght === 'N') {
selected.scenario = 'Cloudy Night - default: ' + sCond;
selected.bg.fileName = '733b1d7b-8283-485a-bf20-d83fda15326e/500x720-bg-cld-nmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.cldN; //THOR CHANGE - textMode
} else {
selected.scenario = 'Cloudy Day - default: ' + sCond;
selected.bg.fileName = '47ab603a-7ecb-42a6-afa6-d26524aea659/500x720-bg-cld-dmezz_HVEC_2mbs.mp4';
selected.bg.textMode = labAd.preview.textMode.cldD; //THOR CHANGE - textMode
}
break;
}
// process selected BG filename dimensions by passing through setSrcSize
selected.bg.fileName = setSrcSize(selected.bg.fileName);
////////////////////////////////////////
// 3: Apply logical BG img dimensions //
////////////////////////////////////////
if (labAd.deviceSize !== 'sm') {
selected.bg.logicalWidth = 500;
selected.bg.logicalHeight = 720; //THOR CHANGE - BG SIZE
}
// log results
labAd.log({ label: 'fileSelection ', detail: selected, type: 'log' });
///////////////////////////////////
// 4: return object with details //
///////////////////////////////////
return selected;
} catch (err) {
labAd.log({ label: 'fileSelection error: ', detail: err, type: 'error', isFatal: true });
}
}
function fireAppEvent(sEventName, oProps) {
try {
logTime('fireAppEvent-' + sEventName);
// stringify oProps object for use in dispatchAppEvent
var payload = JSON.stringify(oProps);
admob.events.dispatchAppEvent(sEventName, payload);
labAd.log({ label: 'fireAppEvent ' + sEventName, detail: oProps, type: 'log' });
} catch (err) {
labAd.log({ label: 'fireAppEvent error: ' + sEventName, detail: err, type: 'error', isFatal: false });
}
}
function getImpPixel(sURL, sEventName) {
try {
var regexp = /(https?:\/\/[^\s]+)/g; // begins with http:// or https://
if (regexp.exec(sURL)) {
// if url is valid, add tracking pixel to labTracking
var pixel = document.createElement('img');
pixel.src = sURL;
pixel.alt = sEventName;
// add new img to tracking div
labAd.els.labTracking.appendChild(pixel);
// log success
labAd.log({ label: 'getImpPixel: ' + sEventName, detail: pixel, type: 'log' });
} else {
// log error for invalid URL.
var eO = { message: 'sEventName: ' + sEventName };
labAd.log({ label: 'getImpPixel - invalid URL: ' + sURL, detail: eO, type: 'error', isFatal: false });
}
} catch (err) {
labAd.log({ label: 'getImpPixel error: ', detail: err, type: 'error', isFatal: false });
}
}
function getAdMode() {
try {
labAd.test.HashParts = getTestHash();
// if the zero index === adstest, we go into test mode
if (labAd.test.HashParts[0] === "adstest") {
labAd.test.mode = 'preview';
} else if (labAd.macros.gamPreview === 'true') { // Macro will return true for GAM Creative Preview. Original macro token will be intact during local dev.
labAd.test.mode = 'gamPreview';
} else {
labAd.test.mode = 'live';
}
labAd.log({ label: 'getAdMode: ' + labAd.test.mode, detail: labAd.test, type: 'log' });
return labAd.test.mode;
} catch (err) {
labAd.log({ label: 'getAdMode error: ', detail: err, type: 'error', isFatal: true });
}
}
function getDeviceSize() {
try {
// return a value for labAd.deviceSize based on WIDTH of ad slot size
// requested. For milli devices, size will be 320x190. All other devices
// will be 360x190.
// default to small & adjust up if needed
var sz = 'sm';
if (labAd.macros.width !== '320') {
// if macro width is not small, return lg
sz = 'lg';
// TODO: any reason (aside from PreviewMode) to pass md or xl?
// adjust meta width
labAd.meta.width = labAd.macros.width;
}
labAd.log({ label: 'getDeviceSize ', detail: sz, type: 'log' });
return sz;
} catch (err) {
labAd.log({ label: 'getDeviceSize error: ', detail: err, type: 'error', isFatal: false });
}
}
function getTestHash() {
try {
// expected format & order #adstest + cnd + dynght + deviceSize + dpr
labAd.test.hashStr = top.window.location.hash.substring(1);
// if this is a local file or gamPreview ith no adstest hash, assign default
// hashStr value to eliminate need to manually add it to url
if ((top.window.location.protocol === 'file:' || labAd.macros.gamPreview === 'true') && labAd.test.hashStr.indexOf('adstest') === -1) {
// get actual deviceSize based on macro value to use as defualt
labAd.deviceSize = getDeviceSize();
// build array of default values mapped to labAd props so changes are automatically picked up here
labAd.test.defaultHashParts = ['adstest', labAd.default.cnd, labAd.default.dynght, labAd.deviceSize, labAd.dpr, labAd.default.previewMode,];
// set the missing hash by joining parts of this array to assemble default adstest hash string
top.window.location.hash = labAd.test.defaultHashParts.join('+');
// now get and use the hashtring
labAd.test.hashStr = top.window.location.hash.substring(1);
labAd.log({ label: 'getTestHash defaultHashParts', detail: labAd.test.defaultHashParts, type: 'log' });
}
// split hash string on '+' delimiter
labAd.test.hashArray = labAd.test.hashStr.split("+");
// validate by filtering out empty values: "", undefined
labAd.test.hashArrayFiltered = labAd.test.hashArray.filter(function (el) {
return el !== null && el !== "";
});
labAd.log({ label: 'getTestHash ', detail: labAd.test.hashArrayFiltered, type: 'log' });
// return validated and sanitized array of #adstest hash values
return labAd.test.hashArrayFiltered;
} catch (err) {
labAd.log({ label: 'getTestHash error: ', detail: err, type: 'error', isFatal: false });
}
}
function imgDPR(sFilename) {
try {
// apply img name based on devicePixelRatio (or argument representing dpr)
// assuming all base filenames should contain '@1x'
var imgName = decodeURIComponent(sFilename);
var adjusted = 'not adjusted.';
if (imgName.indexOf('@') !== -1) { // if decoded filename contains @ symbol
// make DPR adjustments using string replacement
if (labAd.dpr > 3) {
// greater than triple density
imgName = imgName.replace('@1x', '@4x');
adjusted = 'adjusted to 4x.';
} else if (labAd.dpr > 2 && labAd.dpr <= 3) {
// triple density
imgName = imgName.replace('@1x', '@3x');
adjusted = 'adjusted to 3x.';
} else if (labAd.dpr > 1 && labAd.dpr <= 2) {
// double density aka retina
imgName = imgName.replace('@1x', '@2x');
adjusted = 'adjusted to 2x.';
} else {
// no adjustment required since default is '@1x'
}
// log success
labAd.log({ label: 'imgDPR: ' + adjusted, detail: imgName, type: 'log' });
} else {
// warn in console for files missing @ symbol
adjusted = '@ symbol not found in filename so imgDPR adjustment is not possible.'
labAd.log({ label: 'imgDPR: ' + adjusted, detail: imgName, type: 'warn' });
}
return imgName;
} catch (err) {
labAd.log({ label: 'imgDPR error: ', detail: err, type: 'error', isFatal: false });
}
}
function imgObj(sId, sSrc) {
try {
// adjust dpr as needed
var obj = { id: sId, src: imgDPR(sSrc) };
// adjust image size of src
obj.src = setSrcSize(obj.src);
// prepend labAd.dir if src is not absolute path
if (obj.src.indexOf('://') === -1) {
obj.src = labAd.dir + obj.src;
}
// log results
labAd.log({ label: 'imgObj ' + sId + ' - ' + sSrc, detail: obj, type: 'log' });
return obj;
} catch (err) {
labAd.log({ label: 'imgObj error: ', detail: err, type: 'error', isFatal: true });
}
}
function logInfo(oInfo) {
// save to logs array - regardless of type or details
this.logs.push(oInfo);
// console output format will correspond to type of info being logged.
// only local files are eligible for console output.
// steps for error logs
if (oInfo.type === 'error') {
// save to errors array
this.errors.push(oInfo);
// always output log errors so they cannot be suppressed/missed due to logLevel
console[oInfo.type](oInfo);
// if the error is fatal, call showDefault with label passed as reason
if (oInfo.isFatal) {
showDefault(oInfo.label);
}
} else { // steps for non-error logs
// console output is only possible if file is local
if (window.location.href.indexOf("file://") !== -1) {
// adjust type/level of output based on labAd.logLevel
switch (labAd.logLevel) {
case 2:
// output every individual log
console[oInfo.type](oInfo);
break;
case 0:
// output nothing to console
break;
default:
// clear console since this can fire many times but we only need 1 output for this setting
console.clear();
// if there are errors, output them before labAd
if (labAd.errors.length > 0) {
labAd.errors.forEach(function (err, i) {
console.error(err);
})
}
// output labAd object for easy inspection
console.log(labAd);
}
};
}
// update labInfo. Example: statefarm-4693437107-1528401281492
setLabInfo(this.meta.advertiser + '-' + this.meta.placement + '-' + this.meta.width + 'x' + this.meta.height + '-' + this.time.date, this);
}
function logTime(sName) {
try {
// log timing of key events to time.log for tracking purposes
// also return getTime() value so this can be used to get timestamps
var d = new Date();
var order = Object.keys(labAd.time.log).length;
// create new log obj
labAd.time.log[sName] = {};
// stamp this event
labAd.time.log[sName].stamp = d.getTime();
// calc split in ms from previous stamp
if (sName !== 'initAd') {
labAd.time.log[sName].split = (labAd.time.log[sName].stamp - labAd.time.stamp);
} else {
labAd.time.log[sName].split = 0;
}
// calc elapsed from initAd - in ms
labAd.time.log[sName].elapsed = (labAd.time.log[sName].stamp - labAd.time.log['initAd'].stamp);
// capture log order to indicate where in sequence this log occured
labAd.time.log[sName].order = order;
// capture timestamp to use for next split
labAd.time.stamp = d.getTime();
return labAd.time.stamp;
} catch (err) {
labAd.log({ label: 'logTime error: ', detail: err, type: 'error', isFatal: false });
}
}
function setLabInfo(sName, obj) {
try {
// store passed object to labInfo on parent DOM if possible
if (labAd.macros.gamPreview === 'true') {
// skip logging if running in DFP Preview
return;
} else {
parent.labInfo = parent.labInfo || {};
parent.labInfo[sName] = obj;
}
} catch (err) {
labAd.log({ label: 'setLabInfo error: ', detail: err, type: 'error', isFatal: false });
}
}
function setSrcSize(sFilename) {
try {
// perform string replacement to adjust from sm to lg image
var imgName = decodeURIComponent(sFilename);
var adjusted = 'not adjusted.';
if (labAd.deviceSize === 'sm') {
// Adjust to smaller dimensions for small devices
// THOR CHANGE - AD SIZE
if (labAd.product.milliFGImages === true && imgName.indexOf('360x190') !== -1) {
imgName = imgName.replace('360x190', '320x190');
adjusted = "adjusted to 320x190."
}
// THOR CHANGE - BG SIZE
if (labAd.product.milliBGImages === true && imgName.indexOf('500x720') !== -1) {
imgName = imgName.replace('9eb42665-d188-49de-a518-90ef9e06c26f/500x720-bg-clr-dmezz_HVEC_2mbs.mp4', 'f0ae9eae-ecc9-451b-a672-6b9fc0bae237/500x720m-bg-clr-dmezz_HVEC_2mbs.mp4');
imgName = imgName.replace('b66d9f38-55b7-4720-a3cf-2fd6e9272004/500x720-bg-clr-nmezz_HVEC_2mbs.mp4', '1abb7c90-66a9-4679-bdb5-0e6c2a1ca95e/500x720m-bg-clr-nmezz_HVEC_2mbs.mp4');
imgName = imgName.replace('47ab603a-7ecb-42a6-afa6-d26524aea659/500x720-bg-cld-dmezz_HVEC_2mbs.mp4', '309f5057-c97f-439b-b360-0c80e96c5385/500x720m-bg-cld-dmezz_HVEC_2mbs.mp4');
imgName = imgName.replace('733b1d7b-8283-485a-bf20-d83fda15326e/500x720-bg-cld-nmezz_HVEC_2mbs.mp4', 'a008e7a5-eca0-4ed1-9769-c792081d5ebb/500x720m-bg-cld-nmezz_HVEC_2mbs.mp4');
imgName = imgName.replace('371efb78-1545-40d8-a09f-08787d09f494/500x720-bg-rain-dmezz_HVEC_2mbs.mp4', '9a017d59-4c85-47bd-90a8-000b0bb7c54e/500x720m-bg-rain-dmezz_HVEC_2mbs.mp4');
imgName = imgName.replace('73a6a270-55dd-4a81-9e1a-1db410e129d9/500x720-bg-rain-nmezz_HVEC_2mbs.mp4', 'e93676b0-2b2d-480f-b87d-4bcecd88b5c9/500x720m-bg-rain-nmezz_HVEC_2mbs.mp4');
imgName = imgName.replace('d936b416-f74d-4156-ac5c-6c5459ed0a9e/500x720-bg-snow-dmezz_HVEC_2mbs.mp4', '57586289-11da-4844-8a4f-6c8b1d8cde00/500x720m-bg-snow-dmezz_HVEC_2mbs.mp4');
imgName = imgName.replace('42dfb55c-dd3f-4289-8c47-3db9f1b71d21/500x720-bg-snow-nmezz_HVEC_2mbs.mp4', 'd1c5f828-0092-4e77-8787-b9f256a1bb6b/500x720m-bg-snow-nmezz_HVEC_2mbs.mp4');
adjusted = "adjusted to 500x720-milli."
}
} else {
adjusted = 'md, lg, or xl device so no adjustment needed.'
}
// log success
labAd.log({ label: 'setSrcSize: ' + adjusted, detail: imgName, type: 'log' });
return imgName;
} catch (err) {
labAd.log({ label: 'setSrcSize error: ', detail: err, type: 'error', isFatal: false });
}
}
function showAd(aLoadedImages) {
try {
if (!labAd.displayed) {
// flag to prevent ad from displaying multiple times in the event of failure or timeout
labAd.displayed = true;
logTime('showAd');
labAd.log({ label: 'showAd: ', detail: aLoadedImages, type: 'log' });
////////////////////////////////////////////////
// 1: Append AD Area (360x105 or 360x140) images to labAdDiv. //
////////////////////////////////////////////////
aLoadedImages.forEach(function (elem) {// for each aLoadedImages el: assign CSS and behavior(s) then attach to DOM
// add el reference to labAd.els
labAd.els[elem.id] = elem;
// assign common class to all
labAd.els[elem.id].className = 'abs-' + labAd.deviceSize;
labAd.els.labAdDiv.className = labAd.deviceSize;
// append to labAdDiv
labAd.els.labAdDiv.appendChild(labAd.els[elem.id]);
// assign specific behavior/format by id
if (elem.id === 'btnInv') { // assign click handler to invisible btn
labAd.els[elem.id].onclick = exitClick;
}
});
} else {
var err = { message: 'showAd called again.' }
labAd.log({ label: 'Display routine called after labAd.displayed === true.', detail: err, type: 'error', isFatal: false });
}
} catch (err) {
labAd.log({ label: 'showAd error: ', detail: err, type: 'error', isFatal: true });
}
}
function showBg(bgProps) {
try {
logTime('showBg');
///////////////////////////////////////////////////////////////////
// 1: Process BG properties and fireAppEvent based on event name //
///////////////////////////////////////////////////////////////////
if (bgProps.eventName === 'adBg') {
// Static Image BG - assign props from selected
labAd.appBg.baseURL = labAd.dir;
// assign selected BG filename & adjust DPR if needed
labAd.appBg.imgID = imgDPR(bgProps.fileName);
// encode BG image name to prevent '@' from breaking app event.
labAd.appBg.imgID = encodeURIComponent(labAd.appBg.imgID);
// set the logical dimensions
labAd.appBg.widthDP = bgProps.logicalWidth;
labAd.appBg.heightDP = bgProps.logicalHeight;
// set textMode
labAd.appBg.textMode = bgProps.textMode; // THOR CHANGE - textMode
// fire the app event passing updated props
fireAppEvent('adBg', labAd.appBg);
} else { // Video BG
// Assign bgProps.fileName overwrite to @2x video bg every time.
labAd.appBgVid.vidURL = bgProps.fileName.replace('@1x', '@2x');
// encode vidURL - because '@' is not compatible with BG app events
labAd.appBgVid.vidURL = encodeURIComponent(labAd.appBgVid.vidURL);
// prepend labAd.dir to filename to form vidURL
labAd.appBgVid.vidURL = labAd.dir + labAd.appBgVid.vidURL;
// set the logical dimensions
labAd.appBgVid.widthDP = bgProps.logicalWidth;
labAd.appBgVid.heightDP = bgProps.logicalHeight;
// set textMode
labAd.appBgVid.textMode = bgProps.textMode; // THOR CHANGE - textMode
// Set the loopCount property
labAd.appBgVid.loop = bgProps.loopCount;
// fire the app event passing updated props
fireAppEvent('adBgVid', labAd.appBgVid);
}
} catch (err) {
labAd.log({ label: 'showBg error: ', detail: err, type: 'error', isFatal: false });
}
}
function showDefault(sReason) {
try {
if (!labAd.displayed) {
// flag to prevent ad from displaying multiple times in the event of failure or timeout
labAd.displayed = true;
logTime('showDefault');
labAd.default.reason = sReason;
labAd.log({ label: 'showDefault: ', detail: labAd.default.reason, type: 'log' });
if (labAd.mode === 'live') {
} else {
// create error & reason output display for PreviewMode
labAd.els.errOutput = document.createElement('div');
labAd.els.errOutput.id = 'previewErrorOutput';
// create and apppend heading
labAd.els.errReason = document.createElement('h1');
var t = document.createTextNode(sReason);
labAd.els.errReason.appendChild(t);
labAd.els.errOutput.appendChild(labAd.els.errReason);
// create list
labAd.els.errList = document.createElement('ol');
// iterate errors and add list item for each
labAd.errors.forEach(function (err, i) {
// create an li & text node for every error
var li = document.createElement('li');
var lit = document.createTextNode(err.label);
li.appendChild(lit);
// add li to the ul
labAd.els.errList.appendChild(li);
});
// add ul with all list items to output panel
labAd.els.errOutput.appendChild(labAd.els.errList);
// create footer text
var p = document.createElement('footer');
p.innerHTML = '<strong>Note:</strong> The list above represents the earliest occuring errors - which should be diagnosed first. <p>Check <code>labAd.errors</code> for a complete set of errors and details.</p>';
labAd.els.errOutput.appendChild(p);
// empty previewMain and append the errOutput there because GAM Preview
// will not permit insertBefore to place the errOutput before other
// els in the DOM.
labAd.els.previewMain.innerHTML = '';
labAd.els.previewMain.appendChild(labAd.els.errOutput);
labAd.els.previewMain.style.opacity = 1;
}
} else {
var err = { message: 'showDefault called again.' }
labAd.log({ label: 'Display routine called after labAd.displayed === true.', detail: err, type: 'error', isFatal: false });
}
} catch (err) {
// not fatal since there's no fallback for this failure.
labAd.log({ label: 'showDefault error: ', detail: err, type: 'error', isFatal: false });
}
}
document.addEventListener('DOMContentLoaded', initAd, false);
</script>
<style>
/* CORE CSS - DO NOT EDIT */
/* VIEWABILITY FIX ADDED 11/18/19 - DO NOT REMOVE */
html {
height: 100%;
width: 100%;
}
body {
margin: 0;
height: 100%;
width: 100%;
}
#labAdDiv {
height: 190px;
position: relative;
margin: 0 auto;
}
#labAdDiv.sm {
width: 320px;
}
#labAdDiv.md,
#labAdDiv.lg,
#labAdDiv.xl {
width: 360px;
}
/* Ad Slot Size & Position */
.abs-sm {
width: 320px;
height: 190px;
position: absolute;
}
.abs-md,
.abs-lg,
.abs-xl {
width: 360px;
height: 190px;
position: absolute;
}
.preview #labAdDiv.sm {
width: 300px;
bottom: -4px;
}
.preview #labAdDiv.md {
width: 360px;
left: 2px;
bottom: -6px;
}
.preview #labAdDiv.lg {
width: 360px;
left: 0px;
bottom: -10px;
}
.preview #labAdDiv.xl {
width: 360px;
left: 0px;
bottom: -14px;
}
/* Ad Slot Size & Position */
.preview .abs-sm {
left: -8px;
top: 0px;
}
.preview .abs-md,
.preview .abs-lg,
.preview .abs-xl {
left: -18px;
top: 10px;
}
/* Foreground element Size */
.preview #fgGrid.abs-sm {
width: 320px;
height: 190px;
}
.preview #fgGrid.abs-md,
.preview #fgGrid.abs-lg,
.preview #fgGrid.abs-xl {
width: 360px;
height: 190px;
}
/* Foreground element Position */
.preview #fgGrid.abs-md,
.preview #fgGrid.abs-lg,
.preview #fgGrid.abs-xl {
top: 0px;
left: 0px
}
#btnInv {
z-index: 10;
cursor: pointer;
height: 190px;
/* background: rgba(255,255,255, .25);*/
}
/* btnInv element Position */
.preview #btnInv.abs-md,
.preview #btnInv.abs-lg,
.preview #btnInv.abs-xl {
top: 0px;
left: 0px
}
.hidden {
opacity: 0;
}
/* END: CORE CSS - DO NOT EDIT */
/* Preview Mode CSS - DO NOT EDIT β VIEWABILITY FIX ADDED 11/18/19 */
#previewMainContainer {
opacity: 0;
display: none;
}
#previewAltContainer {
display: none;
}
/* END: Preview Mode CSS - DO NOT EDIT */
</style>
</head>
<body>
<!-- #labAdDiv is the main container for all AD elements -->
<div id="labAdDiv"></div>
<!-- Container for all tracking pixels - manually placed or dynamically generated. -->
<div id="labTracking" style="display:none;">
<!-- 3P IAS OR MOAT TRACKING GOES HERE IF APPLICABLE -->
<SCRIPT TYPE="application/javascript" SRC="https://pixel.adsafeprotected.com/rjss/st/2216208/82877213/skeleton.js"></SCRIPT>
</div>
<!-- Preview Mode Structure - DO NOT EDIT -->
<div id="previewMainContainer">
<!-- LEFT FORM COL -->
<div id="previewColLeft">
<hr> <img id="previewFormLogo" src="" alt="The Weather Company">
<hr>
<p id="previewFormProduct"> Mobile App Integrated Marquee </p>
<hr>
<p id="previewFormClientName"> Client Name Here </p>
<hr>
<div id="previewFormDeviceSize">
<p>Device Size</p>
<!-- // added this for XL sizes preview - wodev comment-->
<input type="radio" name="deviceSize" value="sm" id="deviceSmall">
<label for="deviceSmall" id="label-01">SM</label>
<input type="radio" name="deviceSize" value="md" id="deviceMedium">
<label for="deviceMedium" id="label-02">RG</label>
<input type="radio" name="deviceSize" value="lg" id="deviceLarge">
<label for="deviceLarge" id="label-03">LG</label>
<input type="radio" name="deviceSize" value="xl" id="deviceXLarge">
<label for="deviceXLarge" id="label-04">XL</label>
<!-- // added this for medium sizes preview - wodev comment -->
</div>
<hr>
<div id="previewFormCondition">
<div id="btnPrevCond"><img src="https://s.w-x.co/cl/wxcl/preview-mode/idd/prev.png" /></div>
<div id="txtCondName"></div>
<div id="btnNextCond"><img src="https://s.w-x.co/cl/wxcl/preview-mode/idd/next.png" /></div>
</div>
<hr>
<div id="previewFormConditionIndex"></div>
</div>
<!-- RIGHT AD COL -->
<div id="previewColRight">
<div id="previewAppOverlay">
<!-- previewAppOverlay = newer layout with images for device and app ui. -->
<div id="previewDeviceToggleBtn"></div>
<div id="previewDeviceOverlay"></div>
<!-- START: ADD THIS for city, state textfield -->
<!--
<div id="previewCityStateOverlay">
<input type="text" id="cityStateInput" value="Durango, CO" readonly>
</div>
-->
<!-- END: ADD THIS for city, state textfield -->
<div id="previewUiCon">
<div id="previewUiOverlay"></div>
</div>
<div id="previewUiFCOverlay"></div>
<div id="previewBlockerBottom"></div>
</div>
<div id="previewAppLayout">
<!-- previewAppLayout (previously previewAppContainer) = older layout with colored divs representing different regions of app. -->
<div id="previewAppHeader"></div>
<div id="previewAppToggle"></div>
<div id="previewAppChart"></div>
<div id="previewAppInsight"></div>
<div id="previewAppGradient"></div>
<div id="previewAppNav"></div>
<div id="previewAppCoverRight"></div>
<div id="previewAppCoverLeft"></div>
</div>
<!-- End Right Column -->
</div>
</div>
<div id="previewAltContainer"> <img id="previewAltLogo" src="" alt="The Weather Company">
<p>Please increase browser width to view creative preview form.</p>
</div>
</body>
</html>
14
6
139KB
470KB
208.0ms
335.0ms
516.0ms