About

DIGITAL DNA

I’m an online professional, front-end developer, creative technologist, web guru, code monkey, pixel pusher. Call me what you will. Over the past 20+ years, I have designed, developed, project managed, or consulted on hundreds of web sites, web apps, and native apps, for many fortune 500 clients. The best part of it all, is knowing that millions of users each day are interacting with my work.

VIEW MY RESUME

Scroll for more

Employment History

Amazon Web Services - S3 Console
Software Development Manager, Toronto, Ontario, July 2022 to Present

  • Lead a team of software engineers in the design, development, testing, and deployment of highly scalable and reliable web applications for the AWS S3 Console.
  • Collaborate with product managers, UX designers, and other stakeholders to define product requirements, prioritize feature development, and align team goals with business objectives.
  • Drive innovation and continuous improvement in software development processes and technologies to increase team productivity, software quality, and customer satisfaction.
  • Hire, coach, mentor, and motivate a diverse team of software engineers with different levels of experience and expertise, fostering a culture of learning, collaboration, and ownership.
  • Manage team resources, budgets, and timelines to ensure on-time delivery of high-quality software releases that meet or exceed customer expectations.
  • Communicate project status, risks, and opportunities to senior management and cross-functional teams, providing transparency and accountability for team performance and outcomes.
  • Develop and maintain technical skills and knowledge of AWS services, software development methodologies, and industry trends, sharing insights and best practices with the team and the broader AWS community.

Alteris Group
Director of Technology, Windsor, Ontario, May 2018 to July 2022

  • Defining the vision of the Digital arm of Alteris Group.
  • Managing and inspiring the best out of 25 digital team members across multiple disciplines.
  • Working with lead developers and IT specialists to ensure DevOPs best practices are followed.
  • Working closely with DevOPs to ensure best practices and efficiencies are maintained in our client and company AWS tenants. Recently decreased our AWS monthly hosting fees by 40% by switching the reserved instances, switching scattered dns records to route 53, automating cert renewals using Let’s Encrypt, and moving projects to docker and elastic containers.
  • Help define cybersecurity policies and work with our Fortune 500 clients to make sure our processes work within their policies whether they are SOC-2 or ISO 27001 or even custom.
  • Work with lead developers to automate our continuous integration process for mobile and web-based projects.
  • Responsible for integrating TDD, SAST, and DAST processes in to our development workflow improving our average bug count per project astronomically.
  • Lead the development teams and help define features for Alteris’ three core SaaS products. Covid-19 Compliance Coach, BetterAppBuilder, and Learn2Go.
  • Working with the UX Creative Director I helped define a new creative workflow to move projects through the digital teams much more efficiently.
  • Actively involved in most pitches for new digital projects.
  • Built from scratch our own digital marketing and communications service and processes utilizing Mautic, an open source lite CRM used extensively with our Ford Motor Company, Henkel, and other clients.
  • Developed and integrated custom e-commerce purchases in multiple client sites and our own covidcomply.com using Stripe Commerce APIs.
  • Responsible for interviewing, hiring, and on-boarding new developers and digital team members.
  • Estimate developer hours for all digital projects and work with account and project managers to define scope and timelines.
  • Pitched to company owners and implemented innovation processes that allow developers certain amount of time to experiment with new technology that could help current and future projects and processes. Developers then present their findings to the larger digital team.
  • And when I can, get deep into the weeds and help developers debug code, architect solutions, or take on overflow projects because it’s what I enjoy most.

Scott Morgan Consulting
Owner, Manager, Lead Developer, Calgary, Alberta, May 2006 to July 2022

  • Lead Web Developer for all web projects created by mobile agency AppColony.
  • Worked with some of the biggest Marketing and Interactive agencies on award winning projects as an independent contractor.
  • Contacted to convert multiple existing sites to fully responsive or adaptive sites.
  • Client list highlights includes Logitech, Denny’s, Subaru, Audi Canada, Kraft, Clorox, Hockey Canada, Pepperidge Farms, Johnson and Johnson, The Bay, American Express, Ford, Shell Canada, Zellers, Brookfield Homes, and Home Outfitters
  • As an independent development consultant I have worked on the full stack from designing database schemas to interface design.
  • Developed successful award winning projects using the following technologies and libraries, HTML5, JavaScript, CSS, JQuery, node.js, iOS native apps, Android Apps, PHP, WordPress, Flash, Flex, AIR, MySQL, MS SQL, Image Magick, Alive PDF, Papervision 3D, Away 3D, and much more
  • Lead developer on award winning O.B. Tampon Apology viral campaign which generated 1.2 million shares on Facebook, and attracted 47 million unique visitors to the campaign website.
  • Built a single custom CMS solution that allowed a real estate client to not only update multiple websites, they also had the ability to update multiple kiosks and interactive screens throughout the city, and generate press ready, high-res marketing material that is sent directly to the printer without the need and expense of a graphic designer.
  • Developed native iPhone, iPad, and Android apps for Amercan Express, Canadian Olympic Hockey Team, and Kraft Macaroni and cheese.
  • Working with Strut Creative I built software using AIR and helped design the hardware setup for a 60” real estate touch screen kiosks that are used in multiple community show rooms throughout Alberta and allow consumers to view available communities, lots, and models, save lots and models to their favourites so they can access them later on their iPhone or home computer. Touch screen galleries also have interactive image and video galleries for selected models.
  • Built a custom CMS that not only controlled the content on a fully responsive custom built website but also allowed administrators to create high definition, print ready materials using assets uploaded into the CMS and a custom print template management tool.

