Files
bgclock/public_html/index.html
T
2026-08-11 10:52:35 +02:00

684 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
<title>Backgammon Clock</title>
<meta name="theme-color" content="#332e2b">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<link rel="manifest" href="manifest.webmanifest">
<link rel="icon" href="icon-192.png">
<link rel="apple-touch-icon" href="icon-192.png">
<style>
:root{
--accent:#7E9A79;
--accent-ink:#FFFFFF;
--accent-mute:rgba(0,0,0,.34);
--idle:#9A9A9A;
--idle-ink:#2E2E2E;
--idle-mute:rgba(0,0,0,.36);
--bar:#332E2B;
--icon:#8E8880;
--icon-hot:#DFD9D1;
--flag:#A6564E;
--sheet:#232120;
--sheet-line:rgba(255,255,255,.09);
--sheet-ink:#EDE9E4;
--sheet-dim:#A29A92;
--ui:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%;overflow:hidden;background:var(--bar)}
body{
font-family:var(--ui);
font-variant-numeric:tabular-nums;
font-feature-settings:"tnum" 1;
-webkit-tap-highlight-color:transparent;
-webkit-user-select:none;user-select:none;
overscroll-behavior:none;
touch-action:manipulation;
}
/* ---------- frame ---------- */
.app{
height:100dvh;height:100vh;
display:grid;
grid-template-rows:1fr auto 1fr;
}
/* ---------- player panels ---------- */
.panel{
position:relative;
background:var(--idle);
color:var(--idle-ink);
overflow:hidden;
transition:background-color .18s ease;
}
.panel[data-state="active"]{background:var(--accent);color:var(--accent-ink)}
.panel[data-state="flagged"]{background:var(--flag);color:#fff}
.inner{
position:absolute;inset:0;
display:flex;flex-direction:column;
padding:max(14px,env(safe-area-inset-top)) 20px 14px;
}
.panel--top .inner{transform:rotate(180deg)}
.panel--top .inner{padding:14px 20px max(14px,env(safe-area-inset-top))}
.panel--bottom .inner{padding:14px 20px max(14px,env(safe-area-inset-bottom))}
.moves{
align-self:flex-end;
font-size:clamp(13px,3.7vw,20px);
font-weight:700;
letter-spacing:.01em;
color:var(--idle-mute);
order:-1;
}
.panel[data-state="active"] .moves,
.panel[data-state="flagged"] .moves{color:var(--accent-mute)}
.panel[data-state="flagged"] .moves{color:rgba(255,255,255,.55)}
.stack{
flex:1;
display:flex;flex-direction:column;
align-items:center;justify-content:center;
padding-bottom:16%;
gap:clamp(10px,2.4vw,18px);
}
.time{
font-size:clamp(58px,24vw,190px);
line-height:.9;
font-weight:700;
letter-spacing:-.02em;
}
/* ---------- the delay readout (the bit chess.com doesn't have) ---------- */
.delay{
display:flex;align-items:center;
gap:clamp(8px,2.2vw,14px);
opacity:0;
transition:opacity .16s ease;
}
.delay.on{opacity:1}
.track{
width:clamp(72px,20vw,140px);
height:clamp(4px,1.1vw,7px);
border-radius:99px;
background:var(--accent-mute);
opacity:.45;
overflow:hidden;
}
.fill{
display:block;height:100%;width:100%;
border-radius:99px;
background:var(--accent-ink);
transform-origin:left center;
}
.dnum{
font-size:clamp(15px,4.4vw,26px);
font-weight:700;
min-width:1.6em;
}
/* ---------- control bar ---------- */
.bar{
background:var(--bar);
height:clamp(58px,8.2vh,86px);
display:flex;align-items:center;
padding:0 clamp(12px,4vw,28px);
}
.bar .gap{flex:1.5}
.btn{
flex:1;
height:100%;
display:grid;place-items:center;
background:none;border:0;
color:var(--icon);
cursor:pointer;
}
.btn:active{color:var(--icon-hot)}
.btn[aria-pressed="false"]{color:#5E5954}
.btn svg{width:clamp(23px,6.4vw,32px);height:auto;display:block}
.btn:focus-visible{outline:2px solid var(--icon-hot);outline-offset:-6px;border-radius:8px}
/* ---------- settings sheet ---------- */
.scrim{
position:fixed;inset:0;
background:rgba(0,0,0,.55);
opacity:0;pointer-events:none;
transition:opacity .2s ease;
z-index:10;
}
.scrim.on{opacity:1;pointer-events:auto}
.sheet{
position:fixed;left:0;right:0;bottom:0;
z-index:11;
background:var(--sheet);
color:var(--sheet-ink);
border-radius:20px 20px 0 0;
padding:8px 22px max(22px,env(safe-area-inset-bottom));
transform:translateY(101%);
transition:transform .26s cubic-bezier(.32,.72,0,1);
max-height:88dvh;overflow-y:auto;
box-shadow:0 -18px 50px rgba(0,0,0,.45);
}
.sheet.on{transform:translateY(0)}
.grab{width:38px;height:4px;border-radius:99px;background:#4A4644;margin:6px auto 18px}
.row{
display:flex;align-items:center;justify-content:space-between;
gap:16px;
padding:16px 0;
border-top:1px solid var(--sheet-line);
}
.row:first-of-type{border-top:0}
.row h2{font-size:15px;font-weight:600;letter-spacing:.01em}
.row p{font-size:12.5px;color:var(--sheet-dim);margin-top:3px;line-height:1.35}
.stepper{display:flex;align-items:center;gap:2px}
.step{
width:38px;height:38px;flex:none;
border-radius:11px;border:1px solid var(--sheet-line);
background:#2C2A28;color:var(--sheet-ink);
font-size:19px;font-weight:600;line-height:1;
cursor:pointer;
}
.step:active{background:#3A3735}
.val{
min-width:66px;text-align:center;
font-size:19px;font-weight:700;
}
.unit{font-size:11px;color:var(--sheet-dim);display:block;font-weight:600;letter-spacing:.06em;text-transform:uppercase;margin-top:2px}
.swatches{display:flex;gap:11px}
.sw{
width:36px;height:36px;border-radius:50%;
border:2px solid transparent;
box-shadow:inset 0 0 0 2px var(--sheet);
cursor:pointer;padding:0;
}
.sw[aria-checked="true"]{border-color:var(--sheet-ink)}
.toggle{
width:52px;height:31px;flex:none;
border-radius:99px;border:0;
background:#4A4644;
position:relative;cursor:pointer;
transition:background-color .18s ease;
}
.toggle[aria-checked="true"]{background:var(--accent)}
.toggle::after{
content:"";position:absolute;top:3px;left:3px;
width:25px;height:25px;border-radius:50%;
background:#fff;
transition:transform .18s cubic-bezier(.32,.72,0,1);
}
.toggle[aria-checked="true"]::after{transform:translateX(21px)}
.done{
width:100%;margin-top:20px;
padding:15px;border:0;border-radius:13px;
background:var(--accent);color:var(--accent-ink);
font-family:var(--ui);font-size:15.5px;font-weight:700;
cursor:pointer;
}
.hint{
font-size:12px;color:var(--sheet-dim);
text-align:center;margin-top:13px;line-height:1.45;
}
@media (prefers-reduced-motion:reduce){
*{transition-duration:.01ms!important}
}
</style>
</head>
<body>
<div class="app">
<section class="panel panel--top" data-side="0" data-state="idle">
<div class="inner">
<div class="moves">Moves: <span data-moves>0</span></div>
<div class="stack">
<div class="time" data-time>3:00</div>
<div class="delay" data-delay>
<span class="track"><i class="fill" data-fill></i></span>
<span class="dnum" data-dnum>15</span>
</div>
</div>
</div>
</section>
<div class="bar">
<button class="btn" id="reset" aria-label="Reset clock">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 5v6h-6"/><path d="M20 11a8 8 0 1 0-2.3 5.3"/>
</svg>
</button>
<button class="btn" id="playpause" aria-label="Pause clock">
<svg viewBox="0 0 24 24" fill="currentColor" data-icon="pause">
<rect x="6" y="4.5" width="4.4" height="15" rx="1.4"/><rect x="13.6" y="4.5" width="4.4" height="15" rx="1.4"/>
</svg>
<svg viewBox="0 0 24 24" fill="currentColor" data-icon="play" hidden>
<path d="M7.5 4.7a1 1 0 0 1 1.53-.85l9 6.3a1 1 0 0 1 0 1.7l-9 6.3A1 1 0 0 1 7.5 17.3z"/>
</svg>
</button>
<span class="gap"></span>
<button class="btn" id="settings" aria-label="Settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="13.6" r="7.4"/><path d="M12 13.6V9.4"/><path d="M9.4 2.4h5.2"/><path d="M12 2.4v3.6"/><path d="m18.6 6.4 1.9-1.9"/>
</svg>
</button>
<button class="btn" id="sound" aria-label="Sound" aria-pressed="true">
<svg viewBox="0 0 24 24" fill="currentColor" data-icon="on">
<path d="M4 9.2h3.3L12 5.1a.9.9 0 0 1 1.5.7v12.4a.9.9 0 0 1-1.5.7L7.3 14.8H4a.9.9 0 0 1-.9-.9v-3.8a.9.9 0 0 1 .9-.9z"/>
<path d="M16.4 8.6a.95.95 0 0 1 1.34.06 5.1 5.1 0 0 1 0 6.68.95.95 0 1 1-1.4-1.28 3.2 3.2 0 0 0 0-4.12.95.95 0 0 1 .06-1.34z"/>
<path d="M19.1 5.6a.95.95 0 0 1 1.34.05 9.2 9.2 0 0 1 0 12.7.95.95 0 1 1-1.39-1.3 7.3 7.3 0 0 0 0-10.1.95.95 0 0 1 .05-1.35z"/>
</svg>
<svg viewBox="0 0 24 24" fill="currentColor" data-icon="off" hidden>
<path d="M4 9.2h3.3L12 5.1a.9.9 0 0 1 1.5.7v12.4a.9.9 0 0 1-1.5.7L7.3 14.8H4a.9.9 0 0 1-.9-.9v-3.8a.9.9 0 0 1 .9-.9z"/>
<path d="M16.5 9.3a1 1 0 0 1 1.42 0L19.6 11l1.68-1.7a1 1 0 1 1 1.42 1.42L21 12.4l1.7 1.7a1 1 0 0 1-1.42 1.42L19.6 13.8l-1.68 1.7a1 1 0 0 1-1.42-1.42l1.7-1.68-1.7-1.68a1 1 0 0 1 0-1.42z"/>
</svg>
</button>
</div>
<section class="panel panel--bottom" data-side="1" data-state="idle">
<div class="inner">
<div class="moves">Moves: <span data-moves>0</span></div>
<div class="stack">
<div class="time" data-time>3:00</div>
<div class="delay" data-delay>
<span class="track"><i class="fill" data-fill></i></span>
<span class="dnum" data-dnum>15</span>
</div>
</div>
</div>
</section>
</div>
<div class="scrim" id="scrim"></div>
<div class="sheet" id="sheet" role="dialog" aria-modal="true" aria-label="Settings">
<div class="grab"></div>
<div class="row">
<div>
<h2>Time</h2>
<p>Reserve time per player.</p>
</div>
<div class="stepper">
<button class="step" data-time-adj="-60"></button>
<span class="val"><span id="v-min">3</span><span class="unit">min</span></span>
<button class="step" data-time-adj="60">+</button>
<button class="step" data-time-adj="-15" style="margin-left:8px"></button>
<span class="val"><span id="v-sec">00</span><span class="unit">sec</span></span>
<button class="step" data-time-adj="15">+</button>
</div>
</div>
<div class="row">
<div>
<h2>Delay</h2>
<p>Free seconds each turn. Never carries over.</p>
</div>
<div class="stepper">
<button class="step" data-delay-adj="-1"></button>
<span class="val"><span id="v-delay">15</span><span class="unit">sec</span></span>
<button class="step" data-delay-adj="1">+</button>
</div>
</div>
<div class="row">
<div>
<h2>Colour</h2>
<p>Marks the player on turn.</p>
</div>
<div class="swatches" id="swatches" role="radiogroup" aria-label="Colour"></div>
</div>
<div class="row">
<div>
<h2>Sound</h2>
<p>Turn change, delay expiry, time out.</p>
</div>
<button class="toggle" id="t-sound" role="switch" aria-checked="true" aria-label="Sound"></button>
</div>
<button class="done" id="done">Done</button>
<p class="hint">Changing time or delay resets the clock.</p>
</div>
<script>
(function(){
"use strict";
/* ============ persistence (degrades to memory if blocked) ============ */
var store = {
get: function(k, d){ try{ var v = localStorage.getItem(k); return v === null ? d : v; }catch(e){ return d; } },
set: function(k, v){ try{ localStorage.setItem(k, v); }catch(e){} }
};
var THEMES = [
{ id:"sage", accent:"#7E9A79", ink:"#FFFFFF" },
{ id:"brass", accent:"#C3A153", ink:"#FFFFFF" },
{ id:"slate", accent:"#6E8598", ink:"#FFFFFF" },
{ id:"teal", accent:"#5E8D87", ink:"#FFFFFF" },
{ id:"plum", accent:"#8A7396", ink:"#FFFFFF" }
];
var cfg = {
base: parseInt(store.get("bg.base", "180000"), 10),
delay: parseInt(store.get("bg.delay", "15000"), 10),
theme: store.get("bg.theme", "sage"),
sound: store.get("bg.sound", "1") === "1"
};
/* ============ state ============ */
var IDLE = 0, RUN = 1, PAUSE = 2, FLAG = 3;
var st = {
phase: IDLE,
active: -1,
reserve: [cfg.base, cfg.base], // reserve banked at the start of the current turn
moves: [0, 0],
turnAt: 0, // performance.now() when the turn began
held: 0, // frozen elapsed while paused
ticked: false // subtle sound already played this turn
};
/* ============ dom ============ */
var panels = [].slice.call(document.querySelectorAll(".panel"));
var el = panels.map(function(p){
return {
root: p,
time: p.querySelector("[data-time]"),
moves: p.querySelector("[data-moves]"),
delay: p.querySelector("[data-delay]"),
fill: p.querySelector("[data-fill]"),
dnum: p.querySelector("[data-dnum]")
};
});
var btnPlay = document.getElementById("playpause");
var btnSound = document.getElementById("sound");
var sheet = document.getElementById("sheet");
var scrim = document.getElementById("scrim");
/* ============ audio (synthesised — no files, works offline) ============ */
var ac = null;
function audio(){
if(!ac){
var C = window.AudioContext || window.webkitAudioContext;
if(!C) return null;
ac = new C();
}
if(ac.state === "suspended") ac.resume();
return ac;
}
function tone(freq, at, dur, peak, type, partial){
var c = audio(); if(!c) return;
var t0 = c.currentTime + at;
var o = c.createOscillator(), g = c.createGain();
o.type = type || "sine";
o.frequency.setValueAtTime(freq, t0);
g.gain.setValueAtTime(0.0001, t0);
g.gain.exponentialRampToValueAtTime(peak, t0 + 0.012);
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
o.connect(g); g.connect(c.destination);
o.start(t0); o.stop(t0 + dur + 0.05);
if(partial){
var o2 = c.createOscillator(), g2 = c.createGain();
o2.type = "sine";
o2.frequency.setValueAtTime(freq * 2, t0);
g2.gain.setValueAtTime(0.0001, t0);
g2.gain.exponentialRampToValueAtTime(peak * 0.22, t0 + 0.01);
g2.gain.exponentialRampToValueAtTime(0.0001, t0 + dur * 0.5);
o2.connect(g2); g2.connect(c.destination);
o2.start(t0); o2.stop(t0 + dur);
}
}
// warm rising fifth, marimba-ish — the handover
function sndSwap(){ if(!cfg.sound) return; tone(440, 0, 0.34, 0.20, "sine", true); tone(659.25, 0.075, 0.42, 0.17, "sine", true); }
// barely-there wooden tick — delay just ran out, reserve is now moving
function sndTick(){ if(!cfg.sound) return; tone(300, 0, 0.075, 0.035, "sine", false); }
// unmistakable falling triad — flag
function sndFlag(){
if(!cfg.sound) return;
tone(880, 0.00, 0.20, 0.24, "triangle", false);
tone(659.25, 0.16, 0.20, 0.24, "triangle", false);
tone(440, 0.32, 0.55, 0.26, "triangle", true);
}
function buzz(ms){ try{ if(navigator.vibrate) navigator.vibrate(ms); }catch(e){} }
/* ============ helpers ============ */
function elapsed(){
if(st.phase === PAUSE) return st.held;
return performance.now() - st.turnAt;
}
function fmt(ms){
if(ms < 0) ms = 0;
var t = Math.ceil(ms / 100) / 10;
if(ms < 10000) return t.toFixed(1);
var s = Math.ceil(ms / 1000);
var m = Math.floor(s / 60);
s = s % 60;
return m + ":" + (s < 10 ? "0" : "") + s;
}
/* ============ render ============ */
function render(){
var e = elapsed();
for(var i = 0; i < 2; i++){
var live = (i === st.active && (st.phase === RUN || st.phase === PAUSE));
var main = st.reserve[i], dLeft = 0;
if(live){
if(e < cfg.delay){
dLeft = cfg.delay - e;
}else{
main = st.reserve[i] - (e - cfg.delay);
if(!st.ticked && st.phase === RUN){ st.ticked = true; sndTick(); }
}
}
if(live && main <= 0 && st.phase === RUN){ flag(i); return; }
el[i].time.textContent = fmt(main);
el[i].moves.textContent = st.moves[i];
var showDelay = live && cfg.delay > 0 && dLeft > 0;
el[i].delay.classList.toggle("on", showDelay);
if(showDelay){
el[i].fill.style.transform = "scaleX(" + (dLeft / cfg.delay) + ")";
el[i].dnum.textContent = Math.ceil(dLeft / 1000);
}
if(st.phase === FLAG && i === st.active) el[i].root.dataset.state = "flagged";
else el[i].root.dataset.state = (i === st.active && st.phase !== IDLE) ? "active" : "idle";
}
}
function loop(){ render(); requestAnimationFrame(loop); }
/* ============ actions ============ */
function flag(side){
st.reserve[side] = 0;
st.phase = FLAG;
render();
sndFlag();
buzz([90, 70, 90]);
}
function tap(side){
if(st.phase === FLAG) return;
if(st.phase === PAUSE) return;
if(st.phase === IDLE){
st.active = 1 - side; // you tap your own side to start your opponent
st.turnAt = performance.now();
st.ticked = false;
st.phase = RUN;
sndSwap(); buzz(14);
return;
}
if(side !== st.active) return; // the waiting player can't stop the clock
var e = elapsed();
st.reserve[side] = Math.max(0, st.reserve[side] - Math.max(0, e - cfg.delay));
st.moves[side]++;
st.active = 1 - side;
st.turnAt = performance.now();
st.ticked = false;
sndSwap(); buzz(14);
}
function togglePause(){
if(st.phase === RUN){
st.held = elapsed();
st.phase = PAUSE;
}else if(st.phase === PAUSE){
st.turnAt = performance.now() - st.held;
st.phase = RUN;
}else return;
paintPlay();
}
function paintPlay(){
var showPlay = st.phase !== RUN;
btnPlay.querySelector('[data-icon="pause"]').hidden = showPlay;
btnPlay.querySelector('[data-icon="play"]').hidden = !showPlay;
btnPlay.setAttribute("aria-label", showPlay ? "Start clock" : "Pause clock");
}
function reset(){
st.phase = IDLE; st.active = -1;
st.reserve = [cfg.base, cfg.base];
st.moves = [0, 0];
st.held = 0; st.ticked = false;
paintPlay();
render();
}
/* ============ theme ============ */
function applyTheme(){
var t = THEMES.filter(function(x){ return x.id === cfg.theme; })[0] || THEMES[0];
document.documentElement.style.setProperty("--accent", t.accent);
document.documentElement.style.setProperty("--accent-ink", t.ink);
[].forEach.call(document.querySelectorAll(".sw"), function(s){
s.setAttribute("aria-checked", s.dataset.id === cfg.theme ? "true" : "false");
});
}
/* ============ settings ui ============ */
var swWrap = document.getElementById("swatches");
THEMES.forEach(function(t){
var b = document.createElement("button");
b.className = "sw";
b.dataset.id = t.id;
b.style.background = t.accent;
b.setAttribute("role", "radio");
b.setAttribute("aria-label", t.id);
b.setAttribute("aria-checked", "false");
b.addEventListener("click", function(){
cfg.theme = t.id; store.set("bg.theme", t.id); applyTheme();
});
swWrap.appendChild(b);
});
function paintSettings(){
var s = Math.round(cfg.base / 1000);
document.getElementById("v-min").textContent = Math.floor(s / 60);
document.getElementById("v-sec").textContent = ("0" + (s % 60)).slice(-2);
document.getElementById("v-delay").textContent = Math.round(cfg.delay / 1000);
document.getElementById("t-sound").setAttribute("aria-checked", cfg.sound ? "true" : "false");
btnSound.setAttribute("aria-pressed", cfg.sound ? "true" : "false");
btnSound.querySelector('[data-icon="on"]').hidden = !cfg.sound;
btnSound.querySelector('[data-icon="off"]').hidden = cfg.sound;
}
[].forEach.call(document.querySelectorAll("[data-time-adj]"), function(b){
b.addEventListener("click", function(){
var next = cfg.base + parseInt(b.dataset.timeAdj, 10) * 1000;
cfg.base = Math.min(99 * 60000, Math.max(15000, next));
store.set("bg.base", String(cfg.base));
paintSettings(); reset();
});
});
[].forEach.call(document.querySelectorAll("[data-delay-adj]"), function(b){
b.addEventListener("click", function(){
var next = cfg.delay + parseInt(b.dataset.delayAdj, 10) * 1000;
cfg.delay = Math.min(60000, Math.max(0, next));
store.set("bg.delay", String(cfg.delay));
paintSettings(); reset();
});
});
function setSound(on){
cfg.sound = on;
store.set("bg.sound", on ? "1" : "0");
paintSettings();
if(on){ audio(); sndTick(); }
}
document.getElementById("t-sound").addEventListener("click", function(){ setSound(!cfg.sound); });
btnSound.addEventListener("click", function(){ setSound(!cfg.sound); });
function openSheet(on){
sheet.classList.toggle("on", on);
scrim.classList.toggle("on", on);
}
document.getElementById("settings").addEventListener("click", function(){ paintSettings(); openSheet(true); });
document.getElementById("done").addEventListener("click", function(){ openSheet(false); });
scrim.addEventListener("click", function(){ openSheet(false); });
document.getElementById("reset").addEventListener("click", reset);
btnPlay.addEventListener("click", togglePause);
/* ============ panel taps ============ */
var wantFullscreen = true;
panels.forEach(function(p){
p.addEventListener("pointerdown", function(ev){
ev.preventDefault();
audio();
if(wantFullscreen){
wantFullscreen = false;
if(!window.matchMedia("(display-mode: fullscreen)").matches && document.documentElement.requestFullscreen){
document.documentElement.requestFullscreen().catch(function(){});
}
}
tap(parseInt(p.dataset.side, 10));
});
});
/* ============ keep the screen awake ============ */
var lock = null;
function keepAwake(){
if(!navigator.wakeLock) return;
navigator.wakeLock.request("screen").then(function(l){ lock = l; }).catch(function(){});
}
document.addEventListener("visibilitychange", function(){
if(document.visibilityState === "visible" && !lock) keepAwake();
});
keepAwake();
/* ============ go ============ */
applyTheme();
paintSettings();
paintPlay();
reset();
loop();
if("serviceWorker" in navigator && location.protocol.indexOf("http") === 0){
navigator.serviceWorker.register("sw.js").catch(function(){});
}
})();
</script>
</body>
</html>