Compare commits

...

2 Commits

Author SHA1 Message Date
Jon
6892738382 slide show and shortcuts working 2026-04-29 22:07:16 +01:00
Jon
fd743e1146 crypto 2026-04-29 22:06:54 +01:00

View File

@@ -9,6 +9,7 @@
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@100;200;300&display=swap" rel="stylesheet"> <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-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-price-chart-widget.js" async></script>
<script src="https://widgets.coingecko.com/gecko-coin-heatmap-widget.js" async></script>
<style> <style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -86,6 +87,7 @@
display: none; display: none;
flex-direction: column; flex-direction: column;
background: var(--bg); background: var(--bg);
height: calc(100% - var(--tab-bar-h));
} }
.panel.active { display: flex; } .panel.active { display: flex; }
@@ -183,34 +185,42 @@
#clock-empty svg { opacity: 0.25; } #clock-empty svg { opacity: 0.25; }
#clock-empty p { font-size: 17px; text-align: center; line-height: 1.5; } #clock-empty p { font-size: 17px; text-align: center; line-height: 1.5; }
/* ─── CRYPTO PANEL ─── */ .icon-btn.slideshow-active { color: var(--accent2); background: rgba(48,209,88,0.15); }
.crypto-body {
flex: 1; /* ─── WIDGET PANELS (tabs 2-6): compact bar + full-height widget ─── */
padding: 0; .widget-bar {
display: flex; display: flex;
flex-direction: column; align-items: center;
overflow: hidden; 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;
} }
gecko-coin-list-widget { .widget-bar-title {
width: 100% !important; font-size: 13px;
flex: 1; font-weight: 600;
display: block; letter-spacing: 0.02em;
color: var(--subtext);
text-transform: uppercase;
} }
/* ─── CHART PANELS ─── */ .widget-fill {
.chart-body {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 12px 12px 12px; min-height: 0;
overflow: hidden; overflow: hidden;
} }
gecko-coin-price-chart-widget { gecko-coin-list-widget,
gecko-coin-price-chart-widget,
gecko-coin-heatmap-widget {
display: block;
width: 100% !important; width: 100% !important;
flex: 1; flex: 1;
display: block;
min-height: 0; min-height: 0;
} }
@@ -341,6 +351,11 @@
<header class="panel-header"> <header class="panel-header">
<h1>World Clock</h1> <h1>World Clock</h1>
<div class="header-actions"> <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"> <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"> <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"/> <path d="M1 6V1h5M10 1h5v5M15 10v5h-5M6 15H1v-5"/>
@@ -366,55 +381,59 @@
<!-- ══════════════ PANEL 2: CRYPTO ══════════════ --> <!-- ══════════════ PANEL 2: CRYPTO ══════════════ -->
<div class="panel" id="panel-2"> <div class="panel" id="panel-2">
<header class="panel-header"> <div class="widget-bar">
<h1>Crypto</h1> <span class="widget-bar-title">Crypto</span>
<div class="header-actions"> <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>
<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> </div>
</header> <div class="widget-fill">
<div class="crypto-body"> <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>
<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>
</div> </div>
<!-- ══════════════ PANELS 310: PLACEHOLDERS ══════════════ --> <!-- ══════════════ PANEL 3: HEATMAP ══════════════ -->
<!-- ══════════════ PANEL 3: BTC CHART ══════════════ -->
<div class="panel" id="panel-3"> <div class="panel" id="panel-3">
<header class="panel-header"><h1>Bitcoin</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="widget-bar">
<div class="chart-body"> <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> <gecko-coin-price-chart-widget locale="en" dark-mode="true" outlined="true" initial-currency="usd"></gecko-coin-price-chart-widget>
</div> </div>
</div> </div>
<!-- ══════════════ PANEL 4: ETH CHART ══════════════ --> <!-- ══════════════ PANEL 5: ETH CHART ══════════════ -->
<div class="panel" id="panel-4"> <div class="panel" id="panel-5">
<header class="panel-header"><h1>Ethereum</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="widget-bar">
<div class="chart-body"> <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> <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>
</div> </div>
<!-- ══════════════ PANEL 5: PAX GOLD CHART ══════════════ --> <!-- ══════════════ PANEL 6: PAX GOLD CHART ══════════════ -->
<div class="panel" id="panel-5"> <div class="panel" id="panel-6">
<header class="panel-header"><h1>PAX Gold</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="widget-bar">
<div class="chart-body"> <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> <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> </div>
<div class="panel" id="panel-6">
<header class="panel-header"><h1>Tab 6</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">6</div><p>Empty tab — press <strong style="color:var(--accent)">6</strong> to jump here.</p><span class="ph-hint">Keys 19, 0 switch tabs</span></div>
</div>
<div class="panel" id="panel-7"> <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> <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 19, 0 switch tabs</span></div> <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 19, 0 switch tabs</span></div>
@@ -436,10 +455,10 @@
<nav id="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 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="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">BTC</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">ETH</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">PAXG</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">Tab 6</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="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="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="9"><span class="tab-key">9</span><span class="tab-icon"></span><span class="tab-label">Tab 9</span></button>
@@ -464,20 +483,30 @@
<script> <script>
// ─── TAB SYSTEM ─────────────────────────────────────────── // ─── TAB SYSTEM ───────────────────────────────────────────
let activeTab = 1; let activeTab = 1;
let slideshowTimer = null;
const TAB_NAMES = { const TAB_NAMES = {
1:'World Clock', 2:'Crypto', 3:'Bitcoin', 4:'Ethereum', 5:'PAX Gold', 1:'World Clock', 2:'Crypto', 3:'Heatmap', 4:'Bitcoin', 5:'Ethereum', 6:'PAX Gold',
6:'Tab 6', 7:'Tab 7', 8:'Tab 8', 9:'Tab 9', 10:'Tab 10' 7:'Tab 7', 8:'Tab 8', 9:'Tab 9', 10:'Tab 10'
}; };
function switchTab(n) { function switchTab(n, fromSlideshow = false) {
if (n < 1 || n > 10 || n === activeTab) return; if (n < 1 || n > 10 || n === activeTab) return;
// Manual switch stops slideshow
if (!fromSlideshow && slideshowTimer) slideshowOff();
document.getElementById(`panel-${activeTab}`)?.classList.remove('active'); document.getElementById(`panel-${activeTab}`)?.classList.remove('active');
document.querySelector(`.tab-btn[data-tab="${activeTab}"]`)?.classList.remove('active'); document.querySelector(`.tab-btn[data-tab="${activeTab}"]`)?.classList.remove('active');
activeTab = n; activeTab = n;
document.getElementById(`panel-${n}`)?.classList.add('active'); document.getElementById(`panel-${n}`)?.classList.add('active');
document.querySelector(`.tab-btn[data-tab="${n}"]`)?.classList.add('active'); document.querySelector(`.tab-btn[data-tab="${n}"]`)?.classList.add('active');
showToast(`<strong>${n === 10 ? '0' : n}</strong> &nbsp;·&nbsp; ${TAB_NAMES[n]}`); showToast(`<strong>${n === 10 ? '0' : n}</strong> &nbsp;·&nbsp; ${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 => document.querySelectorAll('.tab-btn').forEach(btn =>
@@ -493,6 +522,8 @@ document.addEventListener('keydown', e => {
if (e.key >= '1' && e.key <= '9') { switchTab(+e.key); return; } if (e.key >= '1' && e.key <= '9') { switchTab(+e.key); return; }
if (e.key === '0') { switchTab(10); return; } if (e.key === '0') { switchTab(10); return; }
if (e.key === 'f' || e.key === 'F') toggleFullscreen(); 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 ──────────────────────────────────────────────── // ─── TOAST ────────────────────────────────────────────────
@@ -761,8 +792,97 @@ document.getElementById('modal-overlay').addEventListener('click', e => {
}); });
document.getElementById('modal-search').addEventListener('input', e => renderTZList(e.target.value)); 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 ───────────────────────────────────────────────── // ─── INIT ─────────────────────────────────────────────────
renderClocks(); renderClocks();
sizeWidgets();
setTimeout(sizeWidgets, 200);
setTimeout(sizeWidgets, 600);
setTimeout(sizeWidgets, 1500);
setTimeout(sizeWidgets, 3000);
</script> </script>
</body> </body>
</html> </html>