AppColony Inc.
Lead Senior Front End Developer , Victoria, British Columbia, March 2014 to May 2018

  • Lead Front End Developer for all agency web development projects and most internal projects.
  • Responsible for educating other developers on best front end development practices, including cross browser techniques, responsiveness, optimization, accessibility standards, and overall end user experience.
  • Work closely with back end developers and the design team to ensure functional and business logic, and the visual experience expectations are all met,
  • Currently building a fully responsive, accessible e-commerce solution for one of Canada’s most trusted hardware and home improvement stores. The site is built using a CSS Grid layout with custom SASS mixins that ensure backward compatibility with the different versions of the spec that IE Edge and IE 11 support. The site is built on top of Sitecore and uses the Sitecore Commerce plugin with custom experience manager editor layouts that allow for the same templates to be used in the visual content editor. I also used Vue.js for content templating and CSS3 animations and keyframing for a better user experience during the shopping experience.
  • Recently built multiple sites using Gatsby.JS a React framework for static site generation. Gatsby.JS uses GraphQL to abstract an apps data layer. I have used Gatsby with pure static data (JSON), and with Contentful and Wordpress exposed REST APIs. I have played around with using Redux and Gatsby but haven’t had the opportunity to build an actual app yet.
  • I built a highly successful version of AppColony’s most popular native mobile app MakeShift. What started as a prototype is now a fully functional React single page app using ReactRouter and the Fluxbone pattern. The Fluxbone pattern uses Backbone for it’s data models/stores, collections and out of the box CRUD support. Using the existing mobile auth and API endpoints, I was able to use the exact same backend for my app. The entire multilingual single page app is developed using ES6, some vanilla Javascript, JSX, and SASS. The Moment.JS library is heavily used to handle the extensive timezone logic and date parsing and formatting throughout the app. Originally bundled with Ruby’s Middleman and a handful of gems I most recently converted everything to use Webpack and NPM as the bundler. To date the application has been white-labeled for 3 different clients in 3 different languages, and other than SASS overrides, and some configuration files and environment variables, the main codebase remains the same for each of the clients.Initially the site used Jasmine for testing, but I am currently converting the specs to be used with JEST and like most projects use CircleCI for our continuous integration.
  • Again, using existing mobile APIs I built the web version of AppColony’s MakeShift Time Clock application. This application allows a user to enter their employee ID, if a shift exists within a 1/2 hour buffer both ways, the user can punch in to their shift, or punch out if they are already punched in. Using geolocation the punch in location ensures they are punching in/out where the shift is actually located. If they are not located within a geo-fenced area around their shift location the application will not allow them to punch in/out. This site was built using Ruby on Rails, HTML5 in erb templates, with ES6, and SASS.
  • Using the Stripe SDK I built a custom e-commerce subscription purchasing site for Hockey Canada’s Network application. The fully responsive experience allows users to bulk purchase access keys for the minor hockey coaching application. Using CSV and Excel parsing written in PHP, the user is able to upload a spreadsheet of coaches and using regex I determine which fields contain email addresses and access codes are emailed to each of the coaches once the purchase is validated through the Stripe response. I also use the Gmail API and the Outlook API so a user can manually select coaches email addresses from their address books and send them access codes. Custom email templates are populated and sent out using the Mailgun php SDK.
  • I was part of the team who built the custom CMS for the Hockey Canada Network mobile app. The CMS was built completely in Rails and used React modules throughout. I was responsible for developing the RichText Editor module, Image uploader module, Smart Search module, and the content preview module which gives users the ability to preview their entered data and media in virtual device emulators using the same markup and styles the in app webviews use.
  • In the early days at AppColony I built many custom Wordpress sites for many different clients including the Men’s Olympic Hockey team, Grey Eagle Casino, and The Cooperators insurance company. Most of these Wordpress sites included custom widgets and plugins, completely custom editors and custom themes.
  • I love to learn and expand my skillset so I started our bi-monthly lunch and learn sessions where developers share new languages, or a new feature in an existing language. I have personally demo’d CSS Grid and Flexbox, React and React Native. Other topics that I have sat in on lately are Elixer, Phoenix and Swift.

