Initial version
This commit is contained in:
commit
42ef6c66ea
244
clock.js
Normal file
244
clock.js
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
const DEFAULT_OPTIONS = {showSeconds: true}
|
||||||
|
const HOUR_NAMES = [
|
||||||
|
"Candle", "Ice", "Comet", "Owl", "Yarn", "Mist",
|
||||||
|
"Sprout", "Rainbow", "Worm", "Rabbit", "Blossom", "Nest",
|
||||||
|
"Coral", "Cherry", "Bee", "Melon", "Seashell", "Dragon",
|
||||||
|
"Chestnut", "Kite", "Mushroom", "Lightning", "Mountain", "Lantern",
|
||||||
|
];
|
||||||
|
const HOUR_WIDTH = 70;
|
||||||
|
const HOUR_ICONS = [
|
||||||
|
"🕯️️", "❄️️", "☄️️", "🦉️", "🧶️", "🌫️️",
|
||||||
|
"🌱️", "🌈️", "🪱️", "🐇️", "🌸️", "🪺️",
|
||||||
|
"🪸️", "🍒️", "🐝️", "🍉️", "🐚️", "🐉️",
|
||||||
|
"🌰️", "🪁️", "🍄️", "⚡️️", "⛰️️", "🏮️",
|
||||||
|
];
|
||||||
|
|
||||||
|
const loadSettings = () => {
|
||||||
|
var loadedOpts;
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = localStorage.getItem("options");
|
||||||
|
console.debug("Loaded", data);
|
||||||
|
loadedOpts = JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
loadedOpts = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug("Parsed as", loadedOpts);
|
||||||
|
|
||||||
|
return loadedOpts || DEFAULT_OPTIONS;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveSettings = () => {
|
||||||
|
console.debug("Saving", options)
|
||||||
|
localStorage.setItem("options", JSON.stringify(options));
|
||||||
|
};
|
||||||
|
|
||||||
|
const zeroPad = (num, numZeros) => {
|
||||||
|
if (num == 0) {
|
||||||
|
return "".padStart(numZeros, "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
var an = Math.abs (num);
|
||||||
|
var digitCount = 1 + Math.floor (Math.log (an) / Math.LN10);
|
||||||
|
|
||||||
|
if (digitCount >= numZeros) {
|
||||||
|
return String(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
var zeroString = Math.pow (10, numZeros - digitCount).toString ().substr (1);
|
||||||
|
|
||||||
|
return num < 0 ? "-" + zeroString + an : zeroString + an;
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateClock = () => {
|
||||||
|
var docCenter = document.body.offsetWidth / 2;
|
||||||
|
var momentNow = moment();
|
||||||
|
|
||||||
|
var momentUTC = momentNow.utc();
|
||||||
|
var utcHour = momentUTC.hours();
|
||||||
|
var utcMinute = momentUTC.minutes();
|
||||||
|
|
||||||
|
var momentLocal = momentNow.tz(options.timezone);
|
||||||
|
var localHour = momentLocal.hours();
|
||||||
|
var localMinute = momentLocal.minutes();
|
||||||
|
|
||||||
|
currentTimeContainer.innerHTML = (
|
||||||
|
HOUR_ICONS[utcHour]
|
||||||
|
+ "<br>"
|
||||||
|
+ momentLocal.locale(options.locale).format(options.showSeconds ? "LTS" : "LT")
|
||||||
|
+ "<br>"
|
||||||
|
+ HOUR_NAMES[utcHour]
|
||||||
|
);
|
||||||
|
|
||||||
|
var utcOffset = (
|
||||||
|
24 * HOUR_WIDTH
|
||||||
|
+ utcHour * HOUR_WIDTH
|
||||||
|
+ utcMinute * HOUR_WIDTH / 60
|
||||||
|
);
|
||||||
|
var localOffset = (
|
||||||
|
24 * HOUR_WIDTH
|
||||||
|
+ localHour * HOUR_WIDTH
|
||||||
|
+ localMinute * HOUR_WIDTH / 60
|
||||||
|
);
|
||||||
|
|
||||||
|
utcHourContainer.style.left = (docCenter - utcOffset - HOUR_WIDTH / 2) + "px";
|
||||||
|
hourNameContainer.style.left = (docCenter - utcOffset) + "px";
|
||||||
|
hourIconContainer.style.left = (docCenter - utcOffset) + "px";
|
||||||
|
localHourContainer.style.left = (docCenter - localOffset - HOUR_WIDTH / 2) + "px";
|
||||||
|
marker.style.left = docCenter + "px";
|
||||||
|
|
||||||
|
for (var nameSpan of document.getElementsByClassName("active")) {
|
||||||
|
nameSpan.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentNameSpan = document.getElementById("h-present-" + utcHour);
|
||||||
|
currentNameSpan.classList.add("active");
|
||||||
|
};
|
||||||
|
|
||||||
|
const showSettingsPanel = () => {
|
||||||
|
settingsPanel.style.display = "block";
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideSettingsPanel = () => {
|
||||||
|
settingsPanel.style.display = "none";
|
||||||
|
};
|
||||||
|
|
||||||
|
const createClock = () => {
|
||||||
|
for (round = 0; round < 3; round++) {
|
||||||
|
let prefix;
|
||||||
|
switch (round) {
|
||||||
|
case 0:
|
||||||
|
prefix = "past";
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
prefix = "present";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
prefix = "future";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (hour = 0; hour < 24; hour++) {
|
||||||
|
var nameClass;
|
||||||
|
|
||||||
|
if (hour < 6) {
|
||||||
|
nameClass = "winter";
|
||||||
|
} else if (hour < 12) {
|
||||||
|
nameClass = "spring";
|
||||||
|
} else if (hour < 18) {
|
||||||
|
nameClass = "summer";
|
||||||
|
} else {
|
||||||
|
nameClass = "autumn";
|
||||||
|
}
|
||||||
|
|
||||||
|
let utcSpan = document.createElement("span");
|
||||||
|
utcSpan.id = "u-" + prefix + "-" + hour;
|
||||||
|
utcSpan.innerHTML = "U " + zeroPad(hour, 2);
|
||||||
|
utcHourContainer.appendChild(utcSpan);
|
||||||
|
|
||||||
|
let localSpan = document.createElement("span");
|
||||||
|
localSpan.id = "l-" + prefix + "-" + hour;
|
||||||
|
localSpan.innerHTML = zeroPad(hour, 2);
|
||||||
|
localHourContainer.appendChild(localSpan);
|
||||||
|
|
||||||
|
let hourNameSpan = document.createElement("span");
|
||||||
|
hourNameSpan.id = "h-" + prefix + "-" + hour;
|
||||||
|
hourNameSpan.classList.add(nameClass);
|
||||||
|
hourNameSpan.innerHTML = HOUR_NAMES[hour];
|
||||||
|
hourNameContainer.appendChild(hourNameSpan);
|
||||||
|
|
||||||
|
let hourIconSpan = document.createElement("span");
|
||||||
|
hourIconSpan.id = "i-" + prefix + "-" + hour;
|
||||||
|
hourIconSpan.classList.add(nameClass);
|
||||||
|
hourIconSpan.innerHTML = HOUR_ICONS[hour];
|
||||||
|
hourIconContainer.appendChild(hourIconSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initSettings = () => {
|
||||||
|
var timeZoneSelector = document.getElementById("timezone-selector");
|
||||||
|
|
||||||
|
if (!options.timezone) {
|
||||||
|
options.timezone = moment.tz.guess();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tzname of moment.tz.names()) {
|
||||||
|
var tzElem = document.createElement("option");
|
||||||
|
tzElem.innerHTML = tzname;
|
||||||
|
|
||||||
|
if (tzname == options.timezone) {
|
||||||
|
tzElem.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeZoneSelector.appendChild(tzElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeZoneSelector.addEventListener("change", () => {
|
||||||
|
options.timezone = timeZoneSelector.value;
|
||||||
|
console.log(options)
|
||||||
|
updateClock();
|
||||||
|
saveSettings();
|
||||||
|
})
|
||||||
|
|
||||||
|
var localeSelector = document.getElementById("locale-selector");
|
||||||
|
|
||||||
|
if (!options.locale) {
|
||||||
|
options.locale = moment.locale();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (localename of moment.locales()) {
|
||||||
|
var locElem = document.createElement("option");
|
||||||
|
locElem.innerHTML = localename;
|
||||||
|
|
||||||
|
if (localename == options.locale) {
|
||||||
|
locElem.selected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
localeSelector.appendChild(locElem);
|
||||||
|
}
|
||||||
|
|
||||||
|
localeSelector.addEventListener("change", () => {
|
||||||
|
options.locale = localeSelector.value;
|
||||||
|
updateClock();
|
||||||
|
saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
var showSecondsOption = document.getElementById("show-seconds-option");
|
||||||
|
|
||||||
|
showSecondsOption.checked = options.showSeconds;
|
||||||
|
|
||||||
|
showSecondsOption.addEventListener("click", () => {
|
||||||
|
options.showSeconds = showSecondsOption.checked;
|
||||||
|
updateClock();
|
||||||
|
saveSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
showSettingsButton.addEventListener("click", showSettingsPanel);
|
||||||
|
closeSettingsButton.addEventListener("click", hideSettingsPanel);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run = () => {
|
||||||
|
document.documentElement.style.setProperty("--hour-width", HOUR_WIDTH + "px");
|
||||||
|
|
||||||
|
createClock();
|
||||||
|
initSettings();
|
||||||
|
|
||||||
|
addEventListener("resize", updateClock);
|
||||||
|
updateClock();
|
||||||
|
setInterval(updateClock, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var options = loadSettings();
|
||||||
|
|
||||||
|
let utcHourContainer = document.getElementById("utc-hours-inner");
|
||||||
|
let hourNameContainer = document.getElementById("hour-names-inner");
|
||||||
|
let hourIconContainer = document.getElementById("hour-icons-inner");
|
||||||
|
let localHourContainer = document.getElementById("local-hours-inner");
|
||||||
|
let currentTimeContainer = document.getElementById("current-time");
|
||||||
|
let showSettingsButton = document.getElementById("settings-button");
|
||||||
|
let closeSettingsButton = document.getElementById("settings-close");
|
||||||
|
let settingsPanel = document.getElementById("settings-wrapper")
|
||||||
|
let marker = document.getElementById("marker");
|
||||||
|
|
||||||
|
run();
|
37
index.html
Normal file
37
index.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Seasonal Hours Clock</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="moment-with-locales.min.js"></script>
|
||||||
|
<script src="moment-timezone-with-data.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="marker"></div>
|
||||||
|
<div id="utc-hours"><div id="utc-hours-inner"></div></div>
|
||||||
|
<div id="hour-names"><div id="hour-names-inner"></div></div>
|
||||||
|
<div id="hour-icons"><div id="hour-icons-inner"></div></div>
|
||||||
|
<div id="local-hours"><div id="local-hours-inner"></div></div>
|
||||||
|
<div id="current-time-wrapper"><div id="current-time"></div></div>
|
||||||
|
<div id="settings-button">⚙️</div>
|
||||||
|
<div id="settings-wrapper">
|
||||||
|
<div id="settings-close">×</div>
|
||||||
|
<div id="settings">
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<p>Changes are saved immediately!</p>
|
||||||
|
<div id="timezone-changer">
|
||||||
|
<span>Time zone</span><select id="timezone-selector"></select><br>
|
||||||
|
</div>
|
||||||
|
<div id="locale-changer">
|
||||||
|
<span>Locale</span><select id="locale-selector"></select><br>
|
||||||
|
</div>
|
||||||
|
<div id="show-seconds">
|
||||||
|
<span>Show seconds</span><input type="checkbox" id="show-seconds-option">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="clock.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
moment-timezone-with-data.min.js
vendored
Normal file
1
moment-timezone-with-data.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
moment-with-locales.min.js
vendored
Normal file
2
moment-with-locales.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
182
style.css
Normal file
182
style.css
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
body {
|
||||||
|
background-color: #2e3440;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-wrapper {
|
||||||
|
width: 40%;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: white;
|
||||||
|
padding: 5px;
|
||||||
|
position: relative;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-close {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: calc(5px + .5em);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings {
|
||||||
|
padding: 1em;
|
||||||
|
border: 1px solid red;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings span {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings select,input {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings br {
|
||||||
|
float: none;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings h2 {
|
||||||
|
margin: 0 0 1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#settings-button {
|
||||||
|
position: absolute;
|
||||||
|
left: 5px;
|
||||||
|
bottom: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#marker {
|
||||||
|
width: 1px;
|
||||||
|
height: 75px;
|
||||||
|
background-color: rgba(255, 255, 0, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
top: 3em;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-time-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#current-time {
|
||||||
|
font-size: 300%;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#utc-hours {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#utc-hours-inner {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#utc-hours-inner span {
|
||||||
|
width: var(--hour-width);
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
color: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
#local-hours {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
margin-top: -48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#local-hours-inner {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#local-hours-inner span {
|
||||||
|
width: var(--hour-width);
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
color: #cccccc;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-icons {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-icons-inner {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-icons-inner span {
|
||||||
|
width: var(--hour-width);
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner span {
|
||||||
|
width: var(--hour-width);
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
color: rgb(238, 187, 85);
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .winter {
|
||||||
|
background-color: rgb(70, 62, 108);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .active.winter {
|
||||||
|
background-color: rgb(100, 92, 138);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .spring {
|
||||||
|
background-color: rgb(55, 87, 55);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .active.spring {
|
||||||
|
background-color: rgb(85, 117, 85);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .summer {
|
||||||
|
background-color: rgb(113, 92, 43);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .active.summer {
|
||||||
|
background-color: rgb(143, 122, 73);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .autumn {
|
||||||
|
background-color: rgb(108, 68, 44);
|
||||||
|
}
|
||||||
|
|
||||||
|
#hour-names-inner .active.autumn {
|
||||||
|
background-color: rgb(138, 98, 74);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user