Digital_Wall_Clock / index.html
QuantumHawk42's picture
Update index.html
9ad39a1 verified
Raw History Blame Contribute Delete
48.3 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Four-in-One Clock</title>
<link href="https://fonts.googleapis.com/css2?family=Figtree:wght@500;700&family=Orbitron:wght@500;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#cfc7b8;--c1:#f3ede1;--c2:#ddd2bd;--brass:#a07c42;--face:#fdfcf8;--ink:#1f1b16;--panel:#f7f2e8;--line:#cdbfa6;--txt:#2a241c;--mute:#6f6556;--acc:#d23b2a;--lcdbg:#1a1206;--lcd:var(--tc,#ffb12e);--bz1:#f3dfae;--bz2:var(--brass);--bz3:#5e4620;--tone:#c0392b;--dim:#4a1511;box-sizing:border-box;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#0f0d0b;--c1:#3a2e24;--c2:#1f1913;--brass:#c9a266;--face:#e9ede6;--ink:#1b2420;--panel:#241d17;--line:#4b3e31;--txt:#efe6d8;--mute:#a69a88;--acc:#ff5a3c}}
:root[data-theme="dark"]{--bg:#0f0d0b;--c1:#3a2e24;--c2:#1f1913;--brass:#c9a266;--face:#e9ede6;--ink:#1b2420;--panel:#241d17;--line:#4b3e31;--txt:#efe6d8;--mute:#a69a88;--acc:#ff5a3c}
*,*::before,*::after{box-sizing:inherit}
html{scroll-padding-top:env(safe-area-inset-top,0px);background:var(--bg)}
body{margin:0;background:var(--bg);color:var(--txt);font:500 15px/1.4 Figtree,system-ui,sans-serif;display:flex;justify-content:center;padding:48px 14px 28px;min-height:100vh}
[hidden]{display:none!important}
button{font:inherit;color:inherit}
button:focus-visible,input:focus-visible{outline:2px solid var(--acc);outline-offset:2px}
.wrap{position:relative;width:100%;max-width:880px;height:max-content}
.bell{position:absolute;top:-28px;width:96px;height:50px;border-radius:96px 96px 6px 6px;background:radial-gradient(circle at 35% 30%,#f3dfae,var(--brass) 60%,#5e4620);transform-origin:50% 100%;z-index:0}
.bell.l{left:12%}.bell.r{right:12%}
.case{position:relative;z-index:1;border-radius:38px;padding:26px;background:linear-gradient(160deg,var(--c1),var(--c2));box-shadow:0 30px 60px -20px rgba(0,0,0,.55),inset 0 2px 0 rgba(255,255,255,.3),inset 0 -6px 14px rgba(0,0,0,.2)}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));gap:22px;align-items:center}
.an{display:block;width:100%;max-width:420px;margin:auto;filter:drop-shadow(0 10px 14px rgba(0,0,0,.35))}
#hh,#mh,#sh{transform-origin:100px 100px}
#an.tick #sh{transition:transform .25s cubic-bezier(.4,2.6,.6,1)}
.col{display:grid;gap:16px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:22px;padding:14px 12px}
.lcd{background:var(--lcdbg);color:var(--lcd);border-radius:18px;padding:16px;box-shadow:inset 0 4px 14px #000,0 0 0 3px var(--brass);font-family:Orbitron,ui-monospace,monospace}
.row{display:flex;flex-wrap:nowrap;align-items:flex-end;gap:calc(12px*var(--ds,1));justify-content:center}
.seg7{position:relative;display:inline-block;font-weight:700;font-size:calc(3.2rem*var(--ds,1));line-height:1.1}
.seg7 i{font-style:normal;opacity:.09}
.seg7 b{position:absolute;left:0;top:0;font-weight:700;text-shadow:0 0 10px currentColor;white-space:nowrap}
.seg7.sm{font-size:calc(1.6rem*var(--ds,1))}
.ind{display:flex;flex-direction:column;gap:4px;font-size:calc(.62rem*var(--ds,1));padding-bottom:6px}
.ind span{opacity:.15}.ind span.on{opacity:1}
.lcdinfo{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:12px;font-size:calc(.68rem*var(--ds,1))}
.bin{display:flex;gap:6px;justify-content:center}
.bc,.wl{display:flex;flex-direction:column;align-items:center}
.bc.pair{margin-left:10px}
.t,.h,.d{height:1.5rem;display:grid;place-items:center;font-size:.72rem;font-weight:700;color:var(--mute)}
.d{font-family:Orbitron,monospace;color:var(--txt)}
.r,.b{height:2.2rem;display:grid;place-items:center}
.wl .r{font-size:.72rem;font-weight:700;color:var(--mute);padding-right:4px}
.b{font-size:clamp(1.3rem,5.4vw,1.75rem);transition:opacity .2s,transform .2s}
.b.off{opacity:.3;filter:grayscale(.5)}.b.on{transform:scale(1.1)}.b.x{visibility:hidden}
.note{margin:8px 0 0;text-align:center;font-size:.76rem;color:var(--mute)}
.tabs{display:flex;gap:10px;justify-content:center;margin-top:20px}
.tab{display:flex;gap:8px;align-items:center;border:1px solid var(--line);background:var(--panel);border-radius:999px;padding:8px 16px;font-weight:700;cursor:pointer}
.drawer{position:fixed;left:50%;bottom:calc(10px + env(safe-area-inset-bottom,0px));width:min(900px,calc(100vw - 20px));max-height:calc(100vh - 20px);overflow:auto;z-index:20;border-radius:22px;transform:translate(-50%,calc(100% + 40px));visibility:hidden;transition:transform .35s ease,visibility 0s .35s}
.drawer.open{transform:translate(-50%,0);visibility:visible;transition:transform .35s ease,visibility 0s}
.box{background:var(--panel);border:1px solid var(--line);border-radius:22px;padding:16px}
select{font:inherit;background:var(--bg);color:var(--txt);border:1px solid var(--line);border-radius:10px;padding:5px 8px;max-width:190px}
#bg{position:fixed;top:0;right:0;bottom:0;left:0;width:100%;height:100%;z-index:0;background:#000}
#hf,#hft{width:100%;font:inherit;background:var(--bg);color:var(--txt);border:1px solid var(--line);border-radius:10px;padding:6px 8px}
textarea{font:inherit;width:100%;background:var(--bg);color:var(--txt);border:1px solid var(--line);border-radius:10px;padding:6px 8px;resize:vertical}
body.tm .wrap{position:fixed;top:0;right:0;bottom:0;left:0;width:auto;max-width:none;height:auto;display:grid;place-items:center;pointer-events:none}
.tm .case{background:none;box-shadow:none;padding:0;border-radius:0;backdrop-filter:none}
.tm .bell,.tm #pA,.tm #pB{display:none}
.tm .grid,.tm .col{display:block}
.tm .lcd{position:relative;background:none;box-shadow:none;padding:0;border-radius:0}
.tm .row{position:relative;display:block;text-align:center}
.tm .seg7 i{display:none}
.tm .seg7 b{position:static;text-shadow:0 3px 14px rgba(0,0,0,.75),0 0 10px currentColor}
[data-night] body.tm .seg7 b{text-shadow:none}
.tm .row .seg7.sm{position:absolute;left:100%;bottom:.1em;margin-left:.35em}
.tm .ind{position:absolute;left:100%;top:0;margin-left:.5em;padding:0}
.tm .lcdinfo{position:absolute;top:100%;left:50%;transform:translateX(-50%);white-space:nowrap;gap:2em;margin-top:.4em;text-shadow:0 2px 8px rgba(0,0,0,.7)}
.tm .tabs{position:fixed;left:50%;bottom:calc(12px + env(safe-area-inset-bottom,0px));transform:translateX(-50%);margin:0;opacity:.3;pointer-events:auto}
.tm .tabs:hover,.tm .tabs:focus-within{opacity:1}
.tabs,#na{transition:opacity .6s}
body.idle .tabs,body.idle #na{opacity:0!important;pointer-events:none}
body.idle{cursor:none}
.wrap{z-index:2}
.ss .wrap{position:fixed;width:max-content;min-width:min(var(--ww,380px),calc(100vw - 24px));max-width:calc(100vw - 24px)}
.ss .an{width:calc(min(var(--ww,380px),calc(100vw - 24px)) - 30px);max-width:none}
.ss .bell{display:none}
.ss .case{padding:14px;border-radius:26px;background:color-mix(in srgb,var(--c1) 84%,transparent);backdrop-filter:blur(10px)}
.ss .grid{grid-template-columns:1fr;gap:12px}
.ss .tabs{margin-top:10px}
.ss.c-tl .wrap{top:calc(14px + env(safe-area-inset-top,0px));left:14px}
.ss.c-tr .wrap{top:calc(14px + env(safe-area-inset-top,0px));right:14px}
.ss.c-bl .wrap{bottom:calc(14px + env(safe-area-inset-bottom,0px));left:14px}
.ss.c-br .wrap{bottom:calc(14px + env(safe-area-inset-bottom,0px));right:14px}
:root[data-night]{--bg:#000;--c1:#000;--c2:#050203;--brass:var(--dim);--face:#000;--ink:var(--tone);--panel:#080406;--line:var(--dim);--txt:var(--tone);--mute:var(--dim);--acc:var(--tone);--lcdbg:#000;--lcd:var(--tone);--bz1:var(--dim);--bz2:var(--dim);--bz3:#000}
[data-night] .case{box-shadow:none}
[data-night] .bell{display:none}
[data-night] .grid,[data-night] .tabs,[data-night] .drawer{filter:brightness(var(--nb,.6))}
[data-night] .lcd{box-shadow:inset 0 0 0 1px var(--dim)}
[data-night] .seg7 b{text-shadow:none}
[data-night] .btn{background:transparent;color:var(--tone);border:1px solid var(--dim)}
[data-night] .sg button.on,[data-night] .days button.on{background:var(--dim);color:var(--tone);border-color:var(--dim)}
[data-night] .ring{background:rgba(0,0,0,.88)}
.dh{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.dh h2{margin:0;font-size:1.1rem}
.sets{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
.grp{border:1px solid var(--line);border-radius:16px;padding:12px;display:grid;gap:10px;align-content:start}
.grp.wide{grid-column:1/-1}
.grp h3{margin:0;font-size:.98rem}
.f{display:flex;justify-content:space-between;align-items:center;gap:8px;font-size:.88rem;flex-wrap:wrap}
.sg{display:flex;border:1px solid var(--line);border-radius:999px;overflow:hidden}
.sg button{border:0;background:transparent;font-size:.82rem;padding:5px 11px;cursor:pointer}
.sg button.on{background:var(--brass);color:#1a1206;font-weight:700}
.btn{border:0;background:var(--brass);color:#1a1206;font-weight:700;border-radius:999px;padding:8px 16px;cursor:pointer}
.btn.alt{background:transparent;color:var(--txt);border:1px solid var(--line)}
.ib{border:0;background:transparent;font-size:1.25rem;cursor:pointer;padding:4px}
.al{display:grid;gap:8px;margin:0;padding:0;list-style:none}
.al li{display:flex;align-items:center;gap:10px;border:1px solid var(--line);border-radius:14px;padding:6px 10px}
.al li.off .at{opacity:.4}
.at{font-family:Orbitron,monospace;font-weight:700;font-size:1rem;white-space:nowrap}
.am{flex:1;min-width:0;font-size:.8rem;color:var(--mute);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.add{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
input[type=time],input[type=text]{font:inherit;background:var(--bg);color:var(--txt);border:1px solid var(--line);border-radius:10px;padding:6px 8px}
input[type=text]{flex:1;min-width:120px}
.days{display:flex;gap:4px}
.days button{width:30px;height:30px;border-radius:50%;border:1px solid var(--line);background:transparent;font-weight:700;font-size:.75rem;cursor:pointer}
.days button.on{background:var(--brass);color:#1a1206;border-color:var(--brass)}
.fine{margin:0;font-size:.76rem;color:var(--mute)}
.ring{position:fixed;top:0;right:0;bottom:0;left:0;display:none;place-items:center;background:rgba(10,8,6,.72);backdrop-filter:blur(6px);z-index:9}
.ring.on{display:grid}
.rc{background:var(--panel);border:1px solid var(--line);border-radius:28px;padding:26px 32px;text-align:center;max-width:90vw}
.rc .big{font-size:4rem;animation:shake .5s infinite}
.rc h2{margin:6px 0 0;font:700 2.2rem Orbitron,monospace}
.rc p{margin:4px 0 0;color:var(--mute)}
.rb{display:flex;gap:10px;justify-content:center;margin-top:16px}
@keyframes shake{0%,100%{transform:rotate(-14deg)}50%{transform:rotate(14deg)}}
@keyframes jig{from{transform:rotate(-5deg)}to{transform:rotate(5deg)}}
.case.ringing~.bell,.wrap.ringing .bell{animation:jig .12s infinite alternate}
@media (prefers-reduced-motion:reduce){.rc .big,.wrap.ringing .bell{animation:none}.drawer{transition:none}}
</style>
</head>
<body>
<canvas id="bg" hidden></canvas>
<input type="file" id="fdir" webkitdirectory multiple hidden><input type="file" id="ffil" accept="image/*" multiple hidden><input type="file" id="fvid" accept="video/*" multiple hidden>
<div class="wrap" id="wrap">
<i class="bell l"></i><i class="bell r"></i>
<main class="case" id="case">
<div class="grid">
<section id="pA" aria-label="Analog clock">
<svg id="an" class="an" viewBox="0 0 200 200" role="img" aria-label="Analog clock">
<defs><radialGradient id="bz" cx=".35" cy=".3" r=".9"><stop offset="0" style="stop-color:var(--bz1)"/><stop offset=".6" style="stop-color:var(--bz2)"/><stop offset="1" style="stop-color:var(--bz3)"/></radialGradient></defs>
<circle cx="100" cy="100" r="99" fill="url(#bz)"/>
<circle cx="100" cy="100" r="92" style="fill:var(--face)" stroke="rgba(0,0,0,.35)" stroke-width="1.5"/>
<g id="tk" style="stroke:var(--ink)" stroke-linecap="round"></g>
<g id="nm" style="fill:var(--ink)" font-size="12.5" font-weight="700" text-anchor="middle" font-family="Figtree,sans-serif"></g>
<text id="aph" x="100" y="82" font-size="17" text-anchor="middle">☀️</text>
<text id="adt" x="100" y="140" font-size="8.5" text-anchor="middle" font-weight="700" style="fill:var(--mute)" font-family="Figtree,sans-serif"></text>
<text id="abell" font-size="12" text-anchor="middle" dominant-baseline="central" style="fill:var(--acc)">🔔</text>
<g id="hh"><line x1="100" y1="110" x2="100" y2="54" style="stroke:var(--ink)" stroke-width="5" stroke-linecap="round"/></g>
<g id="mh"><line x1="100" y1="112" x2="100" y2="28" style="stroke:var(--ink)" stroke-width="3.4" stroke-linecap="round"/></g>
<g id="sh"><line x1="100" y1="120" x2="100" y2="20" style="stroke:var(--acc)" stroke-width="1.4" stroke-linecap="round"/><circle cx="100" cy="100" r="4" style="fill:var(--acc)"/></g>
<circle cx="100" cy="100" r="1.8" style="fill:var(--face)"/>
</svg>
</section>
<div class="col" id="col">
<section id="pD" class="lcd" aria-label="Digital clock">
<div class="row">
<div class="ind"><span id="iam">AM</span><span id="ipm">PM</span><span id="ial">⏰</span></div>
<div class="seg7"><i>88:88</i><b id="dt"></b></div>
<div class="seg7 sm" id="psec"><i>88</i><b id="ds"></b></div>
</div>
<div class="lcdinfo" id="li"><span id="dd"></span><span id="na"></span></div>
</section>
<section id="pB" class="card" aria-label="Binary clock">
<div class="bin" id="bin"></div>
<p class="note">Read each column top to bottom: lit dots add up (8 + 4 + 2 + 1).</p>
</section>
</div>
</div>
<div class="tabs">
<button class="tab" id="tab" aria-expanded="false" aria-controls="drawer">⚙️ <span id="tabt">Settings</span></button>
<button class="tab" id="nightb" aria-label="Night mode" title="Night mode">🌙</button>
<button class="tab" id="fs" aria-label="Toggle fullscreen" title="Fullscreen">⛶</button>
</div>
<div class="drawer" id="drawer">
<div class="dri" id="dri">
<div class="box">
<div class="dh"><h2>⚙️ Settings</h2><button class="btn alt" id="min" aria-label="Minimize settings">Minimize ▾</button></div>
<div class="sets">
<div class="grp">
<h3>🕰️ Display</h3>
<div class="f"><span>Time format</span><div class="sg" data-k="h24"><button data-v="false">12 h</button><button data-v="true">24 h</button></div></div>
<div class="f"><span>Seconds</span><div class="sg" data-k="secs"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
<div class="f"><span>Second hand</span><div class="sg" data-k="smooth"><button data-v="true">Sweep</button><button data-v="false">Tick</button></div></div>
<div class="f"><span>Theme</span><div class="sg" data-k="theme"><button data-v="auto">🌗 Auto</button><button data-v="dark">🌙 Dark</button><button data-v="light">☀️ Light</button></div></div>
<div class="f"><span>Show clocks</span><div class="sg" id="shows"><button data-t="a">🕰️ Analog</button><button data-t="d">💡 Digital</button><button data-t="b">🔢 Binary</button></div></div>
<div class="f"><span>Show date</span><div class="sg" data-k="sd"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
<div class="f"><span>Show next alarm</span><div class="sg" data-k="sa"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
<div class="f"><span>Text color (day)</span><div class="sg" data-k="tc"><button data-v="#ffb12e" aria-label="Amber">🟠</button><button data-v="#ffffff" aria-label="White">⚪</button><button data-v="#6cc8ff" aria-label="Blue">🔵</button><button data-v="#6dff8f" aria-label="Green">🟢</button><button data-v="#c79bff" aria-label="Violet">🟣</button><button data-v="#ff5a4a" aria-label="Red">🔴</button><button data-v="#ffe14a" aria-label="Yellow">🟡</button></div></div>
<div class="f"><span>Auto-hide controls</span><div class="sg" data-k="idle"><button data-v="0">Off</button><button data-v="1">1 min</button><button data-v="5">5 min</button><button data-v="15">15 min</button></div></div>
<div class="f"><span>Clock only (transparent)</span><div class="sg" data-k="tm"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
<div class="f"><span>Digital text size</span><div class="sg" data-k="ds"><button data-v="0.7">XS</button><button data-v="1">S</button><button data-v="1.6">M</button><button data-v="2.4">L</button><button data-v="3.4">XL</button><button data-v="5">XXL</button></div></div>
<div class="f"><span>Binary dots</span><div class="sg" data-k="dot"><button data-v="red">🔴</button><button data-v="green">🟢</button><button data-v="moon">🌕</button><button data-v="heart">💖</button></div></div>
<div class="f"><span>Keep screen on</span><div class="sg" data-k="awake"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
</div>
<div class="grp">
<h3>🔊 Sound</h3>
<div class="f"><span>Tone</span><div class="sg" data-k="sound"><button data-v="bell">🔔 Bell</button><button data-v="chime">🎵 Chime</button><button data-v="buzzer">📢 Buzzer</button></div></div>
<div class="f"><span>Volume</span><input type="range" id="vol" min="0" max="1" step=".05" aria-label="Volume"></div>
<div class="f"><span>Snooze</span><div class="sg" data-k="snooze"><button data-v="5">5 min</button><button data-v="10">10 min</button><button data-v="15">15 min</button></div></div>
<div><button class="btn" id="test">▶️ Test sound</button></div>
</div>
<div class="grp">
<h3>🖼️ Slideshow</h3>
<div class="f"><span id="cnt">No pictures yet</span><span><button class="btn" id="bfd">📁 Folder</button> <button class="btn alt" id="bfi">➕ Pictures</button> <button class="btn alt" id="bvd">🎞️ Video</button></span></div>
<div class="f"><span>Remember photos</span><div class="sg" data-k="keep"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
<div class="f"><span class="fine">Remember copies photos into this browser so they return next time. Videos are never saved.</span><button class="btn alt" id="bclp">🗑️ Clear saved</button></div>
<input type="text" id="hf" placeholder="Hugging Face folder: user/space/photos" aria-label="Hugging Face folder or list file URL">
<input type="password" id="hft" placeholder="Access token (private repos only)" aria-label="Hugging Face access token">
<div class="f"><button class="btn" id="bhf">☁️ Load whole folder</button></div>
<textarea id="urls" rows="3" placeholder="Image URLs, one per line (https://… or photos/1.jpg)" aria-label="Image URLs"></textarea>
<div class="f"><span><button class="btn" id="burl">🔗 Use URLs</button> <button class="btn alt" id="brnd">🎲 Random photos</button></span><button class="btn alt" id="bclr">🗑️ Clear online</button></div>
<div class="f"><span>Slideshow</span><div class="sg" data-k="ss"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
<div class="f"><span>Change every</span><div class="sg" data-k="interval"><button data-v="5">5s</button><button data-v="10">10s</button><button data-v="30">30s</button><button data-v="60">1m</button><button data-v="300">5m</button></div></div>
<div class="f"><span>Video length</span><div class="sg" data-k="vmode"><button data-v="full">Play through</button><button data-v="timed">Same as photo</button></div></div>
<div class="f"><span>Order</span><div class="sg" data-k="shuffle"><button data-v="true">Shuffle</button><button data-v="false">In order</button></div></div>
<div class="f"><span>Picture fit</span><div class="sg" data-k="fit"><button data-v="cover">Fill</button><button data-v="contain">Fit</button></div></div>
<div class="f"><span>Transition</span><select id="tsel" aria-label="Transition"></select></div>
<div class="f"><span>Duration</span><div class="sg" data-k="tdur"><button data-v="1">1s</button><button data-v="2">2s</button><button data-v="3">3s</button></div></div>
<div class="f"><span>Dim photos</span><input type="range" id="dim" min="0" max=".7" step=".05" aria-label="Dim photos"></div>
<div class="f"><span>Clock corner</span><div class="sg" data-k="corner"><button data-v="tl">↖</button><button data-v="tr">↗</button><button data-v="bl">↙</button><button data-v="br">↘</button></div></div>
<div class="f"><span>Clock size</span><div class="sg" data-k="ws"><button data-v="280">S</button><button data-v="380">M</button><button data-v="480">L</button></div></div>
<div><button class="btn alt" id="bnext">⏭️ Next picture</button></div>
</div>
<div class="grp">
<h3>🌙 Night mode</h3>
<div class="f"><span>Night mode</span><div class="sg" data-k="night"><button data-v="true">On</button><button data-v="false">Off</button></div></div>
<div class="f"><span>Color</span><div class="sg" data-k="nc"><button data-v="red">🔴 Red</button><button data-v="brown">🟤 Brown</button><button data-v="purple">🟣 Purple</button></div></div>
<div class="f"><span>Brightness</span><input type="range" id="nb" min=".15" max="1" step=".05" aria-label="Night brightness"></div>
<p class="fine">Black background, no pictures, one dim color.</p>
</div>
<div class="grp wide">
<h3>⏰ Alarms</h3>
<ul class="al" id="al"></ul>
<div class="add">
<input type="time" id="nt" value="07:00" aria-label="Alarm time">
<input type="text" id="nl" placeholder="Label (optional)" maxlength="30" aria-label="Alarm label">
<div class="days" id="days"></div>
<button class="btn" id="addb">➕ Add alarm</button>
</div>
<p class="fine">No days selected means the alarm rings once. Alarms ring only while this page stays open, so keep the tab active and your volume up.</p>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<div class="ring" id="ring" role="alertdialog" aria-live="assertive" aria-label="Alarm ringing">
<div class="rc"><div class="big">🔔</div><h2 id="rt"></h2><p id="rl"></p>
<div class="rb"><button class="btn" id="sn"></button><button class="btn alt" id="st">⏹️ Stop</button></div></div>
</div>
<script>
const $=id=>document.getElementById(id);
const DEF={h24:false,secs:true,smooth:true,theme:'auto',dot:'red',sound:'bell',vol:.6,snooze:5,awake:false,keep:false,vmode:'full',sd:true,sa:true,tc:'#ffb12e',idle:5,hf:'',hft:'',tm:false,ds:1,urls:[],ss:true,interval:10,shuffle:true,fit:'cover',trans:'random',tdur:2,dim:.2,corner:'br',ws:380,night:false,nc:'red',nb:.6,show:{a:true,b:true,d:true},alarms:[{id:1,t:'07:00',label:'Wake up',on:false,days:[1,2,3,4,5]}]};
let S=JSON.parse(JSON.stringify(DEF));
try{const o=JSON.parse(localStorage.getItem('clock4')||'{}');Object.assign(S,o);S.show=Object.assign({},DEF.show,o.show)}catch(e){}
const save=()=>{try{localStorage.setItem('clock4',JSON.stringify(S))}catch(e){}};
const DOTS={red:['🔴','⚫'],green:['🟢','⚫'],moon:['🌕','🌑'],heart:['💖','🖤']};
const DF=['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],DN=['S','M','T','W','T','F','S'];
const pad=n=>String(n).padStart(2,'0');
const esc=s=>s.replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));
const parse=v=>v==='true'?true:v==='false'?false:(isNaN(v)?v:+v);
const fmt=t=>{const[h,m]=t.split(':').map(Number);return S.h24?pad(h)+':'+pad(m):((h%12)||12)+':'+pad(m)+(h<12?' AM':' PM')};
const phase=h=>h>=5&&h<8?'🌅':h>=8&&h<17?'☀️':h>=17&&h<20?'🌇':'🌙';
const T0=document.title;
/* analog face */
let tk='',nm='';
for(let i=0;i<60;i++){const a=i*Math.PI/30,s=Math.sin(a),c=-Math.cos(a),r2=i%5?84.5:80;tk+=`<line x1="${100+88*s}" y1="${100+88*c}" x2="${100+r2*s}" y2="${100+r2*c}" stroke-width="${i%5?.8:2.2}"/>`}
for(let i=1;i<=12;i++){const a=i*Math.PI/6;nm+=`<text x="${100+70*Math.sin(a)}" y="${100-70*Math.cos(a)+4.5}">${i}</text>`}
$('tk').innerHTML=tk;$('nm').innerHTML=nm;
/* binary face */
$('bin').innerHTML=`<div class="wl"><span class="t"></span>${[8,4,2,1].map(w=>`<span class="r">${w}</span>`).join('')}<span class="d"></span></div>`+
[['H',2],['H',4],['M',3],['M',4],['S',3],['S',4]].map((c,i)=>`<div class="bc${i==2||i==4?' pair':''}${i>3?' sc':''}"><span class="h">${c[0]}</span>${[8,4,2,1].map(w=>`<span class="b" data-w="${w}"${w>(1<<(c[1]-1))?' data-x="1"':''}></span>`).join('')}<span class="d"></span></div>`).join('');
const cols=[...document.querySelectorAll('.bc')];
/* alarms */
let fired={},ringing=null,snoozeAt=0,snoozeA=null,rt,newDays=[];
function nextOcc(a,n){const[h,m]=a.t.split(':').map(Number);for(let d=0;d<8;d++){const c=new Date(n);c.setDate(n.getDate()+d);c.setHours(h,m,0,0);if(c>n&&(!a.days.length||a.days.includes(c.getDay())))return c}return null}
function nextAlarm(n){let b=null;S.alarms.forEach(a=>{if(!a.on)return;const c=nextOcc(a,n);if(c&&(!b||c<b.c))b={a,c}});return b}
function rem(ms){const m=Math.ceil(ms/60000),d=Math.floor(m/1440),h=Math.floor(m%1440/60);return d?`${d}d ${h}h`:h?`${h}h ${m%60}m`:`${m}m`}
const daysTxt=a=>!a.days.length?'Once':a.days.length==7?'Every day':a.days.map(d=>DF[d]).join(' ');
function renderAlarms(){
$('al').innerHTML=S.alarms.length?S.alarms.map(a=>`<li class="${a.on?'':'off'}"><button class="ib" data-tg="${a.id}" aria-label="Toggle alarm">${a.on?'🔔':'🔕'}</button><span class="at">${fmt(a.t)}</span><span class="am">${esc(a.label||'Alarm')} · ${daysTxt(a)}</span><button class="ib" data-del="${a.id}" aria-label="Delete alarm">🗑️</button></li>`).join(''):'<li><span class="am">No alarms yet. Add one below.</span></li>'}
$('al').onclick=e=>{const b=e.target.closest('button');if(!b)return;
if(b.dataset.tg){const a=S.alarms.find(x=>x.id==b.dataset.tg);a.on=!a.on}
if(b.dataset.del)S.alarms=S.alarms.filter(x=>x.id!=b.dataset.del);
save();renderAlarms();dirty=true;logic()};
$('days').innerHTML=DN.map((d,i)=>`<button type="button" data-d="${i}" aria-label="${DF[i]}" title="${DF[i]}">${d}</button>`).join('');
$('days').onclick=e=>{const b=e.target.closest('button');if(!b)return;const d=+b.dataset.d;newDays=newDays.includes(d)?newDays.filter(x=>x!=d):[...newDays,d];b.classList.toggle('on',newDays.includes(d))};
$('addb').onclick=()=>{S.alarms.push({id:Date.now(),t:$('nt').value||'07:00',label:$('nl').value.trim().slice(0,30),on:true,days:[...newDays].sort()});$('nl').value='';save();renderAlarms();dirty=true;logic()};
/* sound */
let ac;
function unlock(){try{ac=ac||new(window.AudioContext||window.webkitAudioContext)();if(ac.state==='suspended')ac.resume()}catch(e){}}
document.addEventListener('pointerdown',unlock,{once:true});
function tone(f,t0,d,type,g){const o=ac.createOscillator(),v=ac.createGain();o.type=type;o.frequency.value=f;v.gain.setValueAtTime(.0001,t0);v.gain.exponentialRampToValueAtTime(Math.max(.0002,g*S.vol),t0+.01);v.gain.exponentialRampToValueAtTime(.0001,t0+d);o.connect(v);v.connect(ac.destination);o.start(t0);o.stop(t0+d+.05)}
function play(){if(!ac)return;const t=ac.currentTime;
if(S.sound==='bell')for(let i=0;i<8;i++){tone(1760,t+i*.09,.12,'triangle',.5);tone(2637,t+i*.09,.08,'sine',.2)}
else if(S.sound==='chime')[659,784,988,1319].forEach((f,i)=>tone(f,t+i*.2,.7,'sine',.6));
else for(let i=0;i<3;i++)tone(440,t+i*.28,.18,'square',.25)}
$('test').onclick=()=>{unlock();play()};
$('vol').oninput=e=>{S.vol=+e.target.value;save()};
/* ringing */
function startRing(a){ringing=a;$('rt').textContent=fmt(a.t);$('rl').textContent=a.label||'Alarm';$('ring').classList.add('on');$('wrap').classList.add('ringing');document.title='⏰ '+(a.label||'Alarm');unlock();play();clearInterval(rt);rt=setInterval(play,1800);try{navigator.vibrate([400,200,400])}catch(e){}
if(!a.days.length){a.on=false;save();renderAlarms()}}
function stopRing(sn){clearInterval(rt);$('ring').classList.remove('on');$('wrap').classList.remove('ringing');document.title=T0;if(sn){snoozeAt=Date.now()+S.snooze*60000;snoozeA=ringing}ringing=null;try{navigator.vibrate(0)}catch(e){}dirty=true;logic()}
$('sn').onclick=()=>stopRing(true);$('st').onclick=()=>stopRing(false);
/* settings */
let wl;
async function awake(){try{if(S.awake&&'wakeLock' in navigator&&!wl&&document.visibilityState==='visible'){wl=await navigator.wakeLock.request('screen');wl.addEventListener('release',()=>wl=null)}else if(!S.awake&&wl){await wl.release();wl=null}}catch(e){}}
document.addEventListener('visibilitychange',awake);
function sync(){
const r=document.documentElement;S.theme==='auto'?r.removeAttribute('data-theme'):r.setAttribute('data-theme',S.theme);
document.querySelectorAll('.sg[data-k]').forEach(g=>g.querySelectorAll('button').forEach(b=>b.classList.toggle('on',String(S[g.dataset.k])===b.dataset.v)));
$('shows').querySelectorAll('button').forEach(b=>b.classList.toggle('on',S.show[b.dataset.t]));
$('pA').hidden=!S.show.a;$('pD').hidden=!S.show.d&&!S.tm;$('pB').hidden=!S.show.b;$('col').hidden=!S.show.d&&!S.show.b&&!S.tm;
$('psec').hidden=!S.secs;$('sh').style.display=S.secs?'':'none';cols.slice(4).forEach(c=>c.hidden=!S.secs);
$('an').classList.toggle('tick',!S.smooth);$('vol').value=S.vol;$('dim').value=S.dim;$('nb').value=S.nb;$('tsel').value=S.trans;$('sn').textContent='💤 Snooze '+S.snooze+' min';document.documentElement.style.setProperty('--ds',S.ds);document.documentElement.style.setProperty('--tc',S.tc);$('dd').hidden=!S.sd;$('na').hidden=!S.sa;$('li').hidden=!S.sd&&!S.sa;$('li').style.justifyContent=S.sd&&S.sa?'':'center';ssSync()}
function apply(){save();sync();renderAlarms();dirty=true;logic();awake()}
document.querySelectorAll('.sg[data-k]').forEach(g=>g.onclick=e=>{const b=e.target.closest('button');if(!b)return;S[g.dataset.k]=parse(b.dataset.v);apply()});
$('shows').onclick=e=>{const b=e.target.closest('button');if(!b)return;S.show[b.dataset.t]=!S.show[b.dataset.t];apply()};
function toggle(){const o=!$('drawer').classList.contains('open');$('drawer').classList.toggle('open',o);$('tab').setAttribute('aria-expanded',o);$('tabt').textContent=o?'Hide settings':'Settings'}
$('tab').onclick=toggle;$('min').onclick=()=>{toggle();$('tab').focus()};document.body.append($('drawer'));$('nightb').onclick=()=>{S.night=!S.night;apply()};
$('fs').onclick=()=>{try{document.fullscreenElement?document.exitFullscreen():document.documentElement.requestFullscreen()}catch(e){}};
/* clock loop */
let lastS=-1,dirty=true;
function logic(){
const n=new Date(),sec=n.getSeconds();if(sec===lastS&&!dirty)return;lastS=sec;dirty=false;
const H=n.getHours(),M=n.getMinutes(),h=S.h24?H:(H%12)||12;
$('dt').textContent=pad(h)+':'+pad(M);$('ds').textContent=pad(sec);
$('iam').className=!S.h24&&H<12?'on':'';$('ipm').className=!S.h24&&H>=12?'on':'';$('ial').className=S.alarms.some(a=>a.on)?'on':'';$('ial').textContent=S.night?'ALM':'⏰';
$('dd').textContent=`${em(phase(H))} ${DF[n.getDay()]} ${n.toLocaleDateString(undefined,{month:'short',day:'numeric'})}`;
const na=nextAlarm(n);
$('na').textContent=na?`${em('🔔 ')}${fmt(na.a.t)} (${rem(na.c-n)})`:em('🔕 ')+'No alarm';
$('aph').textContent=em(phase(H));$('adt').textContent=`${DF[n.getDay()]} ${n.getDate()}`;
const ab=$('abell');if(na){const a=((na.c.getHours()%12)+na.c.getMinutes()/60)*Math.PI/6;ab.setAttribute('x',100+57*Math.sin(a));ab.setAttribute('y',100-57*Math.cos(a));ab.textContent=S.night?'▲':'🔔';ab.style.display=''}else ab.style.display='none';
const dg=[Math.floor(h/10),h%10,Math.floor(M/10),M%10,Math.floor(sec/10),sec%10],D=S.night?['●','○']:(DOTS[S.dot]||DOTS.red);
cols.forEach((c,i)=>{c.querySelector('.d').textContent=dg[i];c.querySelectorAll('.b').forEach(b=>{const on=dg[i]&(+b.dataset.w);b.textContent=on?D[0]:D[1];b.className='b '+(on?'on':'off')+(b.dataset.x?' x':'')})});
const key=n.toDateString()+H+':'+M;
if(!ringing){
S.alarms.forEach(a=>{if(!ringing&&a.on&&a.t===pad(H)+':'+pad(M)&&fired[a.id]!==key&&(!a.days.length||a.days.includes(n.getDay()))){fired[a.id]=key;startRing(a)}});
if(!ringing&&snoozeAt&&Date.now()>=snoozeAt){snoozeAt=0;startRing(snoozeA)}}
}
function frame(){const n=new Date(),t=n.getHours()*3600+n.getMinutes()*60+n.getSeconds()+(S.smooth?n.getMilliseconds()/1000:0);
$('sh').style.transform=`rotate(${t*6}deg)`;$('mh').style.transform=`rotate(${t/10}deg)`;$('hh').style.transform=`rotate(${t/120}deg)`;requestAnimationFrame(frame)}
/* slideshow */
const bg=$('bg'),c=bg.getContext('2d'),tmp=document.createElement('canvas'),tx=tmp.getContext('2d');
const NP={red:['#c0392b','#4a1511'],brown:['#9c6b43','#3a2516'],purple:['#8b5fbf','#2c1a47']};
const em=x=>S.night?'':x,sm=p=>p*p*(3-2*p),cl=(v,a=0,b=1)=>Math.min(b,Math.max(a,v)),TAU=6.2832,Q=1.5708;
let W=1,H=1,files=[],cur=null,nxt=null,tn='',idx=-1,order=[],timer,anim,running=false,R=[];
function put(g,im,dx=0,dy=0,s=1,a=1,r=0){if(!im)return;const iw=im.videoWidth||im.width,ih=im.videoHeight||im.height;if(!iw)return;const k=(S.fit==='cover'?Math.max:Math.min)(W/iw,H/ih);g.save();g.globalAlpha=a;g.translate(W/2+dx,H/2+dy);g.rotate(r);g.scale(s,s);g.drawImage(im,-iw*k/2,-ih*k/2,iw*k,ih*k);g.restore()}
const A=(...a)=>put(c,cur,...a),B=(...a)=>put(c,nxt,...a),rc=(x,y,w,h)=>c.rect(x,y,w,h);
const K=f=>{c.save();c.beginPath();f();c.clip();B();c.restore()};
const gr=(n,m,f)=>{for(let j=0;j<m;j++)for(let i=0;i<n;i++)f(i,j,W/n,H/m,j*n+i)};
const scr=(fill,al)=>{c.save();c.globalCompositeOperation='screen';c.globalAlpha=al;c.fillStyle=fill;c.fillRect(0,0,W,H);c.restore()};
const SP=(()=>{let o=[],t=0,b=5,l=0,r=7;while(t<=b&&l<=r){for(let i=l;i<=r;i++)o.push([i,t]);t++;for(let j=t;j<=b;j++)o.push([r,j]);r--;if(t<=b){for(let i=r;i>=l;i--)o.push([i,b]);b--}if(l<=r){for(let j=b;j>=t;j--)o.push([l,j]);l++}}const m=[];o.forEach((p,k)=>m[p[1]*8+p[0]]=k);return m})();
const CL={
'Wipe':e=>rc(0,0,W*e,H),
'Band Wipe':e=>gr(1,8,(i,j,w,h)=>j%2?rc(W-W*e,j*h,W*e,h+1):rc(0,j*h,W*e,h+1)),
'Barn Doors':e=>rc(W/2*(1-e),0,W*e,H),
'Checker Wipe':e=>gr(8,6,(i,j,w,h)=>(i+j)%2?rc(i*w+w*(1-e),j*h,w*e+1,h+1):rc(i*w,j*h,w*e+1,h+1)),
'CheckerBoard':e=>gr(8,6,(i,j,w,h)=>{const s=cl(e*2-((i+j)%2));rc(i*w+w*(1-s)/2,j*h+h*(1-s)/2,w*s+1,h*s+1)}),
'Clock Wipe':e=>{c.moveTo(W/2,H/2);c.arc(W/2,H/2,Math.hypot(W,H)/2,-Q,-Q+TAU*e);c.closePath()},
'Inset':e=>rc(0,0,W*e,H*e),
'Pinwheel':e=>{for(let k=0;k<4;k++){c.moveTo(W/2,H/2);c.arc(W/2,H/2,Math.hypot(W,H)/2,k*Q,k*Q+Q*e);c.closePath()}},
'Radial Wipe':e=>{c.moveTo(0,0);c.arc(0,0,Math.hypot(W,H),0,Q*e);c.closePath()},
'Wedge Wipe':e=>{c.moveTo(W/2,H/2);c.arc(W/2,H/2,Math.hypot(W,H)/2,-Q-Math.PI*e,-Q+Math.PI*e);c.closePath()},
'Venetian Blinds':e=>gr(1,10,(i,j,w,h)=>rc(0,j*h,W,h*e+1)),
'Zig-Zag Blocks':e=>gr(8,6,(i,j,w,h)=>{const s=cl(e*1.8-(j%2?7-i:i)*.1);rc(j%2?i*w+w*(1-s):i*w,j*h,w*s+1,h+1)}),
'Random Blocks':e=>gr(12,8,(i,j,w,h,n)=>R[n]<e&&rc(i*w,j*h,w+1,h+1)),
'VR Random Blocks':e=>gr(24,14,(i,j,w,h,n)=>R[n]<e&&rc(i*w,j*h,w+1,h+1)),
'Non-Additive Dissolve':e=>gr(48,27,(i,j,w,h,n)=>R[n]<e&&rc(i*w,j*h,w+1,h+1)),
'Random Wipe':e=>gr(48,1,(i,j,w,h,n)=>rc(i*w,0,w+1,H*cl((e-R[n]*.5)*2))),
'Spiral Boxes':e=>gr(8,6,(i,j,w,h,n)=>SP[n]/48<e*1.05&&rc(i*w,j*h,w+1,h+1)),
'Paint Splatter':e=>R.slice(0,40).forEach((v,k)=>{const x=R[k+40]*W,y=R[k+80]*H,r=cl(e*1.6-v*.6)*Math.hypot(W,H)*.2;c.moveTo(x+r,y);c.arc(x,y,r,0,TAU)}),
'Iris Box':e=>rc(W/2-W*e/2,H/2-H*e/2,W*e,H*e),
'Iris Cross':e=>{rc(W/2-W*e/2,H/2-H*e*e/2,W*e,H*e*e);rc(W/2-W*e*e/2,H/2-H*e/2,W*e*e,H*e)},
'Iris Diamond':e=>{c.moveTo(W/2-W*e,H/2);c.lineTo(W/2,H/2-H*e);c.lineTo(W/2+W*e,H/2);c.lineTo(W/2,H/2+H*e);c.closePath()},
'Iris Round':e=>c.arc(W/2,H/2,Math.hypot(W,H)/2*e,0,TAU)};
const dip=col=>p=>{p<.5?A():B();c.fillStyle=`rgba(${col},${p<.5?p*2:2-2*p})`;c.fillRect(0,0,W,H)};
const F={
'Cross Dissolve':p=>{A();B(0,0,1,p)},
'Additive Dissolve':p=>{A(0,0,1,cl(2-2*p));c.globalCompositeOperation='lighter';B(0,0,1,cl(2*p));c.globalCompositeOperation='source-over'},
'Dip To Black':dip('0,0,0'),'Dip To White':dip('255,255,255'),
'Film Dissolve':p=>{A();B(0,0,1,sm(p));c.fillStyle=`rgba(255,240,220,${.18*Math.sin(Math.PI*p)})`;c.fillRect(0,0,W,H)},
'Morph Cut':p=>{A(0,0,1+.05*p);B(0,0,1.05-.05*p,p)},
'VR Chroma Leaks':p=>{A();B(0,0,1,p);const g=c.createLinearGradient(W*p-W*.4,0,W*p+W*.4,H*.3);g.addColorStop(0,'rgba(255,0,90,0)');g.addColorStop(.35,'rgba(255,0,90,.6)');g.addColorStop(.65,'rgba(0,200,255,.6)');g.addColorStop(1,'rgba(0,200,255,0)');scr(g,Math.sin(Math.PI*p))},
'VR Gradient Wipe':p=>{A();tx.clearRect(0,0,W,H);put(tx,nxt);tx.globalCompositeOperation='destination-in';const g=tx.createLinearGradient(0,0,W,H),s=p*1.5;g.addColorStop(cl(s-.5),'rgba(0,0,0,1)');g.addColorStop(cl(s),'rgba(0,0,0,0)');tx.fillStyle=g;tx.fillRect(0,0,W,H);tx.globalCompositeOperation='source-over';c.drawImage(tmp,0,0)},
'VR Iris Wipe':p=>{A();const r=Math.hypot(W,H)/2*sm(p);K(()=>c.arc(W/2,H/2,r,0,TAU));c.save();c.strokeStyle='rgba(255,255,255,.7)';c.lineWidth=6;c.shadowColor='#fff';c.shadowBlur=30;c.beginPath();c.arc(W/2,H/2,r,0,TAU);c.stroke();c.restore()},
'VR Light Leaks':p=>{A();B(0,0,1,p);const x=W*(.2+.6*p),g=c.createRadialGradient(x,H*.4,0,x,H*.4,W*.6);g.addColorStop(0,'rgba(255,170,60,1)');g.addColorStop(.5,'rgba(255,80,40,.5)');g.addColorStop(1,'rgba(255,80,40,0)');scr(g,Math.sin(Math.PI*p))},
'VR Light Rays':p=>{A();B(0,0,1,p);c.save();c.globalCompositeOperation='screen';c.globalAlpha=Math.sin(Math.PI*p)*.5;c.fillStyle='#fff';for(let k=0;k<9;k++){const a=k*.45+p;c.beginPath();c.moveTo(W*.8,0);c.lineTo(W*.8+Math.cos(a+.9)*W*2,Math.sin(a+.9)*W*2);c.lineTo(W*.8+Math.cos(a+1)*W*2,Math.sin(a+1)*W*2);c.fill()}c.restore()},
'VR Mobius Zoom':p=>{const e=sm(p);A(0,0,1+1.2*e,1,e*.6);B(0,0,2-e,p,(e-1)*.6)},
'VR Spherical Blur':p=>{const s=Math.sin(Math.PI*p);c.filter=`blur(${s*28}px)`;A();B(0,0,1+.06*s,p);c.filter='none'},
'Page Peel':p=>{const t=(W+H)*(1-sm(p));B();c.save();c.beginPath();c.moveTo(0,0);c.lineTo(t,0);c.lineTo(0,t);c.closePath();c.clip();A();c.restore();const w=Math.min(W,H)*.12*Math.sin(Math.PI*sm(p))+2,g=c.createLinearGradient(t/2,t/2,t/2+w*.7,t/2+w*.7);g.addColorStop(0,'rgba(255,255,255,.85)');g.addColorStop(1,'rgba(0,0,0,0)');c.save();c.beginPath();c.moveTo(t,0);c.lineTo(t+w,0);c.lineTo(0,t+w);c.lineTo(0,t);c.closePath();c.fillStyle=g;c.fill();c.restore()},
'Page Turn':p=>{const e=sm(p),x=W*(1-e),w=W*.1*Math.sin(Math.PI*e)+2,g=c.createLinearGradient(x,0,x+w,0);B();c.save();c.beginPath();rc(0,0,x,H);c.clip();A();c.restore();g.addColorStop(0,'rgba(255,255,255,.8)');g.addColorStop(.2,'rgba(0,0,0,.45)');g.addColorStop(1,'rgba(0,0,0,0)');c.fillStyle=g;c.fillRect(x,0,w,H)},
'Slide':p=>{A();B(W*(1-sm(p)))},
'Push':p=>{const e=sm(p);A(-W*e);B(W*(1-e))},
'Whip':p=>{const e=p*p*p*(p*(6*p-15)+10);c.filter=`blur(${Math.sin(Math.PI*p)*22}px)`;A(-W*e);B(W*(1-e));c.filter='none'},
'Band Slide':p=>{A();const e=sm(p);gr(1,8,(i,j,w,h)=>{c.save();c.beginPath();rc(0,j*h,W,h+1);c.clip();B((j%2?-1:1)*W*(1-e));c.restore()})},
'Split':p=>{const e=sm(p);B();[[-1,0],[1,W/2]].forEach(([d,x])=>{c.save();c.beginPath();rc(x+d*W/2*e,0,W/2,H);c.clip();A(d*W/2*e);c.restore()})},
'Center Split':p=>{const e=sm(p);B();[[-1,0],[1,H/2]].forEach(([d,y])=>{c.save();c.beginPath();rc(0,y+d*H/2*e,W,H/2);c.clip();A(0,d*H/2*e);c.restore()})},
'Cross Zoom':p=>{const e=sm(p);A(0,0,1+.8*e);B(0,0,1.8-.8*e,p)}};
const CAT={'Dissolve':['Additive Dissolve','Cross Dissolve','Dip To Black','Dip To White','Film Dissolve','Morph Cut','Non-Additive Dissolve'],'Immersive Video':['VR Chroma Leaks','VR Gradient Wipe','VR Iris Wipe','VR Light Leaks','VR Light Rays','VR Mobius Zoom','VR Random Blocks','VR Spherical Blur'],'Iris':['Iris Box','Iris Cross','Iris Diamond','Iris Round'],'Page Peel':['Page Peel','Page Turn'],'Slide':['Band Slide','Center Split','Push','Slide','Split','Whip'],'Wipe':['Band Wipe','Barn Doors','Checker Wipe','CheckerBoard','Clock Wipe','Inset','Paint Splatter','Pinwheel','Radial Wipe','Random Blocks','Random Wipe','Spiral Boxes','Venetian Blinds','Wedge Wipe','Wipe','Zig-Zag Blocks'],'Zoom':['Cross Zoom']};
const ALL=Object.values(CAT).flat();
if(!ALL.includes(S.trans))S.trans='random';
$('tsel').innerHTML='<option value="random">🎲 Random (all)</option>'+Object.entries(CAT).map(([k,v])=>`<optgroup label="${k}">${v.map(n=>`<option>${n}</option>`).join('')}</optgroup>`).join('');
$('tsel').onchange=e=>{S.trans=e.target.value;save()};
function draw(p){c.fillStyle='#000';c.fillRect(0,0,W,H);if(nxt&&tn){const e=sm(p);if(CL[tn]){A();K(()=>CL[tn](e))}else F[tn](p)}else A();if(S.dim){c.fillStyle=`rgba(0,0,0,${S.dim})`;c.fillRect(0,0,W,H)}}
function size(){const d=Math.min(devicePixelRatio||1,2);W=bg.width=tmp.width=Math.round(innerWidth*d);H=bg.height=tmp.height=Math.round(innerHeight*d)}
function pick(){if(idx<0||idx>=order.length-1){order=[...files.keys()];if(S.shuffle)for(let i=order.length-1;i>0;i--){const j=Math.random()*(i+1)|0;[order[i],order[j]]=[order[j],order[i]]}idx=0}else idx++;return order[idx]}
const VX=/\.(mp4|webm|m4v|mov|ogv)(\?|#|$)/i,isV=it=>typeof it==='string'?VX.test(it):((it.type||'').startsWith('video/')||VX.test(it.name||'')),vp=v=>{try{v.play().catch(()=>{})}catch(e){}};
const rel=o=>{if(!o)return;if(o.close)o.close();else if(o.tagName==='VIDEO'){o.pause();o._u&&URL.revokeObjectURL(o._u);o.removeAttribute('src');o.load()}};
const vid=it=>new Promise((ok,no)=>{const v=document.createElement('video');v.muted=true;v.playsInline=true;v.setAttribute('playsinline','');v.preload='auto';v.onloadeddata=()=>ok(v);v.onerror=no;v._u=typeof it==='string'?'':URL.createObjectURL(it);v.src=typeof it==='string'?it:v._u});
function schedule(){clearTimeout(timer);let ms=S.interval*1000;if(cur&&cur.tagName==='VIDEO'){if(S.vmode==='full'&&isFinite(cur.duration)){cur.loop=false;ms=Math.max(500,(cur.duration-cur.currentTime)*1000+300)}else cur.loop=true}timer=setTimeout(advance,ms)}
const grab=it=>isV(it)?vid(it):typeof it!=='string'?createImageBitmap(it):S.hft&&/huggingface\.co/.test(it)?fetch(it,{headers:hfTok()}).then(r=>{if(!r.ok)throw 0;return r.blob()}).then(b=>createImageBitmap(b)):new Promise((ok,no)=>{const im=new Image();im.referrerPolicy='no-referrer';im.onload=()=>ok(im);im.onerror=no;im.src=it==='picsum'?`https://picsum.photos/1920/1080?random=${Date.now()}`:it});
async function advance(){if(!running||!files.length)return;clearTimeout(timer);const bm=await grab(files[pick()]).catch(()=>null);if(!running)return;if(!bm){timer=setTimeout(advance,2000);return}
if(!cur){cur=bm;bm.tagName==='VIDEO'&&vp(bm);draw(1);schedule();return}
tn=S.trans==='random'?ALL[Math.random()*ALL.length|0]:S.trans;nxt=bm;bm.tagName==='VIDEO'&&vp(bm);R=Array.from({length:1600},Math.random);
const d=S.tdur*1000,t0=performance.now(),f=now=>{const p=cl((now-t0)/d);draw(p);if(p<1)anim=requestAnimationFrame(f);else{rel(cur);cur=nxt;nxt=null;draw(1);schedule()}};f(t0)}
function ssSync(){const on=!!(S.ss&&files.length&&!S.night),b=document.body,r=document.documentElement,P=NP[S.nc]||NP.red;
b.classList.toggle('ss',on&&!S.tm);b.classList.toggle('tm',!!S.tm);['tl','tr','bl','br'].forEach(k=>b.classList.toggle('c-'+k,k===S.corner));b.style.setProperty('--ww',S.ws+'px');bg.hidden=!on;
r.toggleAttribute('data-night',S.night);r.style.setProperty('--tone',P[0]);r.style.setProperty('--dim',P[1]);r.style.setProperty('--nb',S.nb);
if(on&&!running){running=true;size();advance()}else if(!on&&running){running=false;clearTimeout(timer);cancelAnimationFrame(anim)}}
const load=async e=>{let fl=[...e.target.files].filter(f=>f.type.startsWith('image/')||f.type.startsWith('video/')||/\.(jpe?g|png|webp|gif|avif|bmp|mp4|webm|m4v|mov|ogv)$/i.test(f.name));if(!fl.length)return;if(S.keep){const o=[];for(const f of fl){$('cnt').textContent='Saving '+(o.length+1)+'/'+fl.length;const b=isV(f)?f:await shrink(f);isV(b)||await idbPut(b).catch(()=>{});o.push(b)}fl=o}
files=files.concat(fl);idx=-1;order=[];rel(cur);cur=nxt=null;running=false;clearTimeout(timer);cancelAnimationFrame(anim);
cnt();S.ss=true;e.target.value='';apply()};
$('fdir').onchange=$('ffil').onchange=$('fvid').onchange=load;$('bvd').onclick=()=>$('fvid').click();$('bfd').onclick=()=>$('fdir').click();$('bfi').onclick=()=>$('ffil').click();
$('bnext').onclick=()=>{if(!running)return;cancelAnimationFrame(anim);if(nxt){cur=nxt;nxt=null}advance()};
$('dim').oninput=e=>{S.dim=+e.target.value;save();cur&&!nxt&&draw(1)};
$('nb').oninput=e=>{S.nb=+e.target.value;save();document.documentElement.style.setProperty('--nb',S.nb)};
addEventListener('resize',()=>{size();cur&&!nxt&&draw(1)});
const cnt=()=>{const u=S.urls.length,l=files.length-u;$('cnt').textContent=!files.length?'No pictures yet':(l?l+' local':'')+(l&&u?' + ':'')+(u?(S.urls[0]==='picsum'?'random online':u+' online'):'')};
const setU=(u,k)=>{if(!k){S.hf='';$('hf').value=''}S.urls=u;files=[...u,...files.filter(f=>typeof f!=='string')];idx=-1;order=[];rel(cur);cur=nxt=null;running=false;clearTimeout(timer);cancelAnimationFrame(anim);S.ss=true;cnt();apply()};
$('burl').onclick=()=>setU($('urls').value.split(/\n+/).map(s=>s.trim()).filter(Boolean).slice(0,500));
$('brnd').onclick=()=>setU(['picsum']);
$('bclr').onclick=()=>{$('urls').value='';setU([])};
let db;
const idb=()=>new Promise((ok,no)=>{if(db)return ok(db);const r=indexedDB.open('clock4',1);r.onupgradeneeded=()=>r.result.createObjectStore('pics',{autoIncrement:true});r.onsuccess=()=>ok(db=r.result);r.onerror=no});
const idbOp=(mode,f)=>idb().then(d=>new Promise((ok,no)=>{const t=d.transaction('pics',mode),r=f(t.objectStore('pics'));t.oncomplete=()=>ok(r.result);t.onerror=no}));
const idbPut=b=>idbOp('readwrite',s=>s.add(b)),idbAll=()=>idbOp('readonly',s=>s.getAll()),idbClear=()=>idbOp('readwrite',s=>s.clear());
async function shrink(f){try{const b=await createImageBitmap(f),m=Math.max(b.width,b.height);if(m<=2560){b.close&&b.close();return f}const k=2560/m,cv=document.createElement('canvas');cv.width=Math.round(b.width*k);cv.height=Math.round(b.height*k);cv.getContext('2d').drawImage(b,0,0,cv.width,cv.height);b.close&&b.close();return await new Promise(ok=>cv.toBlob(x=>ok(x||f),'image/jpeg',.9))}catch(e){return f}}
$('bclp').onclick=()=>{idbClear().catch(()=>{});files=files.filter(f=>typeof f==='string');idx=-1;order=[];rel(cur);cur=nxt=null;running=false;clearTimeout(timer);cancelAnimationFrame(anim);cnt();apply()};
try{navigator.storage&&navigator.storage.persist&&navigator.storage.persist()}catch(e){}
const hfTok=()=>S.hft?{Authorization:'Bearer '+S.hft}:{};
async function hfList(q){q=q.trim().replace(/^https?:\/\/huggingface\.co\//,'').replace(/^\/+|\/+$/g,'');
if(/^https?:\/\//i.test(q)){const t=await(await fetch(q)).text();return t.split(/\s*\n\s*/).filter(Boolean)}
const p=q.split('/');let kind='spaces';if(['spaces','datasets','models'].includes(p[0]))kind=p.shift();
const[u,n,...f]=p;if(!u||!n)throw 0;const path=f.join('/').replace(/^(tree|blob|resolve)\/[^/]+\/?/,''),pre=kind==='models'?'':kind+'/';
const r=await fetch(`https://huggingface.co/api/${kind}/${u}/${n}/tree/main${path?'/'+path:''}?limit=1000`,{headers:hfTok()});if(!r.ok)throw 0;
return(await r.json()).filter(x=>x.type==='file'&&/\.(jpe?g|png|webp|gif|avif|bmp)$/i.test(x.path)).map(x=>`https://huggingface.co/${pre}${u}/${n}/resolve/main/${x.path}`)}
const hfGo=async()=>{const q=$('hf').value.trim();if(!q)return;S.hf=q;S.hft=$('hft').value.trim();$('cnt').textContent='Loading…';try{const l=await hfList(q);if(!l.length)throw 0;setU(l,1)}catch(e){cnt();$('cnt').textContent='Could not load that folder'}};
$('bhf').onclick=hfGo;
const qp=new URLSearchParams(location.search).get('hf');if(qp){S.hf=qp;save()}
files=[...S.urls];$('urls').value=S.urls[0]==='picsum'||S.hf?'':S.urls.join('\n');$('hf').value=S.hf;$('hft').value=S.hft;cnt();
if(S.hf)hfList(S.hf).then(l=>l.length&&setU(l,1)).catch(()=>{});
S.keep&&idbAll().then(a=>{if(!a.length)return;files=files.concat(a);idx=-1;order=[];cnt();apply()}).catch(()=>{});
(function vl(){if(running&&cur&&cur.tagName==='VIDEO'&&!nxt)draw(1);requestAnimationFrame(vl)})();
let lastAct=Date.now(),idleOn=false,swallow=0;
const wake=()=>{lastAct=Date.now();if(idleOn){idleOn=false;document.body.classList.remove('idle')}};
['pointermove','pointerdown','touchstart','wheel'].forEach(ev=>addEventListener(ev,()=>{if(idleOn&&(ev==='pointerdown'||ev==='touchstart'))swallow=Date.now()+500;wake()},{passive:true,capture:true}));
addEventListener('keydown',e=>{if(idleOn){e.preventDefault();e.stopPropagation()}wake()},true);
addEventListener('click',e=>{if(Date.now()<swallow){e.preventDefault();e.stopPropagation()}},true);
setInterval(()=>{if(S.idle&&!idleOn&&!$('drawer').classList.contains('open')&&Date.now()-lastAct>S.idle*60000){idleOn=true;document.body.classList.add('idle')}},5000);
size();sync();renderAlarms();logic();frame();setInterval(logic,250);awake();
</script>
</body>
</html>