Walt Disney Internet Group
Senior/Lead Front-End Software Engineer, North Hollywood, California, September 24, 2007 to March, 2014

  • Lead developer responsible for building custom, fully responsive, HTML5 WordPress themes, plugins and widgets for the Disney Blog suite (http://blogs.disney.com, http://blogs.disney.com/insider, http://blogs.disney.com/oh-my-disney/, http://blogs.disney.com/music, http://blogs.disney.com/movies)
  • Built a fully responsive multi-column menu system similar to the Pinterest anti-grid for WordPress posting list using HTML5, CSS3, and JQuery.
  • One of many developers responsible for the recent Disney.com HTML5 overhaul.
  • Worked closely with Google engineers to come up with a custom solution that allowed DoubleClick For Publisher ads to run and be refreshed on 100% Flash sites with no traditional page refreshes using AJAX based JS calls triggered by custom ActionScript events.
  • Hand picked to be on internal home page optimization team. As part of the optimization team we increased the load time of the homepage upwards of 70 to 80 per cent.
  • Front-end lead on Project Jupiter, a HTML5 based templated framework used on hundreds of Disney sites including the homepage, music, movies, and books flagship sites. Technologies used were HTML5, Javascript, CSS3, Groovy, Flash, Actionscript, JQuery, and JSP.
  • As Project Jupiter lead I was also part of a team responsible for introducing Agile and Scrum development methodologies to the Disney Interactive Group, the same team was also responsible for converting source control from Perforce to GIT.
  • Lead developer for Flex based CMS presentation layer that allowed non-technical staff to program and schedule content on hundreds of Disney sites using a WYSIWYG interface.
  • Worked directly with the Pixar Wall-e animators to create a never done before, fully immersive, custom homepage takeover.
  • Built framework and dynamic content modules in Actionscript 3.0 and HTML/CSS/JS that controled the homepage and Disney portal sites.
  • Using Papervision3D built a dynamic carousel component for Disney homepage and other areas on the Disney site. I presented at Flash Forward in San Francisco the mechanics of the homepage and the dynamic carousel.
  • Lead team of 9 internal and external developers in huge Actionscript 2.0 to Actionscript 3.0 port of Disney Extreme Digital site.
  • Mentored numerous developers during Actionscript 2.0 / Actionscript 3.0 transition.
  • Worked closely and presented to many creative leads educating them of the possibilities of Actionscript 3.0 and Actionscript 3.0 libraries.
  • Developed innovative prototypes and presented to internal teams and executives allowing them to see alternative, innovative ways of bettering user experiences.
  • Successfully worked remotely for 2.5 years with numerous teams.

Yahoo! Inc.
Senior Platform Engineer, Sunnyvale, California, December 11, 2006 to September 1, 2007

  • Worked with internal teams to better Flash development and evangelizing new technologies at Yahoo!
  • Built original Yahoo! Maps Actionscript 3 API.
  • Built custom components that could be shared by multiple Yahoo! properties which in turn decreased the amount of duplicate development.
  • Developed and policed Yahoo! Actionscript and front-end development best practices.
  • Helped set up and contributed regularly to the Front-end development portal on the Yahoo! Developer Network.
  • Trained other Yahoo! engineers who want to learn more about front-end development or simply better their skills.
  • Looked for opportunities internally and evangelized front-end technologies where a technical match seemed reasonable
  • Work with other properties to ensure all front-end work is optimized and ensured best practices are used
  • Extended our knowledge out to the front-end development community through blogs, and multiple conference presentation, and other online avenues.
  • Integral contributor to the Yahoo Actionscript SDK development
  • Solely architected and developed Yahoos Badge Kit, a completely customizable and extendable architecture that is used by properties within Yahoo to rapidly build badges and tie into their back end systems.

Critical Mass Inc.
Senior Flash Developer and Flash Development Manager, Calgary, Alberta, May 2, 2005 to December 8, 2006

  • Lead Flash developer on multi-million dollar, award-winning Rolex account.
  • Buit a fully SEO (Search Engine Optimized) Flash site with deeplinking and browser back button support using traditional browser based technology and Actionscript. The site was showcased at SES (Search Engine Strategies) Conference.
  • Lead Flash developer on multiple award winning Dell.com flash projects and microsites.
  • Worked with Dell Storm Developers to integrate data dynamically into e-commerce Flash pieces and worked with key Dell employees ensuring that a proper Flash development process was built and followed internally and externally with other vendors.
  • As Flash Development Manager I oversaw all flash development within Critical Mass, worked with other managers to improve process, interviewed potential employees, reviewed current employees, assisted employees in goal development, acted as the "go to" guy for anything Flash related, and ensured Flash Development best practices were adhered to on all projects at Critical Mass.
  • While leading the Rolex.com initiative I successfully built up the flash team from 4 developers to 14 across 3 offices in Canada and the United States.

Innovasium Inc.
Senior Solution Developer, Markham, Ontario, February 24, 2003 to April 27, 2005

  • Design and develop dynamic Flash Remoting web applications using Cold Fusion and .NET connected to SQL server or Access databases.
  • Developed a scalable Flash Remoting framework/site template with Content Management system and complete tracking system that allowed clients to create fully customized Flash and CFML sites from the ground up.
  • Successfully developed a Flash Remoting site, CFML site, and Content Management system for the Government of Ontario (http://www.giftoflife.on.ca).
  • Effectively designed and developed a Flash Remoting site with Content Management system using .NET and C# (http://www.rentshieldcorp.com).
  • Successfully developed a secure, Flash Remoting E-Commerce environment with order processing tools, and product management systems for multi-million dollar companies (http://www.tridentdental.com).
  • Developed a bilingual, secure, customized, role-based warehouse management system with inventory management tools, customer ordering interfaces, catalog management system, and real time reporting tools.
  • Developed a new home configurator and content management system for one of the largest GTA homebuilders. The site allowed users to configure custom homes and print or save dynamic PDF versions of their home.

McGill Multimedia Inc. (formerly Mosaic Inc.)
Senior Interactive Designer / Group Supervisor, Downtown Toronto, May 1998 to February 2003

  • Designed, templated and developed web sites using HTML, ASP, SQL, JavaScript, DHTML, CSS, and Flash/ActionScript for many large clients.
  • Designed and developed SCORM compliant learning objects that were accessible to over 40,000 DaimlerChrysler employees via the web and CD-Rom.
  • Was lead designer and developer for DCA, the DaimlerChrysler Training Management System. This system provided more than 200,000 DaimlerChrysler employees the ability to enroll in online and offline courses, track their progress, run reports, and manages the employee certification program.
  • Was lead designer and information architect for the award-winning, multi-million dollar DaimlerChrysler Vehicle Information Center.
  • Designed and documented the functionality of Internet/Intranet sites, CD-Rom, DVD-Rom / Titles, and M2 applications.
  • Managed a team of technical interactive designers, management duties included: resource management, process development, employee reviews, hiring, etc.
  • Assisted in proposal development including but not limited to prototypes, estimates, timeline, and technical strategies.
  • Administered usability testing on different applications in all stages of production.
  • Was responsible for many other Information Architecture duties including requirement gathering, content definition, content organization, goal verification, and content development.
  • Assisted in the raw designs of many applications on many different platforms (Web, CD-ROM, DVD-Rom, and DVD-Titles).
  • Responsible for research and development of present technologies and software.

Ross Roy Communications Inc.
Interactive Media Traffic Coordinator, Detroit Michigan, January 1998 to April 1998

  • Worked closely with the entire Interactive team during the creation of the DaimlerChrysler Web Sites.
  • Actively participated in brainstorming sessions for DaimlerChrysler Brand Sites.
  • Responsible for routing work throughout the agency.
  • Assisted Project Managers with billing and project budgets.

Education

CIMDI
ASP.NET and Macromedia UltraDev 3.0, Toronto, Ontario, December 2000

  • Advanced course that taught how to use Macromedia UltraDev 3.0.
  • Learned advanced ASP.NET, focused on database connections, SQL statements, SQL optimizations, and record paging.
  • Final project was an ASP.NET e-commerce site and CMS using both UltraDev 3.0 and hand coded ASP.NET.

St. Clair College
Advertising and Graphic Design Program, Windsor, Ontario, 1993 to 1997

  • Studied Copywriting, Design and Production. Account Planning, Web Development, Media Planning, Marketing, and Television and Radio Production.
  • Final Semester G.P.A. was 3.79.
  • Winner of the Creativity in Communication award amongst graduating class.

Awards

  • Gold Clio Award – Public Relations – O.B. Tampons Personal Apology
  • Bronze Cannes Lions – Public Relations – O.B. Tampons Personal Apology
  • The Directory Big Won – O.B. Tampons Personal Apology
  • Marketing Awards – Film Online:Single – O.B. Tampons Personal Apology
  • Marketing Awards – Digital Websites/Microsites – O.B. Tampons Personal Apology
  • Marketing Awards – Pushing the Boundaries – O.B. Tampons Personal Apology
  • Gold and 2 finalist The Bessies 2012 – O.B. Tampons Personal Apology
  • Bronze London International Awards 2012 – O.B. Tampons Personal Apology
  • 2 Gold, 1 Silver, 1 Bronze Media Innovation Awards 2102 – O.B. Tampons Personal Apology
  • Gold Applied Arts Awards – O.B. Tampons Personal Apology
  • 2 Honourees Webby Awards 2012 – O.B. Tampons Personal Apology
  • Ads of The Year 2012
  • Globe & Mail – O.B. Tampons Personal Apology
  • TED The ideas worth spreading – O.B. Tampons Personal Apology
    • Lead Front-end Developer working with the advertising agency Lowe Roche.
  • Webbie – Peoples Voice Award – Disney.com/Games
    • Lead Front-end Developer on Disney Portal Framework upon which the games portal ran on.
  • Adobe Site of the Day – Rolex.com
  • The Favorite Website Awards Site of the Day – Rolex.com
    • Lead Flash Developer and architect
    • Completely dynamic architecture ensured a smooth transition when the site was translated into 5 languages and allowed for a smooth transition when a third party backend Content Management System was added.
  • 2006 Web Marketing Association Arts Standard of Excellence
  • Rolex Ashes and Snow
    • Lead Flash Developer and architect
    • Completely dynamic architecture allowed for easy deployment and translation into multiple languages.
    • Extensive background loading system allowed for best possible experience using full screen video and 1280x960 high res photography
  • 2006 Web Marketing Association Outstanding Web Site
  • Dell XPS Microsite
    • Lead Flash Developer on original iteration of this microsite.
    • Completly dynamic architecture allowed for easy deployment and translation into multiple languages.
  • Macromedia Site of the Day
  • Dell TV Info Center
    • Sole Flash Developer on two interactive sites (business and consumer audiences), all development was completed in 5 days.
    • The purpose of this site is to educate users on all TV technologies available and not available on Dell.com.
  • Macromedia Site of the Day
  • Dell Media Center Edition
    • Lead Flash Developer on project that was entirely completed in less than 2 weeks.
    • The purpose of this site was to educate users about the Media Center Edition computers and lead them into the purchase path on Dell.com.
  • Omni Award
  • Silver
    • Interactive Designer on the award winning DaimlerChrysler Vehicle Information Center.
    • The Vehicle Information Center is used to promote DaimlerChrysler vehicles at autoshows, events and dealerships throughout the United States.
  • DVDA
  • DVD-Excellence Award
    • Interactive Designer on the award winning AutoBasics e-learning CD-Rom.
    • This Interactive CD-ROM provided a general overview of automotive components for non-technical personnel working within the automotive industry.
    • This application features over 70 minutes of video, interactivity and images. The application has been in use for over 7 years. It has also been translated into 8 languages for use in over 15 countries.
  • St. Clair College
  • 1997 Creativity in Communication Award
    • Awarded to the graduating student who demonstrated the best use of creativity in the 3 year program.
Disney.com Wall-e Takeover

PIXAR / DISNEY.COM WALL-E TAKEOVER

ROLE: Lead Senior Front-End Engineer

Still one of the coolest and most fun projects I have ever worked on. While working at Disney I was asked to work on a "special project" with the Pixar team. I was flown up to San Francisco to meet with the animators (and John Lasseter) about their next movie. At the time it wasn't publically known as Wall-e. Getting the opportunity to even see the inside of the Pixar offices would be enough for most. I got a full tour, met with animators and sound engineers working on the film, and I got to see an incomplete screening of Wall-e. Certain scenes were complete, others were storyboards with non-actors reading the lines. They even asked me for my feedback after the screening.

Fan-boy stuff aside, the real reason I was there was to market this new movie/franchise on Disney.com. At the time, I was one of the lead engineers behind the architecture of Disney.com. The Pixar creative team had some amazing (some might call impossible) and very creative ideas that they pitched to me. Storyboards and everything. Set in space, they wanted Wall-e to roll on to the screen. Start building the individual elements of the website, and somehow along the way mess everything up, literally crash the site. Eve, would then fly on screen, and fix the site transitioning in to the actual site with the Wall-e trailer playing in the video player.

Inside my head I was thinking, this is a website, not an Imax theatre. Something this complex will take hours, if not days to load with the typical home bandwidth at that time. But the devil on my other shoulder was screaming "YES, YOU CAN DO THIS!".

Truth be told, Pixar did the majority of the heavy lifting here. They animated the entire scene and provided me with the raw video. I added a few alpha channels and using cue points triggered events to sync up the video events with the actual components on the site and ensured the transition from the end of the video to the actual site was seamless. We also had to seamlessly flip to a smaller video of Wall-e and Eve that loops and remains on screen until the guest (what Disney calls users) leaves the page. To make all this happen I had to add some hooks into the "lightyear" framework that was running the entire site. These hooks would later be used for future takeovers that the marketing team would assemble.

At the time, typical home bandwidth was not what it is today. The H.264 codec was somewhat new, so with much experimentation, and with the help of edge streaming from both Akamai and Level 3, and some preloading scripts that I wrote we were able to execute a flawless delivery.

On a good day Disney.com at the time was getting up to 25 million hits a day. No pressure!

Thanks to fans on YouTube, the legacy of this takeover still exists. Click play below to see my work from days gone by.

Project Stack

TEAM CANADA - MEN'S HOCKEY - SOCHI OLYMPICS 2014

ROLE: Lead Front-End Developer

Being a huge Team Canada hockey fan, this was essentially my dream project. Hockey Canada hired me to build a web portal they could use to communicate with the players and their families while they were at the Olympic Winter Games in Sochi, Russia.

Using Wordpress, I built a custom responsive theme that allowed Hockey Canada to quickly post and update player and family calenders. The site was used to inform players of their travel schedule, inform players of their practice, and game schedules. Provide maps of the Olympic Village and how to get to the different arenas. Provided information on area restaurants and attractions. It even included player and roster information for games. And an ecommerce Team Canada store for familiy and friends who were travelling to Sochi.

The 2014 Winter Games had it's fair share of contreversy. There were many threats of terrorist attacks. Part of the site development was emergency information in case there was such an attack. Secret meeting places. Contact information. Articles on safety and protocols. Information specific to the families about getting out of the country. This content could only be seen by certain individuals who could publish it live if such an event happened. I am glad to say this part of my work was never used.

Probably one of the most exciting parts of this project was having the roster before it was released to the general public. I had to publish the roster as Steve Yzerman announced it to the world on TV.

Project Stack

COVID-19 Compliance Coach App
COVID-19 COMPLIANCE COACH

Scroll for more

COVID-19 COMPLIANCE COACH

ROLE: Director of Technology

It all started on Saturday, March 21, 2020. The 3rd day of our company wide mandatory work from home protocol. When most were trying to figure out how to continue working remotely, the owner of Alteris Group and myself were discussing how we could help small businesses get back to work. The CARES (Coronavirus Aid, Relief, and Economic Security) Act was just released by the US government, and its small businesses compliance requirements were written like a feature list of our Learn2Go platform. Of course, like most, we still didn't know the scope of the pandemic. What we did know, we had to pivot our development, and quickly.

Learn2Go is a SasS, web-based and mobile training platform that helps companies train and communicate with their employees using formal and informal e-learning activities. At the heart of the platform is the Experience API (xAPI, formerly TinCan API) used to communicate with any capable LRS (Learning Record Store) and/or LMS (Learning Management System).

The CARES act mentioned employee compliance, and training would be the key for any back to work strategy. The US government was offering grants to small businesses who invested in COVID Compliance training for their staff. This was perfect for us, we build compliance training solutions for clients, and we already had a platform to deliver it.

Just over a month later, we launched a mobile app, a progressive web app, and an extensive web-based management portal. The COVID-19 Compliance Coach included 6 micro learning modules built in Articulate Rise, a platform to push out notifications to the entire company, specifc locations, departments or even individuals. It includes a TurboTax like editor for building, and publishing a corporate Risk Response Plan that updates automatically based on the current Community Risk Factor set by an administrator. And lastly, each employee has access to a simple health screening questionnaire that notifies management of potential employee health risks.

Project Stack

Code Sample
<script>
    import { format } from 'date-fns';
    import { mapState } from 'vuex';
    import { queryApi } from '@/services/api.service';

    export default {
        name: 'RiskStatusPage',
        props: {
            showProfile: {
            type: Boolean,
            default: true,
        },
    },
    data() {
        return {
            mitigationPlan: null,
        };
    },
    components: {
        ProfileCard: () => import('@/components/profile-card/profile-card.vue'),
    },
    created() {
        this.getBlogs();
    },
    methods: {
        getBlogs() {
            const blogParams = {
                per_page: 25,
                'query[v2_organization_id_eq]': this.defaultOrganization.data.id,
                'query[s]': 'updated_at DESC',
                'query[draft_eq]': 'false',
                'query[within_title_json_cont]': 'mitigationPlan',
            };
            queryApi('blogs', blogParams)
                .then(({ data }) => {
                    const blog = data.data[0];
                    const bodyJson = blog.attributes.body;
                    if (bodyJson.tab1 && bodyJson.tab2 && bodyJson.tab3 && bodyJson.tab4 && bodyJson.tab5) {
                        this.mitigationPlan = blog;
                    }
                    this.mitigationPlan = blog;
                })
                .catch((err) => {
                    if (err && err.response && err.response.data) {
                        this.$notify({
                            text: 'An error occurred while fetching the data.',
                            type: 'error',
                        });
                    }
                });
        },
        renderMitigationPlanTab(jsonName, riskToRender = 'low') {
            let htmlResponse = '';
            const fullRiskLabel = `${riskToRender}Risk`;
            this.mitigationPlan.attributes.body[jsonName]['en-us'].slides
                .map((slide) => slide.questions[0])
                .forEach((question) => {
                    if (question.published) {
                        if (question.oneAnswer) {
                            htmlResponse += `
                                <p class="risk-content-question m-0 mb-1 p-0"><strong>${question.question}</strong></p>
                                <div class="risk-content-answer m-0 p-0">${question.answer}</div>`;
                        } else {
                            htmlResponse += `
                                <p class="risk-content-question m-0 mb-1 p-0"><strong>${question.question}</strong></p>
                                <div class="risk-content-answer m-0 p-0">${question[fullRiskLabel]}</div>`;
                        }
                    }
                });
                return htmlResponse;
            },
        },
        computed: {
            ...mapState(['currentUser', 'currentMember', 'defaultOrganization', 'currentRiskStatus', 'currentRiskStatusReference']),
            riskLevelLastUpdated() {
                return format(new Date(this.currentRiskStatusReference.data[0].meta.updated_at), 'MM/dd/yy h:mm a');
            },
        },
    };
</script>
                        
ITC Showcase Touchscreen
ITC TOUCHSCREEN

Scroll for more

ITC SHOWCASE TOUCHSCREEN

ROLE: Director of Technology

ITC, the largest independent electricity transmission company in the United States, wanted to showcase their current projects with both employees and guests in each of their 4 offices across the midwest. Their main requirements: the delivery must be innovative, simple to use, the content must be dynamic and remotely editable, and very secure (they own the grid, understandable).

Working with CTI we assembled 3 touchscreen kiosks that were installed in a custom designed showcase area in the lobby of ITC offices. A standalone 55” 4k touchscreen, a wall mounted 65” 4k touchscreen, and a wall mounted 3 panel 100” multi-touch/multi-panel display.

At the heart of the platform, a WYSIWYG content management system that allows an administrator to publish content to any individual or all installed kiosks. Administrators can create custom navigation, hierarchy, and content via the custom VueJS components and an extensive publishing mechanism built in Rails that pushes out content bundle updates to the Electron based executables installed on each of the kiosks no matter where they are in off hours.

The kiosks contain 3D animated elements that react to users touch, 4k video, high definition photography, and interactions such as drag and drop, card flipping, multiple choice surveys, live data feeds from ITC’s solar field which powers their offices in Novi Michigan.

Project Stack

LEE VALLEY

ROLE: Lead Front-End Developer

Lee Valley is one of Canada's most loyal gardening and custom tool brands. Unfortunately, their website did not exhibit the same standard of quality their products and in-store experience provide.

Using Sitecore, and Sitecore's Commerce addon we built a completely dynamic, and modern ecommerce solution that enhanced their brand. I wrote an extensive SASS framework for the Lee Valley site that used CSS Grid with a Flexbox fallback for modern browsers that did not yet support CSS Grid. Using extensive SASS mixins I was able to build a dynamic grid system that allowed developers to create complex grid layouts that worked cross-browser with minimal code, while older browsers, via css overrides, used a flexbox solution with standard margins and padding support for grid-gaps. The mixins were also forgiving of the injected Sitecore markup that wreaked havoc on CSS selectors when the static layouts were incorporated into the Sitecore templates.

Extensive subtle CSS3 UI animations were used to enhance the overall user experience. Subtle rollovers, and transitions when searching or filtering product cards added context to ajax based content updates.

Project Stack

Code Sample
aside {
    @include set-grid-columns(1,12);
    @include set-grid-rows(1);
    @include create-grid-columns(1);
    @include grid-gap(18, 5);
    grid-auto-flow: row;
    grid-auto-rows: min-content;
    overflow:hidden;
    @include pixel-to-rem(height, 50);
    @include pixel-to-rem(max-height, 50);
    transition: all 0.5s ease-in-out;

    &.active {
        @include pixel-to-rem(height, 400);
        @include pixel-to-rem(max-height, 400);
    }
    @media #{$desktop-only} {
        @include set-grid-columns(1,3);
        overflow:visible;
        height:auto;
        max-height: unset;
    }
    
    > section {
        > h1 {
            white-space: nowrap;
        }
    }
    
    section {
        margin-top:0;
        background-color:$lightcolor;
        @include pixel-to-rem(padding, 10, 18, 18, 18);
        @media #{$desktop-only} {
            @include pixel-to-rem(padding, 18);
        }
        
        h1 {
            @include set-grid-columns(1,2);
            @include set-grid-rows(1);
            @include font-size(14);
            @include line-height(34);
            color:$basecolor;
            font-weight:bold;
            text-transform: uppercase;
            @include pixel-to-rem(letter-spacing, 1.5);
        }
        
        hr {
            @include set-grid-columns(1, 2);
            @include pixel-to-rem(border-top-width, 1);
            @include pixel-to-rem(margin, 15, 0);
            border-top-style: solid;
            border-top-color: $lightBorderColor;
            padding:0;
            
            &:first-of-type {
                @include set-grid-rows(2);

            }
            &:last-of-type {
                @include set-grid-rows(20);
            }
        }

        h2 {
            @include font-size(12);
            @include line-height(34);
            @include pixel-to-rem(letter-spacing, 1.5);
            color: $basecolor;
            font-weight: $SemiBoldWeight;
        }

        a.lv-button {
            @include pixel-to-rem(margin, 10, 0, 0, 0);
            @include pixel-to-rem(padding, 5, 25);
        }

        nav {
            ul {
                @include unlist();

                li {
                    a,
                    a:visited {
                        @include font-size(13);
                        @include line-height(34);
                        @include pixel-to-rem(letter-spacing, 1.5);
                    }
                    a:hover,
                    a:active,
                    a.active {
                        color: $darklinkcolor;
                        text-decoration: underline;
                        font-weight: bold;
                    }
                }
                
            }
        }
    }
}
                        
Mixin Samples
@mixin font-size($sizeValue: 16) {
    $rem: (($sizeValue) / 10);
    font-size: ($sizeValue) + px;
    font-size: $rem + rem;
}

@mixin line-height($line-height: 16) {
    $rem:(($line-height) / 10);
    line-height: $line-height * 1px;
    line-height: $rem + rem;
}

@mixin pixel-to-rem($prop: width, $pixel1:false, $pixel2:false, $pixel3:false, $pixel4:false) {

    $rem: unquote(0 + 'rem');
    $pixel: unquote(0 + 'px');

    @if $pixel1 {
        $pixel: unquote($pixel1 + 'px');
        $rem: unquote((($pixel1) / 10) + 'rem');
    }
    @if $pixel2 {
        $pixel: unquote($pixel + ' ' + $pixel2 + 'px');
        $rem: unquote($rem + ' ' + (($pixel2) / 10) + 'rem');
    }
    @if $pixel3 {
        $pixel: unquote($pixel + ' ' + $pixel3 + 'px');
        $rem: unquote($rem + ' ' + (($pixel3) / 10) + 'rem');
    }
    @if $pixel4 {
        $pixel: unquote($pixel + ' ' + $pixel4 + 'px');
        $rem: unquote($rem + ' ' + (($pixel4) / 10) + 'rem');
    }
    #{$prop}: $pixel;
    #{$prop}: $rem;
}




@mixin create-grid-columns($num, $pre:false, $post:false, $unit: "1fr") {
    $cols: '';
    $repeatCols: repeat($num, unquote($unit));
    @if $pre {
        $cols: "#{$pre} " + $cols;
        $repeatCols: "#{$pre} " + $repeatCols;
    }

    @for $i from 1 through $num {
        $cols: $cols + ' #{unquote($unit)}';
    }

    @if $post {
        $cols: $cols + " #{$post}";
        $repeatCols: $repeatCols + " #{$post}";
    }

    display:grid;
    grid-template-columns: unquote($repeatCols);
    @supports (-ms-ime-align: auto) {
        grid-template-columns: unquote($cols);
    }

    @media #{$ie-only} {
        grid-template-columns: unquote($cols);
    }
}

