├── .github └── welcome-bot.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── a2hs ├── icon │ └── fox-icon.png ├── images │ ├── fox1.jpg │ ├── fox2.jpg │ ├── fox3.jpg │ └── fox4.jpg ├── index.html ├── index.js ├── manifest.webmanifest ├── style.css └── sw.js ├── cycletracker ├── html_and_css │ ├── index.html │ └── style.css ├── index.html ├── javascript_functionality │ ├── app.js │ ├── index.html │ └── style.css ├── manifest_file │ ├── app.js │ ├── cycletracker.json │ ├── favicon.ico │ ├── icons │ │ ├── circle.ico │ │ ├── circle.svg │ │ ├── tire.svg │ │ └── wheel.svg │ ├── index.html │ └── style.css └── service_workers │ ├── app.js │ ├── cycletracker.json │ ├── favicon.ico │ ├── icons │ ├── circle.ico │ ├── circle.svg │ ├── tire.svg │ └── wheel.svg │ ├── index.html │ ├── style.css │ └── sw.js └── js13kpwa ├── .gitignore ├── app.js ├── data ├── games.js └── img │ ├── a-box-invaders.jpg │ ├── a-snake.jpg │ ├── balloon-problems.jpg │ ├── cat-meow.jpg │ ├── coconutty.jpg │ ├── debriss.jpg │ ├── dont-let-your-dreams-be-memes.jpg │ ├── emma-3d.jpg │ ├── fly-south.jpg │ ├── galacticdiamond.jpg │ ├── give-space.jpg │ ├── lost-in-cyberspace.jpg │ ├── lost-in-guam.jpg │ ├── lost-in-metaverse.jpg │ ├── lost-in-my-mind.jpg │ ├── lost-in-the-forest-dungeon.jpg │ ├── lost-pacman.jpg │ ├── metamorphosis.jpg │ ├── placeholder.png │ ├── polyhedron-runner.jpg │ ├── prisonri0t.jpg │ ├── she-is-my-universe.jpg │ ├── shifted-dimensions.jpg │ ├── spacewrecked.jpg │ ├── vernissage.jpg │ ├── vr-racing.jpg │ ├── wandering-moon.jpg │ ├── wherewhat.jpg │ └── world-lost.jpg ├── favicon.ico ├── fonts ├── graduate.eot ├── graduate.ttf └── graduate.woff ├── icons ├── icon-128.png ├── icon-168.png ├── icon-192.png ├── icon-256.png ├── icon-32.png ├── icon-512.png ├── icon-64.png └── icon-96.png ├── img ├── bg.png └── js13kgames.png ├── index.html ├── js13kpwa.webmanifest ├── style.css └── sw.js /.github/welcome-bot.yml: -------------------------------------------------------------------------------- 1 | 2 | name: "AlloAllo" 3 | 4 | on: 5 | issues: 6 | types: 7 | - opened 8 | pull_request_target: 9 | branches: 10 | - main 11 | types: 12 | - opened 13 | - closed 14 | 15 | jobs: 16 | allo-allo: 17 | uses: mdn/workflows/.github/workflows/allo-allo.yml@main 18 | with: 19 | target-repo: "mdn/pwa-examples" 20 | issue-welcome: > 21 | It looks like this is your first issue. Welcome! 👋 22 | One of the project maintainers will be with you as soon as possible. We 23 | appreciate your patience. To safeguard the health of the project, please 24 | take a moment to read our [code of conduct](../blob/main/CODE_OF_CONDUCT.md). 25 | pr-welcome: > 26 | It looks like this is your first pull request. 🎉 27 | Thank you for your contribution! One of the project maintainers will triage 28 | and assign the pull request for review. We appreciate your patience. To 29 | safeguard the health of the project, please take a moment to read our 30 | [code of conduct](../blob/main/CODE_OF_CONDUCT.md). 31 | pr-merged: > 32 | Congratulations on your first merged pull request. 🎉 Thank you for your contribution! 33 | Did you know we have a [project board](https://github.com/orgs/mdn/projects/25) with high-impact contribution opportunities? 34 | We look forward to your next contribution. 35 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our [How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/) page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PWA Examples 2 | 3 | Examples for progressive web apps. 4 | 5 | In this repo, we currently have: 6 | 7 | * [CycleTracker](cycletracker): A basic app for tracking menstrual cycles. The app's HTML includes a form to add a period cycle start and end dates. The JavaScript app functionality sorts the dates and saves thems to local storage. It also displays the dates retrieved from local storage below the form. The app includes a manifest file with three icons, color scheme, etc. The app also includes a service worker to handle asset caching. 8 | 9 | * [a2hs](a2hs): An example set up to show how Add to home screen (A2HS) works. [See it live here](https://mdn.github.io/pwa-examples/a2hs/). This includes an icon and [manifest file](a2hs/manifest.webmanifest) for allowing the app to be added to home screen, and a [simple service worker](a2hs/sw.js) for making the site work offline. 10 | 11 | * [js13kpwa](js13kpwa): A list of A-Frame entries submitted to the js13kGames 2017 competition, used as an example for the MDN articles about Progressive Web Apps. The js13kPWA have the App Shell structure, works offline with the Service Worker, is installable thanks to the Manifest file and Add to Homescreen feature, and is re-engageable by using Notifications and Push. [See it live here](https://mdn.github.io/pwa-examples/js13kpwa/). 12 | -------------------------------------------------------------------------------- /a2hs/icon/fox-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/a2hs/icon/fox-icon.png -------------------------------------------------------------------------------- /a2hs/images/fox1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/a2hs/images/fox1.jpg -------------------------------------------------------------------------------- /a2hs/images/fox2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/a2hs/images/fox2.jpg -------------------------------------------------------------------------------- /a2hs/images/fox3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/a2hs/images/fox3.jpg -------------------------------------------------------------------------------- /a2hs/images/fox4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/a2hs/images/fox4.jpg -------------------------------------------------------------------------------- /a2hs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A2HS demo 6 | 7 | 8 | 9 | 10 | 11 | a fox picture 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /a2hs/index.js: -------------------------------------------------------------------------------- 1 | const images = ['fox1', 'fox2', 'fox3', 'fox4']; 2 | const imgElem = document.querySelector('img'); 3 | 4 | function randomValueFromArray(array) { 5 | const randomNo = Math.floor(Math.random() * array.length); 6 | return array[randomNo]; 7 | } 8 | 9 | setInterval(() => { 10 | const randomChoice = randomValueFromArray(images); 11 | imgElem.src = `images/${randomChoice}.jpg`; 12 | }, 2000); 13 | 14 | // Register service worker to control making site work offline 15 | 16 | if ('serviceWorker' in navigator) { 17 | navigator.serviceWorker 18 | .register('/pwa-examples/a2hs/sw.js') 19 | .then(() => { console.log('Service Worker Registered'); }); 20 | } 21 | 22 | // Code to handle install prompt on desktop 23 | 24 | let deferredPrompt; 25 | const addBtn = document.querySelector('.add-button'); 26 | 27 | window.addEventListener('beforeinstallprompt', (e) => { 28 | // Prevent Chrome 67 and earlier from automatically showing the prompt 29 | e.preventDefault(); 30 | // Stash the event so it can be triggered later. 31 | deferredPrompt = e; 32 | // Update UI to notify the user they can add to home screen 33 | addBtn.style.display = 'block'; 34 | 35 | addBtn.addEventListener('click', () => { 36 | // hide our user interface that shows our A2HS button 37 | addBtn.style.display = 'none'; 38 | // Show the prompt 39 | deferredPrompt.prompt(); 40 | // Wait for the user to respond to the prompt 41 | deferredPrompt.userChoice.then((choiceResult) => { 42 | if (choiceResult.outcome === 'accepted') { 43 | console.log('User accepted the A2HS prompt'); 44 | } else { 45 | console.log('User dismissed the A2HS prompt'); 46 | } 47 | deferredPrompt = null; 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /a2hs/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "background_color": "purple", 3 | "description": "Shows random fox pictures. Hey, at least it isn't cats.", 4 | "display": "fullscreen", 5 | "icons": [ 6 | { 7 | "src": "icon/fox-icon.png", 8 | "sizes": "192x192", 9 | "type": "image/png" 10 | } 11 | ], 12 | "name": "Awesome fox pictures", 13 | "short_name": "Foxes", 14 | "start_url": "/pwa-examples/a2hs/index.html" 15 | } 16 | -------------------------------------------------------------------------------- /a2hs/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | background: black; 4 | } 5 | 6 | body { 7 | height: inherit; 8 | margin: 0; 9 | } 10 | 11 | img { 12 | display: block; 13 | width: 100%; 14 | } 15 | 16 | .add-button { 17 | position: absolute; 18 | top: 1px; 19 | left: 1px; 20 | display: none; 21 | } 22 | 23 | @media (orientation: landscape) { 24 | img { 25 | height: 100%; 26 | object-fit: cover; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /a2hs/sw.js: -------------------------------------------------------------------------------- 1 | self.addEventListener('install', (e) => { 2 | e.waitUntil( 3 | caches.open('fox-store').then((cache) => cache.addAll([ 4 | '/pwa-examples/a2hs/', 5 | '/pwa-examples/a2hs/index.html', 6 | '/pwa-examples/a2hs/index.js', 7 | '/pwa-examples/a2hs/style.css', 8 | '/pwa-examples/a2hs/images/fox1.jpg', 9 | '/pwa-examples/a2hs/images/fox2.jpg', 10 | '/pwa-examples/a2hs/images/fox3.jpg', 11 | '/pwa-examples/a2hs/images/fox4.jpg', 12 | ])), 13 | ); 14 | }); 15 | 16 | self.addEventListener('fetch', (e) => { 17 | console.log(e.request.url); 18 | e.respondWith( 19 | caches.match(e.request).then((response) => response || fetch(e.request)), 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /cycletracker/html_and_css/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cycle Tracker 7 | 8 | 9 | 10 |

Period tracker

11 |
12 |
13 | Enter your period start and end date 14 |

15 | 16 | 17 |

18 |

19 | 20 | 21 |

22 |
23 |

24 | 25 |

26 |
27 |
28 |

Past periods

29 | 33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /cycletracker/html_and_css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 1vh 1vw; 3 | background-color: #efe; 4 | } 5 | ul, 6 | fieldset, 7 | legend { 8 | border: 1px solid; 9 | background-color: #fff; 10 | } 11 | ul { 12 | padding: 0; 13 | font-family: monospace; 14 | } 15 | li, 16 | legend { 17 | list-style-type: none; 18 | padding: 0.2em 0.5em; 19 | background-color: #cfc; 20 | } 21 | li:nth-of-type(even) { 22 | background-color: inherit; 23 | } 24 | -------------------------------------------------------------------------------- /cycletracker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cycle Tracker PWA 7 | 8 | 9 |

CycleTracker Period tracker PWA progress

10 | 11 |

12 | Tutorial: 13 | CycleTracker: Creating your first PWA 17 |

18 |

Tutorial demos:

19 |
    20 |
  1. HTML and CSS
  2. 21 |
  3. Secure connection (no files)
  4. 22 |
  5. 23 | JavaScript functionality 26 |
  6. 27 |
  7. Manifest file tutorial
  8. 28 |
  9. Service worker tutorial
  10. 29 |
30 |

Tutorial files:

31 |
    32 |
  1. 33 | HTML and CSS 37 |
  2. 38 |
  3. Secure connection (no files)
  4. 39 |
  5. 40 | JavaScript functionality 44 |
  6. 45 |
  7. 46 | Manifest file tutorial 50 |
  8. 51 |
  9. 52 | Service worker tutorial 56 |
  10. 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /cycletracker/javascript_functionality/app.js: -------------------------------------------------------------------------------- 1 | // JS file for cycleTracker app 2 | 3 | // ------------- 4 | // Variable declarations 5 | // ------------- 6 | const newPeriodFormEl = document.getElementsByTagName("form")[0]; 7 | const startDateInputEl = document.getElementById("start-date"); 8 | const endDateInputEl = document.getElementById("end-date"); 9 | const pastPeriodContainer = document.getElementById("past-periods"); 10 | 11 | // Storage key is an app-wide constant 12 | const STORAGE_KEY = "period-tracker"; 13 | 14 | // ------------- 15 | // Event Handlers 16 | // ------------- 17 | newPeriodFormEl.addEventListener("submit", (event) => { 18 | event.preventDefault(); 19 | const startDate = startDateInputEl.value; 20 | const endDate = endDateInputEl.value; 21 | if (checkDatesInvalid(startDate, endDate)) { 22 | return; 23 | } 24 | storeNewPeriod(startDate, endDate); 25 | renderPastPeriods(); 26 | newPeriodFormEl.reset(); 27 | }); 28 | 29 | // ------------- 30 | // Functionality 31 | // ------------- 32 | 33 | // 1. Form validation 34 | function checkDatesInvalid(startDate, endDate) { 35 | if (!startDate || !endDate || startDate > endDate) { 36 | newPeriodFormEl.reset(); 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | // 2. Get, add, sort, and store data 43 | function storeNewPeriod(startDate, endDate) { 44 | const periods = getAllStoredPeriods(); 45 | periods.push({ startDate, endDate }); 46 | periods.sort((a, b) => { 47 | return new Date(b.startDate) - new Date(a.startDate); 48 | }); 49 | window.localStorage.setItem(STORAGE_KEY, JSON.stringify(periods)); 50 | } 51 | 52 | // 3. Get and parse data 53 | function getAllStoredPeriods() { 54 | const data = window.localStorage.getItem(STORAGE_KEY); 55 | const periods = data ? JSON.parse(data) : []; 56 | return periods; 57 | } 58 | 59 | // 4. Display data 60 | function renderPastPeriods() { 61 | const pastPeriodHeader = document.createElement("h2"); 62 | const pastPeriodList = document.createElement("ul"); 63 | const periods = getAllStoredPeriods(); 64 | if (periods.length === 0) { 65 | return; 66 | } 67 | pastPeriodContainer.innerHTML = ""; 68 | pastPeriodHeader.textContent = "Past periods"; 69 | periods.forEach((period) => { 70 | const periodEl = document.createElement("li"); 71 | periodEl.textContent = `From ${formatDate( 72 | period.startDate, 73 | )} to ${formatDate(period.endDate)}`; 74 | pastPeriodList.appendChild(periodEl); 75 | }); 76 | 77 | pastPeriodContainer.appendChild(pastPeriodHeader); 78 | pastPeriodContainer.appendChild(pastPeriodList); 79 | } 80 | 81 | // 5. format dates for display 82 | function formatDate(dateString) { 83 | const date = new Date(dateString); 84 | return date.toLocaleDateString("en-US", { timeZone: "UTC" }); 85 | } 86 | 87 | // ------------- 88 | // Call render on page load 89 | // ------------- 90 | 91 | renderPastPeriods(); 92 | -------------------------------------------------------------------------------- /cycletracker/javascript_functionality/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cycle Tracker 7 | 8 | 9 | 10 |

Period tracker

11 |
12 |
13 | Enter your period start and end date 14 |

15 | 16 | 17 |

18 |

19 | 20 | 21 |

22 |
23 |

24 | 25 |

26 |
27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /cycletracker/javascript_functionality/style.css: -------------------------------------------------------------------------------- 1 | /* CSS for cycleTracker App */ 2 | 3 | body { 4 | margin: 1vh 1vw; 5 | background-color: #efe; 6 | } 7 | ul, 8 | fieldset, 9 | legend { 10 | border: 1px solid; 11 | background-color: #fff; 12 | } 13 | ul { 14 | padding: 0; 15 | font-family: monospace; 16 | } 17 | li, 18 | legend { 19 | list-style-type: none; 20 | padding: 0.2em 0.5em; 21 | background-color: #cfc; 22 | } 23 | li:nth-of-type(even) { 24 | background-color: inherit; 25 | } 26 | -------------------------------------------------------------------------------- /cycletracker/manifest_file/app.js: -------------------------------------------------------------------------------- 1 | // JS file for cycleTracker app 2 | 3 | // ------------- 4 | // Variable declarations 5 | // ------------- 6 | const newPeriodFormEl = document.getElementsByTagName("form")[0]; 7 | const startDateInputEl = document.getElementById("start-date"); 8 | const endDateInputEl = document.getElementById("end-date"); 9 | const pastPeriodContainer = document.getElementById("past-periods"); 10 | 11 | // Storage key is an app-wide constant 12 | const STORAGE_KEY = "period-tracker"; 13 | 14 | // ------------- 15 | // Event Handlers 16 | // ------------- 17 | newPeriodFormEl.addEventListener("submit", (event) => { 18 | event.preventDefault(); 19 | const startDate = startDateInputEl.value; 20 | const endDate = endDateInputEl.value; 21 | if (checkDatesInvalid(startDate, endDate)) { 22 | return; 23 | } 24 | storeNewPeriod(startDate, endDate); 25 | renderPastPeriods(); 26 | newPeriodFormEl.reset(); 27 | }); 28 | 29 | // ------------- 30 | // Functionality 31 | // ------------- 32 | 33 | // 1. Form validation 34 | function checkDatesInvalid(startDate, endDate) { 35 | if (!startDate || !endDate || startDate > endDate) { 36 | newPeriodFormEl.reset(); 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | // 2. Get, add, sort, and store data 43 | function storeNewPeriod(startDate, endDate) { 44 | const periods = getAllStoredPeriods(); 45 | periods.push({ startDate, endDate }); 46 | periods.sort((a, b) => { 47 | return new Date(b.startDate) - new Date(a.startDate); 48 | }); 49 | window.localStorage.setItem(STORAGE_KEY, JSON.stringify(periods)); 50 | } 51 | 52 | // 3. Get and parse data 53 | function getAllStoredPeriods() { 54 | const data = window.localStorage.getItem(STORAGE_KEY); 55 | const periods = data ? JSON.parse(data) : []; 56 | return periods; 57 | } 58 | 59 | // 4. Display data 60 | function renderPastPeriods() { 61 | const pastPeriodHeader = document.createElement("h2"); 62 | const pastPeriodList = document.createElement("ul"); 63 | const periods = getAllStoredPeriods(); 64 | if (periods.length === 0) { 65 | return; 66 | } 67 | pastPeriodContainer.innerHTML = ""; 68 | pastPeriodHeader.textContent = "Past periods"; 69 | periods.forEach((period) => { 70 | const periodEl = document.createElement("li"); 71 | periodEl.textContent = `From ${formatDate( 72 | period.startDate, 73 | )} to ${formatDate(period.endDate)}`; 74 | pastPeriodList.appendChild(periodEl); 75 | }); 76 | 77 | pastPeriodContainer.appendChild(pastPeriodHeader); 78 | pastPeriodContainer.appendChild(pastPeriodList); 79 | } 80 | 81 | // 5. format dates for display 82 | function formatDate(dateString) { 83 | const date = new Date(dateString); 84 | return date.toLocaleDateString("en-US", { timeZone: "UTC" }); 85 | } 86 | 87 | // ------------- 88 | // Call render on page load 89 | // ------------- 90 | 91 | renderPastPeriods(); 92 | -------------------------------------------------------------------------------- /cycletracker/manifest_file/cycletracker.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cycleTracker: Period Tracking app", 3 | "short_name": "CT", 4 | "description": "Securely and confidentially track your menstrual cycle. Enter the start and end dates of your periods, saving your private data to your browser on your device, without sharing it with the rest of the world.", 5 | "start_url": "/", 6 | "theme_color": "#eeffee", 7 | "background_color": "#eeffee", 8 | "display": "standalone", 9 | "icons": [ 10 | { 11 | "src": "favicon.ico", 12 | "sizes": "48x48" 13 | }, 14 | { 15 | "src": "icons/circle.svg", 16 | "sizes": "72x72 96x96", 17 | "purpose": "maskable" 18 | }, 19 | { 20 | "src": "icons/tire.svg", 21 | "sizes": "128x128 256x256" 22 | }, 23 | { 24 | "src": "icons/wheel.svg", 25 | "sizes": "512x512" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /cycletracker/manifest_file/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/cycletracker/manifest_file/favicon.ico -------------------------------------------------------------------------------- /cycletracker/manifest_file/icons/circle.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/cycletracker/manifest_file/icons/circle.ico -------------------------------------------------------------------------------- /cycletracker/manifest_file/icons/circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cycletracker/manifest_file/icons/tire.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cycletracker/manifest_file/icons/wheel.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /cycletracker/manifest_file/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cycle Tracker 7 | 8 | 9 | 10 | 11 | 12 |

Period tracker

13 |
14 |
15 | Enter your period start and end date 16 |

17 | 18 | 19 |

20 |

21 | 22 | 23 |

24 |
25 |

26 | 27 |

28 |
29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /cycletracker/manifest_file/style.css: -------------------------------------------------------------------------------- 1 | /* CSS for cycleTracker App */ 2 | 3 | body { 4 | margin: 1vh 1vw; 5 | background-color: #efe; 6 | } 7 | ul, 8 | fieldset, 9 | legend { 10 | border: 1px solid; 11 | background-color: #fff; 12 | } 13 | ul { 14 | padding: 0; 15 | font-family: monospace; 16 | } 17 | li, 18 | legend { 19 | list-style-type: none; 20 | padding: 0.2em 0.5em; 21 | background-color: #cfc; 22 | } 23 | li:nth-of-type(even) { 24 | background-color: inherit; 25 | } 26 | -------------------------------------------------------------------------------- /cycletracker/service_workers/app.js: -------------------------------------------------------------------------------- 1 | // JS file for cycleTracker app 2 | 3 | // ------------- 4 | // Variable declarations 5 | // ------------- 6 | const newPeriodFormEl = document.getElementsByTagName("form")[0]; 7 | const startDateInputEl = document.getElementById("start-date"); 8 | const endDateInputEl = document.getElementById("end-date"); 9 | const pastPeriodContainer = document.getElementById("past-periods"); 10 | 11 | // Storage key is an app-wide constant 12 | const STORAGE_KEY = "period-tracker"; 13 | 14 | // ------------- 15 | // Event Handlers 16 | // ------------- 17 | newPeriodFormEl.addEventListener("submit", (event) => { 18 | event.preventDefault(); 19 | const startDate = startDateInputEl.value; 20 | const endDate = endDateInputEl.value; 21 | if (checkDatesInvalid(startDate, endDate)) { 22 | return; 23 | } 24 | storeNewPeriod(startDate, endDate); 25 | renderPastPeriods(); 26 | newPeriodFormEl.reset(); 27 | }); 28 | 29 | // ------------- 30 | // Functionality 31 | // ------------- 32 | 33 | // 1. Form validation 34 | function checkDatesInvalid(startDate, endDate) { 35 | if (!startDate || !endDate || startDate > endDate) { 36 | newPeriodFormEl.reset(); 37 | return true; 38 | } 39 | return false; 40 | } 41 | 42 | // 2. Get, add, sort, and store data 43 | function storeNewPeriod(startDate, endDate) { 44 | const periods = getAllStoredPeriods(); 45 | periods.push({ startDate, endDate }); 46 | periods.sort((a, b) => { 47 | return new Date(b.startDate) - new Date(a.startDate); 48 | }); 49 | window.localStorage.setItem(STORAGE_KEY, JSON.stringify(periods)); 50 | } 51 | 52 | // 3. Get and parse data 53 | function getAllStoredPeriods() { 54 | const data = window.localStorage.getItem(STORAGE_KEY); 55 | const periods = data ? JSON.parse(data) : []; 56 | return periods; 57 | } 58 | 59 | // 4. Display data 60 | function renderPastPeriods() { 61 | const pastPeriodHeader = document.createElement("h2"); 62 | const pastPeriodList = document.createElement("ul"); 63 | const periods = getAllStoredPeriods(); 64 | if (periods.length === 0) { 65 | return; 66 | } 67 | pastPeriodContainer.innerHTML = ""; 68 | pastPeriodHeader.textContent = "Past periods"; 69 | periods.forEach((period) => { 70 | const periodEl = document.createElement("li"); 71 | periodEl.textContent = `From ${formatDate( 72 | period.startDate, 73 | )} to ${formatDate(period.endDate)}`; 74 | pastPeriodList.appendChild(periodEl); 75 | }); 76 | 77 | pastPeriodContainer.appendChild(pastPeriodHeader); 78 | pastPeriodContainer.appendChild(pastPeriodList); 79 | } 80 | 81 | // 5. format dates for display 82 | function formatDate(dateString) { 83 | const date = new Date(dateString); 84 | return date.toLocaleDateString("en-US", { timeZone: "UTC" }); 85 | } 86 | 87 | // ------------- 88 | // Call render on page load 89 | // ------------- 90 | 91 | renderPastPeriods(); 92 | -------------------------------------------------------------------------------- /cycletracker/service_workers/cycletracker.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cycleTracker: Period Tracking app", 3 | "short_name": "CT", 4 | "description": "Securely and confidentially track your menstrual cycle. Enter the start and end dates of your periods, saving your private data to your browser on your device, without sharing it with the rest of the world.", 5 | "start_url": "/", 6 | "theme_color": "#eeffee", 7 | "background_color": "#eeffee", 8 | "display": "standalone", 9 | "icons": [ 10 | { 11 | "src": "favicon.ico", 12 | "sizes": "48x48" 13 | }, 14 | { 15 | "src": "icons/circle.svg", 16 | "sizes": "72x72 96x96", 17 | "purpose": "maskable" 18 | }, 19 | { 20 | "src": "icons/tire.svg", 21 | "sizes": "128x128 256x256" 22 | }, 23 | { 24 | "src": "icons/wheel.svg", 25 | "sizes": "512x512" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /cycletracker/service_workers/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/cycletracker/service_workers/favicon.ico -------------------------------------------------------------------------------- /cycletracker/service_workers/icons/circle.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/cycletracker/service_workers/icons/circle.ico -------------------------------------------------------------------------------- /cycletracker/service_workers/icons/circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cycletracker/service_workers/icons/tire.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cycletracker/service_workers/icons/wheel.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /cycletracker/service_workers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cycle Tracker 7 | 8 | 9 | 10 | 11 | 12 |

Period tracker

13 |
14 |
15 | Enter your period start and end date 16 |

17 | 18 | 19 |

20 |

21 | 22 | 23 |

24 |
25 |

26 | 27 |

28 |
29 |
30 | 31 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /cycletracker/service_workers/style.css: -------------------------------------------------------------------------------- 1 | /* CSS for cycleTracker App */ 2 | 3 | body { 4 | margin: 1vh 1vw; 5 | background-color: #efe; 6 | } 7 | ul, 8 | fieldset, 9 | legend { 10 | border: 1px solid; 11 | background-color: #fff; 12 | } 13 | ul { 14 | padding: 0; 15 | font-family: monospace; 16 | } 17 | li, 18 | legend { 19 | list-style-type: none; 20 | padding: 0.2em 0.5em; 21 | background-color: #cfc; 22 | } 23 | li:nth-of-type(even) { 24 | background-color: inherit; 25 | } 26 | -------------------------------------------------------------------------------- /cycletracker/service_workers/sw.js: -------------------------------------------------------------------------------- 1 | // The version of the cache. 2 | const VERSION = "v1"; 3 | 4 | // The name of the cache 5 | const CACHE_NAME = `period-tracker-${VERSION}`; 6 | 7 | // The static resources that the app needs to function. 8 | const APP_STATIC_RESOURCES = [ 9 | "/", 10 | "/index.html", 11 | "/app.js", 12 | "/style.css", 13 | "/icons/wheel.svg", 14 | ]; 15 | 16 | // On install, cache the static resources 17 | self.addEventListener("install", (event) => { 18 | event.waitUntil( 19 | (async () => { 20 | const cache = await caches.open(CACHE_NAME); 21 | cache.addAll(APP_STATIC_RESOURCES); 22 | })() 23 | ); 24 | }); 25 | 26 | // delete old caches on activate 27 | self.addEventListener("activate", (event) => { 28 | event.waitUntil( 29 | (async () => { 30 | const names = await caches.keys(); 31 | await Promise.all( 32 | names.map((name) => { 33 | if (name !== CACHE_NAME) { 34 | return caches.delete(name); 35 | } 36 | }) 37 | ); 38 | await clients.claim(); 39 | })() 40 | ); 41 | }); 42 | 43 | // On fetch, intercept server requests 44 | // and respond with cached responses instead of going to network 45 | self.addEventListener("fetch", (event) => { 46 | // As a single page app, direct app to always go to cached home page. 47 | if (event.request.mode === "navigate") { 48 | event.respondWith(caches.match("/")); 49 | return; 50 | } 51 | 52 | // For all other requests, go to the cache first, and then the network. 53 | event.respondWith( 54 | (async () => { 55 | const cache = await caches.open(CACHE_NAME); 56 | const cachedResponse = await cache.match(event.request); 57 | if (cachedResponse) { 58 | // Return the cached response if it's available. 59 | return cachedResponse; 60 | } 61 | // If resource isn't in the cache, return a 404. 62 | return new Response(null, { status: 404 }); 63 | })() 64 | ); 65 | }); 66 | -------------------------------------------------------------------------------- /js13kpwa/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /js13kpwa/app.js: -------------------------------------------------------------------------------- 1 | // Generating content based on the template 2 | const template = `
3 | NAME 4 |

#POS. NAME

5 | 12 |
`; 13 | let content = ''; 14 | for (let i = 0; i < games.length; i++) { 15 | let entry = template.replace(/POS/g, (i + 1)) 16 | .replace(/SLUG/g, games[i].slug) 17 | .replace(/NAME/g, games[i].name) 18 | .replace(/AUTHOR/g, games[i].author) 19 | .replace(/TWITTER/g, games[i].twitter) 20 | .replace(/WEBSITE/g, games[i].website) 21 | .replace(/GITHUB/g, games[i].github); 22 | entry = entry.replace('', '-'); 23 | content += entry; 24 | } 25 | document.getElementById('content').innerHTML = content; 26 | 27 | // Registering Service Worker 28 | if ('serviceWorker' in navigator) { 29 | navigator.serviceWorker.register('/pwa-examples/js13kpwa/sw.js'); 30 | } 31 | 32 | // Requesting permission for Notifications after clicking on the button 33 | const button = document.getElementById('notifications'); 34 | button.addEventListener('click', () => { 35 | Notification.requestPermission().then((result) => { 36 | if (result === 'granted') { 37 | randomNotification(); 38 | } 39 | }); 40 | }); 41 | 42 | // Setting up random Notification 43 | function randomNotification() { 44 | const randomItem = Math.floor(Math.random() * games.length); 45 | const notifTitle = games[randomItem].name; 46 | const notifBody = `Created by ${games[randomItem].author}.`; 47 | const notifImg = `data/img/${games[randomItem].slug}.jpg`; 48 | const options = { 49 | body: notifBody, 50 | icon: notifImg, 51 | }; 52 | new Notification(notifTitle, options); 53 | setTimeout(randomNotification, 30000); 54 | } 55 | 56 | // Progressive loading images 57 | const imagesToLoad = document.querySelectorAll('img[data-src]'); 58 | const loadImages = (image) => { 59 | image.setAttribute('src', image.getAttribute('data-src')); 60 | image.onload = () => { 61 | image.removeAttribute('data-src'); 62 | }; 63 | }; 64 | if ('IntersectionObserver' in window) { 65 | const observer = new IntersectionObserver((items) => { 66 | items.forEach((item) => { 67 | if (item.isIntersecting) { 68 | loadImages(item.target); 69 | observer.unobserve(item.target); 70 | } 71 | }); 72 | }); 73 | imagesToLoad.forEach((img) => { 74 | observer.observe(img); 75 | }); 76 | } else { 77 | imagesToLoad.forEach((img) => { 78 | loadImages(img); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /js13kpwa/data/games.js: -------------------------------------------------------------------------------- 1 | var games = [ 2 | { 3 | slug: 'lost-in-cyberspace', 4 | name: 'Lost in Cyberspace', 5 | author: 'Zosia and Bartek', 6 | twitter: 'bartaz', 7 | website: '', 8 | github: 'github.com/bartaz/lost-in-cyberspace', 9 | }, 10 | { 11 | slug: 'vernissage', 12 | name: 'Vernissage', 13 | author: 'Platane', 14 | twitter: 'platane_', 15 | website: 'github.com/Platane', 16 | github: 'github.com/Platane/js13k-2017', 17 | }, 18 | { 19 | slug: 'coconutty', 20 | name: 'Coconutty', 21 | author: 'Mary Knize', 22 | twitter: 'captainpainway', 23 | website: 'maryknize.com', 24 | github: 'github.com/captainpainway/coconutty', 25 | }, 26 | { 27 | slug: 'lost-pacman', 28 | name: 'Lost Pacman', 29 | author: 'MarcGuinea', 30 | twitter: 'MarcGuineaCasas', 31 | website: 'marcguinea.com', 32 | github: 'github.com/mguinea/lost-pacman', 33 | }, 34 | { 35 | slug: 'polyhedron-runner', 36 | name: 'Polyhedron Runner', 37 | author: 'Alex Swan', 38 | twitter: 'BoldBigflank', 39 | website: 'bold-it.com', 40 | github: 'github.com/BoldBigflank/js13k-polyhedron', 41 | }, 42 | { 43 | slug: 'she-is-my-universe', 44 | name: 'She is my universe', 45 | author: 'Madmarcel', 46 | twitter: 'madmarcel', 47 | website: '', 48 | github: 'github.com/madmarcel/js13k2017', 49 | }, 50 | { 51 | slug: 'spacewrecked', 52 | name: 'Spacewrecked', 53 | author: 'Sorskoot', 54 | twitter: 'Sorskoot', 55 | website: 'timmykokke.com', 56 | github: 'github.com/sorskoot/js13kgames_2017_Lost', 57 | }, 58 | { 59 | slug: 'shifted-dimensions', 60 | name: 'Shifted Dimensions', 61 | author: 'Nylki', 62 | twitter: 'nylk', 63 | website: 'github.com/nylki', 64 | github: 'github.com/nylki/shifted-dimensions', 65 | }, 66 | { 67 | slug: 'wandering-moon', 68 | name: 'Wandering Moon', 69 | author: 'Jack Greenberg', 70 | twitter: 'thprgrmmrjck', 71 | website: '', 72 | github: 'github.com/theProgrammerJack/js13k2017', 73 | }, 74 | { 75 | slug: 'lost-in-guam', 76 | name: 'Lost in Guam', 77 | author: 'Kenneth Banico', 78 | twitter: 'kjdesigns671', 79 | website: '', 80 | github: 'github.com/kbanico/lost-in-guam-vr-game', 81 | }, 82 | { 83 | slug: 'balloon-problems', 84 | name: 'Balloon Problems', 85 | author: 'Fasility', 86 | twitter: 'Fasility_VR', 87 | website: 'fasility.com', 88 | github: 'github.com/flowerio/balloon-problems', 89 | }, 90 | { 91 | slug: 'lost-in-my-mind', 92 | name: 'Lost in my mind', 93 | author: 'Lasagne Games', 94 | twitter: 'Lazyeels', 95 | website: '', 96 | github: 'github.com/lazyeels/js13kb', 97 | }, 98 | { 99 | slug: 'lost-in-the-forest-dungeon', 100 | name: 'Lost In The Forest Dungeon', 101 | author: 'Luke', 102 | twitter: 'cannl', 103 | website: 'lc-apps.co.uk', 104 | github: 'github.com/lcapps-luke/js13k-lost', 105 | }, 106 | { 107 | slug: 'galacticdiamond', 108 | name: 'GalacticDiamond', 109 | author: 'Mitruska', 110 | twitter: 'mitruska_', 111 | website: '', 112 | github: 'github.com/mitruch/GalacticDiamond-JS13KGames-2017', 113 | }, 114 | { 115 | slug: 'cat-meow', 116 | name: 'Cat Meow', 117 | author: 'Lislis', 118 | twitter: '', 119 | website: '', 120 | github: 'github.com/lislis/cat-meow', 121 | }, 122 | { 123 | slug: 'metamorphosis', 124 | name: 'Metamorphosis', 125 | author: 'Steff and Tanyuan', 126 | twitter: '', 127 | website: '', 128 | github: 'github.com/tanyuan/metamorphosis', 129 | }, 130 | { 131 | slug: 'a-snake', 132 | name: 'A-Snake', 133 | author: 'Nick Frazier', 134 | twitter: 'nrf', 135 | website: 'nickfrazier.com', 136 | github: 'github.com/fraziern/vrsnake', 137 | }, 138 | { 139 | slug: 'wherewhat', 140 | name: 'Where? What?', 141 | author: '..Katu..', 142 | twitter: '', 143 | website: '', 144 | github: 'github.com/katubrd/LostVR', 145 | }, 146 | { 147 | slug: 'dont-let-your-dreams-be-memes', 148 | name: 'Don\'t let your dreams be memes', 149 | author: 'Mark Vasilkov', 150 | twitter: 'mvasilkov', 151 | website: 'mvasilkov.ovh', 152 | github: 'github.com/mvasilkov/aframe13k', 153 | }, 154 | { 155 | slug: 'fly-south', 156 | name: 'Fly South', 157 | author: 'Christian Paul (jaller94)', 158 | twitter: '', 159 | website: 'chrpaul.de', 160 | github: 'github.com/jaller94/fly-south', 161 | }, 162 | { 163 | slug: 'prisonri0t', 164 | name: 'PrisonRi0t', 165 | author: 'Sondor', 166 | twitter: '', 167 | website: '', 168 | github: 'github.com/gabboraron/prison', 169 | }, 170 | { 171 | slug: 'debriss', 172 | name: 'DebrISS', 173 | author: 'Kovolmany', 174 | twitter: '', 175 | website: '', 176 | github: 'github.com/gabboraron/iss', 177 | }, 178 | { 179 | slug: 'vr-racing', 180 | name: 'VR Racing', 181 | author: 'Vedansh Bhartia and Kartikey Pandey', 182 | twitter: '', 183 | website: '', 184 | github: 'github.com/vedanshbhartia/vr_racing', 185 | }, 186 | { 187 | slug: 'a-box-invaders', 188 | name: 'A-box Invaders', 189 | author: 'Felipe Do E. Santo', 190 | twitter: 'felipez3r0', 191 | website: 'hardcodigo.com.br', 192 | github: 'github.com/felipez3r0/a-box-invaders', 193 | }, 194 | { 195 | slug: 'world-lost', 196 | name: 'World Lost', 197 | author: 'Ms. K. Bhuvana Meenakshi', 198 | twitter: 'bhuvanakotees1', 199 | website: 'bhuvanameenakshik.wixsite.com/bhuvanameenakshi', 200 | github: 'github.com/bhuvanameenakshi/World_Lost', 201 | }, 202 | { 203 | slug: 'give-space', 204 | name: 'Give Space', 205 | author: 'Ram', 206 | twitter: 'ram_gurumukhi', 207 | website: 'gurumukhi.wordpress.com', 208 | github: 'github.com/gurumukhi/13kGiveSpaceGame', 209 | }, 210 | { 211 | slug: 'lost-in-metaverse', 212 | name: 'Lost in Metaverse', 213 | author: 'Karan Ganesan', 214 | twitter: 'karanganesan', 215 | website: 'linkedin.com/in/karanganesan', 216 | github: 'github.com/karanganesan/Lost_in_Metaverse', 217 | }, 218 | { 219 | slug: 'emma-3d', 220 | name: 'Emma-3D', 221 | author: 'Prateek Roushan', 222 | twitter: '', 223 | website: '', 224 | github: 'github.com/coderprateek/Emma-3D', 225 | }, 226 | ]; 227 | -------------------------------------------------------------------------------- /js13kpwa/data/img/a-box-invaders.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/a-box-invaders.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/a-snake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/a-snake.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/balloon-problems.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/balloon-problems.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/cat-meow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/cat-meow.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/coconutty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/coconutty.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/debriss.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/debriss.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/dont-let-your-dreams-be-memes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/dont-let-your-dreams-be-memes.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/emma-3d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/emma-3d.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/fly-south.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/fly-south.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/galacticdiamond.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/galacticdiamond.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/give-space.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/give-space.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/lost-in-cyberspace.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/lost-in-cyberspace.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/lost-in-guam.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/lost-in-guam.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/lost-in-metaverse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/lost-in-metaverse.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/lost-in-my-mind.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/lost-in-my-mind.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/lost-in-the-forest-dungeon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/lost-in-the-forest-dungeon.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/lost-pacman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/lost-pacman.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/metamorphosis.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/metamorphosis.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/placeholder.png -------------------------------------------------------------------------------- /js13kpwa/data/img/polyhedron-runner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/polyhedron-runner.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/prisonri0t.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/prisonri0t.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/she-is-my-universe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/she-is-my-universe.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/shifted-dimensions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/shifted-dimensions.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/spacewrecked.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/spacewrecked.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/vernissage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/vernissage.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/vr-racing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/vr-racing.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/wandering-moon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/wandering-moon.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/wherewhat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/wherewhat.jpg -------------------------------------------------------------------------------- /js13kpwa/data/img/world-lost.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/data/img/world-lost.jpg -------------------------------------------------------------------------------- /js13kpwa/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/favicon.ico -------------------------------------------------------------------------------- /js13kpwa/fonts/graduate.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/fonts/graduate.eot -------------------------------------------------------------------------------- /js13kpwa/fonts/graduate.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/fonts/graduate.ttf -------------------------------------------------------------------------------- /js13kpwa/fonts/graduate.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/fonts/graduate.woff -------------------------------------------------------------------------------- /js13kpwa/icons/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-128.png -------------------------------------------------------------------------------- /js13kpwa/icons/icon-168.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-168.png -------------------------------------------------------------------------------- /js13kpwa/icons/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-192.png -------------------------------------------------------------------------------- /js13kpwa/icons/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-256.png -------------------------------------------------------------------------------- /js13kpwa/icons/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-32.png -------------------------------------------------------------------------------- /js13kpwa/icons/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-512.png -------------------------------------------------------------------------------- /js13kpwa/icons/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-64.png -------------------------------------------------------------------------------- /js13kpwa/icons/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/icons/icon-96.png -------------------------------------------------------------------------------- /js13kpwa/img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/img/bg.png -------------------------------------------------------------------------------- /js13kpwa/img/js13kgames.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdn/pwa-examples/6f7509ea99b60a31e6d54c5ebdcace9ba4f1a158/js13kpwa/img/js13kgames.png -------------------------------------------------------------------------------- /js13kpwa/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | js13kGames A-Frame entries 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |

20 |
21 |
22 |

js13kGames A-Frame entries

23 |

List of games submitted to the A-Frame category in the js13kGames 2017 competition. You can fork js13kPWA on GitHub to check its source code.

24 | 25 |
26 |
27 | 30 | 31 | -------------------------------------------------------------------------------- /js13kpwa/js13kpwa.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js13kGames Progressive Web App", 3 | "short_name": "js13kPWA", 4 | "description": "Progressive Web App that lists games submitted to the A-Frame category in the js13kGames 2017 competition.", 5 | "icons": [ 6 | { 7 | "src": "icons/icon-32.png", 8 | "sizes": "32x32", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "icons/icon-64.png", 13 | "sizes": "64x64", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "icons/icon-96.png", 18 | "sizes": "96x96", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "icons/icon-128.png", 23 | "sizes": "128x128", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "icons/icon-168.png", 28 | "sizes": "168x168", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "icons/icon-192.png", 33 | "sizes": "192x192", 34 | "type": "image/png" 35 | }, 36 | { 37 | "src": "icons/icon-256.png", 38 | "sizes": "256x256", 39 | "type": "image/png" 40 | }, 41 | { 42 | "src": "icons/icon-512.png", 43 | "sizes": "512x512", 44 | "type": "image/png" 45 | } 46 | ], 47 | "start_url": "/pwa-examples/js13kpwa/index.html", 48 | "display": "fullscreen", 49 | "theme_color": "#B12A34", 50 | "background_color": "#B12A34" 51 | } -------------------------------------------------------------------------------- /js13kpwa/style.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Graduate'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: local('Graduate'), 6 | local('Graduate-Regular'), 7 | url(fonts/graduate.eot) format('embedded-opentype'), 8 | url(fonts/graduate.ttf) format('truetype'), 9 | url(fonts/graduate.woff) format('woff'); 10 | } 11 | * { 12 | margin: 0; 13 | padding: 0; 14 | } 15 | html { 16 | font-size: 62.5%; 17 | } 18 | body { 19 | background: #efefef; 20 | font: normal 1.6em Helvetica, Arial, sans-serif; 21 | line-height: 1.6; 22 | color: #333; 23 | position: relative; 24 | } 25 | h1, h3 { 26 | font-family: "Graduate", "Times New Roman", Verdana; 27 | font-weight: normal; 28 | margin-top: 0; 29 | letter-spacing: -0.05em; 30 | } 31 | h1 { 32 | font-size: 2em; 33 | line-height: 1.2em; 34 | padding: 0.5em 0.3em 0.2em 0.3em; 35 | text-align: center; 36 | color: #6c6b6b; 37 | } 38 | h1:before { 39 | content: " • "; 40 | } 41 | h1:after { 42 | content: " • "; 43 | } 44 | h3 { 45 | font-size: 1.5em; 46 | line-height: 1em; 47 | padding-top: 0.2em; 48 | margin-bottom: 0.8em; 49 | } 50 | a { 51 | text-decoration: none; 52 | color: #b12a34; 53 | } 54 | header { 55 | background: #2d2d2d url(img/bg.png) repeat; 56 | border-top: 0.2em solid #a52730; 57 | border-bottom: 0.2em solid #fff; 58 | } 59 | header p { 60 | width: 90%; 61 | margin: 1em auto; 62 | position: relative; 63 | } 64 | header img { 65 | display: block; 66 | width: 50%; 67 | min-width: 150px; 68 | max-width: 295px; 69 | } 70 | .description { 71 | text-align: center; 72 | padding: 1em; 73 | } 74 | button { 75 | display: block; 76 | margin: 0 auto 2em; 77 | } 78 | main { 79 | width: 100%; 80 | background: #fafafa; 81 | } 82 | main a:hover { 83 | text-decoration: none; 84 | border-bottom: 1px solid #b12a34; 85 | } 86 | main p { 87 | padding-bottom: 1.5em; 88 | } 89 | main ul { 90 | padding: 0.2em; 91 | } 92 | main li { 93 | line-height: 1.4em; 94 | margin-left: 1em; 95 | } 96 | main img { 97 | float: right; 98 | width: 30%; 99 | min-width: 80px; 100 | max-width: 160px; 101 | padding-bottom: 1em; 102 | } 103 | article { 104 | border-top: 0.1em solid #acabab; 105 | padding: 1em; 106 | } 107 | article img[data-src] { 108 | filter: blur(0.2em); 109 | } 110 | article img { 111 | filter: blur(0em); 112 | transition: filter 0.5s; 113 | } 114 | footer { 115 | background: #2d2d2d url(img/bg.png) repeat; 116 | border-top: 0.2em solid #fff; 117 | border-bottom: 0.2em solid #a52730; 118 | } 119 | footer p { 120 | width: 90%; 121 | margin: 0 auto; 122 | color: #e2e2e2; 123 | font-size: 0.7em; 124 | padding: 2em 0; 125 | position: relative; 126 | text-align: center; 127 | line-height: 1.2; 128 | } 129 | footer a { 130 | color: #fff; 131 | font-weight: bold; 132 | } -------------------------------------------------------------------------------- /js13kpwa/sw.js: -------------------------------------------------------------------------------- 1 | self.importScripts('data/games.js'); 2 | 3 | // Files to cache 4 | const cacheName = 'js13kPWA-v1'; 5 | const appShellFiles = [ 6 | '/pwa-examples/js13kpwa/', 7 | '/pwa-examples/js13kpwa/index.html', 8 | '/pwa-examples/js13kpwa/app.js', 9 | '/pwa-examples/js13kpwa/style.css', 10 | '/pwa-examples/js13kpwa/fonts/graduate.eot', 11 | '/pwa-examples/js13kpwa/fonts/graduate.ttf', 12 | '/pwa-examples/js13kpwa/fonts/graduate.woff', 13 | '/pwa-examples/js13kpwa/favicon.ico', 14 | '/pwa-examples/js13kpwa/img/js13kgames.png', 15 | '/pwa-examples/js13kpwa/img/bg.png', 16 | '/pwa-examples/js13kpwa/icons/icon-32.png', 17 | '/pwa-examples/js13kpwa/icons/icon-64.png', 18 | '/pwa-examples/js13kpwa/icons/icon-96.png', 19 | '/pwa-examples/js13kpwa/icons/icon-128.png', 20 | '/pwa-examples/js13kpwa/icons/icon-168.png', 21 | '/pwa-examples/js13kpwa/icons/icon-192.png', 22 | '/pwa-examples/js13kpwa/icons/icon-256.png', 23 | '/pwa-examples/js13kpwa/icons/icon-512.png', 24 | ]; 25 | const gamesImages = []; 26 | for (let i = 0; i < games.length; i++) { 27 | gamesImages.push(`data/img/${games[i].slug}.jpg`); 28 | } 29 | const contentToCache = appShellFiles.concat(gamesImages); 30 | 31 | // Installing Service Worker 32 | self.addEventListener('install', (e) => { 33 | console.log('[Service Worker] Install'); 34 | e.waitUntil((async () => { 35 | const cache = await caches.open(cacheName); 36 | console.log('[Service Worker] Caching all: app shell and content'); 37 | await cache.addAll(contentToCache); 38 | })()); 39 | }); 40 | 41 | // Fetching content using Service Worker 42 | self.addEventListener('fetch', (e) => { 43 | // Cache http and https only, skip unsupported chrome-extension:// and file://... 44 | if (!( 45 | e.request.url.startsWith('http:') || e.request.url.startsWith('https:') 46 | )) { 47 | return; 48 | } 49 | 50 | e.respondWith((async () => { 51 | const r = await caches.match(e.request); 52 | console.log(`[Service Worker] Fetching resource: ${e.request.url}`); 53 | if (r) return r; 54 | const response = await fetch(e.request); 55 | const cache = await caches.open(cacheName); 56 | console.log(`[Service Worker] Caching new resource: ${e.request.url}`); 57 | cache.put(e.request, response.clone()); 58 | return response; 59 | })()); 60 | }); 61 | --------------------------------------------------------------------------------