mirror of
git://f0xx.org/android_cast
synced 2026-07-29 07:39:15 +03:00
sync
This commit is contained in:
@@ -11,12 +11,34 @@
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="/app/androidcast_project/crashes/assets/css/app.css">
|
||||
<link rel="stylesheet" href="hub.css">
|
||||
<link rel="stylesheet" href="hub.css?v=20260527t">
|
||||
<link rel="stylesheet" href="hub-logo-layers.css">
|
||||
</head>
|
||||
<body class="hub-page">
|
||||
<div id="overtime-alphabet-host" hidden aria-hidden="true"></div>
|
||||
<div id="hub-logo-stage" class="hub-logo-stage" hidden aria-hidden="true"></div>
|
||||
<button id="hub-compass-hit" class="hub-compass-hit" type="button" aria-label="Open deployment model"></button>
|
||||
<section class="hub-clock-widget" id="hub-clock-widget" aria-label="World clock widget">
|
||||
<div class="cw-row cw-row-main">
|
||||
<div class="cw-cell cw-main-cell">
|
||||
<div class="cw-line cw-line-time">
|
||||
<strong class="cw-time-main">
|
||||
<span id="cw-hm-lcd" class="ot-lcd-line ot-lcd-bold" aria-label="Hours and minutes"></span>
|
||||
<span id="cw-ss-lcd" class="ot-lcd-line ot-lcd-sec ot-lcd-bold" aria-label="Seconds"></span>
|
||||
<span id="cw-tz-text" class="ot-lcd-line ot-lcd-tz ot-lcd-bold" aria-label="Timezone offset"></span>
|
||||
</strong>
|
||||
</div>
|
||||
<div class="cw-line cw-line-region" id="cw-region">EUROPE/WARSAW</div>
|
||||
<div class="cw-line cw-line-date" id="cw-date-line">WED 27 MAY 2026</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cw-row cw-row-mid">
|
||||
<div class="cw-cell"></div>
|
||||
<div class="cw-cell"></div>
|
||||
<div class="cw-cell"></div>
|
||||
<div class="cw-cell"></div>
|
||||
</div>
|
||||
<div class="cw-row cw-row-world" id="cw-world-row"></div>
|
||||
</section>
|
||||
<div id="hub-docs-modal" class="hub-docs-modal" hidden aria-hidden="true">
|
||||
<div class="hub-docs-backdrop" data-close-docs></div>
|
||||
<section class="hub-docs-panel card card--lift" role="dialog" aria-modal="true" aria-labelledby="hub-docs-title">
|
||||
@@ -24,8 +46,11 @@
|
||||
<h2 id="hub-docs-title">Deployment model</h2>
|
||||
<button type="button" class="hub-docs-close" data-close-docs aria-label="Close">x</button>
|
||||
</header>
|
||||
<div id="hub-docs-body" class="hub-docs-body">
|
||||
<p class="muted">Loading...</p>
|
||||
<div class="hub-docs-layout">
|
||||
<aside class="hub-docs-toc detail-tree" id="hub-docs-toc" aria-label="Table of contents">
|
||||
<p class="muted hub-docs-toc-title">Table of contents</p>
|
||||
</aside>
|
||||
<div class="hub-docs-panels" id="hub-docs-panels"></div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -35,7 +60,6 @@
|
||||
<div class="hub-brand">
|
||||
<img class="hub-logo-mark" src="assets/hub-logo-transparent.svg" alt="" width="112" height="84" decoding="async">
|
||||
<h1 class="hub-title">Android Cast</h1>
|
||||
<img class="hub-thunder" src="assets/hub-thunder.svg" alt="Thunder underline" width="312" height="46" decoding="async">
|
||||
</div>
|
||||
<p class="muted hub-subtitle">Project hub</p>
|
||||
<label class="toolbar-select hub-theme">
|
||||
@@ -90,17 +114,18 @@
|
||||
var supportsLayout = !!(window.CSS && CSS.supports && CSS.supports('object-fit', 'cover'));
|
||||
if (!supportsSvg || !supportsLayout) return;
|
||||
|
||||
var logoBuild = '20260527j';
|
||||
var logoBuild = '20260527s';
|
||||
var fragments = {
|
||||
space: 'assets/hub-logo-fragment-space.svg',
|
||||
globe: 'assets/hub-logo-fragment-globe.svg',
|
||||
continents: 'assets/hub-logo-continents-mercator-globe.svg',
|
||||
gridOverlay: 'assets/hub-logo-fragment-globe-grid-overlay.svg'
|
||||
gridOverlay: 'assets/hub-logo-fragment-globe-grid-overlay.svg',
|
||||
compass: 'assets/hub-logo-fragment-compass.svg'
|
||||
};
|
||||
var parts = {
|
||||
full: ['space', 'globe', 'continents', 'gridOverlay'],
|
||||
full: ['space', 'globe', 'continents', 'gridOverlay', 'compass'],
|
||||
space: ['space'],
|
||||
globe: ['globe', 'continents', 'gridOverlay']
|
||||
globe: ['globe', 'continents', 'gridOverlay', 'compass']
|
||||
};
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
@@ -123,6 +148,12 @@
|
||||
img.alt = '';
|
||||
img.decoding = 'async';
|
||||
img.loading = 'lazy';
|
||||
if (name === 'compass') {
|
||||
img.id = 'hub-compass-layer';
|
||||
img.setAttribute('role', 'button');
|
||||
img.setAttribute('tabindex', '0');
|
||||
img.setAttribute('aria-label', 'Open deployment model');
|
||||
}
|
||||
stage.appendChild(img);
|
||||
});
|
||||
|
||||
@@ -135,9 +166,14 @@
|
||||
stage.setAttribute('aria-hidden', 'true');
|
||||
|
||||
var docsModal = document.getElementById('hub-docs-modal');
|
||||
var docsBody = document.getElementById('hub-docs-body');
|
||||
var compassHit = document.getElementById('hub-compass-hit');
|
||||
if (!docsModal || !docsBody || !compassHit) return;
|
||||
var docsToc = document.getElementById('hub-docs-toc');
|
||||
var docsPanels = document.getElementById('hub-docs-panels');
|
||||
var compassLayer = document.getElementById('hub-compass-layer');
|
||||
var docsApi = '/app/androidcast_project/crashes/api/hub_deploy_docs.php';
|
||||
var docsQueue = [];
|
||||
var docsLoading = false;
|
||||
var docsCache = {};
|
||||
var docsPanelsById = {};
|
||||
|
||||
function closeDocs() {
|
||||
docsModal.hidden = true;
|
||||
@@ -149,42 +185,540 @@
|
||||
docsModal.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
|
||||
function loadDocs(section) {
|
||||
var url = '/app/androidcast_project/crashes/api/hub_deploy_docs.php';
|
||||
if (section) url += '?section=' + encodeURIComponent(section);
|
||||
docsBody.innerHTML = '<p class="muted">Loading...</p>';
|
||||
openDocs();
|
||||
fetch(url, { headers: { 'Accept': 'application/json' } })
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (payload) {
|
||||
if (!payload || !payload.ok) {
|
||||
docsBody.innerHTML = '<p class="muted">Failed to load deployment model.</p>';
|
||||
return;
|
||||
}
|
||||
docsBody.innerHTML = payload.html || '<p class="muted">No content.</p>';
|
||||
})
|
||||
.catch(function () {
|
||||
docsBody.innerHTML = '<p class="muted">Network error while loading deployment model.</p>';
|
||||
});
|
||||
function xhrJson(url, onOk, onErr) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.setRequestHeader('Accept', 'application/json');
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState !== 4) return;
|
||||
if (xhr.status < 200 || xhr.status >= 300) {
|
||||
if (onErr) onErr();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
onOk(JSON.parse(xhr.responseText));
|
||||
} catch (e) {
|
||||
if (onErr) onErr();
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
compassHit.addEventListener('click', function () { loadDocs('intro'); });
|
||||
function ensureDocsPanel(sectionId, title) {
|
||||
if (docsPanelsById[sectionId]) return docsPanelsById[sectionId];
|
||||
var item = document.createElement('div');
|
||||
item.className = 'detail-tree-item hub-docs-section';
|
||||
item.dataset.section = sectionId;
|
||||
var panelId = 'hub-doc-panel-' + sectionId;
|
||||
item.innerHTML =
|
||||
'<div class="detail-tree-row report-row" role="button" tabindex="0" data-tree-toggle data-controls="' + panelId + '">' +
|
||||
'<span class="report-tree-cell">' +
|
||||
'<button type="button" class="report-tree-toggle" aria-expanded="false" aria-controls="' + panelId + '" tabindex="-1">' +
|
||||
'<span class="report-tree-arrow" aria-hidden="true"></span>' +
|
||||
'</button>' +
|
||||
'</span>' +
|
||||
'<span class="detail-tree-caption">' + title + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="detail-tree-body" id="' + panelId + '" hidden><p class="muted">Loading...</p></div>';
|
||||
docsPanels.appendChild(item);
|
||||
docsPanelsById[sectionId] = item;
|
||||
bindDocsTree(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
function bindDocsTree(root) {
|
||||
root.querySelectorAll('[data-tree-toggle]').forEach(function (row) {
|
||||
var panelId = row.getAttribute('data-controls');
|
||||
var panel = panelId ? document.getElementById(panelId) : null;
|
||||
var btn = row.querySelector('.report-tree-toggle');
|
||||
if (!panel) return;
|
||||
var toggle = function () {
|
||||
var open = panel.hidden;
|
||||
panel.hidden = !open;
|
||||
if (btn) btn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
row.classList.toggle('report-row--open', open);
|
||||
};
|
||||
if (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
toggle();
|
||||
});
|
||||
}
|
||||
row.addEventListener('click', toggle);
|
||||
row.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
toggle();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderSectionHtml(sectionId, html) {
|
||||
var item = docsPanelsById[sectionId];
|
||||
if (!item) return;
|
||||
var body = item.querySelector('.detail-tree-body');
|
||||
var row = item.querySelector('.detail-tree-row');
|
||||
var btn = item.querySelector('.report-tree-toggle');
|
||||
if (body) body.innerHTML = html || '<p class="muted">No content.</p>';
|
||||
if (body && row && body.hidden) {
|
||||
body.hidden = false;
|
||||
row.classList.add('report-row--open');
|
||||
if (btn) btn.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
function pumpDocsQueue() {
|
||||
if (docsLoading || docsQueue.length === 0) return;
|
||||
docsLoading = true;
|
||||
var sectionId = docsQueue.shift();
|
||||
if (docsCache[sectionId]) {
|
||||
renderSectionHtml(sectionId, docsCache[sectionId]);
|
||||
docsLoading = false;
|
||||
pumpDocsQueue();
|
||||
return;
|
||||
}
|
||||
xhrJson(docsApi + '?section=' + encodeURIComponent(sectionId), function (payload) {
|
||||
if (payload && payload.ok) {
|
||||
docsCache[sectionId] = payload.html || '';
|
||||
renderSectionHtml(sectionId, docsCache[sectionId]);
|
||||
} else {
|
||||
renderSectionHtml(sectionId, '<p class="muted">Failed to load section.</p>');
|
||||
}
|
||||
docsLoading = false;
|
||||
pumpDocsQueue();
|
||||
}, function () {
|
||||
renderSectionHtml(sectionId, '<p class="muted">Network error while loading section.</p>');
|
||||
docsLoading = false;
|
||||
pumpDocsQueue();
|
||||
});
|
||||
}
|
||||
|
||||
function queueSection(sectionId, front) {
|
||||
if (!sectionId) return;
|
||||
if (front) docsQueue.unshift(sectionId);
|
||||
else docsQueue.push(sectionId);
|
||||
pumpDocsQueue();
|
||||
}
|
||||
|
||||
function buildTocNode(node, parentEl) {
|
||||
var children = node.children || [];
|
||||
if (children.length) {
|
||||
children.forEach(function (child) {
|
||||
ensureDocsPanel(child.id, child.title);
|
||||
queueSection(child.id, false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
ensureDocsPanel(node.id, node.title);
|
||||
queueSection(node.id, false);
|
||||
}
|
||||
|
||||
function openDeploymentModel(prioritySection) {
|
||||
openDocs();
|
||||
docsPanels.innerHTML = '';
|
||||
docsPanelsById = {};
|
||||
docsCache = {};
|
||||
docsQueue = [];
|
||||
docsLoading = false;
|
||||
docsToc.innerHTML = '<p class="muted hub-docs-toc-title">Table of contents</p>';
|
||||
|
||||
xhrJson(docsApi + '?action=toc', function (payload) {
|
||||
if (!payload || !payload.ok || !payload.sections) {
|
||||
docsPanels.innerHTML = '<p class="muted">Failed to load table of contents.</p>';
|
||||
return;
|
||||
}
|
||||
payload.sections.forEach(function (node) {
|
||||
var group = document.createElement('div');
|
||||
group.className = 'hub-docs-toc-group';
|
||||
var head = document.createElement('button');
|
||||
head.type = 'button';
|
||||
head.className = 'hub-docs-toc-head';
|
||||
head.textContent = node.title;
|
||||
head.dataset.section = node.id;
|
||||
head.addEventListener('click', function () {
|
||||
var kids = node.children || [];
|
||||
if (kids.length) queueSection(kids[0].id, true);
|
||||
else queueSection(node.id, true);
|
||||
});
|
||||
group.appendChild(head);
|
||||
(node.children || []).forEach(function (child) {
|
||||
var leaf = document.createElement('button');
|
||||
leaf.type = 'button';
|
||||
leaf.className = 'hub-docs-toc-leaf';
|
||||
leaf.textContent = child.title;
|
||||
leaf.dataset.section = child.id;
|
||||
leaf.addEventListener('click', function () { queueSection(child.id, true); });
|
||||
group.appendChild(leaf);
|
||||
});
|
||||
docsToc.appendChild(group);
|
||||
buildTocNode(node, docsPanels);
|
||||
});
|
||||
if (prioritySection) queueSection(prioritySection, true);
|
||||
else if (payload.default_section) queueSection(payload.default_section, true);
|
||||
}, function () {
|
||||
docsPanels.innerHTML = '<p class="muted">Network error while loading deployment model.</p>';
|
||||
});
|
||||
}
|
||||
|
||||
function bindCompassOpen() {
|
||||
if (!compassLayer) return;
|
||||
var open = function () { openDeploymentModel(); };
|
||||
compassLayer.addEventListener('click', open);
|
||||
compassLayer.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Enter' || ev.key === ' ') {
|
||||
ev.preventDefault();
|
||||
open();
|
||||
}
|
||||
});
|
||||
}
|
||||
bindCompassOpen();
|
||||
|
||||
docsModal.addEventListener('click', function (ev) {
|
||||
var target = ev.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
if (target.hasAttribute('data-close-docs')) {
|
||||
closeDocs();
|
||||
return;
|
||||
}
|
||||
var link = target.closest('[data-doc-link]');
|
||||
if (link) {
|
||||
ev.preventDefault();
|
||||
loadDocs(link.getAttribute('data-doc-link') || 'intro');
|
||||
}
|
||||
if (target.hasAttribute('data-close-docs')) closeDocs();
|
||||
});
|
||||
document.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Escape' && !docsModal.hidden) closeDocs();
|
||||
});
|
||||
|
||||
var cityLabels = {
|
||||
'America/New_York': 'NEW YORK',
|
||||
'Europe/Moscow': 'MOSCOW',
|
||||
'Asia/Shanghai': 'BEIJING',
|
||||
'Africa/Johannesburg': 'CAPETOWN'
|
||||
};
|
||||
var roman = ['XII', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI'];
|
||||
|
||||
function pad2(v) { return String(v).padStart(2, '0'); }
|
||||
function systemZone() { return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; }
|
||||
|
||||
function secondaryZones(primary) {
|
||||
var preferred = ['America/New_York', 'Europe/Moscow', 'Asia/Shanghai'];
|
||||
var list = preferred.filter(function (z) { return z !== primary; });
|
||||
while (list.length < 3) {
|
||||
if (!list.includes('Africa/Johannesburg') && primary !== 'Africa/Johannesburg') list.push('Africa/Johannesburg');
|
||||
else list.push('UTC');
|
||||
}
|
||||
return list.slice(0, 3);
|
||||
}
|
||||
|
||||
function timeParts(date, zone) {
|
||||
var parts = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: zone, hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit'
|
||||
}).formatToParts(date);
|
||||
var map = {};
|
||||
parts.forEach(function (p) { if (p.type !== 'literal') map[p.type] = p.value; });
|
||||
return { hh: map.hour || '00', mm: map.minute || '00', ss: map.second || '00' };
|
||||
}
|
||||
|
||||
function formatDate(date, zone) {
|
||||
var parts = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: zone, weekday: 'short', day: '2-digit', month: 'short', year: 'numeric'
|
||||
}).formatToParts(date);
|
||||
var map = {};
|
||||
parts.forEach(function (p) { if (p.type !== 'literal') map[p.type] = p.value; });
|
||||
return (map.weekday || '---').slice(0, 3).toUpperCase() + ' '
|
||||
+ (map.day || '00') + ' '
|
||||
+ (map.month || '---').slice(0, 3).toUpperCase() + ' '
|
||||
+ (map.year || '0000');
|
||||
}
|
||||
|
||||
function buildWorldClock(tz) {
|
||||
var cell = document.createElement('div');
|
||||
cell.className = 'cw-cell cw-world-cell';
|
||||
var label = document.createElement('div');
|
||||
label.className = 'cw-world-label';
|
||||
label.textContent = cityLabels[tz] || tz.toUpperCase();
|
||||
cell.appendChild(label);
|
||||
|
||||
var dial = document.createElement('div');
|
||||
dial.className = 'cw-dial';
|
||||
for (var i = 0; i < 12; i++) {
|
||||
var n = document.createElement('div');
|
||||
n.className = 'cw-roman';
|
||||
if (i === 0 || i === 3 || i === 6 || i === 9) n.className += ' cw-roman-major';
|
||||
else n.className += ' cw-roman-minor';
|
||||
n.style.setProperty('--i', String(i));
|
||||
n.textContent = roman[i];
|
||||
dial.appendChild(n);
|
||||
}
|
||||
|
||||
var hour = document.createElement('div');
|
||||
hour.className = 'cw-hand cw-hour';
|
||||
var minute = document.createElement('div');
|
||||
minute.className = 'cw-hand cw-minute';
|
||||
var second = document.createElement('div');
|
||||
second.className = 'cw-hand cw-second';
|
||||
var pin = document.createElement('div');
|
||||
pin.className = 'cw-pin';
|
||||
dial.appendChild(hour);
|
||||
dial.appendChild(minute);
|
||||
dial.appendChild(second);
|
||||
dial.appendChild(pin);
|
||||
cell.appendChild(dial);
|
||||
|
||||
return { tz: tz, hour: hour, minute: minute, second: second, root: cell };
|
||||
}
|
||||
|
||||
var overtimeReady = false;
|
||||
var overtimeSymbolIds = {};
|
||||
|
||||
function overtimeSymbolId(ch) {
|
||||
if (ch >= '0' && ch <= '9') return 'ot-' + ch;
|
||||
if (ch === ':') return 'ot-colon';
|
||||
if (ch === '+') return 'ot-plus';
|
||||
if (ch === '-') return 'ot-minus';
|
||||
if (ch === ' ') return 'ot-space';
|
||||
if (ch >= 'A' && ch <= 'Z') return 'ot-' + ch;
|
||||
return null;
|
||||
}
|
||||
|
||||
function loadOvertimeAlphabet(done) {
|
||||
var host = document.getElementById('overtime-alphabet-host');
|
||||
if (!host) { done(false); return; }
|
||||
fetch('assets/overtime-01-alphabet.svg?v=' + logoBuild)
|
||||
.then(function (res) { return res.text(); })
|
||||
.then(function (svgText) {
|
||||
host.innerHTML = svgText;
|
||||
host.querySelectorAll('symbol').forEach(function (sym) {
|
||||
if (sym.id) overtimeSymbolIds[sym.id] = sym;
|
||||
});
|
||||
overtimeReady = true;
|
||||
done(true);
|
||||
})
|
||||
.catch(function () { done(false); });
|
||||
}
|
||||
|
||||
function buildGlyphGrid(svg, vb) {
|
||||
var seg = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
||||
seg.setAttribute('class', 'ot-grid');
|
||||
var w = vb.width || 20;
|
||||
var h = vb.height || 32;
|
||||
var hPad = Math.max(1, w * 0.12);
|
||||
var topY = Math.max(1, h * 0.04);
|
||||
var midY = h * 0.47;
|
||||
var botY = h * 0.86;
|
||||
var segW = Math.max(6, w - hPad * 2);
|
||||
var segH = Math.max(2, h * 0.09);
|
||||
var vW = Math.max(2, w * 0.14);
|
||||
var vH = Math.max(6, h * 0.33);
|
||||
var leftX = Math.max(1, hPad * 0.45);
|
||||
var rightX = Math.max(leftX + vW + 1, w - leftX - vW);
|
||||
var upperY = topY + segH;
|
||||
var lowerY = midY + segH * 0.6;
|
||||
var defs = [
|
||||
[hPad, topY, segW, segH],
|
||||
[leftX, upperY, vW, vH],
|
||||
[rightX, upperY, vW, vH],
|
||||
[hPad, midY, segW, segH],
|
||||
[leftX, lowerY, vW, vH],
|
||||
[rightX, lowerY, vW, vH],
|
||||
[hPad, botY, segW, segH]
|
||||
];
|
||||
defs.forEach(function (d) {
|
||||
var r = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
r.setAttribute('x', d[0].toFixed(2));
|
||||
r.setAttribute('y', d[1].toFixed(2));
|
||||
r.setAttribute('width', d[2].toFixed(2));
|
||||
r.setAttribute('height', d[3].toFixed(2));
|
||||
r.setAttribute('rx', '1');
|
||||
seg.appendChild(r);
|
||||
});
|
||||
if (w >= 22) {
|
||||
var d1 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
d1.setAttribute('x', (w * 0.34).toFixed(2));
|
||||
d1.setAttribute('y', (h * 0.15).toFixed(2));
|
||||
d1.setAttribute('width', Math.max(2, w * 0.10).toFixed(2));
|
||||
d1.setAttribute('height', Math.max(6, h * 0.34).toFixed(2));
|
||||
d1.setAttribute('rx', '1');
|
||||
d1.setAttribute('transform', 'rotate(22 ' + (w * 0.39).toFixed(2) + ' ' + (h * 0.32).toFixed(2) + ')');
|
||||
seg.appendChild(d1);
|
||||
var d2 = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
||||
d2.setAttribute('x', (w * 0.56).toFixed(2));
|
||||
d2.setAttribute('y', (h * 0.15).toFixed(2));
|
||||
d2.setAttribute('width', Math.max(2, w * 0.10).toFixed(2));
|
||||
d2.setAttribute('height', Math.max(6, h * 0.34).toFixed(2));
|
||||
d2.setAttribute('rx', '1');
|
||||
d2.setAttribute('transform', 'rotate(-22 ' + (w * 0.61).toFixed(2) + ' ' + (h * 0.32).toFixed(2) + ')');
|
||||
seg.appendChild(d2);
|
||||
}
|
||||
svg.appendChild(seg);
|
||||
}
|
||||
|
||||
function renderOvertimeText(el, text, opts) {
|
||||
opts = opts || {};
|
||||
if (!el) return;
|
||||
el.innerHTML = '';
|
||||
if (!overtimeReady) {
|
||||
el.textContent = text;
|
||||
return;
|
||||
}
|
||||
String(text).toUpperCase().split('').forEach(function (ch) {
|
||||
var sid = overtimeSymbolId(ch);
|
||||
if (!sid || !overtimeSymbolIds[sid]) return;
|
||||
var wrap = document.createElement('span');
|
||||
wrap.className = 'ot-glyph-wrap' + (opts.bold ? ' ot-glyph-wrap--bold' : '');
|
||||
if (opts.blinkColon && ch === ':') wrap.className += ' ot-colon-blink';
|
||||
var sym = overtimeSymbolIds[sid];
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
var viewBox = sym.getAttribute('viewBox') || '0 0 20 32';
|
||||
svg.setAttribute('viewBox', viewBox);
|
||||
svg.setAttribute('class', 'ot-glyph');
|
||||
var vb = viewBox.split(/\s+/).map(function (v) { return Number(v) || 0; });
|
||||
buildGlyphGrid(svg, { width: vb[2] || 20, height: vb[3] || 32 });
|
||||
var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
|
||||
use.setAttribute('href', '#' + sid);
|
||||
use.setAttribute('class', 'ot-active');
|
||||
svg.appendChild(use);
|
||||
wrap.appendChild(svg);
|
||||
el.appendChild(wrap);
|
||||
});
|
||||
}
|
||||
|
||||
function renderOvertimeDigits(el, text, blinkColon, bold) {
|
||||
renderOvertimeText(el, text, { blinkColon: blinkColon, bold: bold });
|
||||
}
|
||||
|
||||
function regionLabel(tz) {
|
||||
var parts = tz.split('/');
|
||||
if (parts.length >= 2) {
|
||||
return parts[0].replace(/_/g, ' ').toUpperCase() + '/' + parts[parts.length - 1].replace(/_/g, ' ').toUpperCase();
|
||||
}
|
||||
return tz.toUpperCase();
|
||||
}
|
||||
|
||||
function gmtOffsetLabel(date, tz) {
|
||||
try {
|
||||
var parts = new Intl.DateTimeFormat('en-US', { timeZone: tz, timeZoneName: 'shortOffset' }).formatToParts(date);
|
||||
var name = parts.find(function (p) { return p.type === 'timeZoneName'; });
|
||||
if (name && name.value) {
|
||||
return name.value.replace('GMT', 'GMT').replace('UTC', 'GMT').toUpperCase();
|
||||
}
|
||||
} catch (e) {}
|
||||
return 'GMT+0';
|
||||
}
|
||||
|
||||
function formatDateLine(date, zone) {
|
||||
var parts = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone: zone, weekday: 'short', day: '2-digit', month: 'short', year: 'numeric'
|
||||
}).formatToParts(date);
|
||||
var map = {};
|
||||
parts.forEach(function (p) { if (p.type !== 'literal') map[p.type] = p.value; });
|
||||
return (map.weekday || '---').slice(0, 3).toUpperCase() + ' '
|
||||
+ String(Number(map.day || '0')).padStart(2, '0') + ' '
|
||||
+ (map.month || '---').slice(0, 3).toUpperCase() + ' '
|
||||
+ (map.year || '0000');
|
||||
}
|
||||
|
||||
var primaryTz = systemZone();
|
||||
var worldRow = document.getElementById('cw-world-row');
|
||||
var secondary = secondaryZones(primaryTz).map(function (tz) {
|
||||
var model = buildWorldClock(tz);
|
||||
worldRow.appendChild(model.root);
|
||||
return model;
|
||||
});
|
||||
|
||||
function updateClockWidget() {
|
||||
var now = new Date();
|
||||
var p = timeParts(now, primaryTz);
|
||||
renderOvertimeDigits(document.getElementById('cw-hm-lcd'), p.hh + ':' + p.mm, false, true);
|
||||
renderOvertimeDigits(document.getElementById('cw-ss-lcd'), ':' + p.ss, true, true);
|
||||
renderOvertimeText(document.getElementById('cw-tz-text'), gmtOffsetLabel(now, primaryTz), { bold: true });
|
||||
document.getElementById('cw-region').textContent = regionLabel(primaryTz);
|
||||
document.getElementById('cw-date-line').textContent = formatDateLine(now, primaryTz);
|
||||
|
||||
secondary.forEach(function (clock) {
|
||||
var t = timeParts(now, clock.tz);
|
||||
var h = Number(t.hh), m = Number(t.mm), s = Number(t.ss);
|
||||
var hDeg = ((h % 12) + (m / 60) + (s / 3600)) * 30;
|
||||
var mDeg = (m + (s / 60)) * 6;
|
||||
var sDeg = s * 6;
|
||||
clock.hour.style.transform = 'translate(-50%, -100%) rotate(' + hDeg + 'deg)';
|
||||
clock.minute.style.transform = 'translate(-50%, -100%) rotate(' + mDeg + 'deg)';
|
||||
clock.second.style.transform = 'translate(-50%, -100%) rotate(' + sDeg + 'deg)';
|
||||
});
|
||||
}
|
||||
|
||||
(function makeClockWidgetInteractive() {
|
||||
var widget = document.getElementById('hub-clock-widget');
|
||||
if (!widget) return;
|
||||
var arrowPath = 'M44.106,42.943H3.987L44.933,1.995c0.456-0.455,0.456-1.196,0-1.651c-0.452-0.455-1.195-0.455-1.651,0L2.336,41.292V1.168C2.336,0.524,1.81,0,1.168,0C0.523,0,0,0.524,0,1.168v44.111h44.111c0.644,0,1.168-0.522,1.168-1.168C45.279,43.469,44.755,42.943,44.106,42.943z';
|
||||
var handles = ['nw', 'ne', 'sw', 'se'];
|
||||
handles.forEach(function (pos) {
|
||||
var h = document.createElement('span');
|
||||
h.className = 'cw-resize-handle cw-resize-' + pos;
|
||||
h.dataset.resize = pos;
|
||||
h.setAttribute('aria-hidden', 'true');
|
||||
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.setAttribute('viewBox', '0 0 45.279 45.279');
|
||||
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
||||
path.setAttribute('d', arrowPath);
|
||||
svg.appendChild(path);
|
||||
h.appendChild(svg);
|
||||
widget.appendChild(h);
|
||||
});
|
||||
|
||||
var drag = null;
|
||||
widget.addEventListener('pointerdown', function (ev) {
|
||||
var target = ev.target;
|
||||
if (!(target instanceof Element)) return;
|
||||
var box = widget.getBoundingClientRect();
|
||||
var handle = target.closest('[data-resize]');
|
||||
var resizeDir = handle ? handle.getAttribute('data-resize') : null;
|
||||
if (resizeDir) {
|
||||
drag = {
|
||||
mode: 'resize',
|
||||
dir: resizeDir,
|
||||
x: ev.clientX, y: ev.clientY,
|
||||
left: box.left, top: box.top, width: box.width, height: box.height
|
||||
};
|
||||
} else {
|
||||
drag = { mode: 'move', dx: ev.clientX - box.left, dy: ev.clientY - box.top };
|
||||
}
|
||||
widget.setPointerCapture(ev.pointerId);
|
||||
ev.preventDefault();
|
||||
});
|
||||
|
||||
widget.addEventListener('pointermove', function (ev) {
|
||||
if (!drag) return;
|
||||
if (drag.mode === 'move') {
|
||||
widget.style.left = Math.max(0, ev.clientX - drag.dx) + 'px';
|
||||
widget.style.top = Math.max(0, ev.clientY - drag.dy) + 'px';
|
||||
} else {
|
||||
var minW = 280, minH = 210;
|
||||
var dx = ev.clientX - drag.x;
|
||||
var dy = ev.clientY - drag.y;
|
||||
var left = drag.left, top = drag.top, width = drag.width, height = drag.height;
|
||||
if (drag.dir.indexOf('e') !== -1) width = Math.max(minW, drag.width + dx);
|
||||
if (drag.dir.indexOf('s') !== -1) height = Math.max(minH, drag.height + dy);
|
||||
if (drag.dir.indexOf('w') !== -1) {
|
||||
width = Math.max(minW, drag.width - dx);
|
||||
left = drag.left + (drag.width - width);
|
||||
}
|
||||
if (drag.dir.indexOf('n') !== -1) {
|
||||
height = Math.max(minH, drag.height - dy);
|
||||
top = drag.top + (drag.height - height);
|
||||
}
|
||||
widget.style.left = Math.max(0, left) + 'px';
|
||||
widget.style.top = Math.max(0, top) + 'px';
|
||||
widget.style.width = width + 'px';
|
||||
widget.style.minWidth = width + 'px';
|
||||
widget.style.transform = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
widget.addEventListener('pointerup', function (ev) {
|
||||
if (!drag) return;
|
||||
drag = null;
|
||||
widget.releasePointerCapture(ev.pointerId);
|
||||
});
|
||||
})();
|
||||
loadOvertimeAlphabet(function () {
|
||||
updateClockWidget();
|
||||
setInterval(updateClockWidget, 1000);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user