@mixin set-column-matrix($max: 12) {
    @for $i from 1 through $max {
        @for $x from 1 through $max {
        &.lv-col#{$i}_#{$x} {
            @media #{$desktop-only} {
            @include set-grid-columns(#{$i}, #{$x});
            }
        }
        }
    }
}

@mixin set-row-matrix($max: 12) {
    @for $i from 1 through $max {
        &.lv-row#{$i} {
            grid-row: #{$i} !important;
        }
    }
}

@mixin set-grid-columns($col, $span:false) {
    -ms-grid-column: $col;
    -ms-grid-column-span: $span;
    @if $span {
        grid-column: $col / span $span;
    } @else {
        grid-column: $col;
    }
}

@mixin create-grid-rows($num, $pre:false, $post:false, $unit: "auto") {
    $rows: '';
    $repeatRows: "";
    @if $num > 0 {
        $repeatRows: repeat($num, unquote($unit));
    }
    @if $pre {
        $rows: "#{$pre} " + $rows;
        $repeatRows: "#{$pre} " + $repeatRows;
    }

    @for $i from 1 through $num {
        $rows: $rows + ' #{unquote($unit)}';
    }

    @if $post {
        $rows: $rows + " #{$post}";
        $repeatRows: $repeatRows + " #{$post}";
    }

    grid-template-rows: unquote($repeatRows);
    @supports (-ms-ime-align: auto) {
        grid-template-rows: unquote($rows);
    }

    @media #{$ie-only} {
        grid-template-rows: unquote($rows);
    }
}

