mirror of
git://f0xx.org/android_cast
synced 2026-07-29 06:58:51 +03:00
sync
This commit is contained in:
482
examples/app_hub/clock-widget.html
Normal file
482
examples/app_hub/clock-widget.html
Normal file
@@ -0,0 +1,482 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user