Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
123 lines (113 loc) · 5.13 KB
/
Copy pathindex.html
File metadata and controls
123 lines (113 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8" />
<!-- interactive-widget=resizes-content: Android Chrome paints the keyboard
over fixed bottom sheets unless the layout viewport shrinks instead. -->
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content" />
<title>FTW</title>
<meta name="description" content="Your home's energy, from wherever you are." />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/icons/icon.svg" type="image/svg+xml" />
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png" />
<!-- Standalone display on iOS. Keep the status bar opaque: WebKit's
black-translucent mode can move the standalone render edge up by
the top safe-area inset and clip the tab bar at the bottom. Black
matches FTW's launch surface without triggering that viewport bug. -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="FTW" />
<meta name="theme-color" content="#0d0d0d" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#f4f4f2" media="(prefers-color-scheme: light)" />
<!-- Two things happen before the bundle is fetched, and both are here
rather than in module code because module code is already too late.
1. Resolve the theme. A flash of the wrong theme on every cold start
is the most visible way to look like a web page, not an app.
2. Start reading the cached snapshot. IndexedDB is asynchronous, so
the round trip overlaps bundle download, parse and evaluation
instead of following them. By the time Svelte mounts, the data is
usually already in hand — which is what lets the first frame show
real readings rather than a spinner.
Kept small and dependency-free on purpose: everything here is on the
critical path of every single launch. -->
<script>
;(() => {
try {
const stored = localStorage.getItem('ftw.theme')
const theme =
stored === 'light' || stored === 'dark'
? stored
: matchMedia('(prefers-color-scheme: light)').matches
? 'light'
: 'dark'
document.documentElement.dataset.theme = theme
} catch {
/* Private mode or blocked storage: the dark default already applies. */
}
// Which site to show. Plaintext because it is only an identifier, and
// because a decrypt here would defeat the point of starting early.
const siteId = (() => {
try {
return localStorage.getItem('ftw.site')
} catch {
return null
}
})()
if (!siteId) return
window.__ftwBoot = new Promise((resolve) => {
let settled = false
const done = (v) => {
if (!settled) {
settled = true
resolve(v)
}
}
// Never let a blocked or upgrading database hold up the launch.
setTimeout(() => done(null), 400)
try {
// Omit the version so this opens whatever schema the installed
// app already owns. Pinning this probe to v1 made every healthy
// v2 database throw VersionError, which disabled the warm-launch
// path on every current install.
const req = indexedDB.open('ftw')
req.onerror = () => done(null)
// An upgrade here means the database does not exist yet — and
// opening it CREATED it, at version 1, with no object stores. The
// app's own open() uses the same version, so its upgrade would
// never run and the stores would never appear: every read and
// write broken for the life of the install, which the user meets
// as a pairing screen for a house they already paired.
//
// Aborting undoes the creation, so the app's open is the one that
// builds the schema. There is nothing to read either way.
req.onupgradeneeded = () => {
try {
req.transaction.abort()
} catch {
/* nothing to undo */
}
done(null)
}
req.onsuccess = () => {
try {
const database = req.result
if (!database.objectStoreNames.contains('snapshot')) return done(null)
const get = database.transaction('snapshot', 'readonly').objectStore('snapshot').get(siteId)
get.onsuccess = () => done(get.result ?? null)
get.onerror = () => done(null)
} catch {
done(null)
}
}
} catch {
done(null)
}
})
})()
</script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>