@mixin set-grid-rows($row, $span:false) {
    -ms-grid-row: $row;
    -ms-grid-row-span: $span;
    @if $span {
        grid-row: $row / span $span;
    } @else {
        grid-row: $row;
    }
}

@mixin grid-gap($column:false, $row:false) {
    @if $column {
        @include pixel-to-rem(grid-column-gap, $column);
        
        @supports (-ms-ime-align: auto) {
            > * {
                @include pixel-to-rem(margin-right, $column);
            }
        }
        
        @media #{$ie-only} {
            > * {
                @include pixel-to-rem(margin-right, $column);
            } 
        }
    }

    @if $row {
        @include pixel-to-rem(grid-row-gap, $row);
        
        @supports (-ms-ime-align: auto) {
        > * {
            @include pixel-to-rem(margin-bottom, $row);
            
            &:last-child {
            margin-bottom:0;
            }
        }  
        }
        
        @media #{$ie-only} {
        > * {
            @include pixel-to-rem(margin-bottom, $row);
            
            &:last-child {
            margin-bottom:0;
            }
        } 
        }
    }
}

@mixin ms-grid-auto-row-flow($offset:4, $child-class:'*', $row-count:1, $elements:40) {
    $isIE: false;
    $column-count:1;
    $row-column-count:1;
    @media #{$ie-only} {
        $isIE: true;
    }

    @supports (-ms-ime-align: auto) {
        $isIE: true;
    }
    @if $isIE {
        @include create-grid-rows($elements, false, false, 'minmax(min-content,auto)');
        @for $i from 1 through $elements {
            @if $row-column-count == 4 {
                $row-count: $row-count + 1;
                $column-count: 1;
                $row-column-count: 1;
            }
            > #{$child-class} {
                &:nth-of-type(#{$i}) {
                    -ms-grid-column: #{$column-count};
                    -ms-grid-row: #{$row-count};
                }
            }
            $column-count: $column-count + 1;
            $column-count: $column-count + $offset;
            $row-column-count: $row-column-count + 1;
        }
    }
}

