Add PWA capabilities
BIN
appicons/apple-touch-icon-114x114.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
BIN
appicons/apple-touch-icon-120x120.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
appicons/apple-touch-icon-144x144.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
appicons/apple-touch-icon-152x152.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
appicons/apple-touch-icon-57x57.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
appicons/apple-touch-icon-60x60.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
appicons/apple-touch-icon-72x72.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
appicons/apple-touch-icon-76x76.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
appicons/favicon-128.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
appicons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 555 B |
BIN
appicons/favicon-196x196.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
appicons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
appicons/favicon-96x96.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
appicons/mstile-144x144.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
appicons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
appicons/mstile-310x150.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
appicons/mstile-310x310.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
appicons/mstile-70x70.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
appicons/splash.png
Normal file
After Width: | Height: | Size: 92 KiB |
BIN
favicon.ico
Normal file
After Width: | Height: | Size: 34 KiB |
41
index.html
@ -7,9 +7,31 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="DevNexus presentation Vanilla.js: Modern 1st Party JavaScript">
|
||||
<meta name="theme-color" content="#FFFFFF" />
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self' 'unsafe-eval'; child-src 'none'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; style-src 'self';">
|
||||
<link rel="shortcut icon" href="/images/logo.png" type="image/png" />
|
||||
content="default-src 'self'; script-src 'self' 'unsafe-eval' 'sha256-TJ4T4ywZLNJftBZ/C40uficokKH5JwSWtCX5PNccNbI='; child-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; style-src 'self';">
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/appicons/apple-touch-icon-57x57.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/appicons/apple-touch-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/appicons/apple-touch-icon-72x72.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/appicons/apple-touch-icon-144x144.png" />
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/appicons/apple-touch-icon-60x60.png" />
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/appicons/apple-touch-icon-120x120.png" />
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/appicons/apple-touch-icon-76x76.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/appicons/apple-touch-icon-152x152.png" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-196x196.png" sizes="196x196" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-16x16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-128.png" sizes="128x128" />
|
||||
<meta name="application-name" content="Vanilla.js: Modern 1st Party JavaScript" />
|
||||
<meta name="msapplication-TileColor" content="#FFFFFF" />
|
||||
<meta name="msapplication-TileImage" content="/appicons/mstile-144x144.png" />
|
||||
<meta name="msapplication-square70x70logo" content="/appicons/mstile-70x70.png" />
|
||||
<meta name="msapplication-square150x150logo" content="/appicons/mstile-150x150.png" />
|
||||
<meta name="msapplication-wide310x150logo" content="/appicons/mstile-310x150.png" />
|
||||
<meta name="msapplication-square310x310logo" content="/appicons/mstile-310x310.png" />
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="css/animations.css">
|
||||
</head>
|
||||
@ -28,6 +50,21 @@
|
||||
<div class="footer right">
|
||||
<a href="https://twitter.com/JeremyLikness">@JeremyLikness</a>
|
||||
</div>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker
|
||||
.register('/pwa.js', { scope: '/' })
|
||||
.then(() => {
|
||||
console.info('Vanilla.js Service Worker Registered');
|
||||
}, err => console.error("Vanilla.js Service Worker registration failed: ", err));
|
||||
|
||||
navigator.serviceWorker
|
||||
.ready
|
||||
.then(() => {
|
||||
console.info('Vanilla.js Service Worker Ready');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
31
manifest.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "Vanilla.js: Modern 1st Party JavaScript",
|
||||
"short_name": "vanilla-js",
|
||||
"description": "A Single Page Application built in 100% pure modern JavaScript with no frameworks.",
|
||||
"icons": [{
|
||||
"src": "/appicons/favicon-128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/appicons/apple-touch-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/appicons/apple-touch-icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/appicons/favicon-196x196.png",
|
||||
"sizes": "196x196",
|
||||
"type": "image/png"
|
||||
}, {
|
||||
"src": "/appicons/splash.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}],
|
||||
"start_url": "/index.html",
|
||||
"display": "standalone",
|
||||
"orientation" : "any",
|
||||
"background_color": "#FFFFFF",
|
||||
"theme_color": "#FFFFFF"
|
||||
}
|
265
pwa.js
Normal file
@ -0,0 +1,265 @@
|
||||
const CACHE_VERSION = 1.0;
|
||||
|
||||
const BASE_CACHE_FILES = [
|
||||
'/index.html',
|
||||
'/pwa/404.html',
|
||||
'/pwa/offline.html',
|
||||
'/css/style.css',
|
||||
'/css/animations.css',
|
||||
'/manifest.json',
|
||||
'/images/logo.png',
|
||||
'/js/app.js',
|
||||
];
|
||||
|
||||
const OFFLINE_CACHE_FILES = [
|
||||
'/pwa/offline.html'
|
||||
];
|
||||
|
||||
const NOT_FOUND_CACHE_FILES = [
|
||||
'/pwa/404.html'
|
||||
];
|
||||
|
||||
const OFFLINE_PAGE = '/pwa/offline.html';
|
||||
const NOT_FOUND_PAGE = '/pwa/404.html';
|
||||
|
||||
const CACHE_VERSIONS = {
|
||||
assets: 'assets-v' + CACHE_VERSION,
|
||||
content: 'content-v' + CACHE_VERSION,
|
||||
offline: 'offline-v' + CACHE_VERSION,
|
||||
notFound: '404-v' + CACHE_VERSION,
|
||||
};
|
||||
|
||||
// Define MAX_TTL's in SECONDS for specific file extensions
|
||||
const MAX_TTL = {
|
||||
'/': 3600,
|
||||
html: 43200,
|
||||
json: 43200,
|
||||
js: 86400,
|
||||
css: 86400,
|
||||
};
|
||||
|
||||
const SUPPORTED_METHODS = [
|
||||
'GET',
|
||||
];
|
||||
|
||||
/**
|
||||
* getFileExtension
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
function getFileExtension(url) {
|
||||
let extension = url.split('.').reverse()[0].split('?')[0];
|
||||
return (extension.endsWith('/')) ? '/' : extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* getTTL
|
||||
* @param {string} url
|
||||
*/
|
||||
function getTTL(url) {
|
||||
if (typeof url === 'string') {
|
||||
let extension = getFileExtension(url);
|
||||
if (typeof MAX_TTL[extension] === 'number') {
|
||||
return MAX_TTL[extension];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* installServiceWorker
|
||||
* @returns {Promise}
|
||||
*/
|
||||
async function installServiceWorker() {
|
||||
try {
|
||||
await Promise.all([
|
||||
caches.open(CACHE_VERSIONS.assets).then((cache) => {
|
||||
return cache.addAll(BASE_CACHE_FILES);
|
||||
}, err => console.error(`Error with ${CACHE_VERSIONS.assets}`, err)),
|
||||
caches.open(CACHE_VERSIONS.offline).then((cache_1) => {
|
||||
return cache_1.addAll(OFFLINE_CACHE_FILES);
|
||||
}, err_1 => console.error(`Error with ${CACHE_VERSIONS.offline}`, err_1)),
|
||||
caches.open(CACHE_VERSIONS.notFound).then((cache_2) => {
|
||||
return cache_2.addAll(NOT_FOUND_CACHE_FILES);
|
||||
}, err_2 => console.error(`Error with ${CACHE_VERSIONS.notFound}`, err_2))]);
|
||||
return self.skipWaiting();
|
||||
}
|
||||
catch (err_3) {
|
||||
return console.error("Error with installation: ", err_3);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cleanupLegacyCache
|
||||
* @returns {Promise}
|
||||
*/
|
||||
function cleanupLegacyCache() {
|
||||
|
||||
const currentCaches = Object.keys(CACHE_VERSIONS).map((key) => {
|
||||
return CACHE_VERSIONS[key];
|
||||
});
|
||||
|
||||
return new Promise(
|
||||
(resolve, reject) => {
|
||||
|
||||
caches.keys().then((keys) => {
|
||||
return legacyKeys = keys.filter((key) => {
|
||||
return !~currentCaches.indexOf(key);
|
||||
});
|
||||
}).then((legacy) => {
|
||||
if (legacy.length) {
|
||||
Promise.all(legacy.map((legacyKey) => {
|
||||
return caches.delete(legacyKey)
|
||||
})
|
||||
).then(() => {
|
||||
resolve()
|
||||
}).catch((err) => {
|
||||
console.error("Error in legacy cleanup: ", err);
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.error("Error in legacy cleanup: ", err);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
function preCacheUrl(url) {
|
||||
caches.open(CACHE_VERSIONS.content).then((cache) => {
|
||||
cache.match(url).then((response) => {
|
||||
if (!response) {
|
||||
return fetch(url);
|
||||
} else {
|
||||
// already in cache, nothing to do.
|
||||
return null;
|
||||
}
|
||||
}).then((response) => {
|
||||
if (response) {
|
||||
return cache.put(url, response.clone());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
self.addEventListener('install', event => {
|
||||
event.waitUntil(
|
||||
Promise.all([
|
||||
installServiceWorker(),
|
||||
self.skipWaiting(),
|
||||
]));
|
||||
});
|
||||
|
||||
// The activate handler takes care of cleaning up old caches.
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(Promise.all(
|
||||
[cleanupLegacyCache(),
|
||||
self.clients.claim(),
|
||||
self.skipWaiting()]).catch((err) => {
|
||||
console.error("Activation error: ", err);
|
||||
event.skipWaiting();
|
||||
}));
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', event => {
|
||||
event.respondWith(
|
||||
caches.open(CACHE_VERSIONS.content).then((cache) => {
|
||||
return cache.match(event.request).then((response) => {
|
||||
if (response) {
|
||||
let headers = response.headers.entries();
|
||||
let date = null;
|
||||
|
||||
for (let pair of headers) {
|
||||
if (pair[0] === 'date') {
|
||||
date = new Date(pair[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (date) {
|
||||
let age = parseInt((new Date().getTime() - date.getTime()) / 1000);
|
||||
let ttl = getTTL(event.request.url);
|
||||
|
||||
if (ttl && age > ttl) {
|
||||
return new Promise(async (resolve) => {
|
||||
try {
|
||||
const updatedResponse = await fetch(event.request.clone());
|
||||
if (updatedResponse) {
|
||||
cache.put(event.request, updatedResponse.clone());
|
||||
resolve(updatedResponse);
|
||||
}
|
||||
else {
|
||||
resolve(response);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
resolve(response);
|
||||
}
|
||||
}).catch(() => {
|
||||
return response;
|
||||
});
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).then((response) => {
|
||||
if (response) {
|
||||
return response;
|
||||
} else {
|
||||
return fetch(event.request.clone()).then(
|
||||
(response) => {
|
||||
if (response.status < 400) {
|
||||
if (~SUPPORTED_METHODS.indexOf(event.request.method)) {
|
||||
cache.put(event.request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} else {
|
||||
return caches.open(CACHE_VERSIONS.notFound).then((cache) => {
|
||||
return cache.match(NOT_FOUND_PAGE);
|
||||
});
|
||||
}
|
||||
}).then((response) => {
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
}).catch(() => {
|
||||
return caches.open(CACHE_VERSIONS.offline).then(
|
||||
(offlineCache) => {
|
||||
return offlineCache.match(OFFLINE_PAGE)
|
||||
});
|
||||
});
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('Error in fetch handler:', error);
|
||||
throw error;
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (typeof event.data === 'object' &&
|
||||
typeof event.data.action === 'string') {
|
||||
switch (event.data.action) {
|
||||
case 'cache':
|
||||
preCacheUrl(event.data.url);
|
||||
break;
|
||||
default:
|
||||
console.log('Unknown action: ' + event.data.action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
56
pwa/404.html
Normal file
@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
|
||||
<head>
|
||||
<title>Vanilla.js - Oops!</title>
|
||||
<base href="/" target="_blank">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="DevNexus presentation Vanilla.js: Modern 1st Party JavaScript">
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self' 'unsafe-eval'; child-src 'none'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; style-src 'self';">
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="/appicons/apple-touch-icon-57x57.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="/appicons/apple-touch-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="/appicons/apple-touch-icon-72x72.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/appicons/apple-touch-icon-144x144.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="/appicons/apple-touch-icon-60x60.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="/appicons/apple-touch-icon-120x120.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="/appicons/apple-touch-icon-76x76.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="/appicons/apple-touch-icon-152x152.png" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-196x196.png" sizes="196x196" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-16x16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-128.png" sizes="128x128" />
|
||||
<meta name="application-name" content="Vanilla.js: Modern 1st Party JavaScript" />
|
||||
<meta name="msapplication-TileColor" content="#FFFFFF" />
|
||||
<meta name="msapplication-TileImage" content="/appicons/mstile-144x144.png" />
|
||||
<meta name="msapplication-square70x70logo" content="/appicons/mstile-70x70.png" />
|
||||
<meta name="msapplication-square150x150logo" content="/appicons/mstile-150x150.png" />
|
||||
<meta name="msapplication-wide310x150logo" content="/appicons/mstile-310x150.png" />
|
||||
<meta name="msapplication-square310x310logo" content="/appicons/mstile-310x310.png" />
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/animations.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<slide-deck>
|
||||
<div>
|
||||
<h1>Oops!</h1>
|
||||
<h2>I'm afraid this isn't the HTML page you're looking for.</h2>
|
||||
<p>Perhaps <a href="/index.html#1" title="First slide">try this (first slide)</a> instead?</p>
|
||||
</div>
|
||||
</slide-deck>
|
||||
<div class="footer left">
|
||||
<img src="./images/devnexus.png" alt="DevNexus logo" title="DevNexus logo" />
|
||||
</div>
|
||||
<slide-controls deck="main" class="footer center">---</slide-controls>
|
||||
<div class="footer right">
|
||||
<a href="https://twitter.com/JeremyLikness">@JeremyLikness</a>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
54
pwa/offline.html
Normal file
@ -0,0 +1,54 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
|
||||
<head>
|
||||
<title>Vanilla.js - No Network!</title>
|
||||
<base href="/" target="_blank">
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="DevNexus presentation Vanilla.js: Modern 1st Party JavaScript">
|
||||
<meta http-equiv="Content-Security-Policy"
|
||||
content="default-src 'self'; script-src 'self' 'unsafe-eval'; child-src 'none'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; media-src 'self'; object-src 'none'; style-src 'self';">
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="57x57" href="/appicons/apple-touch-icon-57x57.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="/appicons/apple-touch-icon-114x114.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="/appicons/apple-touch-icon-72x72.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="/appicons/apple-touch-icon-144x144.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="60x60" href="/appicons/apple-touch-icon-60x60.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="/appicons/apple-touch-icon-120x120.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76" href="/appicons/apple-touch-icon-76x76.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="/appicons/apple-touch-icon-152x152.png" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-196x196.png" sizes="196x196" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-16x16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="/appicons/favicon-128.png" sizes="128x128" />
|
||||
<meta name="application-name" content="Vanilla.js: Modern 1st Party JavaScript" />
|
||||
<meta name="msapplication-TileColor" content="#FFFFFF" />
|
||||
<meta name="msapplication-TileImage" content="/appicons/mstile-144x144.png" />
|
||||
<meta name="msapplication-square70x70logo" content="/appicons/mstile-70x70.png" />
|
||||
<meta name="msapplication-square150x150logo" content="/appicons/mstile-150x150.png" />
|
||||
<meta name="msapplication-wide310x150logo" content="/appicons/mstile-310x150.png" />
|
||||
<meta name="msapplication-square310x310logo" content="/appicons/mstile-310x310.png" />
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/animations.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<slide-deck>
|
||||
<div>
|
||||
<h1>I can't find the network!</h1>
|
||||
<h2>If you revisit this page online, I'll be sure to cache it for offline access.</h2>
|
||||
</div>
|
||||
</slide-deck>
|
||||
<div class="footer left">
|
||||
<img src="./images/devnexus.png" alt="DevNexus logo" title="DevNexus logo" />
|
||||
</div>
|
||||
<slide-controls deck="main" class="footer center">---</slide-controls>
|
||||
<div class="footer right">
|
||||
<a href="https://twitter.com/JeremyLikness">@JeremyLikness</a>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
2
robots.txt
Normal file
@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Allow: /
|