822c0b7c0b
Uniform stock management for healthcare linen rooms: the coordinator app, the phone counter and the staff app, for your own server. Built from 8685140 on 2026-09-13. Licensed under the Functional Source License (FSL-1.1-ALv2).
85 lines
4.5 KiB
HTML
85 lines
4.5 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||
<title>No connection — ThreadCount</title>
|
||
<style>
|
||
/* Bundled with the app, so it has to stand on its own: no web fonts, no network, no images.
|
||
Archivo is the brand face; the system stack is what actually renders here. */
|
||
:root { --paper:#f3f2f2; --ink:#201e1d; --accent:#ec3013; --n600:#7a7573; --n700:#57534f; }
|
||
* { box-sizing:border-box; margin:0; padding:0; }
|
||
body {
|
||
font-family:"Archivo",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
||
background:var(--paper); color:var(--ink);
|
||
min-height:100dvh; display:flex; flex-direction:column;
|
||
padding-top:env(safe-area-inset-top); padding-bottom:env(safe-area-inset-bottom);
|
||
}
|
||
header { height:56px; flex:0 0 56px; background:var(--ink); color:var(--paper);
|
||
display:flex; align-items:center; gap:10px; padding:0 16px; }
|
||
.sq { width:15px; height:15px; background:var(--accent); flex:none; }
|
||
.wm { font-weight:800; font-size:17px; letter-spacing:-.01em; }
|
||
.rule { height:4px; background:var(--accent); }
|
||
main { flex:1; padding:36px 20px; }
|
||
h1 { font-weight:800; font-size:30px; line-height:1.05; letter-spacing:-.03em; }
|
||
.mark { width:56px; height:4px; background:var(--accent); margin:18px 0 0; }
|
||
p { font-size:15.5px; line-height:1.65; color:var(--n700); margin-top:20px; max-width:44ch; }
|
||
ul { margin-top:18px; padding:0; list-style:none; border-top:2px solid var(--ink); }
|
||
li { display:flex; gap:11px; padding:12px 0; border-bottom:1px solid #cfcccb;
|
||
font-size:14.5px; line-height:1.5; color:var(--n700); }
|
||
li i { width:7px; height:7px; background:var(--accent); flex:none; margin-top:7px; }
|
||
button {
|
||
width:100%; min-height:64px; border:0; background:var(--accent); color:#fff;
|
||
font-family:inherit; font-weight:800; font-size:14px; letter-spacing:.08em;
|
||
text-transform:uppercase; text-align:left; padding:0 20px; cursor:pointer;
|
||
display:flex; align-items:center; gap:12px;
|
||
}
|
||
button:active { background:#b8240e; }
|
||
.foot { font-size:13px; color:var(--n600); padding:0 20px 22px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<header><span class="sq"></span><span class="wm">ThreadCount</span></header>
|
||
<div class="rule"></div>
|
||
<main>
|
||
<h1>No connection.</h1>
|
||
<div class="mark"></div>
|
||
<p>ThreadCount can’t reach the linen room’s records, so it won’t let you issue or count
|
||
anything right now — nothing is being saved up in the background.</p>
|
||
<ul>
|
||
<li><i></i>Check the ward’s wifi, or step somewhere with signal.</li>
|
||
<li><i></i>Nothing you had already recorded is lost. A count you hadn’t filed is still
|
||
on this phone and will be there when you get back.</li>
|
||
<li><i></i>If the wifi is fine and this keeps happening, it’s our end — the contact form
|
||
on threadcount.tech reaches the person who wrote it.</li>
|
||
</ul>
|
||
</main>
|
||
<button id="again">Try again <span aria-hidden="true">→</span></button>
|
||
<div class="foot" id="stamp"></div>
|
||
<script>
|
||
/* "Try again" has to leave this page. Capacitor serves it from the app's own origin
|
||
(https://localhost/error.html), so location.reload() re-requests a local file that always
|
||
loads — the same screen comes back no matter how good the signal is, and the only way out is
|
||
to force-quit the app. index.html records where it was headed under the same origin, so this
|
||
goes back there instead; if there is nothing recorded, the app's home screen will do. */
|
||
var SITE = "https://threadcount.tech";
|
||
try { var s = localStorage.getItem("tc.server"); if (s && /^https:\/\/[a-z0-9.-]+(:[0-9]+)?$/i.test(s)) SITE = s; } catch (e) { /* private mode is fine */ }
|
||
var HOME = "/m";
|
||
document.getElementById('again').addEventListener('click', function () {
|
||
var path = HOME;
|
||
try { path = localStorage.getItem('tc.lastpath') || HOME; } catch (e) { /* private mode is fine */ }
|
||
// A stored value only ever came from this app, but it is still concatenated onto a URL, so it
|
||
// is checked: one leading slash, and no second one that would make it another host entirely.
|
||
if (path.charAt(0) !== '/' || path.charAt(1) === '/') path = HOME;
|
||
location.replace(SITE + path);
|
||
});
|
||
|
||
// A timestamp turns "it says no connection" into something answerable over the phone.
|
||
try {
|
||
document.getElementById('stamp').textContent =
|
||
'Last tried ' + new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||
} catch (e) { /* nothing to show is fine */ }
|
||
</script>
|
||
</body>
|
||
</html>
|