@mixin add-ms-row-count($num-of-elements) {

    $isIE: false;
    @media #{$ie-only} {
        $isIE: true;
    }

    @supports (-ms-ime-align: auto) {
        $isIE: true;
    }

    @if $isIE {
            @for $i from 1 through $num-of-elements {
            > *:nth-child(#{$i}) {
                -ms-grid-row: $i;
            }
        }
    }
}

@mixin add-ms-column-count($start-num:1, $num-of-elements:4, $column-count:3) {
    @for $i from 1 through $num-of-elements {
        > *:nth-child(#{$start-num}) {
        -ms-grid-column: $column-count * $i / span $column-count;
        }
        $start-num: $start-num + 1;
    }
}

@mixin create-columns($start-num:1, $num-of-elements:4, $column-count:3) {
    $column-pos: 1;
    @for $i from $column-pos through $num-of-elements {
        > *:nth-child(#{$start-num}) {
        grid-column:  $column-pos / span $column-count;
        }
        $start-num: $start-num + 1;
        $column-pos: $column-count * $i + 1;
    }
}
                        
Covid Compliance Marketing Site

COVID COMPLIANCE COACH MARKETING SITE

ROLE: Director of Technology / Lead Developer

With any SaaS product, you have to market it. With that said, the CovidComply.com marketing site is more than your average brochureware. Not only does the site inform users of the app's features, it allows them to search, sign up, and schedule webinars using GoToWebinar's API. It allows users to dynamically price out licences based on their number of employees. Users can sign up for a 14-day free trial, set up accounts, and unique locations, assign adminstrators, enter credit card information and pay for their annual subscription using a seamless Stripe integration.

