1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dupre World — vertical, even spacing</title>
<style>
:root{
--ground:#050505; --cream:#f3e7c5; --silver:#bfc4d0; --steel:#969385;
--dim:#7c838a; --gold:#dab53d; --gold-hi:#ffd75f; --slate:#54677d;
--night:#39435a;
--mono:"BerkeleyMono Nerd Font","Berkeley Mono",ui-monospace,monospace;
}
* { box-sizing: border-box; }
body {
margin:0; min-height:100vh; background:var(--ground);
display:flex; flex-direction:column; justify-content:center;
padding:48px 64px; gap:22px;
font-family:var(--mono); color:var(--silver);
cursor:none; overflow:hidden;
}
.masthead {
color:var(--steel); font-size:12px; letter-spacing:0.35em;
text-transform:uppercase; margin-bottom:8px;
}
.masthead b { color:var(--gold); font-weight:400; }
.row { display:flex; align-items:baseline; gap:16px; }
.city { color:var(--steel); font-size:13px; letter-spacing:0.22em;
text-transform:uppercase; min-width:150px; }
.time { color:var(--cream); font-size:30px; letter-spacing:0.05em;
min-width:150px; }
.meta { color:var(--dim); font-size:12px; letter-spacing:0.12em; }
.meta .tz { color:var(--steel); }
.row.night .time { color:var(--slate); }
.row.night .city::after { content:" ☾"; color:var(--night); }
.row.home .time { color:var(--gold-hi); }
.row.home .city { color:var(--gold); }
</style>
</head>
<body>
<div class="masthead"><b>DUPRE</b> · WORLD WATCH</div>
<div id="stack"></div>
<script>
/* Roster mirrors ~/.config/waybar/worldclock.conf (tz|label|lat|lon). The
real face receives this as ?cities=<json>; embedded here for the prototype. */
const ROSTER = [
{ tz:"Pacific/Auckland", label:"Wellington", lon:174.78 },
{ tz:"Australia/Sydney", label:"Sydney", lon:151.21 },
{ tz:"Asia/Tokyo", label:"Tokyo", lon:139.69 },
{ tz:"Asia/Shanghai", label:"Shanghai", lon:121.47 },
{ tz:"Asia/Kolkata", label:"Delhi", lon:77.21 },
{ tz:"Asia/Yerevan", label:"Yerevan", lon:44.51 },
{ tz:"Europe/Istanbul", label:"Istanbul", lon:28.98 },
{ tz:"Europe/Athens", label:"Athens", lon:23.73 },
{ tz:"Europe/Paris", label:"Paris", lon:2.35 },
{ tz:"Europe/London", label:"London", lon:-0.13 },
{ tz:"America/New_York", label:"New York", lon:-74.01 },
{ tz:"America/Chicago", label:"New Orleans", lon:-90.07 },
{ tz:"America/Los_Angeles", label:"Berkeley", lon:-122.27},
{ tz:"America/Anchorage", label:"Anchorage", lon:-149.90},
{ tz:"Pacific/Honolulu", label:"Honolulu", lon:-157.86},
];
/* Region names in Craig's "US Central" style, not the tz's major city.
Intl longGeneric is the fallback for a zone not in the map. */
const TZNAME = {
"Pacific/Honolulu":"US Hawaii", "America/Anchorage":"US Alaska",
"America/Los_Angeles":"US Pacific", "America/Chicago":"US Central",
"America/New_York":"US Eastern", "Europe/London":"UK",
"Europe/Paris":"Central Europe", "Europe/Athens":"Eastern Europe",
"Europe/Istanbul":"Türkiye", "Asia/Yerevan":"Armenia",
"Asia/Kolkata":"India", "Asia/Shanghai":"China", "Asia/Tokyo":"Japan",
"Australia/Sydney":"Australia East", "Pacific/Auckland":"New Zealand",
};
function tzName(tz) {
if (TZNAME[tz]) return TZNAME[tz];
try {
const parts = new Intl.DateTimeFormat("en-US",
{ timeZone: tz, timeZoneName: "longGeneric" }).formatToParts(new Date());
const p = parts.find((x) => x.type === "timeZoneName");
return p ? p.value : tz.split("/").pop().replace(/_/g, " ");
} catch (e) { return tz.split("/").pop().replace(/_/g, " "); }
}
const home = Intl.DateTimeFormat().resolvedOptions().timeZone;
const stack = document.getElementById("stack");
stack.className = "";
const rows = ROSTER.map((c) => {
const row = document.createElement("div");
row.className = "row";
row.style.marginBottom = "18px";
const city = document.createElement("div"); city.className = "city";
city.textContent = c.label;
const time = document.createElement("div"); time.className = "time";
const meta = document.createElement("div"); meta.className = "meta";
row.append(city, time, meta);
stack.appendChild(row);
return { c, row, time, meta };
});
function fmt(tz) {
const now = new Date();
const p = new Intl.DateTimeFormat("en-US", {
timeZone: tz, hour12: true, weekday: "short",
day: "numeric", hour: "numeric", minute: "2-digit",
}).formatToParts(now);
const g = (t) => (p.find((x) => x.type === t) || {}).value || "";
const h24 = new Intl.DateTimeFormat("en-US",
{ timeZone: tz, hour12: false, hour: "2-digit" }).formatToParts(now)
.find((x) => x.type === "hour").value;
return {
time: g("hour") + ":" + g("minute") + " " + g("dayPeriod"),
date: g("weekday") + " " + g("day"),
night: (parseInt(h24, 10) % 24) >= 19 || (parseInt(h24, 10) % 24) < 7,
};
}
function tick() {
for (const e of rows) {
const f = fmt(e.c.tz);
e.time.textContent = f.time;
e.meta.innerHTML = f.date + " · <span class='tz'>"
+ tzName(e.c.tz) + "</span>";
const isHome = e.c.tz === home;
e.row.classList.toggle("home", isHome);
e.row.classList.toggle("night", f.night && !isHome);
}
}
tick();
setInterval(tick, 5000);
</script>
</body>
</html>
|