mirror of
git://f0xx.org/android_cast
synced 2026-07-29 09:38:08 +03:00
483 lines
13 KiB
HTML
483 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Clock Widget</title>
|
|
<style>
|
|
:root {
|
|
--bg: #0f1218;
|
|
--panel: #171c24;
|
|
--panel-2: #131820;
|
|
--border: rgba(255, 255, 255, 0.72);
|
|
--text: #eef3ff;
|
|
--muted: #9aa5ba;
|
|
--accent: #dbe7ff;
|
|
--digit: #d8f6b8;
|
|
--digit-dim: #76906a;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
min-height: 100vh;
|
|
display: grid;
|
|
place-items: center;
|
|
background: radial-gradient(circle at 20% 15%, #1f2a3b 0%, var(--bg) 50%, #090c12 100%);
|
|
color: var(--text);
|
|
font-family: "Segoe UI", Tahoma, sans-serif;
|
|
padding: 18px;
|
|
}
|
|
|
|
.clock-widget {
|
|
width: min(980px, 100%);
|
|
border: 1px solid var(--border);
|
|
box-shadow: 0 10px 34px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.08) inset;
|
|
background: linear-gradient(180deg, #141a22 0%, #0f131a 100%);
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.row {
|
|
width: 100%;
|
|
display: grid;
|
|
border-top: 1px solid rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
.row:first-child {
|
|
border-top: 0;
|
|
}
|
|
|
|
.cell {
|
|
border-left: 1px solid rgba(255, 255, 255, 0.3);
|
|
min-height: 72px;
|
|
padding: 10px;
|
|
background: var(--panel);
|
|
}
|
|
|
|
.cell:first-child {
|
|
border-left: 0;
|
|
}
|
|
|
|
.row-1 {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.row-2 {
|
|
grid-template-columns: repeat(4, 1fr);
|
|
}
|
|
|
|
.row-3 {
|
|
grid-template-columns: repeat(3, 1fr);
|
|
}
|
|
|
|
.main-clock {
|
|
min-height: 220px;
|
|
background:
|
|
linear-gradient(180deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0)),
|
|
repeating-linear-gradient(
|
|
90deg,
|
|
rgba(255, 255, 255, 0.02) 0,
|
|
rgba(255, 255, 255, 0.02) 1px,
|
|
transparent 1px,
|
|
transparent 6px
|
|
),
|
|
#111822;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 10px;
|
|
text-align: center;
|
|
}
|
|
|
|
.primary-zone {
|
|
color: var(--muted);
|
|
letter-spacing: 0.14em;
|
|
font-weight: 600;
|
|
font-size: 0.82rem;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.primary-time {
|
|
display: inline-flex;
|
|
align-items: flex-end;
|
|
gap: 6px;
|
|
padding: 10px 16px 8px;
|
|
border: 1px solid rgba(220, 235, 180, 0.45);
|
|
border-radius: 6px;
|
|
background: linear-gradient(180deg, #202a1e 0%, #181f16 100%);
|
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04), 0 8px 18px rgba(0, 0, 0, 0.45);
|
|
font-family: "Overtime LCD", "DS-Digital", "Digital-7 Mono", "Consolas", monospace;
|
|
font-variant-numeric: tabular-nums;
|
|
color: var(--digit);
|
|
text-shadow: 0 0 5px rgba(190, 240, 150, 0.35), 0 0 1px rgba(180, 220, 145, 0.7);
|
|
letter-spacing: 0.06em;
|
|
line-height: 1;
|
|
}
|
|
|
|
.primary-time .hh,
|
|
.primary-time .mm {
|
|
font-size: clamp(3.1rem, 9.2vw, 5.7rem);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.primary-time .colon {
|
|
display: inline-block;
|
|
width: 0.55em;
|
|
text-align: center;
|
|
font-size: clamp(3.1rem, 9.2vw, 5.7rem);
|
|
animation: casioBlink 1.25s linear infinite;
|
|
}
|
|
|
|
.primary-time .ss {
|
|
min-width: 2ch;
|
|
font-size: clamp(1.35rem, 3.1vw, 2rem);
|
|
margin-left: 2px;
|
|
margin-bottom: 0.5em;
|
|
color: var(--digit);
|
|
}
|
|
|
|
.date-cell {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
background: var(--panel-2);
|
|
color: #e8ecf6;
|
|
font-family: "Consolas", "Menlo", monospace;
|
|
font-size: clamp(0.92rem, 1.5vw, 1.1rem);
|
|
letter-spacing: 0.08em;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
text-align: center;
|
|
}
|
|
|
|
.placeholder {
|
|
background: linear-gradient(180deg, #161d27 0%, #121923 100%);
|
|
}
|
|
|
|
.analog-cell {
|
|
min-height: 250px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
gap: 12px;
|
|
background: radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 55%), #121923;
|
|
}
|
|
|
|
.tz-label {
|
|
margin-top: 2px;
|
|
font-size: 0.82rem;
|
|
letter-spacing: 0.14em;
|
|
color: var(--muted);
|
|
text-transform: uppercase;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.wall-clock {
|
|
width: min(94%, 220px);
|
|
aspect-ratio: 1 / 1;
|
|
border-radius: 50%;
|
|
position: relative;
|
|
border: 4px solid rgba(239, 243, 252, 0.78);
|
|
background:
|
|
radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0) 36%),
|
|
radial-gradient(circle at 50% 50%, #f6f8fc 0%, #e5ebf6 62%, #d3dceb 100%);
|
|
box-shadow:
|
|
0 12px 24px rgba(0, 0, 0, 0.45),
|
|
inset -8px -8px 18px rgba(0, 0, 0, 0.12),
|
|
inset 8px 10px 14px rgba(255, 255, 255, 0.4);
|
|
}
|
|
|
|
.roman {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
width: 20px;
|
|
height: 20px;
|
|
margin: -10px;
|
|
transform: rotate(calc(var(--i) * 30deg)) translateY(calc(var(--radius) * -1)) rotate(calc(var(--i) * -30deg));
|
|
font-family: "Times New Roman", serif;
|
|
font-size: clamp(0.76rem, 1.1vw, 0.95rem);
|
|
font-weight: 700;
|
|
color: #2a3447;
|
|
text-align: center;
|
|
user-select: none;
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
|
|
.hand {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform-origin: 50% 100%;
|
|
transform: translate(-50%, -100%) rotate(0deg);
|
|
border-radius: 999px;
|
|
transition: transform 0.1s linear;
|
|
}
|
|
|
|
.hand.hour {
|
|
width: 6px;
|
|
height: 28%;
|
|
background: #1f2f46;
|
|
}
|
|
|
|
.hand.minute {
|
|
width: 4px;
|
|
height: 38%;
|
|
background: #2e4767;
|
|
}
|
|
|
|
.hand.second {
|
|
width: 2px;
|
|
height: 42%;
|
|
background: #b63b3b;
|
|
}
|
|
|
|
.pin {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
width: 12px;
|
|
height: 12px;
|
|
margin-left: -6px;
|
|
margin-top: -6px;
|
|
border-radius: 50%;
|
|
background: #111928;
|
|
border: 2px solid #f3f6ff;
|
|
z-index: 10;
|
|
}
|
|
|
|
.digital-subtime {
|
|
margin-top: auto;
|
|
margin-bottom: 3px;
|
|
font-family: "Consolas", monospace;
|
|
font-size: 0.9rem;
|
|
color: #d6deef;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
|
|
@keyframes casioBlink {
|
|
0%, 20% {
|
|
opacity: 0;
|
|
}
|
|
20.01%, 60% {
|
|
opacity: 1;
|
|
}
|
|
60.01%, 100% {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.row-2 {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.row-3 {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="clock-widget" id="clockWidget">
|
|
<div class="row row-1">
|
|
<div class="cell main-clock">
|
|
<div class="primary-zone" id="primaryZoneLabel"></div>
|
|
<div class="primary-time" aria-label="Primary time">
|
|
<span class="hh" id="primaryHH">00</span>
|
|
<span class="colon">:</span>
|
|
<span class="mm" id="primaryMM">00</span>
|
|
<span class="ss" id="primarySS">00</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row row-2">
|
|
<div class="cell placeholder"></div>
|
|
<div class="cell placeholder"></div>
|
|
<div class="cell placeholder"></div>
|
|
<div class="cell date-cell" id="dateCell">MON 01 JAN 2026</div>
|
|
</div>
|
|
|
|
<div class="row row-3" id="secondaryRow"></div>
|
|
</div>
|
|
|
|
<script>
|
|
(function () {
|
|
var CITY_LABELS = {
|
|
"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"];
|
|
var WEEK = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
var MONTH = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"];
|
|
|
|
function pad2(v) {
|
|
return String(v).padStart(2, "0");
|
|
}
|
|
|
|
function getSystemTimeZone() {
|
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
}
|
|
|
|
function computeSecondaryZones(primaryZone) {
|
|
var preferred = ["America/New_York", "Europe/Moscow", "Asia/Shanghai"];
|
|
var secondaries = preferred.filter(function (z) { return z !== primaryZone; });
|
|
while (secondaries.length < 3) {
|
|
if (!secondaries.includes("Africa/Johannesburg") && primaryZone !== "Africa/Johannesburg") {
|
|
secondaries.push("Africa/Johannesburg");
|
|
} else {
|
|
secondaries.push("UTC");
|
|
}
|
|
}
|
|
return secondaries.slice(0, 3);
|
|
}
|
|
|
|
function formatDatePrimary(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;
|
|
});
|
|
|
|
var day = (map.weekday || "---").slice(0, 3).toUpperCase();
|
|
var dd = map.day || "00";
|
|
var mon = (map.month || "---").slice(0, 3).toUpperCase();
|
|
var yyyy = map.year || "0000";
|
|
return day + " " + dd + " " + mon + " " + yyyy;
|
|
}
|
|
|
|
function getTimeParts(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 buildSecondaryClock(tz) {
|
|
var cell = document.createElement("div");
|
|
cell.className = "cell analog-cell";
|
|
|
|
var label = document.createElement("div");
|
|
label.className = "tz-label";
|
|
label.textContent = CITY_LABELS[tz] || tz.toUpperCase();
|
|
cell.appendChild(label);
|
|
|
|
var clock = document.createElement("div");
|
|
clock.className = "wall-clock";
|
|
|
|
for (var i = 0; i < 12; i++) {
|
|
var numeral = document.createElement("div");
|
|
numeral.className = "roman";
|
|
numeral.style.setProperty("--i", String(i));
|
|
numeral.style.setProperty("--radius", "41%");
|
|
numeral.textContent = ROMAN[i];
|
|
clock.appendChild(numeral);
|
|
}
|
|
|
|
var hour = document.createElement("div");
|
|
hour.className = "hand hour";
|
|
var minute = document.createElement("div");
|
|
minute.className = "hand minute";
|
|
var second = document.createElement("div");
|
|
second.className = "hand second";
|
|
var pin = document.createElement("div");
|
|
pin.className = "pin";
|
|
|
|
clock.appendChild(hour);
|
|
clock.appendChild(minute);
|
|
clock.appendChild(second);
|
|
clock.appendChild(pin);
|
|
cell.appendChild(clock);
|
|
|
|
var digital = document.createElement("div");
|
|
digital.className = "digital-subtime";
|
|
digital.textContent = "--:--:--";
|
|
cell.appendChild(digital);
|
|
|
|
return {
|
|
tz: tz,
|
|
root: cell,
|
|
hour: hour,
|
|
minute: minute,
|
|
second: second,
|
|
digital: digital
|
|
};
|
|
}
|
|
|
|
var primaryZone = getSystemTimeZone();
|
|
var secondaryZones = computeSecondaryZones(primaryZone);
|
|
|
|
var secondaryRow = document.getElementById("secondaryRow");
|
|
var secondaryClocks = secondaryZones.map(function (tz) {
|
|
var c = buildSecondaryClock(tz);
|
|
secondaryRow.appendChild(c.root);
|
|
return c;
|
|
});
|
|
|
|
document.getElementById("primaryZoneLabel").textContent = "PRIMARY TZ: " + (CITY_LABELS[primaryZone] || primaryZone.toUpperCase());
|
|
|
|
function update() {
|
|
var now = new Date();
|
|
var primary = getTimeParts(now, primaryZone);
|
|
|
|
document.getElementById("primaryHH").textContent = primary.hh;
|
|
document.getElementById("primaryMM").textContent = primary.mm;
|
|
document.getElementById("primarySS").textContent = primary.ss;
|
|
document.getElementById("dateCell").textContent = formatDatePrimary(now, primaryZone);
|
|
|
|
secondaryClocks.forEach(function (clock) {
|
|
var p = getTimeParts(now, clock.tz);
|
|
var hNum = Number(p.hh);
|
|
var mNum = Number(p.mm);
|
|
var sNum = Number(p.ss);
|
|
|
|
var hourDeg = ((hNum % 12) + mNum / 60 + sNum / 3600) * 30;
|
|
var minuteDeg = (mNum + sNum / 60) * 6;
|
|
var secondDeg = sNum * 6;
|
|
|
|
clock.hour.style.transform = "translate(-50%, -100%) rotate(" + hourDeg + "deg)";
|
|
clock.minute.style.transform = "translate(-50%, -100%) rotate(" + minuteDeg + "deg)";
|
|
clock.second.style.transform = "translate(-50%, -100%) rotate(" + secondDeg + "deg)";
|
|
clock.digital.textContent = p.hh + ":" + p.mm + ":" + p.ss;
|
|
});
|
|
}
|
|
|
|
update();
|
|
setInterval(update, 1000);
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|