::: Loading :::

Runmore 10k

2016

A challenging project with Adidas to promote their 10k running club culminating in a 10k run the extra hour during daylight savings time at midnight.

The main page had a custom canvas implementation which showed the 10k logo reflecting the image around it on a carousel.

Running map paths were generated using google maps and then animated as an svg. A banner was also created to promote the event using this animation technique.

When results were made available it was a searchable list by name or by bib number.

Ouija

OuiJa Board

2017

Still adjusting to the high of working in a new exciting position, Tribal had a monthly event called Hot Dog Friday. Each month a department was elected to put together a lunch on a limited budget.

I wanted to make a fun interactive for our Halloween themed invite and forged ahead making this little guy. It will send a custom message and a user was able to send their own message at the end of the current one.

A simple but effective use of animation, graphics and ambience to create a page that fit well with the time of year.

Build and Price

2018

A full rewrite of the build and price page for VW. Requirements were that it work in dealers sites as well as on the vw.ca main website.

Data was pulled in from a few sources depending on the type of data being retrieved.

Built as a single page application, the page constantly recalculated costs, taxes, and options for leasing, financing and cash purchases. As well, there is a 360 degree vehicle viewer, trim option comparison, and credit application integration.

Game

Winter 2017, 2018, 2019

To gather information from potential buyers at the Canadian Auto Show a web app was needed to capture information through a form on iPads. Enticement to get people to enter the info was done through a game and prizes. Around twenty iPads were populated with the app.

The game changed each year. The requirement of the game be that it should be very quick to get through. Wheel spin, slots, and 'Plinko' were the games made each year as they met the requirement.

Data for the forms had to be stored locally in case a wi-fi or 3G connection was not available at the time. When a connection became available it would upload any form data that was currently remaining in the Queue. A limited backup was also programmed in as a failsafe in case the data transmissions were lost due to unforseen circumstances.

In case of disconnect the app must also be self-contained and properly reset for the next user. This included form fields, animations and page progression.

Winning at the game was predetermined based on the odds of winning which shifted slightly based on how many prizes in each category remained day to day and as Mazda moved from show to show.

Discover Nova Scotia (defunct)

Summer/Falls 2018

The goal was to make a curated selection of experiences that someone visiting Nova Scotia might want to experience.

To start we worked with UX and User Testing. Once a framework was agreed upon a matrix of all the experiences was created and weighted so they would appear with certain selections.

Data was pulled from the Drupal database through multiple JSON feeds depending on the step and based on the matrix cross reference.

Results included google maps as well as the ability to save/share selections for later viewing.

This page only works up to the selections as the DB is no longer accessible

Give a Dam

Summer 2018

This page is a one off stunt that is a cornerstone of a lot of Skittles' work. Tight timelines are also their calling card and there's a lot here for less than a week's time.

The page had three states, depending on the current date and the target date. All states would switch automatically on certain timer targets.

Before the date of the even the page would show lead up animations and graphics with a countdown. During the execution a video played where we kept track of video plays in a simple text file. After the event a user was able to still watch the video and see the total views that were accumulated before the end date.

Skittles Pride

Spring 2019

Another quick execution, but now we can leverage some of the previously built page for time savings and in turn, give us more time to spend on massaging the animation in this one.

The page had one less state than the previous execution, and but is still dependant on current date and the target date.

Approaching the event, the user would see a stay tuned type page. Once the date occured, the user would see a selection of LBGTQ couples to celebrate the day.

Last Minute Xmas

Winter 2019

This one had a physical store in downtown Toronto that was only open for one minute.

Not an overly complicated execution. It required only some simple javascript functions like snow and rotations. Also, a calendar invite was created which needed to be as compatible as possible with the major calender programs out there.

As a bonus, it also includes, possibly, the dumbest game that exists on the internet.

Figr Website

Fall 2019/Spring 2020

Multiple projects were involved in this site.

First, was the migration of a bare-bones php site to Episerver, which required rewriting of many modules and SCSS.

Second, the addition of the vapourizers to the site. This required a minor rebuild of the navigation and many of the pages did not coincide with existing models and had new code written.

Lastly, in the major changes, was the second carousel on the home page went through an overhaul. Tabs were added that switch between two carousels and code was modified for better support in mobile.

Mazda Forms

Summer/Fall 2020