Project Stack

Code Sample
$app->post('/create-customer', function (Request $request, Response $response, array $args) {
    $body = json_decode($request->getBody());
    $stripe = $this->stripe;

    // Create a new customer object
    $customer = $stripe->customers->create([
        'email' => $body->email,
    ]);

    return $response->withJson(['customer' => $customer]);
});

$app->post('/create-subscription', function (Request $request, Response $response, array $args) {
    $body = json_decode($request->getBody());
    $stripe = $this->stripe;

    try {
        $payment_method = $stripe->paymentMethods->retrieve(
            $body->paymentMethodId
        );
        $payment_method->attach([
            'customer' => $body->customerId,
        ]);
    } catch (Exception $e) {
        return $response->withJson($e->jsonBody);
    }

    // Set the default payment method on the customer
    $stripe->customers->update($body->customerId, [
        'invoice_settings' => [
            'default_payment_method' => $body->paymentMethodId,
        ],
    ]);

    // Create the subscription
    if ($body->freeTrial != true) {
        $subscription = $stripe->subscriptions->create([
            'customer' => $body->customerId,
            'items' => [
                [
                    'price' => $body->priceId,
                    'quantity' => $body->quantity,
                ],
            ],
            'add_invoice_items' => [[
                'price' => $body->setupId,
            ]],
            'expand' => ['latest_invoice.payment_intent'],
        ]);
    } else {
        $trialTimeStamp = strtotime("+14 days");
        $subscription = $stripe->subscriptions->create([
            'customer' => $body->customerId,
            'items' => [
                [
                    'price' => $body->priceId,
                    'quantity' => $body->quantity,
                ],
            ],
            'trial_end' => $trialTimeStamp,
            'expand' => ['latest_invoice.payment_intent'],
        ]);
    }

    return $response->withJson($subscription);
});
                        
MakeShift Employee Web
MAKESHIFT EMPLOYEE WEB

Scroll for more

MAKESHIFT EMPLOYEE WEB

ROLE: Lead Front-End Developer

What started as a prototype is now a fully functional, React site using the Fluxbone pattern. Fluxbone is a unidirectional data flow pattern that uses Backbone for it’s models, collections and out of the box CRUD support, and the Flux pattern for its Stores and Actions. Using the same restful APIs as the mobile app I was able to share the same tested endpoints and auth in the Employee Web app. The entire site is written in ES6, JSX, and SASS. Moment.js is heavily used to handle timezone logic, date parsing and date and time formatting.

Originally bundled using Ruby’s Middleman and a handful of middleman specific gems. Recently however, I converted the entire site to use Webpack as the build tool. To date the application has been white-labeled for 3 different clients in multiple languages. Other than a few CSS overrides, and some client specific configuration data, the main codebase wasn't touched.

I am currently testing the codebase using JEST, however, like many projects with tight timelines there could be way more tests and I find myself writing tests during our bi-monthly chore days.

Project Stack

Code Sample
const TimeSheetNav = require('components/timesheets/TimeSheetNav.js.es6').default;
const TimeSheetPreview = require('components/timesheets/TimeSheetPreview.js.es6').default;
const PreviousTimeSheetPreview = require('components/timesheets/PreviousTimeSheetPreview.js.es6').default;
const UIStatusDialog = require('components/UIStatusDialog.js.es6').default;
const TimeSheetAction = require('actions/TimeSheetActions.js').default;
const TimeSheetActions = new TimeSheetAction();

const React = require('react');

