[
  {
    "path": "README.md",
    "content": "# Notes\n<img src=\"https://sii.im/playground/notes/notes-ss.png\" alt=\"Notes app\" width='590px'>\n<img src=\"https://sii.im/playground/notes/notes-pwa.png\" alt=\"Notes app PWA score\" width='610px'>\n\nA simple, offline-based notepad, based on usage of [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) and [PWA techniques.](https://www.smashingmagazine.com/2016/08/a-beginners-guide-to-progressive-web-apps/)\n\nView the demo [here](https://sii.im/playground/notes) on a [supported device.](http://caniuse.com/#feat=serviceworkers)\n\n## Features\n\n  - Write notes and save them to [localStorage](https://developer.mozilla.org/en/docs/Web/API/Window/localStorage)\n  - Use [Service Worker](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers) to enable offline viewing\n  - \"Add To Home Screen\" feature on both Android and iOS supported devices\n"
  },
  {
    "path": "css/style.css",
    "content": "* {\n  box-sizing: border-box;\n  margin: 0;\n  padding: 0;\n}\n\nhtml,\nbody {\n  font-family: Helvetica Neue, sans-serif;\n}\n\nbody {\n  height: 100vh;\n  background: linear-gradient(to left, #B24592 , #F15F79);\n}\n\n@media screen and (max-width: 500px) {\n  body {\n    height: auto;\n    min-height: 100vh;\n    background: none;\n  }\n}\n\n@keyframes fadein {\n  from { \n    opacity: 0;\n    transform: translate3d(0, 10px, 0);\n  }\n  \n  to   { \n    opacity: 1;\n    transform: translate3d(0, 0, 0);\n  }\n}\n\n.wrapper {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  margin: auto;\n  width: 400px;\n  height: 620px;\n  background-color: #fff;\n  overflow: hidden;\n  border-radius: 5px;\n  box-shadow: 0 20px 80px rgba(0, 0, 0, 0.6);\n  animation: fadein 1s;\n}\n\n@media screen and (max-width: 500px) {\n  .wrapper {\n    width: 100%;\n    height: 100vh;\n    margin: 0;\n    overflow: scroll;\n    box-shadow: none;\n    animation: none;\n  }\n}\n\n  .wrapper__inner {\n    position: relative;\n    width: 100%;\n    height: 100%;\n  }\n\n@media screen and (max-width: 500px) {\n  .notepad {\n    padding: 15px;\n  }\n}\n\n  .notepad__heading {\n    width: 100%;\n    padding: 60px 40px 15px;\n    border-bottom: 1px solid #f5f4f4;\n    color: #5f5f5f;\n    font-weight: 400;\n  }\n\n  .notepad__form-label {\n    clip: rect(1px, 1px, 1px, 1px);\n    height: 1px;\n    overflow: hidden;\n    position: absolute;\n    width: 1px;\n  }\n\n  .notepad__form-input {\n    border: 0;\n    width: 100%;\n    line-height: 1.3;\n    margin: 0;\n    font-size: 19px;\n    color: #5f5f5f;\n    padding: 30px 40px;\n    background-color: transparent;\n    transition: background-color 300ms ease;\n\n    @media screen and (max-width: 500px) { \n      .notepad__form-input {\n        height: auto;\n      }\n    }\n  }\n\n  .notepad__form-input:focus {\n    outline: 0;\n    background-color: #f5f5f5;\n  }\n\n  .notepad__list {\n    height: 365px;\n    overflow: scroll;\n  }\n\n  @media screen and (max-width: 500px) {\n    .notepad__list {\n      height: auto;\n    }\n  }\n\n  .notepad__list-item {\n    width: 100%;\n    padding: 30px 40px;\n    border: 0;\n    border-bottom: 1px solid #f5f4f4;\n    line-height: 1.3;\n    font-size: 19px;\n    list-style-type: none;\n    color: #5f5f5f;\n    background-color: transparent;\n  }\n\n  .notepad__list-item:first-of-type {\n    border-top: 1px solid #f5f4f4;\n  }\n\n  .notepad__clear {\n    position: absolute;\n    bottom: 0;\n    display: none;\n    width: 100%;\n    padding: 20px;\n    border: 0;\n    font-size: 12px;\n    text-transform: uppercase;\n    background-color: #fff;\n    color: #333;\n    cursor: pointer;\n    font-family: Helvetica Neue, sans-serif;\n    letter-spacing: 2px;\n    transition:\n      color 300ms ease,\n      background-color 300ms ease;\n  }\n\n  .notepad__clear:focus {\n    outline: 0;\n  }\n\n  .notepad__clear:hover {\n    color: #fff;\n    background-color: #ea3860;\n  }\n\n  .notepad__clear--display {\n    display: block;\n  }\n\n  @media screen and (max-width: 500px) {\n    .notepad__clear {\n      position: static;\n    }\n  }\n\n  .info,\n  .twitter {\n    position: fixed;\n    top: 0;\n    right: 0;\n    padding: 15px 15px 12px 15px;\n    transition:\n      background-color 300ms ease;\n  }\n\n  .info span,\n  .twitter span {\n    display: none;\n  }\n\n  @media screen and (max-width: 500px) {\n    .info,\n    .twitter {\n      position: absolute;\n      z-index: 2;\n    }\n  }\n\n  @media screen and (max-width: 500px) {\n   .info {\n    right: 40px;\n   } \n  }\n\n  .twitter {\n    top: 45px;\n  }\n\n  @media screen and (max-width: 500px) {\n   .twitter {\n    top: 0;\n   } \n  }\n\n  .info svg,\n  .twitter svg {\n    width: 30px;\n    height: 30px;\n    fill: #fff;\n    transition:\n      fill 300ms ease;\n  }\n\n  @media screen and (max-width: 500px) {\n   .info svg,\n   .twitter svg {\n    width: 20px;\n    fill: #333;\n   } \n  }\n\n  .info:hover svg,\n  .twitter:hover svg {\n    fill: #333;\n  }\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en-gb\">\n  <head>\n    <title>Notes</title>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"icon\" href=\"favicon.ico\">\n    <link href=\"css/style.css\" rel=\"stylesheet\">\n    <meta name=\"application-name\" content=\"Notes\" />\n    <meta name=\"theme-color\" content=\"#ffffff\">\n    <link rel=\"manifest\" href=\"manifest.json\">\n  </head>\n  <body>\n    <a href=\"https://github.com/SimonDEvans/notes\" class=\"info\" aria-label=\"GitHub\">\n      <svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" fill-rule=\"evenodd\" clip-rule=\"evenodd\" stroke-linejoin=\"round\" stroke-miterlimit=\"1.414\"><path d=\"M8 0C3.58 0 0 3.582 0 8c0 3.535 2.292 6.533 5.47 7.59.4.075.547-.172.547-.385 0-.19-.007-.693-.01-1.36-2.226.483-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.725-.496.056-.486.056-.486.803.056 1.225.824 1.225.824.714 1.223 1.873.87 2.33.665.072-.517.278-.87.507-1.07-1.777-.2-3.644-.888-3.644-3.953 0-.873.31-1.587.823-2.147-.09-.202-.36-1.015.07-2.117 0 0 .67-.215 2.2.82.64-.178 1.32-.266 2-.27.68.004 1.36.092 2 .27 1.52-1.035 2.19-.82 2.19-.82.43 1.102.16 1.915.08 2.117.51.56.82 1.274.82 2.147 0 3.073-1.87 3.75-3.65 3.947.28.24.54.73.54 1.48 0 1.07-.01 1.93-.01 2.19 0 .21.14.46.55.38C13.71 14.53 16 11.53 16 8c0-4.418-3.582-8-8-8\"/></svg>\n      <span>GitHub</span>\n    </a>\n    <a href=\"https://twitter.com/siimonevans\" class=\"twitter\" aria-label=\"Twitter\">\n      <svg viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\" fill-rule=\"evenodd\" clip-rule=\"evenodd\" stroke-linejoin=\"round\" stroke-miterlimit=\"1.414\"><path d=\"M16 3.038c-.59.26-1.22.437-1.885.517.677-.407 1.198-1.05 1.443-1.816-.634.37-1.337.64-2.085.79-.598-.64-1.45-1.04-2.396-1.04-1.812 0-3.282 1.47-3.282 3.28 0 .26.03.51.085.75-2.728-.13-5.147-1.44-6.766-3.42C.83 2.58.67 3.14.67 3.75c0 1.14.58 2.143 1.46 2.732-.538-.017-1.045-.165-1.487-.41v.04c0 1.59 1.13 2.918 2.633 3.22-.276.074-.566.114-.865.114-.21 0-.41-.02-.61-.058.42 1.304 1.63 2.253 3.07 2.28-1.12.88-2.54 1.404-4.07 1.404-.26 0-.52-.015-.78-.045 1.46.93 3.18 1.474 5.04 1.474 6.04 0 9.34-5 9.34-9.33 0-.14 0-.28-.01-.42.64-.46 1.2-1.04 1.64-1.7z\" fill-rule=\"nonzero\"/></svg>\n      <span>Twitter</span>\n    </a>\n    <div class=\"wrapper\">\n      <div class=\"wrapper__inner\">\n        <div class=\"notepad\">\n          <h1 class=\"notepad__heading\">Notes</h1>\n          <form class=\"notepad__form\">\n            <input class=\"notepad__form-input\" placeholder=\"Start typing&hellip;\" id=\"form-input\" />\n            <label class=\"notepad__form-label\" for=\"form-input\">Add an item</label>\n          </form>\n          <ul class=\"notepad__list\"></ul>\n          <button class=\"notepad__clear\">Clear list</button>\n        </div>\n      </div>\n    </div>\n    <script src=\"js/jquery.min.js\"></script>\n    <script src=\"js/app.js\"></script>\n    <script src=\"js/note-list.js\"></script>\n  </body>\n</html>"
  },
  {
    "path": "js/app.js",
    "content": "// Registering ServiceWorker\nif ( 'serviceWorker' in navigator ) {\n  navigator.serviceWorker.register( 'sw.js' ).then(function(registration) {\n\n    // Registration was successful\n    console.log( 'ServiceWorker registration successful. Scope: ' + registration.scope )\n  }).catch(function(err) {\n\n    // Registration failed with error\n    console.log( 'ServiceWorker registration failed. Error: ' + err);\n  });\n}"
  },
  {
    "path": "js/cache-polyfill.js",
    "content": "/**\n * Copyright 2015 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n */\n\n(function() {\n  var nativeAddAll = Cache.prototype.addAll;\n  var userAgent = navigator.userAgent.match(/(Firefox|Chrome)\\/(\\d+\\.)/);\n\n  // Has nice behavior of `var` which everyone hates\n  if (userAgent) {\n    var agent = userAgent[1];\n    var version = parseInt(userAgent[2]);\n  }\n\n  if (\n    nativeAddAll && (!userAgent ||\n      (agent === 'Firefox' && version >= 46) ||\n      (agent === 'Chrome'  && version >= 50)\n    )\n  ) {\n    return;\n  }\n\n  Cache.prototype.addAll = function addAll(requests) {\n    var cache = this;\n\n    // Since DOMExceptions are not constructable:\n    function NetworkError(message) {\n      this.name = 'NetworkError';\n      this.code = 19;\n      this.message = message;\n    }\n\n    NetworkError.prototype = Object.create(Error.prototype);\n\n    return Promise.resolve().then(function() {\n      if (arguments.length < 1) throw new TypeError();\n\n      // Simulate sequence<(Request or USVString)> binding:\n      var sequence = [];\n\n      requests = requests.map(function(request) {\n        if (request instanceof Request) {\n          return request;\n        }\n        else {\n          return String(request); // may throw TypeError\n        }\n      });\n\n      return Promise.all(\n        requests.map(function(request) {\n          if (typeof request === 'string') {\n            request = new Request(request);\n          }\n\n          var scheme = new URL(request.url).protocol;\n\n          if (scheme !== 'http:' && scheme !== 'https:') {\n            throw new NetworkError(\"Invalid scheme\");\n          }\n\n          return fetch(request.clone());\n        })\n      );\n    }).then(function(responses) {\n      // If some of the responses has not OK-eish status,\n      // then whole operation should reject\n      if (responses.some(function(response) {\n        return !response.ok;\n      })) {\n        throw new NetworkError('Incorrect response status');\n      }\n\n      // TODO: check that requests don't overwrite one another\n      // (don't think this is possible to polyfill due to opaque responses)\n      return Promise.all(\n        responses.map(function(response, i) {\n          return cache.put(requests[i], response);\n        })\n      );\n    }).then(function() {\n      return undefined;\n    });\n  };\n\n  Cache.prototype.add = function add(request) {\n    return this.addAll([request]);\n  };\n}());"
  },
  {
    "path": "js/note-list.js",
    "content": "$(document).ready(function () {\n\n    var noteList = function() {\n\n      var $notepad          = $(' .notepad' ),\n          $noteList         = $(' .notepad__list' ),\n          $noteListItem     = $( '.notepad__list-item' ),\n          $noteForm         = $( '.notepad__form' ),\n          $noteFormInput    = $( '.notepad__form-input' ),\n          $clearList        = $( '.notepad__clear' ),\n          clearListDisplay  = 'notepad__clear--display',\n          noteCount         = 0;\n\n      function displayNotes() {\n        for (noteCount = 0; noteCount < localStorage.length; noteCount++) {\n          var noteID        = 'task-' + noteCount;\n\n          // Build note list\n          $noteList.append(\"<li class='notepad__list-item' id='\" + noteID + \"'>\" + localStorage.getItem(noteID) + \"</li>\");\n\n          // Show reset button\n          $clearList.addClass( clearListDisplay );\n        }\n      }\n\n      function storeNote() {\n        if ( $noteFormInput.val() !== '' ) {\n            var noteID      = 'task-' + noteCount,\n                task        = $( '#' + noteID ),\n                taskMessage = $noteFormInput.val();\n\n            localStorage.setItem( noteID, taskMessage );\n\n            // Add to note list\n            $noteList.append( \"<li class='notepad__list-item' id='\" + noteID + \"'>\" + taskMessage + \"</li>\" );\n\n            // Display reset button\n            if ( !$clearList.hasClass( clearListDisplay ) ) {\n              $clearList.addClass( clearListDisplay );\n            }\n\n            // Reset\n            $noteFormInput.val('');\n            noteCount++;\n        }\n      }\n\n      function clearNotes() {\n\n          // Update DOM\n          $noteList.empty();\n          $clearList.removeClass( clearListDisplay );\n\n          // Clear storage\n          localStorage.clear();\n          noteCount = 0;\n      }\n\n      function bindEvents() {\n\n        // Show any existing notes from localStorage\n        displayNotes();\n\n        // Create new note\n        $noteForm.on( 'submit', function () {\n            storeNote();\n            return false;\n        });\n\n        // Reset notes\n        $clearList.on( 'click', function () {\n          clearNotes();\n        });\n      }\n\n      bindEvents();\n    };\n\n    noteList();\n});\n"
  },
  {
    "path": "manifest.json",
    "content": "{\n  \"short_name\": \"notes\",\n  \"name\": \"notes\",\n  \"description\" : \"An offline-capable notes app, using localStorage and ServiceWorker\",\n  \"display\": \"standalone\",\n  \"orientation\": \"portrait\",\n  \"start_url\": \"index.html\",\n  \"theme_color\": \"#ffffff\",\n  \"background_color\": \"#ffffff\",\n  \"icons\": [\n    {\n      \"src\": \"img/icon-60.png\",\n      \"sizes\": \"48x48\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"img/icon-114.png\",\n      \"sizes\": \"114x114\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"img/icon-152.png\",\n      \"sizes\": \"152x152\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"img/icon-558.png\",\n      \"sizes\": \"558x558\",\n      \"type\": \"image/png\"\n    }\n  ]\n}\n"
  },
  {
    "path": "sw.js",
    "content": "// Polyfill for Chrome caching\nimportScripts('js/cache-polyfill.js');\n\n// Install the ServiceWorker\nself.addEventListener('install', function(event) {\n  event.waitUntil(\n\n    // Open a cache\n    caches.open('v1').then(function(cache) {\n\n      // Define what we want to cache\n      return cache.addAll([\n        '/',\n        'index.html',\n        'js/app.js',\n        'js/jquery.min.js',\n        'js/note-list.js',\n        'css/style.css',\n        'favicon.ico',\n        'manifest.json',\n        'img/icon-60.png',\n        'img/icon-114.png',\n        'img/icon-152.png',\n        'img/icon-558.png'\n      ]);\n    })\n  );\n});\n\n// Use ServiceWorker (or not) to fetch data\nself.addEventListener('fetch', function(event) {\n\n  event.respondWith(\n\n    // Look for something in the cache that matches the request\n    caches.match(event.request).then(function(response) {\n\n      // If we find something, return it\n      // Otherwise, use the network instead\n      return response || fetch(event.request);\n    })\n  );\n});"
  }
]