All forms were in need of a ux upgrade and migration to a different API. As the forms are used across multiple technologies (Angular 1.5, Wordpress, Episerver) it was thought the optimal execution in the interim was Vanilla JS. Forms involved were Contact a Dealer, Request a Quote, Book a Test Drive and Contact Us.

Previously these forms all used different codebases and functionality, and content was not consistent between them.

These forms access a dealer API to retrieve JSON data. When the dealer list shows, then a google map is shown alongside, with a limited amount of dealers available at a time.

The build and price version has three of the forms existing on the same page. So some of the form data must act independently of each other but some musts also work together for the user to have less interactions to have to deal with.

You can see an example of the programming and file structure in the code section.

Mazda Holiday Card

Summer/Fall 2020

Created for the mazda client to send out to their own clients. The main features of the execution is a large zoom out and an animated fire.

The animations are done in greensock. The fire uses a giant spritesheet to play as a video. This allowed for full browser support without have to press a play button or unwanted interactions in mobile.

Code

Vanilla JS Controller

				
					var controller = (function(genCtrl, uiCtrl, dealerCtrl, formCtrl, formValCtrl){

				    var DOM = uiCtrl.getDomStrings();
				    var DAT = uiCtrl.getDataVars();

					var firstRun=true;
				    var formNum=1;
				    //which menu item to start at.
				    var startForm=2;
				    var lang= genCtrl.detectLanguage();
				    var root;

				    function setFormRoot(elementNum){
				        genCtrl.setRoot(elementNum, DOM.root);
				        root=document.querySelectorAll(DOM.root)[elementNum];
				    }

				    //loading in any cdns required
				    function loadCDNs(){
						if(firstRun){
							firstRun=false;
							var bodyEl=genCtrl.findEl(document, 'body');
					        //load gsap via cdns
					        var script =  document.createElement('script');
					        script.onload = function () {
					            var script =  document.createElement('script');
					            script.src = 'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/ScrollToPlugin.min.js';
					            script.onload = function () {
					                for(var i=0;i<formNum;i++){
					                    setFormRoot(i);
					                    uiCtrl.toggleAccordion(0);
					                }
					                if(genCtrl.findEl(document, DOM.formSelector, true).length > 0){
					                    genCtrl.findEl(genCtrl.findEl(document, DOM.formSelector+ ' li', true)[startForm], 'a').click();
					                }
					            };
					            bodyEl.appendChild(script);
					        };
					        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/gsap/3.5.1/gsap.min.js';
					        bodyEl.appendChild(script);
					        //load fontawesome
					        script =  document.createElement('script');
					        script.src = 'https://kit.fontawesome.com/95aa5ba783.js';
					        bodyEl.appendChild(script);

					        //check for book appointment
					        if(genCtrl.findEl(root, DOM.apptCont) != undefined){
					            script =  document.createElement('script');
					            script.onload = function () {
					                //choose lang for book appt js
					                if(lang=='en'){
					                    flatpickr(DOM.apptPicker, {
					                        dateFormat:'d/m/y',
					                        minDate: 'today',
					                        maxDate: new Date().fp_incr(365),
					                        onChange:function(){
					                             uiCtrl.checkApptFields();
					                        }
					                    });
					                }else{
					                    var script =  document.createElement('script');
					                    script.src = 'https://npmcdn.com/flatpickr/dist/l10n/fr.js';
					                    script.onload = function () {
					                        flatpickr(DOM.apptPicker, {
					                            'locale': 'fr',
												dateFormat:'d/m/y',
						                        minDate: 'today',
						                        maxDate: new Date().fp_incr(365),
						                        onChange:function(){
						                             uiCtrl.checkApptFields();
						                        }
					                        });
					                    };
					                    bodyEl.appendChild(script);
					                }

					            }
					            script.src = 'https://cdn.jsdelivr.net/npm/flatpickr';
					            bodyEl.appendChild(script);
					            //<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
					            //<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
					        }
						}
				    }


				    function setupEventListeners(){
						//select menu
				        var selectArr = genCtrl.findEl(document, '.formsMenu ' + DOM.dropdownHelper1, true);
				        selectArr.forEach(function(current, index, array) {
				            current.addEventListener('click', showHideSelect)
				        });

				        //postal/dealer button
				        if(genCtrl.findEl(root, DOM.postalButton) != undefined){
				            genCtrl.findEl(root, DOM.postalButton).addEventListener('click', checkPostal);
				        }

				    };
				    function changeForm(e){
				        e.preventDefault();

				        uiCtrl.changeForm(e);

						genCtrl.sendTracking('change form - ' + e.target.innerHTML);
				    }
				    function showHideSelect(e){
				        e.preventDefault();
				        genCtrl.setNewRoot(e.target, DOM.root);

				        e.target.parentNode.classList.toggle('selected');

				        var parentSelect = genCtrl.findParent(e.target, '.asset-select');

				        var optionTarget = genCtrl.findEl(parentSelect, DOM.dropdownHelper2);

				        optionTarget.classList.toggle('open');

						genCtrl.sendTracking('contact - select - email subject');
				    };

				    return{
				        //run initial setup
				        init: function(){
				            console.log('started : controller : init');
				            if(document.querySelectorAll('.mazdaFormsBP').length != 0){
				                formNum=document.querySelectorAll('.mazdaFormsBP form').length;
				            }
				            for(var i=0;i<formNum;i++){
				                setFormRoot(i);
				                loadCDNs();
				                uiCtrl.init();
				                formCtrl.init();
				                dealerController.init();
				                formValCtrl.init();
				                setupEventListeners();
				            }
				        }
				    };
				})(generalController, uiController, dealerController, formController, formValidationController);

				controller.init();

				
			

