889 lines
41 KiB
HTML
889 lines
41 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||
<title>Dashboard</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@100;200;300&display=swap" rel="stylesheet">
|
||
<script src="https://widgets.coingecko.com/gecko-coin-list-widget.js" async></script>
|
||
<script src="https://widgets.coingecko.com/gecko-coin-price-chart-widget.js" async></script>
|
||
<script src="https://widgets.coingecko.com/gecko-coin-heatmap-widget.js" async></script>
|
||
<style>
|
||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
:root {
|
||
--bg: #000000;
|
||
--text: #ffffff;
|
||
--subtext: #8e8e93;
|
||
--divider: #1c1c1e;
|
||
--accent: #ff9f0a;
|
||
--modal-bg: #1c1c1e;
|
||
--modal-hover: #2c2c2e;
|
||
--tab-bar-h: 54px;
|
||
}
|
||
|
||
html, body {
|
||
height: 100%;
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-family: -apple-system, 'Helvetica Neue', sans-serif;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ─── TAB BAR ─── */
|
||
#tab-bar {
|
||
position: fixed;
|
||
bottom: 0; left: 0; right: 0;
|
||
height: var(--tab-bar-h);
|
||
background: rgba(8,8,8,0.94);
|
||
backdrop-filter: blur(24px);
|
||
-webkit-backdrop-filter: blur(24px);
|
||
border-top: 1px solid var(--divider);
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 6px;
|
||
z-index: 300;
|
||
gap: 1px;
|
||
}
|
||
|
||
.tab-btn {
|
||
flex: 1;
|
||
height: 42px;
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1px;
|
||
color: var(--subtext);
|
||
transition: background 0.15s, color 0.15s;
|
||
position: relative;
|
||
}
|
||
.tab-btn:hover { background: rgba(255,255,255,0.05); }
|
||
.tab-btn.active { color: var(--accent); }
|
||
|
||
.tab-icon { font-size: 14px; line-height: 1; }
|
||
.tab-label { font-size: 8px; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; }
|
||
.tab-key {
|
||
position: absolute;
|
||
top: 3px; right: 5px;
|
||
font-size: 6.5px;
|
||
font-family: 'Roboto Mono', monospace;
|
||
opacity: 0.35;
|
||
}
|
||
.tab-btn.active .tab-key { opacity: 0.55; }
|
||
|
||
/* ─── PANELS ─── */
|
||
.panel {
|
||
position: fixed;
|
||
inset: 0;
|
||
bottom: var(--tab-bar-h);
|
||
overflow-y: auto;
|
||
display: none;
|
||
flex-direction: column;
|
||
background: var(--bg);
|
||
height: calc(100% - var(--tab-bar-h));
|
||
}
|
||
.panel.active { display: flex; }
|
||
|
||
/* ─── PANEL HEADER ─── */
|
||
.panel-header {
|
||
position: sticky;
|
||
top: 0; z-index: 100;
|
||
padding: 20px 28px 12px;
|
||
background: rgba(0,0,0,0.88);
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border-bottom: 1px solid var(--divider);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-shrink: 0;
|
||
}
|
||
.panel-header h1 {
|
||
font-size: 34px;
|
||
font-weight: 700;
|
||
letter-spacing: -0.5px;
|
||
}
|
||
.header-actions { display: flex; gap: 12px; align-items: center; }
|
||
|
||
.icon-btn {
|
||
background: var(--modal-bg);
|
||
border: none;
|
||
color: var(--accent);
|
||
width: 36px; height: 36px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: background 0.15s, transform 0.1s;
|
||
}
|
||
.icon-btn:hover { background: var(--modal-hover); transform: scale(1.08); }
|
||
.icon-btn:active { transform: scale(0.94); }
|
||
|
||
/* ─── CLOCK LIST ─── */
|
||
#clock-list { flex: 1; padding: 0; list-style: none; }
|
||
|
||
.clock-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 20px 28px;
|
||
border-bottom: 1px solid var(--divider);
|
||
animation: fadeIn 0.35s ease both;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; transform: translateY(14px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.clock-location { font-size: 13px; color: var(--subtext); letter-spacing: 0.04em; text-transform: uppercase; margin-bottom: 2px; }
|
||
.clock-city { font-size: 22px; font-weight: 600; letter-spacing: -0.3px; margin-bottom: 4px; }
|
||
.clock-day-info { font-size: 13px; color: var(--subtext); }
|
||
|
||
.clock-right {
|
||
text-align: right;
|
||
display: flex; flex-direction: column; align-items: flex-end; gap: 4px;
|
||
}
|
||
|
||
.clock-digital {
|
||
font-family: 'Roboto Mono', monospace;
|
||
font-size: 52px;
|
||
font-weight: 100;
|
||
letter-spacing: -1px;
|
||
line-height: 1;
|
||
text-shadow: 0 0 30px rgba(255,255,255,0.07);
|
||
}
|
||
.clock-digital .colon { animation: blink 1s step-start infinite; }
|
||
@keyframes blink { 0%,100% { opacity:1; } 50% { opacity:0.15; } }
|
||
|
||
.clock-ampm { font-size: 16px; font-weight: 300; color: var(--subtext); font-family: inherit; margin-left: 4px; }
|
||
.clock-seconds { font-family: 'Roboto Mono', monospace; font-size: 13px; color: var(--subtext); }
|
||
|
||
.delete-btn {
|
||
position: absolute; left: 0; top: 0;
|
||
height: 100%; width: 36px;
|
||
background: #ff3b30; border: none; color: #fff; font-size: 20px;
|
||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||
transform: translateX(-100%);
|
||
transition: transform 0.22s ease;
|
||
}
|
||
.clock-item:hover .delete-btn { transform: translateX(0); }
|
||
.clock-item:hover { padding-left: 64px; transition: padding 0.22s ease; }
|
||
|
||
#clock-empty {
|
||
display: none;
|
||
flex-direction: column; align-items: center; justify-content: center;
|
||
flex: 1; gap: 12px; color: var(--subtext); padding: 60px 28px;
|
||
}
|
||
#clock-empty svg { opacity: 0.25; }
|
||
#clock-empty p { font-size: 17px; text-align: center; line-height: 1.5; }
|
||
|
||
.icon-btn.slideshow-active { color: var(--accent2); background: rgba(48,209,88,0.15); }
|
||
|
||
/* ─── WIDGET PANELS (tabs 2-6): compact bar + full-height widget ─── */
|
||
.widget-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 6px 12px 6px 16px;
|
||
flex-shrink: 0;
|
||
border-bottom: 1px solid var(--divider);
|
||
background: rgba(0,0,0,0.9);
|
||
height: 38px;
|
||
}
|
||
|
||
.widget-bar-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.02em;
|
||
color: var(--subtext);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.widget-fill {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
gecko-coin-list-widget,
|
||
gecko-coin-price-chart-widget,
|
||
gecko-coin-heatmap-widget {
|
||
display: block;
|
||
width: 100% !important;
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
|
||
/* ─── PLACEHOLDER PANELS ─── */
|
||
.placeholder-body {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 14px;
|
||
color: var(--subtext);
|
||
padding: 40px;
|
||
}
|
||
.ph-key {
|
||
font-family: 'Roboto Mono', monospace;
|
||
font-size: 64px;
|
||
font-weight: 100;
|
||
color: #1a1a1a;
|
||
letter-spacing: -4px;
|
||
}
|
||
.placeholder-body p { font-size: 15px; text-align: center; line-height: 1.6; max-width: 260px; }
|
||
.ph-hint {
|
||
font-size: 11px;
|
||
background: var(--modal-bg);
|
||
border-radius: 8px;
|
||
padding: 7px 14px;
|
||
font-family: 'Roboto Mono', monospace;
|
||
}
|
||
|
||
/* ─── MODAL ─── */
|
||
.modal-overlay {
|
||
position: fixed; inset: 0;
|
||
background: rgba(0,0,0,0.72);
|
||
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
||
z-index: 400;
|
||
display: flex; align-items: flex-end; justify-content: center;
|
||
opacity: 0; pointer-events: none;
|
||
transition: opacity 0.25s;
|
||
}
|
||
.modal-overlay.open { opacity: 1; pointer-events: all; }
|
||
|
||
.modal {
|
||
background: var(--modal-bg);
|
||
border-radius: 16px 16px 0 0;
|
||
width: 100%; max-width: 540px; max-height: 80vh;
|
||
display: flex; flex-direction: column;
|
||
transform: translateY(100%);
|
||
transition: transform 0.32s cubic-bezier(0.32,0.72,0,1);
|
||
margin-bottom: var(--tab-bar-h);
|
||
}
|
||
.modal-overlay.open .modal { transform: translateY(0); }
|
||
|
||
.modal-handle { width: 36px; height: 4px; background: var(--subtext); border-radius: 2px; margin: 12px auto 0; opacity: 0.4; }
|
||
|
||
.modal-header {
|
||
padding: 16px 20px 12px;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
border-bottom: 1px solid #333;
|
||
}
|
||
.modal-header h2 { font-size: 18px; font-weight: 600; }
|
||
|
||
.modal-close {
|
||
background: #3a3a3c; border: none; color: var(--subtext);
|
||
width: 28px; height: 28px; border-radius: 50%; font-size: 14px;
|
||
cursor: pointer; display: flex; align-items: center; justify-content: center;
|
||
}
|
||
.modal-close:hover { background: #48484a; }
|
||
|
||
.modal-search {
|
||
margin: 12px 16px;
|
||
background: #2c2c2e; border: none; border-radius: 10px;
|
||
color: var(--text); font-size: 15px; padding: 10px 14px;
|
||
width: calc(100% - 32px); outline: none;
|
||
}
|
||
.modal-search::placeholder { color: var(--subtext); }
|
||
|
||
.tz-list { overflow-y: auto; flex: 1; list-style: none; }
|
||
.tz-list::-webkit-scrollbar { width: 4px; }
|
||
.tz-list::-webkit-scrollbar-thumb { background: #3a3a3c; border-radius: 2px; }
|
||
|
||
.tz-group-label {
|
||
font-size: 12px; font-weight: 600; color: var(--subtext);
|
||
text-transform: uppercase; letter-spacing: 0.08em;
|
||
padding: 10px 20px 4px;
|
||
background: var(--modal-bg); position: sticky; top: 0;
|
||
}
|
||
|
||
.tz-item {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 14px 20px; cursor: pointer;
|
||
border-bottom: 1px solid #2c2c2e;
|
||
transition: background 0.12s;
|
||
}
|
||
.tz-item:hover { background: var(--modal-hover); }
|
||
.tz-city-name { font-size: 16px; font-weight: 500; }
|
||
.tz-tz-name { font-size: 12px; color: var(--subtext); margin-top: 2px; }
|
||
.tz-current-time { font-family: 'Roboto Mono', monospace; font-size: 15px; font-weight: 300; color: var(--subtext); }
|
||
.tz-item.added .tz-city-name { color: var(--accent); }
|
||
.tz-added-badge { font-size: 11px; background: var(--accent); color: #000; border-radius: 4px; padding: 2px 6px; font-weight: 600; margin-left: 8px; }
|
||
|
||
/* ─── TOAST ─── */
|
||
#key-toast {
|
||
position: fixed;
|
||
top: 16px; left: 50%;
|
||
transform: translateX(-50%) translateY(-50px);
|
||
background: rgba(28,28,30,0.96);
|
||
border: 1px solid #2c2c2e;
|
||
border-radius: 10px;
|
||
padding: 7px 16px;
|
||
font-size: 12px;
|
||
font-family: 'Roboto Mono', monospace;
|
||
color: var(--subtext);
|
||
z-index: 500;
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
transition: transform 0.28s cubic-bezier(0.32,0.72,0,1), opacity 0.28s;
|
||
white-space: nowrap;
|
||
}
|
||
#key-toast.show { transform: translateX(-50%) translateY(0); opacity: 1; }
|
||
#key-toast strong { color: var(--accent); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- ══════════════ PANEL 1: WORLD CLOCK ══════════════ -->
|
||
<div class="panel active" id="panel-1">
|
||
<header class="panel-header">
|
||
<h1>World Clock</h1>
|
||
<div class="header-actions">
|
||
<button class="icon-btn" id="slideshow-btn" title="Start slideshow (S)">
|
||
<svg id="slideshow-icon" width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||
<polygon points="3,2 14,8 3,14"/>
|
||
</svg>
|
||
</button>
|
||
<button class="icon-btn" id="fs-btn" title="Toggle fullscreen">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/>
|
||
</svg>
|
||
</button>
|
||
<button class="icon-btn" id="add-btn" title="Add clock">
|
||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<line x1="8" y1="1" x2="8" y2="15"/><line x1="1" y1="8" x2="15" y2="8"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</header>
|
||
<ul id="clock-list"></ul>
|
||
<div id="clock-empty">
|
||
<svg width="52" height="52" viewBox="0 0 52 52" fill="none" stroke="white" stroke-width="1.5" stroke-linecap="round">
|
||
<circle cx="26" cy="26" r="22"/>
|
||
<line x1="26" y1="14" x2="26" y2="26"/><line x1="26" y1="26" x2="35" y2="32"/>
|
||
<circle cx="26" cy="26" r="2" fill="white"/>
|
||
</svg>
|
||
<p>No clocks added yet.<br/>Tap <strong style="color:var(--accent)">+</strong> to add a city.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════ PANEL 2: CRYPTO ══════════════ -->
|
||
<div class="panel" id="panel-2">
|
||
<div class="widget-bar">
|
||
<span class="widget-bar-title">Crypto</span>
|
||
<button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button>
|
||
</div>
|
||
<div class="widget-fill">
|
||
<gecko-coin-list-widget locale="en" dark-mode="true" outlined="true" coin-ids="bitcoin,ethereum,tether,pax-gold,solana,binancecoin,ripple" initial-currency="usd"></gecko-coin-list-widget>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════ PANEL 3: HEATMAP ══════════════ -->
|
||
<div class="panel" id="panel-3">
|
||
<div class="widget-bar">
|
||
<span class="widget-bar-title">Heatmap</span>
|
||
<button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button>
|
||
</div>
|
||
<div class="widget-fill">
|
||
<gecko-coin-heatmap-widget locale="en" dark-mode="true" outlined="true" top="10"></gecko-coin-heatmap-widget>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════ PANEL 4: BTC CHART ══════════════ -->
|
||
<div class="panel" id="panel-4">
|
||
<div class="widget-bar">
|
||
<span class="widget-bar-title">Bitcoin</span>
|
||
<button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button>
|
||
</div>
|
||
<div class="widget-fill">
|
||
<gecko-coin-price-chart-widget locale="en" dark-mode="true" outlined="true" initial-currency="usd"></gecko-coin-price-chart-widget>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════ PANEL 5: ETH CHART ══════════════ -->
|
||
<div class="panel" id="panel-5">
|
||
<div class="widget-bar">
|
||
<span class="widget-bar-title">Ethereum</span>
|
||
<button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button>
|
||
</div>
|
||
<div class="widget-fill">
|
||
<gecko-coin-price-chart-widget locale="en" dark-mode="true" outlined="true" coin-id="ethereum" initial-currency="usd"></gecko-coin-price-chart-widget>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ══════════════ PANEL 6: PAX GOLD CHART ══════════════ -->
|
||
<div class="panel" id="panel-6">
|
||
<div class="widget-bar">
|
||
<span class="widget-bar-title">PAX Gold</span>
|
||
<button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button>
|
||
</div>
|
||
<div class="widget-fill">
|
||
<gecko-coin-price-chart-widget locale="en" dark-mode="true" outlined="true" coin-id="pax-gold" initial-currency="usd"></gecko-coin-price-chart-widget>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel" id="panel-7">
|
||
<header class="panel-header"><h1>Tab 7</h1><div class="header-actions"><button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button></div></header>
|
||
<div class="placeholder-body"><div class="ph-key">7</div><p>Empty tab — press <strong style="color:var(--accent)">7</strong> to jump here.</p><span class="ph-hint">Keys 1–9, 0 switch tabs</span></div>
|
||
</div>
|
||
<div class="panel" id="panel-8">
|
||
<header class="panel-header"><h1>Tab 8</h1><div class="header-actions"><button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button></div></header>
|
||
<div class="placeholder-body"><div class="ph-key">8</div><p>Empty tab — press <strong style="color:var(--accent)">8</strong> to jump here.</p><span class="ph-hint">Keys 1–9, 0 switch tabs</span></div>
|
||
</div>
|
||
<div class="panel" id="panel-9">
|
||
<header class="panel-header"><h1>Tab 9</h1><div class="header-actions"><button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button></div></header>
|
||
<div class="placeholder-body"><div class="ph-key">9</div><p>Empty tab — press <strong style="color:var(--accent)">9</strong> to jump here.</p><span class="ph-hint">Keys 1–9, 0 switch tabs</span></div>
|
||
</div>
|
||
<div class="panel" id="panel-10">
|
||
<header class="panel-header"><h1>Tab 10</h1><div class="header-actions"><button class="icon-btn fs-all" title="Toggle fullscreen"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg></button></div></header>
|
||
<div class="placeholder-body"><div class="ph-key">0</div><p>Empty tab — press <strong style="color:var(--accent)">0</strong> to jump here.</p><span class="ph-hint">Keys 1–9, 0 switch tabs</span></div>
|
||
</div>
|
||
|
||
<!-- ══════════════ TAB BAR ══════════════ -->
|
||
<nav id="tab-bar">
|
||
<button class="tab-btn active" data-tab="1"><span class="tab-key">1</span><span class="tab-icon">🕐</span><span class="tab-label">Clock</span></button>
|
||
<button class="tab-btn" data-tab="2"><span class="tab-key">2</span><span class="tab-icon">₿</span><span class="tab-label">Crypto</span></button>
|
||
<button class="tab-btn" data-tab="3"><span class="tab-key">3</span><span class="tab-icon">🔥</span><span class="tab-label">Heat</span></button>
|
||
<button class="tab-btn" data-tab="4"><span class="tab-key">4</span><span class="tab-icon">₿</span><span class="tab-label">BTC</span></button>
|
||
<button class="tab-btn" data-tab="5"><span class="tab-key">5</span><span class="tab-icon">Ξ</span><span class="tab-label">ETH</span></button>
|
||
<button class="tab-btn" data-tab="6"><span class="tab-key">6</span><span class="tab-icon">🥇</span><span class="tab-label">PAXG</span></button>
|
||
<button class="tab-btn" data-tab="7"><span class="tab-key">7</span><span class="tab-icon">+</span><span class="tab-label">Tab 7</span></button>
|
||
<button class="tab-btn" data-tab="8"><span class="tab-key">8</span><span class="tab-icon">+</span><span class="tab-label">Tab 8</span></button>
|
||
<button class="tab-btn" data-tab="9"><span class="tab-key">9</span><span class="tab-icon">+</span><span class="tab-label">Tab 9</span></button>
|
||
<button class="tab-btn" data-tab="10"><span class="tab-key">0</span><span class="tab-icon">+</span><span class="tab-label">Tab 10</span></button>
|
||
</nav>
|
||
|
||
<!-- CLOCK MODAL -->
|
||
<div class="modal-overlay" id="modal-overlay">
|
||
<div class="modal" role="dialog">
|
||
<div class="modal-handle"></div>
|
||
<div class="modal-header">
|
||
<h2>Choose a City</h2>
|
||
<button class="modal-close" id="modal-close">✕</button>
|
||
</div>
|
||
<input class="modal-search" id="modal-search" type="text" placeholder="Search city or timezone…" autocomplete="off" />
|
||
<ul class="tz-list" id="tz-list"></ul>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="key-toast"></div>
|
||
|
||
<script>
|
||
// ─── TAB SYSTEM ───────────────────────────────────────────
|
||
let activeTab = 1;
|
||
let slideshowTimer = null;
|
||
|
||
const TAB_NAMES = {
|
||
1:'World Clock', 2:'Crypto', 3:'Heatmap', 4:'Bitcoin', 5:'Ethereum', 6:'PAX Gold',
|
||
7:'Tab 7', 8:'Tab 8', 9:'Tab 9', 10:'Tab 10'
|
||
};
|
||
|
||
function switchTab(n, fromSlideshow = false) {
|
||
if (n < 1 || n > 10 || n === activeTab) return;
|
||
// Manual switch stops slideshow
|
||
if (!fromSlideshow && slideshowTimer) slideshowOff();
|
||
document.getElementById(`panel-${activeTab}`)?.classList.remove('active');
|
||
document.querySelector(`.tab-btn[data-tab="${activeTab}"]`)?.classList.remove('active');
|
||
activeTab = n;
|
||
document.getElementById(`panel-${n}`)?.classList.add('active');
|
||
document.querySelector(`.tab-btn[data-tab="${n}"]`)?.classList.add('active');
|
||
showToast(`<strong>${n === 10 ? '0' : n}</strong> · ${TAB_NAMES[n]}`);
|
||
// Force widgets to fill their container by setting explicit pixel height
|
||
requestAnimationFrame(() => {
|
||
sizeWidgets();
|
||
window.dispatchEvent(new Event('resize'));
|
||
// second pass after widget may have initialised
|
||
setTimeout(() => { sizeWidgets(); window.dispatchEvent(new Event('resize')); }, 300);
|
||
});
|
||
}
|
||
|
||
document.querySelectorAll('.tab-btn').forEach(btn =>
|
||
btn.addEventListener('click', () => switchTab(+btn.dataset.tab))
|
||
);
|
||
|
||
// ─── KEYBOARD ─────────────────────────────────────────────
|
||
document.addEventListener('keydown', e => {
|
||
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
||
const modalOpen = document.getElementById('modal-overlay').classList.contains('open');
|
||
if (e.key === 'Escape' && modalOpen) { closeModal(); return; }
|
||
if (modalOpen) return;
|
||
if (e.key >= '1' && e.key <= '9') { switchTab(+e.key); return; }
|
||
if (e.key === '0') { switchTab(10); return; }
|
||
if (e.key === 'f' || e.key === 'F') toggleFullscreen();
|
||
if (e.key === 'r' || e.key === 'R') { showToast('↻ Refreshing…'); setTimeout(() => location.reload(), 400); }
|
||
if (e.key === 's' || e.key === 'S') toggleSlideshow();
|
||
});
|
||
|
||
// ─── TOAST ────────────────────────────────────────────────
|
||
let toastTimer;
|
||
function showToast(msg) {
|
||
const t = document.getElementById('key-toast');
|
||
t.innerHTML = msg;
|
||
t.classList.add('show');
|
||
clearTimeout(toastTimer);
|
||
toastTimer = setTimeout(() => t.classList.remove('show'), 1500);
|
||
}
|
||
|
||
// ─── FULLSCREEN ───────────────────────────────────────────
|
||
function toggleFullscreen() {
|
||
if (!document.fullscreenElement) {
|
||
document.documentElement.requestFullscreen().catch(() => {});
|
||
} else {
|
||
document.exitFullscreen().catch(() => {});
|
||
}
|
||
}
|
||
|
||
const FS_ICON_OUT = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/></svg>`;
|
||
const FS_ICON_IN = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 1H1v5M10 1h5v5M15 10h-5v5M6 15H1v-5"/></svg>`;
|
||
|
||
document.addEventListener('fullscreenchange', () => {
|
||
const inFS = !!document.fullscreenElement;
|
||
document.querySelectorAll('#fs-btn, .fs-all').forEach(b => {
|
||
b.innerHTML = inFS ? FS_ICON_IN : FS_ICON_OUT;
|
||
b.title = inFS ? 'Exit fullscreen' : 'Toggle fullscreen';
|
||
});
|
||
});
|
||
|
||
document.getElementById('fs-btn').addEventListener('click', toggleFullscreen);
|
||
document.querySelectorAll('.fs-all').forEach(b => b.addEventListener('click', toggleFullscreen));
|
||
|
||
// ─── CLOCK DATA ───────────────────────────────────────────
|
||
const ALL_TZ = [
|
||
{ city:"New York", tz:"America/New_York", region:"Americas" },
|
||
{ city:"Los Angeles", tz:"America/Los_Angeles", region:"Americas" },
|
||
{ city:"Chicago", tz:"America/Chicago", region:"Americas" },
|
||
{ city:"Denver", tz:"America/Denver", region:"Americas" },
|
||
{ city:"Phoenix", tz:"America/Phoenix", region:"Americas" },
|
||
{ city:"Toronto", tz:"America/Toronto", region:"Americas" },
|
||
{ city:"Vancouver", tz:"America/Vancouver", region:"Americas" },
|
||
{ city:"Mexico City", tz:"America/Mexico_City", region:"Americas" },
|
||
{ city:"São Paulo", tz:"America/Sao_Paulo", region:"Americas" },
|
||
{ city:"Buenos Aires", tz:"America/Argentina/Buenos_Aires", region:"Americas" },
|
||
{ city:"Bogotá", tz:"America/Bogota", region:"Americas" },
|
||
{ city:"Lima", tz:"America/Lima", region:"Americas" },
|
||
{ city:"Santiago", tz:"America/Santiago", region:"Americas" },
|
||
{ city:"Caracas", tz:"America/Caracas", region:"Americas" },
|
||
{ city:"Havana", tz:"America/Havana", region:"Americas" },
|
||
{ city:"Anchorage", tz:"America/Anchorage", region:"Americas" },
|
||
{ city:"Honolulu", tz:"Pacific/Honolulu", region:"Americas" },
|
||
{ city:"London", tz:"Europe/London", region:"Europe" },
|
||
{ city:"Paris", tz:"Europe/Paris", region:"Europe" },
|
||
{ city:"Berlin", tz:"Europe/Berlin", region:"Europe" },
|
||
{ city:"Madrid", tz:"Europe/Madrid", region:"Europe" },
|
||
{ city:"Rome", tz:"Europe/Rome", region:"Europe" },
|
||
{ city:"Amsterdam", tz:"Europe/Amsterdam", region:"Europe" },
|
||
{ city:"Brussels", tz:"Europe/Brussels", region:"Europe" },
|
||
{ city:"Zurich", tz:"Europe/Zurich", region:"Europe" },
|
||
{ city:"Vienna", tz:"Europe/Vienna", region:"Europe" },
|
||
{ city:"Stockholm", tz:"Europe/Stockholm", region:"Europe" },
|
||
{ city:"Oslo", tz:"Europe/Oslo", region:"Europe" },
|
||
{ city:"Copenhagen", tz:"Europe/Copenhagen", region:"Europe" },
|
||
{ city:"Helsinki", tz:"Europe/Helsinki", region:"Europe" },
|
||
{ city:"Warsaw", tz:"Europe/Warsaw", region:"Europe" },
|
||
{ city:"Prague", tz:"Europe/Prague", region:"Europe" },
|
||
{ city:"Budapest", tz:"Europe/Budapest", region:"Europe" },
|
||
{ city:"Bucharest", tz:"Europe/Bucharest", region:"Europe" },
|
||
{ city:"Athens", tz:"Europe/Athens", region:"Europe" },
|
||
{ city:"Istanbul", tz:"Europe/Istanbul", region:"Europe" },
|
||
{ city:"Kyiv", tz:"Europe/Kyiv", region:"Europe" },
|
||
{ city:"Moscow", tz:"Europe/Moscow", region:"Europe" },
|
||
{ city:"Lisbon", tz:"Europe/Lisbon", region:"Europe" },
|
||
{ city:"Dublin", tz:"Europe/Dublin", region:"Europe" },
|
||
{ city:"Dubai", tz:"Asia/Dubai", region:"Asia & Pacific" },
|
||
{ city:"Mumbai", tz:"Asia/Kolkata", region:"Asia & Pacific" },
|
||
{ city:"Delhi", tz:"Asia/Kolkata", region:"Asia & Pacific" },
|
||
{ city:"Karachi", tz:"Asia/Karachi", region:"Asia & Pacific" },
|
||
{ city:"Dhaka", tz:"Asia/Dhaka", region:"Asia & Pacific" },
|
||
{ city:"Kathmandu", tz:"Asia/Kathmandu", region:"Asia & Pacific" },
|
||
{ city:"Bangkok", tz:"Asia/Bangkok", region:"Asia & Pacific" },
|
||
{ city:"Ho Chi Minh", tz:"Asia/Ho_Chi_Minh", region:"Asia & Pacific" },
|
||
{ city:"Singapore", tz:"Asia/Singapore", region:"Asia & Pacific" },
|
||
{ city:"Kuala Lumpur", tz:"Asia/Kuala_Lumpur", region:"Asia & Pacific" },
|
||
{ city:"Jakarta", tz:"Asia/Jakarta", region:"Asia & Pacific" },
|
||
{ city:"Manila", tz:"Asia/Manila", region:"Asia & Pacific" },
|
||
{ city:"Hong Kong", tz:"Asia/Hong_Kong", region:"Asia & Pacific" },
|
||
{ city:"Taipei", tz:"Asia/Taipei", region:"Asia & Pacific" },
|
||
{ city:"Shanghai", tz:"Asia/Shanghai", region:"Asia & Pacific" },
|
||
{ city:"Beijing", tz:"Asia/Shanghai", region:"Asia & Pacific" },
|
||
{ city:"Seoul", tz:"Asia/Seoul", region:"Asia & Pacific" },
|
||
{ city:"Tokyo", tz:"Asia/Tokyo", region:"Asia & Pacific" },
|
||
{ city:"Osaka", tz:"Asia/Tokyo", region:"Asia & Pacific" },
|
||
{ city:"Riyadh", tz:"Asia/Riyadh", region:"Asia & Pacific" },
|
||
{ city:"Tehran", tz:"Asia/Tehran", region:"Asia & Pacific" },
|
||
{ city:"Baghdad", tz:"Asia/Baghdad", region:"Asia & Pacific" },
|
||
{ city:"Tel Aviv", tz:"Asia/Jerusalem", region:"Asia & Pacific" },
|
||
{ city:"Almaty", tz:"Asia/Almaty", region:"Asia & Pacific" },
|
||
{ city:"Ulaanbaatar", tz:"Asia/Ulaanbaatar", region:"Asia & Pacific" },
|
||
{ city:"Vladivostok", tz:"Asia/Vladivostok", region:"Asia & Pacific" },
|
||
{ city:"Sydney", tz:"Australia/Sydney", region:"Asia & Pacific" },
|
||
{ city:"Melbourne", tz:"Australia/Melbourne", region:"Asia & Pacific" },
|
||
{ city:"Brisbane", tz:"Australia/Brisbane", region:"Asia & Pacific" },
|
||
{ city:"Perth", tz:"Australia/Perth", region:"Asia & Pacific" },
|
||
{ city:"Auckland", tz:"Pacific/Auckland", region:"Asia & Pacific" },
|
||
{ city:"Fiji", tz:"Pacific/Fiji", region:"Asia & Pacific" },
|
||
{ city:"Guam", tz:"Pacific/Guam", region:"Asia & Pacific" },
|
||
{ city:"Cairo", tz:"Africa/Cairo", region:"Africa" },
|
||
{ city:"Lagos", tz:"Africa/Lagos", region:"Africa" },
|
||
{ city:"Nairobi", tz:"Africa/Nairobi", region:"Africa" },
|
||
{ city:"Johannesburg", tz:"Africa/Johannesburg", region:"Africa" },
|
||
{ city:"Casablanca", tz:"Africa/Casablanca", region:"Africa" },
|
||
{ city:"Accra", tz:"Africa/Accra", region:"Africa" },
|
||
{ city:"Addis Ababa", tz:"Africa/Addis_Ababa", region:"Africa" },
|
||
{ city:"Dar es Salaam", tz:"Africa/Dar_es_Salaam", region:"Africa" },
|
||
{ city:"Algiers", tz:"Africa/Algiers", region:"Africa" },
|
||
{ city:"Khartoum", tz:"Africa/Khartoum", region:"Africa" },
|
||
{ city:"UTC", tz:"UTC", region:"UTC" },
|
||
{ city:"Reykjavik", tz:"Atlantic/Reykjavik", region:"UTC" },
|
||
];
|
||
|
||
// ─── CLOCK LOGIC ──────────────────────────────────────────
|
||
let clocks = JSON.parse(localStorage.getItem('worldclocks') || '[]');
|
||
const save = () => localStorage.setItem('worldclocks', JSON.stringify(clocks));
|
||
|
||
function nowIn(tz) { return new Date(new Date().toLocaleString('en-US', { timeZone: tz })); }
|
||
|
||
function tzOffset(tz) {
|
||
const now = new Date();
|
||
const local = new Date(now.toLocaleString('en-US', { timeZone: tz }));
|
||
const diff = (local - now) / 60000;
|
||
const sign = diff >= 0 ? '+' : '-';
|
||
const abs = Math.abs(Math.round(diff));
|
||
const h = Math.floor(abs / 60), m = abs % 60;
|
||
return `UTC${sign}${h}${m ? ':' + String(m).padStart(2,'0') : ''}`;
|
||
}
|
||
|
||
function formatTime(d) {
|
||
let h = d.getHours();
|
||
const m = String(d.getMinutes()).padStart(2,'0');
|
||
const ampm = h >= 12 ? 'PM' : 'AM';
|
||
return { h: String(h % 12 || 12), m, ampm };
|
||
}
|
||
|
||
function dayRelative(tz) {
|
||
const here = new Date(), there = nowIn(tz);
|
||
const hd = new Date(here.getFullYear(), here.getMonth(), here.getDate());
|
||
const td = new Date(there.getFullYear(), there.getMonth(), there.getDate());
|
||
const diff = Math.round((td - hd) / 86400000);
|
||
const dn = there.toLocaleDateString('en-US', { weekday:'long', timeZone:tz });
|
||
if (diff === 0) return dn + ', today';
|
||
if (diff === 1) return dn + ', tomorrow';
|
||
if (diff === -1) return dn + ', yesterday';
|
||
return dn + (diff > 0 ? ` +${diff}d` : ` ${diff}d`);
|
||
}
|
||
|
||
function renderClocks() {
|
||
const list = document.getElementById('clock-list');
|
||
const empty = document.getElementById('clock-empty');
|
||
if (!clocks.length) { list.innerHTML = ''; empty.style.display = 'flex'; return; }
|
||
empty.style.display = 'none';
|
||
const sorted = [...clocks].sort((a,b) => nowIn(a.tz) - nowIn(b.tz));
|
||
|
||
if (list.children.length !== clocks.length) {
|
||
list.innerHTML = '';
|
||
sorted.forEach((c, i) => {
|
||
const li = document.createElement('li');
|
||
li.className = 'clock-item';
|
||
li.style.animationDelay = `${i * 0.06}s`;
|
||
li.innerHTML = clockHTML(c);
|
||
li.querySelector('.delete-btn').addEventListener('click', () => {
|
||
clocks = clocks.filter(x => !(x.city === c.city && x.tz === c.tz));
|
||
save(); renderClocks();
|
||
});
|
||
list.appendChild(li);
|
||
});
|
||
} else {
|
||
sorted.forEach((c, i) => tickClock(list.children[i], c));
|
||
}
|
||
}
|
||
|
||
function clockHTML(c) {
|
||
const d = nowIn(c.tz); const { h, m, ampm } = formatTime(d);
|
||
const sec = String(d.getSeconds()).padStart(2,'0');
|
||
return `
|
||
<button class="delete-btn" title="Remove">✕</button>
|
||
<div class="clock-left">
|
||
<div class="clock-location">${tzOffset(c.tz)}</div>
|
||
<div class="clock-city">${c.city}</div>
|
||
<div class="clock-day-info">${dayRelative(c.tz)}</div>
|
||
</div>
|
||
<div class="clock-right">
|
||
<div class="clock-digital">${h}<span class="colon">:</span>${m}<span class="clock-ampm">${ampm}</span></div>
|
||
<div class="clock-seconds">:${sec}</div>
|
||
</div>`;
|
||
}
|
||
|
||
function tickClock(li, c) {
|
||
const d = nowIn(c.tz); const { h, m, ampm } = formatTime(d);
|
||
const sec = String(d.getSeconds()).padStart(2,'0');
|
||
const dig = li.querySelector('.clock-digital');
|
||
if (dig) dig.innerHTML = `${h}<span class="colon">:</span>${m}<span class="clock-ampm">${ampm}</span>`;
|
||
const secEl = li.querySelector('.clock-seconds');
|
||
if (secEl) secEl.textContent = ':' + sec;
|
||
const dayEl = li.querySelector('.clock-day-info');
|
||
if (dayEl) dayEl.textContent = dayRelative(c.tz);
|
||
}
|
||
|
||
setInterval(renderClocks, 1000);
|
||
|
||
// ─── MODAL ────────────────────────────────────────────────
|
||
function openModal() {
|
||
document.getElementById('modal-overlay').classList.add('open');
|
||
document.getElementById('modal-search').focus();
|
||
renderTZList('');
|
||
}
|
||
function closeModal() {
|
||
document.getElementById('modal-overlay').classList.remove('open');
|
||
document.getElementById('modal-search').value = '';
|
||
}
|
||
|
||
function renderTZList(query) {
|
||
const ul = document.getElementById('tz-list');
|
||
const q = query.toLowerCase();
|
||
const filtered = ALL_TZ.filter(t =>
|
||
t.city.toLowerCase().includes(q) || t.tz.toLowerCase().includes(q) || t.region.toLowerCase().includes(q)
|
||
);
|
||
const groups = {};
|
||
filtered.forEach(t => { if (!groups[t.region]) groups[t.region] = []; groups[t.region].push(t); });
|
||
ul.innerHTML = '';
|
||
for (const [region, items] of Object.entries(groups)) {
|
||
const lbl = document.createElement('li');
|
||
lbl.className = 'tz-group-label';
|
||
lbl.textContent = region;
|
||
ul.appendChild(lbl);
|
||
items.forEach(t => {
|
||
const isAdded = clocks.some(c => c.city === t.city && c.tz === t.tz);
|
||
const { h, m, ampm } = formatTime(nowIn(t.tz));
|
||
const li = document.createElement('li');
|
||
li.className = 'tz-item' + (isAdded ? ' added' : '');
|
||
li.innerHTML = `
|
||
<div class="tz-item-left">
|
||
<div class="tz-city-name">${t.city}${isAdded ? '<span class="tz-added-badge">Added</span>' : ''}</div>
|
||
<div class="tz-tz-name">${t.tz} · ${tzOffset(t.tz)}</div>
|
||
</div>
|
||
<div class="tz-current-time">${h}:${m} ${ampm}</div>`;
|
||
li.addEventListener('click', () => {
|
||
if (!clocks.find(c => c.city === t.city && c.tz === t.tz)) {
|
||
clocks.push({ city: t.city, tz: t.tz }); save(); renderClocks();
|
||
}
|
||
closeModal();
|
||
});
|
||
ul.appendChild(li);
|
||
});
|
||
}
|
||
if (!filtered.length) ul.innerHTML = '<li style="padding:24px;text-align:center;color:#8e8e93;">No results found</li>';
|
||
}
|
||
|
||
document.getElementById('add-btn').addEventListener('click', openModal);
|
||
document.getElementById('modal-close').addEventListener('click', closeModal);
|
||
document.getElementById('modal-overlay').addEventListener('click', e => {
|
||
if (e.target === document.getElementById('modal-overlay')) closeModal();
|
||
});
|
||
document.getElementById('modal-search').addEventListener('input', e => renderTZList(e.target.value));
|
||
|
||
// ─── WIDGET SIZING ────────────────────────────────────────
|
||
function sizeWidgets() {
|
||
const tabBarH = document.getElementById('tab-bar').offsetHeight;
|
||
const viewH = window.innerHeight;
|
||
|
||
['gecko-coin-list-widget', 'gecko-coin-price-chart-widget', 'gecko-coin-heatmap-widget'].forEach(sel => {
|
||
document.querySelectorAll(sel).forEach(el => {
|
||
const bar = el.closest('.panel')?.querySelector('.widget-bar');
|
||
const barH = bar ? bar.offsetHeight : 0;
|
||
const h = viewH - tabBarH - barH;
|
||
// Apply to the custom element itself
|
||
el.style.cssText += `height:${h}px !important; min-height:${h}px !important; max-height:${h}px !important;`;
|
||
el.setAttribute('height', h);
|
||
// Apply to any child iframe (gecko widgets render one)
|
||
const tryFrame = (root) => {
|
||
if (!root) return;
|
||
root.querySelectorAll('iframe').forEach(f => {
|
||
f.style.height = h + 'px';
|
||
f.style.minHeight = h + 'px';
|
||
f.height = h;
|
||
});
|
||
};
|
||
tryFrame(el);
|
||
tryFrame(el.shadowRoot);
|
||
});
|
||
});
|
||
|
||
// Also force widget-fill divs to exact remaining height
|
||
document.querySelectorAll('.widget-fill').forEach(fill => {
|
||
const bar = fill.closest('.panel')?.querySelector('.widget-bar');
|
||
const barH = bar ? bar.offsetHeight : 0;
|
||
const h = viewH - tabBarH - barH;
|
||
fill.style.height = h + 'px';
|
||
fill.style.minHeight = h + 'px';
|
||
fill.style.maxHeight = h + 'px';
|
||
});
|
||
}
|
||
|
||
window.addEventListener('resize', sizeWidgets);
|
||
|
||
// Re-run sizeWidgets whenever any new iframe appears inside a gecko widget
|
||
// (they load asynchronously after the custom element registers)
|
||
const iframeObserver = new MutationObserver(() => sizeWidgets());
|
||
document.querySelectorAll('gecko-coin-list-widget, gecko-coin-price-chart-widget, gecko-coin-heatmap-widget').forEach(el => {
|
||
iframeObserver.observe(el, { childList: true, subtree: true });
|
||
});
|
||
|
||
// ─── SLIDESHOW ────────────────────────────────────────────
|
||
const SLIDESHOW_TABS = [1, 2, 3, 4, 5, 6]; // only populated tabs
|
||
const SLIDESHOW_INTERVAL = 10000;
|
||
const PLAY_ICON = `<polygon points="3,2 14,8 3,14"/>`;
|
||
const PAUSE_ICON = `<rect x="3" y="2" width="4" height="12" rx="1"/><rect x="9" y="2" width="4" height="12" rx="1"/>`;
|
||
|
||
function slideshowOn() {
|
||
const btn = document.getElementById('slideshow-btn');
|
||
btn.classList.add('slideshow-active');
|
||
btn.title = 'Stop slideshow (S)';
|
||
btn.querySelector('svg').innerHTML = PAUSE_ICON;
|
||
// advance immediately then every 10s
|
||
advanceSlideshow();
|
||
slideshowTimer = setInterval(advanceSlideshow, SLIDESHOW_INTERVAL);
|
||
}
|
||
|
||
function slideshowOff() {
|
||
clearInterval(slideshowTimer);
|
||
slideshowTimer = null;
|
||
const btn = document.getElementById('slideshow-btn');
|
||
btn.classList.remove('slideshow-active');
|
||
btn.title = 'Start slideshow (S)';
|
||
btn.querySelector('svg').innerHTML = PLAY_ICON;
|
||
}
|
||
|
||
function toggleSlideshow() {
|
||
slideshowTimer ? slideshowOff() : slideshowOn();
|
||
}
|
||
|
||
function advanceSlideshow() {
|
||
const idx = SLIDESHOW_TABS.indexOf(activeTab);
|
||
const next = SLIDESHOW_TABS[(idx + 1) % SLIDESHOW_TABS.length];
|
||
switchTab(next, true);
|
||
}
|
||
|
||
document.getElementById('slideshow-btn').addEventListener('click', toggleSlideshow);
|
||
|
||
// ─── INIT ─────────────────────────────────────────────────
|
||
renderClocks();
|
||
sizeWidgets();
|
||
setTimeout(sizeWidgets, 200);
|
||
setTimeout(sizeWidgets, 600);
|
||
setTimeout(sizeWidgets, 1500);
|
||
setTimeout(sizeWidgets, 3000);
|
||
</script>
|
||
</body>
|
||
</html>
|