-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathindex.html
More file actions
128 lines (122 loc) · 7.01 KB
/
index.html
File metadata and controls
128 lines (122 loc) · 7.01 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
124
125
126
127
128
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<!-- CSP disabled: iOS Safari has issues with wildcard connect-src (*.supabase.co) -->
<!-- TODO: re-enable with explicit origins once verified on iOS -->
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: blob: https:; connect-src 'self' https://*.supabase.co wss://*.supabase.co https://*.posthog.com https://*.sentry.io https://api.anthropic.com; worker-src 'self'; manifest-src 'self'; frame-src 'none'; object-src 'none'; base-uri 'self';" /> -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="theme-color" content="#1F1E1D" />
<meta name="description" content="kernel.chat — Magazine for City Coders. An independent editorial covering the culture, craft, and clothes of the city coder. Published monthly." />
<meta name="keywords" content="magazine, editorial, coder culture, tech fashion, Tokyo, independent publication, design, style" />
<link rel="apple-touch-icon" href="/logo-mark-192.png" />
<title>kernel.chat — Magazine for City Coders</title>
<link rel="canonical" href="https://kernel.chat/" />
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Periodical",
"name": "kernel.chat",
"alternateName": "Magazine for City Coders",
"url": "https://kernel.chat",
"description": "An independent editorial magazine covering the culture, craft, and clothes of the city coder. Published monthly.",
"publisher": {
"@type": "Organization",
"name": "kernel.chat group"
}
}
</script>
<!-- Social / Open Graph -->
<meta property="og:title" content="kernel.chat — Magazine for City Coders" />
<meta property="og:description" content="An independent editorial magazine covering the culture, craft, and clothes of the city coder. Published monthly." />
<meta property="og:type" content="website" />
<meta property="og:image" content="https://kernel.chat/concepts/og-card-bg.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="kernel.chat — Magazine for City Coders" />
<meta name="twitter:description" content="An independent editorial magazine covering the culture, craft, and clothes of the city coder. Published monthly." />
<meta name="twitter:image" content="https://kernel.chat/concepts/og-card-bg.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://eoxxpyixdieprsxlpwcs.supabase.co">
<!-- Non-blocking font load: media="print" prevents render-blocking, onload swaps to "all" -->
<link
href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Courier+Prime:ital,wght@0,400;0,700;1,400;1,700&display=swap"
rel="stylesheet" media="print" onload="this.media='all'">
<noscript><link href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400..800;1,400..800&family=Courier+Prime:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet"></noscript>
</head>
<body>
<div id="root">
<!-- Inline loading indicator — visible instantly while JS loads -->
<div id="splash" style="display:flex;align-items:center;justify-content:center;height:100vh;background:#FAF9F6;font-family:Georgia,'Times New Roman',serif;color:#1F1E1D">
<div style="opacity:.35;font-size:15px;letter-spacing:.08em">kernel</div>
</div>
</div>
<noscript><h1>kernel.chat — Magazine for City Coders</h1></noscript>
<script type="module" src="/src/main.tsx" onerror="_kernelRecover()"></script>
<script>
// Recovery: if the app JS fails to load (stale SW cache after deploy),
// clear all caches, unregister SW, and reload.
// Debounce: only run once per recovery attempt.
var _recovering = false;
function _kernelRecover() {
if (_recovering) return;
_recovering = true;
var count = parseInt(sessionStorage.getItem('_rc') || '0', 10);
if (count >= 3) {
// Exhausted retries — show manual reload that nukes everything
var splash = document.getElementById('splash');
if (splash) {
splash.innerHTML = '<div style="text-align:center;max-width:280px">'
+ '<div style="opacity:.35;font-size:15px;letter-spacing:.08em;margin-bottom:24px">kernel</div>'
+ '<div style="font-size:14px;opacity:.6;margin-bottom:16px">Updating — one moment.</div>'
+ '<button onclick="sessionStorage.clear();location.reload()" style="font-family:inherit;font-size:14px;padding:10px 24px;border:1px solid #1F1E1D;border-radius:8px;background:none;color:#1F1E1D;cursor:pointer">Reload</button>'
+ '</div>';
}
return;
}
sessionStorage.setItem('_rc', String(count + 1));
// Wait for BOTH SW unregister AND cache clear before reloading
var swDone = Promise.resolve();
if ('serviceWorker' in navigator) {
swDone = navigator.serviceWorker.getRegistrations().then(function(regs) {
return Promise.all(regs.map(function(r) { return r.unregister(); }));
}).catch(function() {});
}
var cacheDone = caches.keys().then(function(names) {
return Promise.all(names.map(function(n) { return caches.delete(n); }));
}).catch(function() {});
Promise.all([swDone, cacheDone]).then(function() {
var url = new URL(location.href);
url.searchParams.set('_cb', Date.now().toString(36));
location.replace(url.toString());
});
}
// Catch JS asset load errors (404 on hashed bundles)
// e.target.src catches <script> load failures; e.filename catches runtime errors
window.addEventListener('error', function(e) {
var src = (e.target && e.target.src) || e.filename || '';
if (/assets\/.*\.(js|css)/.test(src)) _kernelRecover();
}, true);
// Backup: stuck splash screen (8s — enough for slow 3G, fast enough to not frustrate)
setTimeout(function() {
var root = document.getElementById('root');
if (root && root.querySelector('#splash')) _kernelRecover();
}, 8000);
// Clear recovery counter on successful load
window.addEventListener('load', function() {
setTimeout(function() {
if (!document.getElementById('splash')) sessionStorage.removeItem('_rc');
}, 2000);
});
</script>
</body>
</html>