Summer/Fall 2020

A portion of the Controller for the Vanilla JS forms for Mazda.

The controller, as it's name implies imports most js files. Initializes event listeners and loads some required libraries if not already active.

Vanilla JS AJAX functions

				
					//get ajax data
			        doAJAXcall: function(tempURL, callback, sendType, beforeSend, outputData){
			            //force type get
			            if(sendType == undefined){
			                sendType='POST';
			            }
			            var xhr = new XMLHttpRequest();
			            xhr.onload = function () {
			            	// Process our return data
			            	if (xhr.status >= 200 && xhr.status < 300) {
			                    //We will strip any newline from our response.
			                    var TempResponse = xhr.response.replace(/(\r\n|\n|\r)/gm, "");
			            		// return the data
			                    callback(JSON.parse(TempResponse));
			            	} else {
			            		// What do when the request fails
			                    var tempObj = new Object();
			                    tempObj.sucess = false;
			                    //you can change this error and add specific messaging in error.js
			                    tempObj.state = 'general';
			                    callback(tempObj);
			            	}
			            };
			            xhr.onerror = function(){
			                var tempObj = new Object();
			                tempObj.sucess = false;
			                tempObj.state = 'general';
			                callback(tempObj);
			            };


			            //send credentials
			            if(beforeSend != undefined){
			                xhr.withCredentials = false;
			                xhr.open(sendType, tempURL, true);

			                xhr.setRequestHeader(beforeSend[0], beforeSend[1]);
			                xhr.setRequestHeader("Ocp-Apim-Trace", "true");
			            }else{
			                xhr.open(sendType, tempURL);
			                xhr.setRequestHeader("Accept", "application/json");
			                xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
			            }
			            if(outputData== undefined){
			                xhr.send();
			            }else{
			                xhr.send(outputData);
			            }
			        },
				
			

Summer/Fall 2020

A function written in Vanilla JS for AJAX calls.

This function allows data to be passed through the AJAX call, recieve data and make a callback function.

jQuery

				
					//initial load of API content
					build_price.load_api = function(){
						//initial load of api
						$.ajax({
							url:bp_api_url + '/api/?' + $.param(build_price.url_params),
							type:'POST',
							contentType: 'application/json; charset=utf-8',
							dataType: 'html',
							success: function(data) {
								if($('div[data-bp-build]').length) {
							    	$('div[data-bp-build]').html(data);
								} else if(build_price.url_params.scr == '-1'){
									$('body').append(data);
								}
						    	if(build_price.url_params['scr']==0 || (build_price.url_params['scr']==1 && build_price.layout!='dealer')){
									$('#vw_ca_bp [data-js-menu-container]').hide();
									$('#vw_ca_bp [data-bp-menu]').hide();
								}
						    	build_price.set_variable('s', 0);
						    	build_price.set_variable('m', 0);
						    	build_price.set_variable('bp', 0);
						    	if(build_price.get_variable('t60')==1){
						    		build_price.load_three_sixty();
						    	}
						    	if(build_price.get_variable('scr')>1){
							    	if(typeof build_price.get_exceptions === 'function'){
							    		build_price.get_exceptions();
							    	}
							    }
								// only jump to top if own website and inside NGW
								if(window.location.pathname == '/build/' || window.location.pathname == '/build') {
				                    window.scrollTo(0, 0);
								}
								build_price.add_legal();
						  	},
						  	fail: function(e) {
						    	alert( 'error'+ e);
						  	}
						})
					};

				
			

