aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.ts4
-rw-r--r--src/components/Nav.svelte60
-rw-r--r--src/components/PredictedSpaceWeather.svelte174
-rw-r--r--src/components/PredictedSpaceWeatherThing.svelte64
-rw-r--r--src/components/WeatherCurrent.svelte210
-rw-r--r--src/routes/_error.svelte40
-rw-r--r--src/routes/_layout.svelte54
-rw-r--r--src/routes/index.svelte28
-rw-r--r--src/server.ts18
-rw-r--r--src/service-worker.ts82
-rw-r--r--src/template.html35
11 files changed, 769 insertions, 0 deletions
diff --git a/src/client.ts b/src/client.ts
new file mode 100644
index 0000000..1a2fbc3
--- /dev/null
+++ b/src/client.ts
@@ -0,0 +1,4 @@
+import * as sapper from '@sapper/app';
+sapper.start({
+ target: document.querySelector('.app')
+});
diff --git a/src/components/Nav.svelte b/src/components/Nav.svelte
new file mode 100644
index 0000000..3240ecf
--- /dev/null
+++ b/src/components/Nav.svelte
@@ -0,0 +1,60 @@
+<script lang="ts">
+ export let segment: string;
+</script>
+
+<style>
+ nav {
+ border-bottom: 1px solid rgba(255,62,0,0.1);
+ font-weight: 300;
+ padding: 0 1em;
+ }
+
+ ul {
+ margin: 0;
+ padding: 0;
+ }
+
+ /* clearfix */
+ ul::after {
+ content: '';
+ display: block;
+ clear: both;
+ }
+
+ li {
+ display: block;
+ float: left;
+ }
+
+ [aria-current] {
+ position: relative;
+ display: inline-block;
+ }
+
+ [aria-current]::after {
+ position: absolute;
+ content: '';
+ width: calc(100% - 1em);
+ height: 2px;
+ background-color: rgb(255,62,0);
+ display: block;
+ bottom: -1px;
+ }
+
+ a {
+ text-decoration: none;
+ padding: 1em 0.5em;
+ display: block;
+ }
+</style>
+
+<nav>
+ <ul>
+ <li><a aria-current="{segment === undefined ? 'page' : undefined}" href=".">home</a></li>
+ <li><a aria-current="{segment === 'about' ? 'page' : undefined}" href="about">about</a></li>
+
+ <!-- for the blog link, we're using rel=prefetch so that Sapper prefetches
+ the blog data when we hover over the link or tap it on a touchscreen -->
+ <li><a rel=prefetch aria-current="{segment === 'blog' ? 'page' : undefined}" href="blog">blog</a></li>
+ </ul>
+</nav>
diff --git a/src/components/PredictedSpaceWeather.svelte b/src/components/PredictedSpaceWeather.svelte
new file mode 100644
index 0000000..9194447
--- /dev/null
+++ b/src/components/PredictedSpaceWeather.svelte
@@ -0,0 +1,174 @@
+<script lang="ts">
+ // import { onMount } from 'svelte';
+ import PredictedSpaceWeatherThing from './PredictedSpaceWeatherThing.svelte';
+
+ let predictions;
+ // async function haschange() {
+ // let data = await fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${latitude}&lon=${longitude}`).then(res => res.json());
+ // let data = await fetch("https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json").then(res => res.json());
+ // data.shift();
+ // let updatedPredictions = [];
+ // data.forEach((pred, i) => {
+ // if (pred[2] != "observed") {
+ // updatedPredictions.push(pred);
+ // }
+ // });
+ // predictions = updatedPredictions;
+ // }
+ //
+ // onMount(haschange);
+
+
+
+ import { onMount } from 'svelte';
+ const monthNames = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ ];
+
+ let longitude;
+ let latitude;
+ let locationSupported;
+ let dataLoading = true;
+ let defaultLocation = false;
+ let weather;
+ let days = Array();
+
+ async function getWeather(longitude, latitude) {
+ let yr_data = await fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${latitude}&lon=${longitude}`).then(res => res.json());
+
+ let kp_data = await fetch("https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json").then(res => res.json());
+ kp_data.shift();
+ let updatedPredictions = [];
+ kp_data.forEach((pred, i) => {
+ if (pred[2] != "observed") {
+ let temp;
+ let clouds;
+ let cDate = new Date(pred[0]);
+ let closestDate = new Date(0,0,0);
+ yr_data["properties"]["timeseries"].forEach((pred, i) => {
+ let predDate = new Date(pred["time"]);
+ if (Math.abs(predDate.getTime() - cDate.getTime()) < Math.abs(closestDate.getTime() - cDate.getTime())) {
+ closestDate = predDate;
+ temp = (pred["data"]["instant"]["details"]["air_temperature"]);
+ clouds = pred["data"]["instant"]["details"]["cloud_area_fraction"];
+ }
+ })
+
+ updatedPredictions.push({
+ "time": pred[0],
+ "kp": pred[1],
+ "temp": temp,
+ "clouds": clouds
+ });
+ }
+ });
+ predictions = updatedPredictions;
+ }
+ function getDays(daily) {
+ var data = daily.data.slice(0,5)
+ data.forEach(element => {
+ var a = new Date(element.time*1000)
+ var dayStrings = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
+ days.push(dayStrings[a.getDay()])
+ });
+ }
+ function getLocation() {
+ if (navigator.geolocation) {
+ dataLoading = true
+ locationSupported = true
+ navigator.geolocation.getCurrentPosition(setLocation, locationError)
+ } else {
+ locationSupported = false
+ noLocation()
+ }
+ }
+
+ function setLocation(position) {
+ longitude = position.coords.longitude
+ latitude = position.coords.latitude
+ getWeather(longitude, latitude)
+ }
+
+ function locationError(err) {
+ noLocation()
+ }
+
+ function noLocation() {
+ longitude = 28.283333
+ latitude = -15.416667
+ toggleLoading()
+ }
+ function toggleLoading() {
+ dataLoading = !dataLoading
+ }
+ function toggleDefault() {
+ defaultLocation = !defaultLocation
+ }
+ onMount(getLocation);
+</script>
+
+<style>
+ .predicted-weather {
+ --bg-opacity: 1;
+ background-color: #f7fafc;
+ background-color: rgba(247, 250, 252, var(--bg-opacity));
+ padding: 1.5rem;
+ --text-opacity: 1;
+ color: #1a202c;
+ color: rgba(26, 32, 44, var(--text-opacity));
+ height: 100%;
+ overflow: hidden;
+
+ /* border-top-left-radius: 1rem;
+ border-top-right-radius: 1rem;
+ transform: translatey(-1rem); */
+ }
+
+ @media (min-width: 640px), (min-height: 720px) {
+ .predicted-weather {
+ padding: 2rem;
+ padding-top: 1.5rem;
+ }
+ }
+
+ @media (min-width: 640px) {
+ .predicted-weather {
+ border-bottom-right-radius: 1rem;
+ border-bottom-left-radius: 1rem;
+ }
+ }
+
+ .predicted-weather h2 {
+ text-transform: uppercase;
+ font-size: 0.875rem;
+ letter-spacing: 0.1em;
+ font-weight: 700;
+ margin-top: 0.25rem;
+ margin-bottom: 0.5rem;
+ }
+
+ .prediction-table {
+ height: 100%;
+ overflow-y: scroll;
+ padding-bottom: 1rem;
+ }
+
+ .prediction-table::-webkit-scrollbar {
+ display: none;
+ }
+</style>
+
+<div class="predicted-weather">
+ <div className="flex flex-row justify-between items-top">
+ <h2>Predicted</h2>
+ </div>
+ <div class="prediction-table">
+ {#if predictions}
+ {#each predictions as prediction, i}
+ <PredictedSpaceWeatherThing {prediction}/>
+ {/each}
+ {:else}
+ Cannot connect to NOAA
+ {/if}
+ </div>
+</div>
diff --git a/src/components/PredictedSpaceWeatherThing.svelte b/src/components/PredictedSpaceWeatherThing.svelte
new file mode 100644
index 0000000..0a40e79
--- /dev/null
+++ b/src/components/PredictedSpaceWeatherThing.svelte
@@ -0,0 +1,64 @@
+<script>
+ export let prediction;
+
+ const monthNames = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ ];
+
+ let kp = prediction["kp"];
+ let dateTime = prediction["time"].split(" ");
+ let date = Number(dateTime[0].split("-")[2]) + ". " + monthNames[Number(dateTime[0].split("-")[1])];
+ let time = dateTime[1].substring(0,5);
+ let temp = prediction["temp"];
+ let clouds = prediction["clouds"];
+</script>
+
+<style>
+ .prediction-details {
+ display: flex;
+ justify-content: space-between;
+ border-bottom-width: 1px;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ font-size: 0.75rem;
+ letter-spacing: 0.05em;
+ }
+
+ .prediction-details:last-of-type {
+ border-width: 0;
+ padding-bottom: 0;
+ }
+
+ .prediction-details h3 {
+ font-size: 0.875rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ margin-right: 0.5rem;
+ }
+
+ .prediction-details .data {
+ display: flex;
+ flex-direction: row;
+ }
+
+ .prediction-details .data h2 {
+ margin-right: 1rem;
+ font-size: 1.5rem;
+ }
+
+</style>
+
+<div class="prediction-details">
+ <div>
+ <h3>{time}</h3>
+ <p>{date}</p>
+ </div>
+ <div class="data">
+ <h2>{kp}</h2>
+ <div>
+ <p><i class="fas fa-thermometer-half"></i> {temp}°C</p>
+ <p><i class="fas fa-cloud"></i> {clouds}%</p>
+ </div>
+ </div>
+</div>
diff --git a/src/components/WeatherCurrent.svelte b/src/components/WeatherCurrent.svelte
new file mode 100644
index 0000000..6f92b20
--- /dev/null
+++ b/src/components/WeatherCurrent.svelte
@@ -0,0 +1,210 @@
+<script lang="ts">
+ import { onMount } from 'svelte';
+ const monthNames = ["January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"
+ ];
+
+ let longitude;
+ let latitude;
+ let locationSupported;
+ let dataLoading = true;
+ let defaultLocation = false;
+ let weather;
+ let days = Array();
+
+ let location = "The earth";
+ let date = "-";
+ let kp_now = "-";
+ let kp_min = "-";
+ let kp_max = "-";
+ let bz = "-";
+ let clouds = "-";
+
+ async function getWeather(longitude, latitude) {
+ let yr_data = await fetch(`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${latitude}&lon=${longitude}`).then(res => res.json());
+ clouds = yr_data["properties"]["timeseries"][0]["data"]["instant"]["details"]["cloud_area_fraction"];
+ bz = (await fetch("https://services.swpc.noaa.gov/products/summary/solar-wind-mag-field.json").then(res => res.json()))["Bz"];
+ //console.log (await fetch(`http://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&sensor=true`).then(res => res.json()));
+ let kp_data = await fetch('https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json').then(res => res.json());
+ kp_data.shift();
+ let cDate = new Date();
+ let closestDate = new Date(0,0,0);
+ let minkp = 1000; // Just a larger number than any plausable value
+ let maxkp = 0;
+
+ kp_data.forEach((pred, i) => {
+ if (pred[1] > maxkp) {
+ maxkp = pred[1];
+ }
+ if (pred[1] < minkp) {
+ minkp = pred[1];
+ }
+ let predDate = new Date(pred[0]);
+ if (Math.abs(predDate.getTime() - cDate.getTime()) < Math.abs(closestDate.getTime() - cDate.getTime())) {
+ closestDate = predDate;
+ kp_now = pred[1];
+ }
+ });
+ kp_min = minkp.toString();
+ kp_max = maxkp.toString();
+
+ date = cDate.getDay() + ". " + monthNames[cDate.getMonth()] + " " + cDate.getHours() + ":" + cDate.getMinutes();
+ }
+ function getDays(daily) {
+ var data = daily.data.slice(0,5)
+ data.forEach(element => {
+ var a = new Date(element.time*1000)
+ var dayStrings = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
+ days.push(dayStrings[a.getDay()])
+ });
+ }
+ function getLocation() {
+ if (navigator.geolocation) {
+ dataLoading = true
+ locationSupported = true
+ navigator.geolocation.getCurrentPosition(setLocation, locationError)
+ } else {
+ locationSupported = false
+ noLocation()
+ }
+ }
+
+ function setLocation(position) {
+ longitude = position.coords.longitude
+ latitude = position.coords.latitude
+ getWeather(longitude, latitude)
+ }
+
+ function locationError(err) {
+ noLocation()
+ }
+
+ function noLocation() {
+ longitude = 28.283333
+ latitude = -15.416667
+ toggleLoading()
+ }
+ function toggleLoading() {
+ dataLoading = !dataLoading
+ }
+ function toggleDefault() {
+ defaultLocation = !defaultLocation
+ }
+ onMount(getLocation);
+</script>
+
+<style>
+
+ .weatherCurrent-wrapper {
+ height: 100%;
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+ letter-spacing: 0.05em;
+ --bg-opacity: 1;
+ background-color: #1a202c;
+ background-color: rgba(26, 32, 44, var(--bg-opacity));
+ background:
+ linear-gradient(
+ rgba(0, 0, 0, 0.5),
+ rgba(0, 0, 0, 0.5)
+ ), url(/aurora.jpg);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ }
+
+ .weatherCurrent-data {
+ width: 100%;
+ padding: 1.5rem;
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ --text-opacity: 1;
+ color: #fff;
+ color: rgba(255, 255, 255, var(--text-opacity));
+ text-align: center;
+ letter-spacing: 0.05em;
+ text-shadow: 1px 1px 2px rgba(0,0,0,.75);
+ }
+
+ .weatherCurrent-data-location {
+ display: flex;
+ justify-content: center;
+ align-content: center;
+ }
+
+ .weatherCurrent-data-location .symbol {
+ width: 1.5rem;
+ height: 1.5rem;
+ margin-right: 1rem;
+ }
+
+
+ .weatherCurrent-data-location h1 {
+ text-transform: uppercase;
+ font-family: Roboto Condensed, sans-serif;
+ font-size: 1.125rem;
+ letter-spacing: 0.1em;
+ margin-bottom: 0;
+ }
+
+ .weatherCurrent-data-kp h2 {
+ font-weight: 700;
+ font-size: 3rem;
+ letter-spacing: 0.05em;
+ line-height: 1.25;
+ }
+
+ .current-details {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ grid-gap: 1rem;
+ gap: 1rem;
+ text-shadow: 1px 1px 2px rgba(0,0,0,.75);
+ }
+
+ .current-details p {
+ line-height: 1.375;
+ }
+
+
+</style>
+
+<div class="weatherCurrent-wrapper">
+ <div class="weatherCurrent-data">
+ <div class="weatherCurrent-data-location">
+ <i class="symbol fas fa-map-marker-alt"></i>
+ <h1>{location}</h1>
+ </div>
+
+ <div class="weatherCurrent-data-date">
+ <p>{date}</p>
+ </div>
+
+ <div class="weatherCurrent-data-kp">
+ <h2>KP {kp_now}</h2>
+ <p>
+ <span className="pr-2">&uarr; KP {kp_max}</span>
+ <span className="pl-2">&darr; KP {kp_min}</span>
+ </p>
+ </div>
+
+ <div class="current-details">
+ <div>
+ <p>BZ</p>
+ <p>{bz}</p>
+ </div>
+ <div>
+ <p>Probability</p>
+ <p>10%</p>
+ </div>
+ <div>
+ <p>Clouds</p>
+ <p>{clouds}%</p>
+ </div>
+ </div>
+
+ </div>
+</div>
diff --git a/src/routes/_error.svelte b/src/routes/_error.svelte
new file mode 100644
index 0000000..92fcca8
--- /dev/null
+++ b/src/routes/_error.svelte
@@ -0,0 +1,40 @@
+<script lang="ts">
+ export let status: number;
+ export let error: Error;
+
+ const dev = process.env.NODE_ENV === 'development';
+</script>
+
+<style>
+ h1, p {
+ margin: 0 auto;
+ }
+
+ h1 {
+ font-size: 2.8em;
+ font-weight: 700;
+ margin: 0 0 0.5em 0;
+ }
+
+ p {
+ margin: 1em auto;
+ }
+
+ @media (min-width: 480px) {
+ h1 {
+ font-size: 4em;
+ }
+ }
+</style>
+
+<svelte:head>
+ <title>{status}</title>
+</svelte:head>
+
+<h1>{status}</h1>
+
+<p>{error.message}</p>
+
+{#if dev && error.stack}
+ <pre>{error.stack}</pre>
+{/if}
diff --git a/src/routes/_layout.svelte b/src/routes/_layout.svelte
new file mode 100644
index 0000000..bab4188
--- /dev/null
+++ b/src/routes/_layout.svelte
@@ -0,0 +1,54 @@
+<script lang="ts">
+</script>
+
+<style>
+ .app-container {
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+ letter-spacing: 0.05em;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
+ --bg-opacity: 1;
+ background-color: #1a202c;
+ background-color: rgba(26, 32, 44, var(--bg-opacity));
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ }
+
+ @media (min-width: 640px) {
+ .app-container {
+ border-radius: 1rem;
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ font-family: Roboto, sans-serif;
+ font-size: 1rem;
+ letter-spacing: 0.05em;
+ height: 100%;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ width: 375px;
+ height: 812px;
+ }
+ }
+
+ .menu-button {
+ position: relative;
+ top: .25rem;
+ left: .75rem;
+ color: #ffffff;
+ font-size: 1.5rem;
+
+ }
+
+</style>
+
+<div class="app-container">
+ <!-- <div class="menu-button">
+ <i class="fas fa-bars"></i>
+ </div> -->
+
+ <slot></slot>
+</div>
diff --git a/src/routes/index.svelte b/src/routes/index.svelte
new file mode 100644
index 0000000..655f0c4
--- /dev/null
+++ b/src/routes/index.svelte
@@ -0,0 +1,28 @@
+<script lang="ts">
+ import WeatherCurrent from '../components/WeatherCurrent.svelte';
+ import PredictedSpaceWeather from '../components/PredictedSpaceWeather.svelte';
+</script>
+
+
+<style>
+ .homescreen {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ height: 100%;
+ --bg-opacity: 1;
+ background-color: #1a202c;
+ background-color: rgba(26, 32, 44, var(--bg-opacity));
+ --bg-opacity: 0.25;
+ }
+</style>
+
+<svelte:head>
+ <title>Aurora data</title>
+</svelte:head>
+
+
+<div class="homescreen">
+ <WeatherCurrent/>
+ <PredictedSpaceWeather />
+</div>
diff --git a/src/server.ts b/src/server.ts
new file mode 100644
index 0000000..8425b2c
--- /dev/null
+++ b/src/server.ts
@@ -0,0 +1,18 @@
+import sirv from 'sirv';
+import polka from 'polka';
+import compression from 'compression';
+import * as sapper from '@sapper/server';
+
+const { PORT, NODE_ENV } = process.env;
+const dev = NODE_ENV === 'development';
+
+polka() // You can also use Express
+ .use(
+ 'aurora-data',
+ compression({ threshold: 0 }),
+ sirv('static', { dev }),
+ sapper.middleware()
+ )
+ .listen(PORT, err => {
+ if (err) console.log('error', err);
+ });
diff --git a/src/service-worker.ts b/src/service-worker.ts
new file mode 100644
index 0000000..1441676
--- /dev/null
+++ b/src/service-worker.ts
@@ -0,0 +1,82 @@
+import { timestamp, files, shell, routes } from '@sapper/service-worker';
+
+const ASSETS = `cache${timestamp}`;
+
+// `shell` is an array of all the files generated by the bundler,
+// `files` is an array of everything in the `static` directory
+const to_cache = (shell as string[]).concat(files as string[]);
+const cached = new Set(to_cache);
+
+self.addEventListener('install', <EventType extends ExtendableEvent>(event: EventType) => {
+ event.waitUntil(
+ caches
+ .open(ASSETS)
+ .then(cache => cache.addAll(to_cache))
+ .then(() => {
+ ((self as any) as ServiceWorkerGlobalScope).skipWaiting();
+ })
+ );
+});
+
+self.addEventListener('activate', <EventType extends ExtendableEvent>(event: EventType) => {
+ event.waitUntil(
+ caches.keys().then(async keys => {
+ // delete old caches
+ for (const key of keys) {
+ if (key !== ASSETS) await caches.delete(key);
+ }
+
+ ((self as any) as ServiceWorkerGlobalScope).clients.claim();
+ })
+ );
+});
+
+self.addEventListener('fetch', <EventType extends FetchEvent>(event: EventType) => {
+ if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
+
+ const url = new URL(event.request.url);
+
+ // don't try to handle e.g. data: URIs
+ if (!url.protocol.startsWith('http')) return;
+
+ // ignore dev server requests
+ if (url.hostname === self.location.hostname && url.port !== self.location.port) return;
+
+ // always serve static files and bundler-generated assets from cache
+ if (url.host === self.location.host && cached.has(url.pathname)) {
+ event.respondWith(caches.match(event.request));
+ return;
+ }
+
+ // for pages, you might want to serve a shell `service-worker-index.html` file,
+ // which Sapper has generated for you. It's not right for every
+ // app, but if it's right for yours then uncomment this section
+ /*
+ if (url.origin === self.origin && routes.find(route => route.pattern.test(url.pathname))) {
+ event.respondWith(caches.match('/service-worker-index.html'));
+ return;
+ }
+ */
+
+ if (event.request.cache === 'only-if-cached') return;
+
+ // for everything else, try the network first, falling back to
+ // cache if the user is offline. (If the pages never change, you
+ // might prefer a cache-first approach to a network-first one.)
+ event.respondWith(
+ caches
+ .open(`offline${timestamp}`)
+ .then(async cache => {
+ try {
+ const response = await fetch(event.request);
+ cache.put(event.request, response.clone());
+ return response;
+ } catch(err) {
+ const response = await cache.match(event.request);
+ if (response) return response;
+
+ throw err;
+ }
+ })
+ );
+});
diff --git a/src/template.html b/src/template.html
new file mode 100644
index 0000000..f7382aa
--- /dev/null
+++ b/src/template.html
@@ -0,0 +1,35 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1.0">
+ <meta name="theme-color" content="#333333">
+
+ %sapper.base%
+
+ <link rel="stylesheet" href="index.css">
+ <link rel="manifest" href="manifest.json" crossorigin="use-credentials">
+ <link rel="icon" type="image/png" href="favicon.png">
+ <script src="https://kit.fontawesome.com/9fa565d9ec.js" crossorigin="anonymous"></script>
+
+ <!-- Sapper creates a <script> tag containing `src/client.js`
+ and anything else it needs to hydrate the app and
+ initialise the router -->
+ %sapper.scripts%
+
+ <!-- Sapper generates a <style> tag containing critical CSS
+ for the current page. CSS for the rest of the app is
+ lazily loaded when it precaches secondary pages -->
+ %sapper.styles%
+
+ <!-- This contains the contents of the <svelte:head> component, if
+ the current page has one -->
+ %sapper.head%
+</head>
+<body>
+ <div id="svelte" class="app">
+ %sapper.html%
+ </div>
+ <!-- <div id="sapper">%sapper.html%</div> -->
+</body>
+</html>