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">
<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; }
@@ -86,6 +87,7 @@
display: none;
flex-direction: column;
background: var(--bg);
height: calc(100% - var(--tab-bar-h));
}
.panel.active { display: flex; }
@@ -183,34 +185,42 @@
#clock-empty svg { opacity: 0.25; }
#clock-empty p { font-size: 17px; text-align: center; line-height: 1.5; }
/* ─── CRYPTO PANEL ─── */
.crypto-body {
flex: 1;
padding: 0;
.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;
flex-direction: column;
overflow: hidden;
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;
}
gecko-coin-list-widget {
width: 100% !important;
flex: 1;
display: block;
.widget-bar-title {
font-size: 13px;
font-weight: 600;
letter-spacing: 0.02em;
color: var(--subtext);
text-transform: uppercase;
}
/* ─── CHART PANELS ─── */
.chart-body {
.widget-fill {
flex: 1;
display: flex;
flex-direction: column;
padding: 12px 12px 12px;
min-height: 0;
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;
flex: 1;
display: block;
min-height: 0;
}
@@ -341,6 +351,11 @@
<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"/>
@@ -366,55 +381,59 @@
<!-- ══════════════ PANEL 2: CRYPTO ══════════════ -->
<div class="panel" id="panel-2">
<header class="panel-header">
<h1>Crypto</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="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>
<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>
<!-- ══════════════ PANELS 310: PLACEHOLDERS ══════════════ -->
<!-- ══════════════ PANEL 3: BTC CHART ══════════════ -->
<!-- ══════════════ PANEL 3: HEATMAP ══════════════ -->
<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="chart-body">
<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 4: ETH CHART ══════════════ -->
<div class="panel" id="panel-4">
<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="chart-body">
<!-- ══════════════ 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 5: PAX GOLD CHART ══════════════ -->
<div class="panel" id="panel-5">
<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="chart-body">
<!-- ══════════════ 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-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">
<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>
@@ -436,10 +455,10 @@
<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">BTC</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="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="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="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>
@@ -464,20 +483,30 @@
<script>
// ─── TAB SYSTEM ───────────────────────────────────────────
let activeTab = 1;
let slideshowTimer = null;
const TAB_NAMES = {
1:'World Clock', 2:'Crypto', 3:'Bitcoin', 4:'Ethereum', 5:'PAX Gold',
6:'Tab 6', 7:'Tab 7', 8:'Tab 8', 9:'Tab 9', 10:'Tab 10'
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) {
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> &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 =>
@@ -493,6 +522,8 @@ document.addEventListener('keydown', e => {
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 ────────────────────────────────────────────────
@@ -761,8 +792,97 @@ document.getElementById('modal-overlay').addEventListener('click', e => {
});
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>