Summer/Fall 2018

An example of some JS using jQuery.

PHP

				
					<?php

					$build_params=$_GET;

					if(isset($build_params['lang'])) {
						Language::setLang($build_params['lang']);
					}

					$LEGAL = BuildAndPrice::getLegal();
					?>

					<script type="text/javascript">
						build_price.version = '<?php echo BUILDANDPRICE_VERSION; ?>';
						build_price.lang = '<?php echo Language::getLang(); ?>';
						build_price.hostname = '<?php echo HOSTNAME; ?>';
						build_price.env = '<?php echo ENV; ?>';
						build_price.vw_binaries = '<?php echo VW_BINARIES; ?>';

						build_price.isiOS = '<?php echo Browser::isApple() ? 'true' : 'false'; ?>';
						build_price.isAndroid = '<?php echo Browser::isAndroid() ? 'true' : 'false'; ?>';
						build_price.isPaymentEstimator = <?php echo BuildAndPrice::isPaymentEstimator() ? 'true' : 'false'; ?>;
						build_price.creditformURL = '<?php echo BUILDANDPRICE_CREDITFORMURL; ?>';

						build_price.threeSixty=18;
						build_price.threeSixtyStart=17;

						build_price.messages=<?php echo json_encode(Language::getAllTranslations('messages')); ?>;

						build_price.LEGALdata=<?php echo json_encode($LEGAL); ?>;
					</script>

					<?php
					//set some initial vars
					if(isset($build_params['l']) && $build_params['l']=='d'){
						$layout='dealer';
					}else if(isset($build_params['l']) && $build_params['l']=='m'){
						$layout='model';
					}else if(isset($build_params['l']) && $build_params['l']=='g'){
						$layout='german';
					}else{
						$layout='default';
					}

					//pass default values for vehicle
					if(isset($build_params['y']) && $build_params['y']!=''){
						$year = $build_params['y'];
					}
					if(isset($build_params['fa']) && $build_params['fa']!=''){
						$family_abbreviation = $build_params['fa'];
					}
					if(isset($build_params['ma']) && $build_params['ma']!=''){
						$model_abbreviation = $build_params['ma'];
					}
					$trim_abbreviation = '';
					if(isset($build_params['ta']) && $build_params['ta']!=''){
						$trim_abbreviation = $build_params['ta'];
					}

					if (isset($build_params['y']) && $build_params['y']!='') {
						$triminformation = BuildAndPrice::getTrimInformation($year, $family_abbreviation, $model_abbreviation, $trim_abbreviation);
					}

					$lang = Language::getShortLang();

					$build_params=$_GET;
					if( isset($_SERVER['HTTPS'] ) ) {
						$server='https://';
					}else{
						$server='http://';
					}
					//reset css for dealers.
					//css is put inline to overwrite their inline code
					if($layout=='dealer'){
						$inline_css='#vw_ca_bp *, #vw_ca_bp *:before, #vw_ca_bp *:after{margin: 0;
						padding: 0;
						border: 0;
						font-size: 100%;
						font: inherit;
						vertical-align: baseline;
						font-family:inherit;
						color:inherit;
						line-height:initial;
						};';
					}
					//initial build
					if(isset($build_params['s']) && $build_params['s']==1){
						if($layout=='dealer'){
							echo '<div id="vw_ca_bp" class="vw_ca_dealer">';
						}else{
							echo '<div id="vw_ca_bp">';
						}

						//load style guide css
						if($build_params['sgc']==1){
							if($layout=='dealer'){
								$inline_css.=nl2br(file_get_contents(STYLEGUIDE_URL .'css/vw-styleguide.css'));
							}else{
								echo '<link rel="stylesheet" href="'. STYLEGUIDE_URL .'css/vw-styleguide.css">';
							}
						}
						//load style guide js
						if($build_params['sgj']==1){
							echo '<script src="'. STYLEGUIDE_URL .'js/vw-scripts.js"></script>';
						}
						//add css and js
						if($layout=='dealer'){
							$inline_css.=nl2br(file_get_contents($server . HOST .'/assets/css/default.css'));
						}else{
							echo '<link rel="stylesheet" href="//'. HOST . '/assets/css/default.css">';
						}

						if($build_params['owl']==1){
							if($layout=='dealer'){
								$inline_css.=nl2br(file_get_contents(STYLEGUIDE_URL .'css/owl.carousel.min.css'));
							}else{
								echo '<link rel="stylesheet" href="'. STYLEGUIDE_URL .'css/owl.carousel.min.css">';
							}
							echo '<script src="'. STYLEGUIDE_URL .'js/owl.carousel.min.js"></script>';
						}
						if($layout=='dealer'){
							//css overwrite for menu margin from top
							if(!empty($build_params['mtm'])) {
								$inline_css .= '#vw_ca_bp .menu_container{top:'.$build_params['mtm'].'px;}';
							}
							if(!empty($build_params['mtd'])) {
								$inline_css .= '@media (min-width: 767px){#vw_ca_bp .menu_container{top:'.$build_params['mtd'].'px;}}';
							}
							$inline_css2 = str_replace('url("../fonts/', ('url("'. STYLEGUIDE_URL .'fonts/'), $inline_css);
							echo '<style>' . $inline_css2 . '</style>';
						}
						if(isset($build_params['scr']) && $build_params['scr']!=-1){
							//add js files????
							echo '<script src="//'. HOST . '/assets/js/payment_calcs.js"></script>';
							echo '<script src="//'. HOST . '/assets/js/exceptions.js"></script>';
						}
					}

					//load build and price layout
					if(isset($build_params['bp']) && $build_params['bp']==1){
						echo '<div data-bp-container>';

					}

					//load in menu
					if(isset($build_params['m'])){
						if($build_params['m']==1){
							include_once(WWW . 'menu.php');
						}
					}

					if (isset($build_params['bp']) && $build_params['scr'] != 0 && $build_params['scr'] != 1 && $build_params['scr'] != 7 && isset($build_params['po']) && $build_params['po']==1) {
						echo '<div class="bp_flex_container">';
					}

						//set up basic vehicle variables for use in JS
						if (isset($build_params['y']) && isset($build_params['scr']) && $build_params['scr']!=-1) {
							include_once('trim_info.php');
						}

						//which module to send
						if(isset($build_params['scr'])){
							if($build_params['scr']==0){
								//send model select

							} else if($build_params['scr']==1){
								//send trim

							}else if($build_params['scr']==2){
								//send exterior

							}else if($build_params['scr']==3){
								//send interior

							}else if($build_params['scr']==4){
								//send packages

							}else if($build_params['scr']==5){
								//send accessories

							}else if($build_params['scr']==6){
								//send maintenance

							}else if($build_params['scr']==7){
								//send payment

							}
						}

					if (isset($build_params['bp']) && $build_params['scr'] != 0 && $build_params['scr'] != 1 && $build_params['scr'] != -1 ) {

						if(isset($build_params['po']) && $build_params['po']==1 && $build_params['scr'] != 7){
							echo '<div class="exception_warning">'.
									'<div><i class="icon-warning"></i><p></p></div>'.
									'<p></p>'.
								'</div>';
						}

						echo '<div class="message_overlay">'.
								'<div>'.
									'<div class="message_close" data-js-feature_close><i class="icon-close"></i> </div>'.
									'<i class="icon-warning"></i><p></p>'.
								'</div>'.
							'</div>'.
						'</div>'.
						'<div class="legal" data-js-legal>'.
							'<!-- legal inserted here through JS -->'.
						'</div>';
					}
					if(isset($build_params['bp']) && $build_params['bp']==1){
						echo '</div>';
					}
					if(isset($build_params['s']) && $build_params['s']==1){

						//close wrapper
						echo '</div>';
					}
					?>

				
			

Summer/Fall 2018

A portion of the PHP that initializes what content will be sent to site depending on variables passed.

Folder Structure

While somewhat minor. An example of a typical folder structure that I would employ in a project.