diff options
Diffstat (limited to 'docs/prototypes/2026-07-23-world-clock-vertical-even.html')
| -rw-r--r-- | docs/prototypes/2026-07-23-world-clock-vertical-even.html | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/docs/prototypes/2026-07-23-world-clock-vertical-even.html b/docs/prototypes/2026-07-23-world-clock-vertical-even.html new file mode 100644 index 0000000..58d24a1 --- /dev/null +++ b/docs/prototypes/2026-07-23-world-clock-vertical-even.html @@ -0,0 +1,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> |