export default class TimeSheetsView extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            activePage: 'currentTimeSheet',
            currentTimeSheet: null,
            previousTimeSheet: null,
            currentLocation: null,
            statuses: [],
            noTimesheets: null,
            lockActivePage: false
        }
    }

    componentDidMount() {
        TimeSheetStore.on('timesheet:approve:success', this._onTimeSheetPunchesApproved);
        TimeSheetStore.on('timesheet:approve:error', this._onTimeSheetApprovalError);
    }

    componentWillUnmount() {
        TimeSheetStore.off('timesheet:approve:success', this._onTimeSheetPunchesApproved);
        TimeSheetStore.off('timesheet:approve:error', this._onTimeSheetApprovalError);
        this.state.activePage = null;
    }
                        
    _changeLocation(id) {
        let currentData = TimeSheetStore.getCurrentTimeSheet(id);
        this.setState({
            currentTimeSheet: currentData,
            previousTimeSheet: TimeSheetStore.getPreviousTimeSheets(id),
            currentLocation: TimeSheetStore.getLocationById(id),
            activePage: currentData != null ? 'currentTimeSheet' : 'previousTimeSheet'
        });
    }

    _approvePunches(punches) {
        if (punches) {    
            TimeSheetActions.approvePunches(punches);
        }
    }

    _onTimeSheetPunchesApproved(response) {
        TimeSheetActions.getTimeSheets();

        this.state.statuses.push({
            title: "Success", 
            message: 'Timesheet Approved.', 
            status: "success", 
            key:_.uniqueId() 
        });

        this.setState({
            statuses: this.state.statuses
        });

        this.state.lockActivePage = true;

        TimeSheetActions.getTimeSheets();
        Utils.enableGlobalSpinner();
    }

    _onTimeSheetApprovalError(error) {
        this.state.statuses.push({
            title: "Error", 
            message: 'Something went wrong when approving your timesheet. Please try again.', 
            status: "error", 
            key:_.uniqueId() 
        })
        this.setState({
            statuses: this.state.statuses
        })
    }

    _closeStatusDialog(props) {
        let count = 0;
        let idx = this.state.statuses.map(function(status) {
            if (props.status.key == status.key) {
                return count;
            }
            count++;
        })
        
        if (idx) this.state.statuses.splice(idx, 1);

        this.setState({
            statuses:this.state.statuses
        })
    }

    render() {

        let statusDialogs = [];
        for (let i = 0; i < this.state.statuses.length; i++) {
            statusDialogs.push(<UIStatusDialog status={this.state.statuses[i]} key={this.state.statuses[i].key} closeDialog={this._closeStatusDialog.bind(this)} />);
        }

        let timesheetClass = '';
        let noTimeSheetClass = 'hide';
        if (this.state.noTimesheets) {
            timesheetClass = 'hide';
            noTimeSheetClass = '';
        }

        return (
            <div>
                <section id="timesheet" className={timesheetClass}>
                    <aside>
                        <TimeSheetNav 
                            currentSheet={this.state.currentTimeSheet} 
                            previousSheets={this.state.previousTimeSheet} 
                            activePage={this.state.activePage} 
                            onClickHandler={this._onClickHandler.bind(this)} 
                        />
                    </aside>
                    <main>
                        <TimeSheetPreview 
                            shouldRender={this.state.currentTimeSheet != null} 
                            type="current" 
                            showHeader={true}
                            data={this.state.currentTimeSheet} 
                            activeState={this.state.activePage == 'currentTimeSheet' ? 'active' : 'hidden'} 
                            user={this.state.user || {}}
                            punches={this.state.punches || {}}
                            locations={this.state.locations || {}}
                            currentLocation={this.state.currentLocation || {}}
                            onLocationChange={this._changeLocation.bind(this)}
                            onApprovePunches={this._approvePunches.bind(this)}
                        />
                        <PreviousTimeSheetPreview 
                            shouldRender={this.state.previousTimeSheet != null} 
                            type="previous" 
                            data={this.state.previousTimeSheet} 
                            activeState={this.state.activePage == 'previousTimeSheet' ? 'active' : 'hidden'} 
                            user={this.state.user || {}}
                            punches={this.state.punches || {}}
                            locations={this.state.locations || {}}
                            currentLocation={this.state.currentLocation || {}}
                            onLocationChange={this._changeLocation.bind(this)}
                            onApprovePunches={this._approvePunches.bind(this)}
                        />
                        {statusDialogs}
                    </main>
                </section>
                <section id="no-timesheets" className={noTimeSheetClass}>
                    <img src="/images/Timesheet_Icon.svg" id="no-timesheet-icon" />
                    <h1>You don't have any timesheets to view.</h1>
                    <p>Check back later to see a breakdown of your hours worked.</p>
                </section>
            </div>
        )

    }
}
                    
                        

HOCKEY CANADA NETWORK

ROLE: Lead Front-End Developer

With Hockey Canada Network, teams at all levels have on-demand access to videos, drills, articles, training plans and more. As a hockey coach myself I had a personal connection to this project. I wore many hats on this project, lead front-end web developer on the marketing site and ecommerce module, front-end web developer on the custom made React and Rails CMS, front-end developer for the in app content pages, and as a minor hockey coach a lot of the content and interaction was tested on me.

alt text goes here

Marketing Site

  • Fully responsive
  • Multilingual
  • Custom CSS3 parallax transitions
  • Custom content and background imagery based on device detection
  • Ecommerce and Stripe integration
  • Custom lightweight JS single page app framework with content lazy loading
  • Restful API integration during subscription / purchase flow
alt text goes here

React in Rails CMS

  • Created custom React editor for table based content entry that outputs only divs and dynamic flexbox css (fall back to floats for older browsers) to control the layout.
  • Created custom React image uploader component that allowed users to drag assets on to their browser. Once uploaded, multiple sizes and crops of the image were created using ImageMagick and uploaded to an S3 bucket. Used pusher gem with web sockets to communicate progress between the different apps.
  • Styled all CMS controls and added validation JavaScript, simple CSS3 transitions that enhanced the user experience, and ensured the overall feel of the app matched the style guide provided by the designers.

In App Web Views

  • Created cross-device (Android and iOS) dynamic templates that displayed content loaded in via RESTful endpoints that returned data from the CMS.

DOWNTOWN CALGARY PARKING

ROLE: Lead Front-End Developer

The Downtown Calgary BIA wanted to create a mobile first parking assistant. One of the challenges of visiting a large downtown core is finding parking. Using the Google Maps javascript API extensively, the Downtown Calgary Parking app plots both city lots and non-city lots with custom markers, it embeds a street view look at the lot when you click on the marker, and it also pulls in dynamic data from city feeds that show how many spots are available in each city lot. Using the polyline features of the Google Maps API, and other city feeds, street level parking is drawn on to the map, pricing based on the time of day is displayed, and how many spots remain in a given block. Users are also able to search for any address or landmark in downtown calgary and the parking search will centre around that location.

Project Stack

DISNEY.COM BLOGS

ROLE: Lead Front-End Developer

As one of the lead front-end developers on the Disney.com team I was asked to rebuild the current static Disney Insider and D23 blogs in Wordpress. At the same time they also wanted to add a third blog, Disney OMD. Oh My Disney is Disney's lighthearted take on Disney news, gossip, and entertainment. Content written specifically to be shared on social media.

Using a multi-site set up, and three custom Wordpress themes the Disney blog suite was built. Custom Disney theme widgets were built to share content across the three sites. A Disney Music widget, a Disney Movies widget, and a Disney Books widget that linked to the most popular content. As with anything Disney, the themes had to be media rich, accessible to all, responsive, and incorporate the global Disney footer and header that were not built with Wordpress or PHP in mind. The PHP scraped the current header and footer html from the Disney.com homepage, parsed the content and displayed it within the Wordpress theme header and footer includes.

Project Stack

Contact Me

Hello

SEND ME AN EMAIL

Scroll for more

LOCATION.

TORONTO

Toronto Ontario, Canada

519 796 9995

scott@scottgmorgan.com

+view on map