<!DOCTYPE html>
<head>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-9R8Z4RLCGV"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'G-9R8Z4RLCGV');
</script>
<title>
Josh Lipinski | tech, the outdoors, and Rhode Island
</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme_color" content="#d0d0d0" />
<link rel="manifest" href="manifest.json">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css">
<style>
@import url('https://fonts.googleapis.com/css?family=Playfair+Display:400,400i,700,700i&display=swap');

html, body, input, textarea, button {font-family: 'Playfair Display', arial, sans-serif}
html, body {height: 100%; font-size: 100%; line-height: 130%; font-size: 16px;}
body {margin: 0px; background: #d0d0d0; background: #d0d0d0 repeating-linear-gradient(135deg, #ffffff, #ffffff 2px, transparent 2px, transparent 8px) fixed;}
.background_wrapper {position: fixed; width: 100%; height: 100%; height: 100vh; z-index: 0; background: #efefef; background: transparent linear-gradient(135deg, transparent 0%, #ffffff 50%, transparent 100%) fixed;}
a {color: #009900}
a.in_use {text-decoration: none; background: #e0e0e0}
  a[href^='sms'] {white-space: nowrap; font-family: monospace}
a:hover, .button:hover, button {text-decoration: none; opacity: .7}
button, .button, #c_form_box [type=button].button {border: .1em solid #d0d0d0; border-radius: .2em; padding: .3em; line-height: 130%; font-size: 1.2em; font-weight: bold; cursor: pointer; width: auto; margin: auto; margin-top: 1em; text-transform: uppercase; padding: .5em 1em .5em 1em; color: #ffffff; background: #009900}
  .in_progress, .in_progress:hover {background: transparent; animation: colorTint 1s infinite alternate; cursor: default}
img, figure {border: 0px; margin: 0px; padding: 0px; max-width: 100%;}
img.circle {border-radius: 50%}
nav#topnav .toplogo {margin: auto; text-align: center; padding-top: 20px; padding-bottom: 20px; display: block; padding-left: 0px; padding-right: 0px}
nav#topnav header img, nav#topnav .toplogo {width: 25%; margin: auto}
nav#topnav figure {width: 100%; margin: auto; display: block; border-radius: 50%; background: url(images/josh_lipinski_bio.jpg) transparent no-repeat center center; background-size: contain; width: 100%; height: 0; padding-top: 32.6%}
nav#topnav {overflow: hidden; text-align: center}
nav#topnav > div {display: inline-block; width: 100%; text-transform: uppercase; font-size: .8em; padding-bottom: 4px; background: #ffffff; box-shadow: 0px 0px 80px #efefef}
nav#topnav .menu_item {display: inline-block}
nav#topnav a, #topnav i.fa {display: block; text-align: center; padding: 8px 15px 10px 15px; text-decoration: none; font-size: 1.5em; line-height: 1.5em;}
nav#topnav a:hover {background: #efefef; background: #efefef linear-gradient(225deg, #efefef 0%, #ffffff 30%, #ffffff 70%, #efefef 100%); color: #000000; opacity:  1}
nav#topnav a.active, nav#topnav a.active:hover {color: #000000; background: transparent; opacity: 1}
nav#topnav a.icon {display: none; font-weight: bold; user-select: none; -webkit-tap-highlight-color: transparent;}
nav#topnav i.fa {color: #d0d0d0; text-shadow: 1px 1px 0px #000000;}

main {width: 100%; position: relative; z-index: 1}
main .main_wrapper {background: #ffffff; background: #ffffff linear-gradient(225deg, #efefef 0%, #ffffff 9%, #ffffff 91%, #efefef 100%); border: 4px solid #ffffff; width: 80%; margin: auto}
main section {color: #000000; padding: 3em; padding-top: 1em; padding-bottom: 2.5em; font-size: 1.1em; line-height: 160%;}
main section a {}
main section ul {width: 90%; margin-left: auto; margin-right: auto; margin-top: .5em; margin-bottom: 1em; list-style-type: square; padding-left: 1em}
main  section figure {text-align: center; display: block; padding: 1em; margin: 0px}
main  section figure img {margin: auto; width: 50%}
  main section figure.toplogo {padding: 1em; padding-top: 0px}
  main section figure.toplogo img {width: 35%}
main  section figure figcaption {color: #999999; font-size: .9em; padding: 1em}
main  section header {text-shadow: 1px 1px 1px #d0d0d0; font-size: 2em; text-align: center; padding: .5em; padding-top: 0px; line-height: 110%}
  main section header .small_stuff {font-size: .5em; line-height: 130%; margin-top: .5em; text-shadow: none; text-transform: uppercase}
main section .icon_row_wrapper {max-width: 85%; margin: auto; padding-bottom: 1em}
main section .icon_row {display: flex; flex-wrap: wrap; margin: auto; justify-content: center}
main section .icon_row img {margin-bottom: .5em}
  main section .icon_row a {display: inline-block; width: 40%; margin: 3%; flex-flow: column; flex-grow: 0; flex-shrink: 1; flex-basis: 40%; vertical-align: top; text-align: center; line-height: 100%; line-height: 130%; text-transform: uppercase}
    main section .icon_row.basis15 a {flex-basis: 15%}
  main section i.fab, main section i.fa, main section i.far {font-size: 4em; margin: .2em; text-decoration: none !important}

  /* modal stuff */
.modal {position: fixed; z-index: -1; top: 0; left: 0; display: block; overflow: hidden; opacity: 0; width: 100%; height: 100%;}
.modal .modal-overlay {opacity: .2; transition: opacity 300ms}
.modal .modal-wrapper {margin: -20px; max-height: 100%; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); background: #ffffff; z-index: 11; border-radius: 2px; box-shadow: 0px 0px 10px 1px #000000; overflow: auto; -webkit-overflow-scrolling: touch;}
.modal.modalin {opacity: 1; z-index: 10000; overflow: visible;}
.modal.modalin .modal-overlay {position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #000000; opacity: .7; cursor: pointer}
.modal.modalin .modal-wrapper {position: absolute; margin: 0px auto; transition: margin 300ms; min-width: 75%;}
body.modalin {overflow: hidden} /* prevents page scroll in the background when modal is visible */
.modal-header {position: relative;}
	.modal-header .modal-close {position: absolute; top: 0; right: 0; padding: 10px; height: 20px; width: 20px; fill: #efefef; background: none; cursor: pointer}
	.modal-header .modal-close:hover {opacity: .5}
.modal-content {margin: 0px; padding: 2em 3em 2em 3em}
	.modal-content h2 {font-size: 1.1em; padding: 0px; padding-bottom: 1em; margin: 0px; text-align: center}
	.modal-content .body_details {text-align: left}
	.modal-content .body_details h1, .modal-content .body_details h2, .modal-content .body_details h3, .modal-content .body_details h4, .modal-content .body_details h5, .modal-content .body_details h6 {line-height: 100%}
	.modal.whatis .body_details .description {text-align: center; font-weight: 500}
	.modal-content button {width: auto; margin: auto; margin-top: 30px; padding-left: 3em; padding-right: 3em; display: block; background: #999999}
	.modal-content .close_text, .modal-content .close_text:hover {cursor: pointer; display: inline-block; text-transform: uppercase; text-decoration: none; font-size: .9em; color: inherit}
	.modal-content .centered, .modal-content.centered {text-align: center}

footer {padding: 5px; text-align: center; color: #000000; text-transform: uppercase; font-size: .9em; font-weight: 700; line-height: 220%}
footer .icon_row {display: inline-block;}
footer .chunk, .icon_row a {margin-left: 10px; margin-right: 10px}
footer a {white-space: nowrap}
footer .icon_row i {margin-left: .2em; margin-right: .2em; text-decoration: none !important}
footer .icon_row a {color: #666666}

.contact_error, .contact_success {text-align: center; margin-bottom: .5em; margin-top: .5em; padding: .5em; background: #efefef}
.contact_error {color: #cc0000}
.contact_success {}
.contact_success b {}
#c_form_box {margin: auto; margin-top: 1em; max-width: 600px;}
#c_form_box input, #c_form_box textarea {color: #5D2716; display: inline-block; width: 95%; width: calc(100% - .8em); border: .1em solid #d0d0d0; border-radius: .2em; padding: .3em; line-height: 130%; font-size: 1.2em}
	#c_form_box .contact_instructions {padding-top: .5em; text-transform: uppercase; text-align: center}
	#c_form_box .contact_instructions i.fa {display: none}
	#c_form_box span {padding: .2em 0 .2em 0; display: block; text-align: left;}
	#c_form_box .required {font-style: italic}
	#c_form_box .c_form_submit {text-align: center;}
	#c_form_box input.button {background: #00cc00}
  #c_form_box input.button[disabled], #c_form_box input.button[disabled]:hover, .button[disabled]:hover {background: #d0d0d0; cursor: default}

/* flashing button animation */
@keyframes colorTint{
  0% {opacity: 1}
  100% {opacity: .25}
}

@media screen and (max-width: 800px) {
  nav#topnav .menu_item {display: block; text-align: left}
  nav#topnav a {display: inline-block; padding-top: 4px; padding-bottom: 4px}
  nav#topnav a:hover {background: transparent}
  nav#topnav a.active {font-weight: 700}
  nav#topnav .menu_item:not(.no_toggle) {display: none;}
  nav#topnav > div {position: relative; display: block; padding: 0px; border: none}
  nav#topnav a.icon {position: absolute; right: 0; top: 0; display: block;}
  nav#topnav.responsive:not(.toggling) .menu_item, nav#topnav.responsive.toggling .no_toggle {display: block !important}
  nav#topnav.toggling .menu_item {display: none}

  main section {margin: 0px; padding: 1.1em; padding-bottom: 1.6em}
  main .main_wrapper {width: 100%; border: none}
  main  section figure {padding-left: 0px; padding-right: 0px}
  main  section figure img {margin: auto; width: 100%}
    main section figure.toplogo img {width: 65%}
  main section .icon_row_wrapper {width: 100%; max-width: none}

  nav#topnav header img {width: 50%; margin: auto}
  .nojs nav#topnav .menu_item {text-align: center}
  .nojs nav#topnav a.icon {display: none}

  .background_wrapper {display: none}
  body {background: #efefef}

  .modal.modalin {overflow: auto; -webkit-overflow-scrolling: touch;}
	.modal .modal-wrapper {left: auto; top: auto; transform: none; border-radius: unset; box-shadow: none; max-height: none; }
	.modal.modalin .modal-wrapper {margin: auto; width: 100%;}
	.modal-content .toplogo {width: 50%; padding-left: 0px; padding-right: 0px}
}

@media screen and (min-width: 800px) {
  nav#topnav .menu_item {display: inline-block !important}
}

.nojs .nojs_hide {display: none}
.nojs main section {padding-top: 2em; padding-bottom: 2em; box-shadow: 0px 0px 80px #efefef; background: #ffffff}
.nojs #c_form_box {display: none}
</style>
</head>
<body class="nojs">
<div class="background_wrapper"></div>
<main>
<div class="main_wrapper">
<nav id="topnav" class="responsive">
<div>
<div class="menu_item no_toggle"><a href="#welcome" data-show="welcome">Josh Lipinski</a></div>
<div class="menu_item"><a href="#professional" data-show="professional">Professional</a></div>
<div class="menu_item"><a href="#freelance" data-show="freelance">Freelance</a></div>
<div class="menu_item"><a href="#contact-me" data-show="contact-me">Contact</a></div>
<div class="menu_item no_toggle"><a href="#" class="icon dropdown no_toggle">&#9776;</a></div>
</div>
</nav>
<section id="welcome">
<figure class="toplogo">
<img src="images/josh_lipinski_bio.jpg" class="circle">
</figure>
<header>
Josh Lipinski
<div class="small_stuff">
Boston, Massachusetts<br>
Rhode Island
</header>
<div class="icon_row_wrapper">
  <div class="icon_row basis15">
    <a href="http://github.com/jlipinski3" target="_blank"><i class="fab fa-github"></i><br>GitHub</a>
    <a href="https://www.linkedin.com/in/joshua-lipinski/" target="_blank"><i class="fab fa-linkedin"></i><br>LinkedIn</a>
    <a href="https://docs.google.com/document/d/1C6_leRk6Cdsewv7TB1EDvmx7pI3uhUAs5kiCwD3R-to/edit?usp=sharing" target="_blank"><i class="far fa-file"></i><br>Resume</a>
  </div>
</div>
<b>I love bringing awesome ideas to life.</b><br><br>
I started building on the web in the 90's, and I've seen the tech landscape shift quite a bit. From 28.8kbps to 5G. From Tripod and Geocities to the cloud. From HTML with no CSS to SPA's.<br><br>
I've been lucky to have experience in many different teams and orgs - from teams of marketers to teams of engineers. I've produced beta's for start-ups, complex MLS-synced sites for real estate companies, Single Page Apps and Progressive Web Apps at the enterprise level, and e-commerce architectures that scale.<br><br>
It's important to me to grow teams, to both teach and learn from the next wave of builders, choose our tech stack smartly, and to understand the underlying code that powers our frameworks.<br><br>
When I'm not at my laptop, I'm outside. On two legs (running, hiking), two wheels (cycling), on the water (kayaking, paddleboarding, fishing), or on the basketball court.<br><br>
Take a look at my <a href="http://github.com/jlipinski3" target="_blank">GitHub</a>, <a href="https://www.linkedin.com/in/joshua-lipinski/" target="_blank">LinkedIn</a>, and <a href="https://docs.google.com/document/d/1C6_leRk6Cdsewv7TB1EDvmx7pI3uhUAs5kiCwD3R-to/edit?usp=sharing" target="_blank">resume</a> for more. Or <a href="#contact-me" data-show="contact-me">contact me online</a>, or via phone/text: <a href="sms:4132818645">413-281-8645</a>.
</section>
<section id="professional">
<header>
Professional
</header>
<div class="icon_row_wrapper">
  <div class="icon_row basis15">
    <a href="http://github.com/jlipinski3" target="_blank"><i class="fab fa-github"></i><br>GitHub</a>
    <a href="https://www.linkedin.com/in/joshua-lipinski/" target="_blank"><i class="fab fa-linkedin"></i><br>LinkedIn</a>
    <a href="https://docs.google.com/document/d/1C6_leRk6Cdsewv7TB1EDvmx7pI3uhUAs5kiCwD3R-to/edit?usp=sharing" target="_blank"><i class="far fa-file"></i><br>Resume</a>
  </div>
</div>
I'm an experienced, passionate advocate for technology - specifically the web.
<div style="text-align: center; text-transform: uppercase; margin-top: 2em">Technical</div>
<ul>
<li>Full-Stack Engineer with 15+ years of professional engineering and management experience, with a thorough understanding of the technologies listed below, and best practices for building scalable, performant, maintainable tech.</li>
<li>Backend: PHP, Node.js, creation of API's</li>
<li>Frontend: JavaScript (jQuery, React), HTML (HTML5), CSS (LESS, SASS, Responsive), SPA's, PWA's, Service Workers, SEO, consumption of API's (JSON)
<li>Relational Databases: SQL (MySQL, MSSQL, PostGres)</li>
<li>Server/Infrastructure/DevOps: AWS, CI (Circle), Firebase, Google Cloud, Lambda, IIS, CRON, Docker, Apache</li>
<li>Automated Testing: Selenium</li>
<li>CMS: Wordpress</li>
<li>E-Commerce: WooCommerce, Salesforce Commerce Cloud
<li>Design: Adobe CS, Invision, Gravit Designer, Affinity</li>
</ul>
<div style="text-align: center; text-transform: uppercase; margin-top: 2em">Culture</div>
<ul>
<li>Familiar with Agile and waterfall methodologies, at pod-based and vertical orgs</li>
<li>Previously Senior Manager of Web Engineering at global athletic supplier</li>
<li>Spent 6 years in IT/dev for a global financial services company</li>
<li>Lead dev at a small digital agency startup</li>
<li>Lead dev in the marketing department at a national real estate company</li>
</ul>
</section>
<section id="freelance">
<header>
Selected Freelance Work
</header>
<div class="icon_row_wrapper">
  <div class="icon_row">
    <a href="http://www.stonemountainhomebuilders.com/?lead=freelance" target="_blank"><img src="images/stonemountainhomebuilderscom.jpg">Stone Mountain Home Builders</a>
    <a href="https://www.greylockrealty.com/?lead=freelance" target="_blank"><img src="images/greylockrealtycom.jpg">Greylock Realty</a>
<a href="http://www.redhorserealestate.com/?lead=freelance" target="_blank"><img src="images/redhorserealestatecom.jpg">Red Horse Real Estate</a>
<a href="https://www.westernmassland.com/?lead=freelance" target="_blank"><img src="images/westernmasslandcom.jpg">Western Mass Land</a>
  </div>
</div>
I've been freelancing for years because I love to take small businesses and put them on a level playing field with bigger, deeper-pocketed competitors. Above are a sample of sites I've built over the years, most in Wordpress, and some in a PHP-based CMS I built years back.<br><br>
The real estate sites are synced with the Multiple Listing Service (MLS) three times daily using a cron job. They've generated thousands and thousands of leads for their respective businesses. Harnessing both marketing and engineering is where the power of the web shines, so keeping in mind the entire 360 view of social, email, lead generation, imagery, analytics, and SEO is both amazing and gratifying.<br><br>
I'm always up for helping out small business, so <a href="#contact-me" data-show="contact-me">drop me a note</a> and I'll let you know if I can help.
</section>
<section id="contact-me">
<header>
Drop Me a Line
</header>
<div style="text-align: center">Drop me a note below, or get in touch via phone or text: <a href="sms:4132818645">413-281-8645</a>.</div>
<div id='c_form_box'><div id='c_form_box_status'></div><span>Note: <textarea id='c_questions' rows='2'></textarea></span> <span>Name <font class='required'>(required)</font>:<input id='c_firstname' type='text' maxlength='50'></span>
<span>Email <font class='required'>(required)</font>:<input id='c_email' type='text' maxlength='100'></span>
<span class='c_form_submit'><input type='button' id='submit_form' value='Submit' class='button offline-msg'></span></div>
</section>

<section id="how-i-built-this">
<header>
How I Built This
</header>
I built this in a homegrown SPA framework I made a few years back for a presentation to hackers at <a href="https://hackbeanpot.com/" target="_blank">HackBeanpot</a> in Boston. I wanted to focus on graceful degradation and progressive enhancement, as well as break down what exactly a "Single Page App" is. As someone who's been doing the web thing for a good long while, making sure the stuff you build today will play well on the platforms of tomorrow is paramount. There's a big difference between building something that has to get rewritten in 2-3 years versus something that's good for 3-5+ years.<br><br>
That said, having control and understanding of what you build, and what you use, is just as important. Yes - I'm specifically referring to using frameworks without understanding the underlying code and principles behind them. It's important to know <i>the code behind the code</i>.<br><br>
This is essentially a single HTML file, with some help from jQuery, two font libraries (from FontAwesome and Google Fonts), a styles block, and JavaScript IIFE's that use the <a href="https://gist.github.com/zcaceres/bb0eec99c02dda6aac0e041d0d4d7bf2" target="_blank">revealing module pattern</a> to rewrite browser history and show/hide content based on the url.<br><br>
It's hosted on Firebase and uses an express.js API as an email relay for the contact form. It's a simple Single Page Application.<br><br>
To specifically point out both progressive enhancement and graceful degradation:
<ul><li>Progressive Enhancement: this works offline, with help from a service worker. To try it, put your phone in airplane mode and refresh the page.</li>
<li>Graceful Degradation: go ahead and disable JavaScript. Now it may not be all the rage, but it's nice to not expect too much from the client.</li>
</ul>
</section>

</div>
<!-- end main wrapper -->

<footer>
<span class="chunk">Created by Joshua Lipinski</span>
<a href="#how-i-built-this" data-show="how-i-built-this" class="chunk">How I Built This</a>
<a href="#contact-me" data-show="contact-me" class="chunk">Contact</a>
<div class='icon_row'>
<a href="http://github.com/jlipinski3" target="_blank"><i class="fab fa-github"></i>GitHub</a>
<a href="https://www.linkedin.com/in/joshua-lipinski/" target="_blank"><i class="fab fa-linkedin"></i>LinkedIn</a>
</div>
</footer>
</main>

<!--offline modal-->
<div class="modal offline" id="modal_offline">
    <div class="modal-overlay modal-toggle"></div>
    <div class="modal-wrapper modal-transition">
        <div class="modal-header">
            <span class="modal-close modal-toggle"><svg class="icon-close icon" viewBox="0 0 32 32">
                    <use xlink:href="#icon-close"></use>
                </svg></span>
        </div>
        <div class="modal-body">
            <div class="modal-content">
                <div class="body_details centered">
                    <h2 class="description">Offline</h2>
                    Try again after restoring your internet connection.
                    <button class="white modal-toggle">
                        Close
                    </button>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- end offline modal -->

<!-- modal close x icon -->
<svg display="none" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="768" height="800" viewBox="0 0 768 800"><defs><g id="icon-close"><path class="path1" d="M31.708 25.708c-0-0-0-0-0-0l-9.708-9.708 9.708-9.708c0-0 0-0 0-0 0.105-0.105 0.18-0.227 0.229-0.357 0.133-0.356 0.057-0.771-0.229-1.057l-4.586-4.586c-0.286-0.286-0.702-0.361-1.057-0.229-0.13 0.048-0.252 0.124-0.357 0.228 0 0-0 0-0 0l-9.708 9.708-9.708-9.708c-0-0-0-0-0-0-0.105-0.104-0.227-0.18-0.357-0.228-0.356-0.133-0.771-0.057-1.057 0.229l-4.586 4.586c-0.286 0.286-0.361 0.702-0.229 1.057 0.049 0.13 0.124 0.252 0.229 0.357 0 0 0 0 0 0l9.708 9.708-9.708 9.708c-0 0-0 0-0 0-0.104 0.105-0.18 0.227-0.229 0.357-0.133 0.355-0.057 0.771 0.229 1.057l4.586 4.586c0.286 0.286 0.702 0.361 1.057 0.229 0.13-0.049 0.252-0.124 0.357-0.229 0-0 0-0 0-0l9.708-9.708 9.708 9.708c0 0 0 0 0 0 0.105 0.105 0.227 0.18 0.357 0.229 0.356 0.133 0.771 0.057 1.057-0.229l4.586-4.586c0.286-0.286 0.362-0.702 0.229-1.057-0.049-0.13-0.124-0.252-0.229-0.357z"></path></g></defs></svg>
<!-- end close icon -->

<script src="js/jquery-3.3.1.min.js"></script>
<script>
//constants to be used when invoking and initializing objects/functions
//the screens are set here because some events need to proceed to particular screens based on actions
//extended because initial config props are set in the style config up top
var config = {
  defaults: {
    view: "welcome"
  },
  default_state: {
    view: "welcome"
  },
  keep_params: [],
  content_area: {},
  screens: {
    redirects: {},
    default: 'welcome'
  }
};

var state = $.extend({}, config.default_state); //make a copy, not a pointer

$(function(){
  config.content_area.nav = $("nav#topnav"); //nav menu
  config.content_area.sections = $("main section"); //content area with articles
  config.content_area.default_show = (config.content_area.nav).find("a").first().data("show"); //if no view param, show this first
  $("body").removeClass("nojs"); //by getting this far, js is supported
  config.content_area.nav.removeClass("responsive"); //remove hamburger menu functionality

  window.onpopstate = function(event){ //"onpopstate" is fired when using the browser back and forward buttons. add a "nopush" property to state when passing to active_tab so the back and forward actions dont append to state, but instead allow the user to traverse existing state.
    if(event && event.state)
    {
      clone_state = $.extend({}, event.state); //must do this for IE since it doesn't allow manipulation of event.state object
      if(clone_state.view){clone_state.nopush = true;}
      newview.set(clone_state);
    } else if('state' in window.history){window.location.reload();} //reload if using a hard server param like "?param="
  }

  //intercept nav clicks below to toggle the hamburger menu and to show specific content using the data-show attribute
  $(document).on("click", "a", function(e){ //intercept anchor clicks
    if($(this).hasClass("dropdown")) //the hamburger icon has the dropdown class and will collapse or expand using the responsive class on the nav
    {
      e.preventDefault();
      menu.toggle();
    } else if($(this).attr("data-show")){
      e.preventDefault();
      newview.set({view: $(this).data("show")});
    } //pass the data-show active section function below
  });

  //MODALS
  //just add the "modal-toggle" class to any element to toggle open/close
  $(document).on("click", "a, .modal-toggle, button", function (e) {
    if(!$("body").hasClass("modalin") || ($("body").hasClass("modalin") && $(this).parents(".modal").length)) { //only allow to close an open modal if clicking on a .modal-toggle that is inside the modal. Otherwise hitting enter may trigger a click on the toggle behind the modal
      if (($(this).is("a") && $(this).attr("data-modal")) || !$(this).is("a") && $(this).hasClass("modal-toggle")) {//anchor and button tags can launch modal using just data-modal attribute. non-links can close their parent modal if they have the modal-toggle attribute.
        e.preventDefault();
        if ($(this).parents('.modal').length) { //click anything inside modal (including overlay) that has a .modal-toggle class
          $(document).trigger("modal", [$(this).parents('.modal')]);
        } else if ($(this).attr("data-modal")) { //toggle modal using data-modal attribute
          $(document).trigger("modal", [$('.modal.' + $(this).data('modal'))]);
        }
      }
    }
  });

  //trigger to launch modal
  $(document).on("modal", function (e, modal) {
    modal.toggleClass("modalin");
    $("body").toggleClass("modalin");
  });

  //check if online if clicking on anything tagged with .offline-msg class
  $(document).on('click', '.offline-msg', function(e) {
    if (navigator && navigator.onLine === false) {
      e.preventDefault();
      e.stopPropagation();
      $(document).trigger('modal', [$('.modal.offline')]); //show offline modal
    }
  });

  //INITIALIZE!
  newview.set(); //call newview module without params to show default content only and hide the rest
});

var url_param = function(name) {
  //quick function for parsing url params
  var results = new RegExp('[?&#/]' + name + '=([^!&#]*)').exec(window.location.href);
  if (results) {
    return results[1] || "";
  }
  return "";
};

//NEWVIEW MODULE
//swap visible content and push to browser history
//immediately invoked using the config vars for what params to keep from url and the content area this should be used for. can be created in multiple instances by removing IIFE.
var newview = function(keep_params, content_box, default_params, screens){
  var url_params = function () { //grab url params to bring along for the ride
    var p = {};
    for (var x = 0; x < keep_params.length; x++) {
      var param = url_param(keep_params[x]);
      if (param === "" && config.defaults[keep_params[x]] === ""){ //how to reset a state param when default is blank: do "&param=&"
        p[keep_params[x]] = config.defaults[keep_params[x]];
      } else if (param) {
        // This will make sure boolean parameters (as strings) are converted to boolean values when parsed from the URL
        // Otherwise we need to make sure to do this everywhere those parameters are used which is uglier...
        p[keep_params[x]] = (param==='true') ? true : (param==='false') ? false : decodeURIComponent(param);
      }
    }
    //TODO: uncomment to use endpoint as view param. ALSO: remove "view" from config.keep_params
    //special case for "view" param since it's the endpoint in the pathname
    if(get_endpoint() !== ""){
      p.view = get_endpoint();
    }
    return p;
  };

  var clean_params = function (dirty) { //rinse a set of params, only keeping what we want
    var p = {};
    for (var x = 0; x < keep_params.length; x++) {
      // need to check if the parameter is defined (since boolean values could result in false)
      if (dirty[keep_params[x]] || dirty[keep_params[x]] === false) { //don't carry blank params but carry false ones
        p[keep_params[x]] = decodeURIComponent(dirty[keep_params[x]]);
      }
    }
    return p;
  };

  var rewrite_history = function(title){ //for synthetic browser history rewriting
    if (history.pushState && !state.nopush) {
      //ensure browser supports pushState and this isn't a back or forward click
      //prepare to push to browser history object
      var trailing_fragment = "!new"; //add this as fragment at end of url so browser registers a unique push
      var clean_url = $.extend({}, clean_params(state)); //duplicate the params object to allow for manipulation before sending to history object, and rinse
      var url_add = $.param(clean_url) + (landing_hash && landing_hash !== state.view ? "#" + landing_hash : "") + trailing_fragment; //rebuild the query string, taking only what is desired and add "!new" so the browser registers a unique push

      //TODO: uncomment to use the endpoint as the view param
      var new_endpoint = (location.pathname).replace(/(.*\/)(.*)$/, "$1"+state.view); //the state view is going to replace the endpoint
      //var new_endpoint = location.pathname; //uncomment to just use "view" param instead of endpoint as view
      var push_url = (location.protocol + '//' + location.host + new_endpoint + (url_add !== trailing_fragment ? "?" : "") + url_add); //rebuild the url using fresh parameterization
      state.title = title; //set the title of the page and save to history params

      if (landing_hash == state.view || first_load) //for a #view hash which is replaced to "?view" or this is a default_show
      {
        history.replaceState(state, title, push_url);
      } else if (!first_load) //if not the first load then proceed with the pushStates to modify the browser history
      {
        history.pushState(state, title, push_url);
      }
    } else if (state.hasOwnProperty("nopush")) {
      delete state.nopush;
    } //remove the nopush as it's only good for one-time use on a back/forward

    set_links_in_use(); //apple "in_use" class to links that match the current screen
  };

  var get_endpoint = function() {
    return location.pathname.split("/").pop().replace(/(.*)(\!.*)/, "$1"); //get endpoint and remove everything after trailing exclamation
  };

  var set_links_in_use = function(){ //apply an "in_use" class to links that reference the current page as a visual cue
    $(".in_use").removeClass("in_use");
    $("[href$='#"+state.view+"'],[data-show='"+state.view+"']").addClass("in_use");
  };

	//set some initial vars
	var landing_hash = decodeURIComponent(window.location.hash.substring(1)).toLowerCase().replace(/\s+/g,''); //support "#" links as they are the nonjs fallback and should be treated as the primary (view) param
  var init_params = {};
  //view param is a special case because it can be a url param, the hash ("#[view]"), or the endpoint in the url pathname
  if (url_param("view") !== "") {
    init_params.view = url_param("view");
  } else if (landing_hash !== "") {
    init_params.view = landing_hash;
  } else if(get_endpoint() !== ""){
    init_params.view = get_endpoint();
  } //add "view" to init_params if it's defined
	var first_load = true; //for use below so that the history state isnt pushed on initial page load
		/*
	NOTE HERE: page_params could easily be built out beyond a single key-value pair. example, using server-rendered PHP:
	var page_params = <?php echo json_encode(array_intersect_key($_GET, (array_flip(preg_grep('/blog_|db|filter|post/i', array_keys($_GET)))))); ?>;
	this would take any query string params that match "blog_", "db", "filter", or "post" and create JSON object with those key-values, using the power of server-side preprocessing to do so
	*/

	return{
		set: function(param_set){
			//as pointed out above, params can be built out beyond just the "view" property
      var first_load_params = $.extend({}, default_params, url_params(), init_params); //baseline list of params. defaults + "keeper" url params + non-keeper init params
      var url_and_first_load = first_load ? first_load_params : url_params(); //if this is the first_load, use that. otherwise only use url params.
      params = first_load ? $.extend({}, first_load_params, param_set) : param_set; //critical for initial experience: need to merge any passed params into first_load stuff. goal here is for "params" to only include what is changing.
      var hist_params = history.pushState ? $.extend({}, url_and_first_load, params) : params; //if browser supports history object and this request wasn't sent with a specific "fresh_url: true" (like during logout), we carry url params with us and rewrite url with them
      state = $.extend(state, hist_params); //merge into state so we have a global truth for this session
      var title = document.title.split(" - ")[0]; //for compiling and rewriting title later

			if(!!params.view)
			{
        var $section = (content_box.sections).filter("#" + params.view); //jquery pointer to the area specified in url

        //to scale out and use url params to perform addl actions, then handle those params here
        if ($section.length) //before going forward, ensure there is a matching screen for this param
        {
          //this may be where an ajax call is made using the params supplied to the function, but for this exercise we are using content already on the page
          var $nav_link = (content_box.nav).find("a[data-show='" + params.view + "']"); //jquery pointer to the nav link specified in url
          $(".active", content_box.nav).removeClass("active"); //remove all active links before setting a new active
          $nav_link.addClass("active"); //set current to active

          if(!!$nav_link.length && !$nav_link.is(":visible")){ //if a nav link exists and it's hidden, toggle the menu
            menu.toggle(); //slide down menu if not opened but nav link is inside. case: mobile, collapsed menu, link is inside
          } else if(!!$nav_link.length && $nav_link.is(":visible") && !content_box.nav.hasClass("responsive") && !$nav_link.closest(".menu_item").is(":first-child")) {
            content_box.nav.addClass("responsive"); //add responsive class if the link is visible, it's not the first item, and the menu is not responsive. case: desktop, clicking on nav link. this ensures when flipped to mobile, the menu is open
          } else if (content_box.nav.hasClass("responsive") && (!$nav_link.length || $nav_link.closest(".menu_item").is(":first-child"))){
            //slide up menu if 1. menu has responsive class, so it will be open on mobile 2. the link is not in the menu, 3. the dropdown toggle is visible. case: mobile, menu is open, clicking on link that isn't in menu
            if($(".dropdown", content_box.nav).is(":visible")){ //if this is the mobile view, then toggle (slideUp)
              menu.toggle();
            } else {
              content_box.nav.removeClass("responsive"); //not mobile view, so prepare if user flips their phone. remove the responsive class
            }
          }

          (content_box.sections).not("#" + params.view).hide(); //hide all articles that are not this one (using id attr)

          $section.fadeIn(300, function(){
              if(params.nopush !== true) { //if this isn't a back/forward browser request, then scroll user to top
                $("html,body").animate({scrollTop: $('main').position().top},500); //scroll to top when this screen is displayed
              }
            });

          //extra trigger stuff
          $("[data-trigger]", $section).each(function () { //trigger anything using the data-trigger attribute
            $(document).trigger($(this).data("trigger"), [$(this)]);
          });

          var title_case = (params.view).replace("-", " ").replace(/\w\S*/g, function(t){return t.charAt(0).toUpperCase() + t.substr(1).toLowerCase();}); //convert to title case using regex
          title += (" - " + title_case); //compile new title
          document.title = title; //set page title using javascript
				} else {
          newview.set({view: screens.default}); //call recursively and go back to default because this is an invalid view
        }
			}

      rewrite_history(title); //we're rewriting history! well, browser history
      first_load = landing_hash = false; //clear these so we know app has been initialized and history rewriting can commence
		}
	};
}(config.keep_params, config.content_area, config.defaults, config.screens);

var menu = function(){
  var toggle_menu = function(){
    (config.content_area.nav).addClass("toggling");
    if(config.content_area.nav.is(".responsive")){
      $(".menu_item", config.content_area.nav).not(".no_toggle").each(function(){
        $(this).css("display", "block"); //this is a weird one. if not set explicitly to block here then if open when page first loads it won't slide up.
        $(this).slideUp();
      });
      (config.content_area.nav).removeClass("responsive");
    } else {
      (config.content_area.nav).addClass("responsive");
      $(".menu_item", config.content_area.nav).not(".no_toggle").each(function(){
        $(this).slideDown(function(){});
      });
    }
      (config.content_area.nav).removeClass("toggling");
    };

    return {
      toggle: toggle_menu
    };
}();

</script>
<script>
//contact form functionality
$(function(){
	$('#c_form_box #submit_form').not(".in_progress").click(function(e){
    if (navigator && navigator.onLine === false) { //user is offline
      e.preventDefault();
      e.stopPropagation();
      $(document).trigger('modal', [$('.modal.offline')]); //show offline modal
    } else {
      var $button = $(this);

      var lead_sheet = "www.joshlipinski.com Website Submission\n\n" + "Note:\n\t\t\t\t\t" + $('#c_form_box #c_questions').val() + "\n\nName:\n\t\t\t\t\t" + $('#c_form_box #c_firstname').val()  + "\n\nEmail:\n\t\t\t\t\t" + $('#c_form_box #c_email').val() +  "\n\n=====-=====-=====-=====-=====-=====\nForm generated by " + window.location.host +  "\nURL of Submission: " + window.location.href + "\nPage Title of Submission: " + window.document.title + "\n==================================";

      if($('#c_form_box #c_email').val() != "" && $('#c_form_box #c_firstname').val() != "" && !$button.attr('disabled'))
      {
         $.ajax({
           method: "POST",
           beforeSend: function(){
            $button.addClass("in_progress") //flashing button animation
           },
           url: "https://us-central1-josh-lipinski.cloudfunctions.net/api/send",
           data: {msg:lead_sheet,email:$("#c_form_box #c_email").val(),realname:$("#c_form_box #c_firstname").val(),lead_sheet_title:"Website Submission",lead_recipients:"jlipin3@yahoo.com",primary_url:window.location.host},
           dataType: "html",
           cache: false,
        }).done(function(return_data){
            $('#c_form_box_status').html('<div class="contact_success block_content">' + return_data + ' To reach me directly, call me at 4132818645.</div>');
            $('#c_form_box #submit_form').attr('disabled', 'disabled');
            setTimeout(function(){ //disable the button from rapidfire submits
              $('#c_form_box #submit_form').removeAttr('disabled');
            }, 10000);
        }).fail(function(return_data){
          $('#c_form_box_status').html('<div class="contact_error block_content">' + (!!return_data.responseText ? return_data.responseText : "Error with the submission. Check your connection and try again.") + '</div>');
        }).always(function(){
          $button.removeClass("in_progress"); //stop button flashing
        })
      } else {
        $('#c_form_box_status').html('<div class="contact_error block_content">Fill in the required fields please.</div>');
      }
    }
  });
});
</script>
<!-- If you enabled Analytics in your project, add the Firebase SDK for Analytics -->
<script src="/__/firebase/7.5.0/firebase-app.js"></script>
<script src="/__/firebase/7.5.0/firebase-analytics.js"></script>
    <script>
      var cache_v = '12202019_510pm'; //replaced during build. to compare build version vs service worker version

      //es5 below since html extensions are not transpiled
      try {
        if ('serviceWorker' in navigator) {
            //below: this first part checks whether the serviceworker is outdated by comparing
            //the build version of this file vs the build version of the sw.js file...they should be in sync.
            var sw_register; //make avail globally

            caches.keys().then(function(current_cache){
                //outdated cache, so unregister serviceworker, which will install new one
                navigator.serviceWorker.getRegistrations().then(function(registrations) {
                    if (
                        current_cache != cache_v &&
                        registrations.length &&
                        navigator.onLine !== false //ensure the serviceworker is only unregistered if online, because unregistering will remove our cache pointer
                    ) {
                        registrations.forEach(function(registration){
                            //loop through all serviceworkers
                            registration.unregister(); //unregister current serviceworker
                        });
                    }

                    //fetch a new sw.js and run through pre-cache of the entire web app during the install. see sw.js
                    navigator.serviceWorker.register('sw.js').then(function(sw) {
                        sw_register = sw;
                    }).catch(function(e){console.log(e);});
                }).catch(function(e){console.log(e);});
            }).catch(function(e){console.log(e);});
        }
      } catch(e){}
    </script>
</body>
</html>
