diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..c92d2ee
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/webtools.iml b/.idea/webtools.iml
new file mode 100644
index 0000000..5e764c4
--- /dev/null
+++ b/.idea/webtools.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3b92b15
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,23 @@
+GOROOT := $(shell go env | grep GOROOT | sed -e "s/GOROOT=//g" | sed -e "s/[']//g")
+
+.Phony: build
+build: clean build.wasm build.site
+
+.Phony: clean
+clean:
+ -rm -r target
+ mkdir target
+
+.Phony: build.wasm
+build.wasm:
+ mkdir target/wasm
+ GOOS=js GOARCH=wasm go build -o target/wasm/clocks.wasm ./cmds/clocks
+ cp $(GOROOT)/lib/wasm/wasm_exec.js target/wasm/.
+
+.Phony: build.site
+build.site:
+ cp -r site/* target/.
+
+.Phony: dev
+dev: build
+ ( cd target ; python3 -m http.server )
\ No newline at end of file
diff --git a/cmds/clocks/elems.go b/cmds/clocks/elems.go
new file mode 100644
index 0000000..6bbfa1b
--- /dev/null
+++ b/cmds/clocks/elems.go
@@ -0,0 +1,13 @@
+package main
+
+import "syscall/js"
+
+func findClockElements(id string) htmlElements {
+ doc := js.Global().Get("document")
+ rootClockElem := doc.Call("querySelector", "#"+id)
+ return htmlElements{
+ locationDiv: rootClockElem.Call("querySelector", ".location"),
+ timeDiv: rootClockElem.Call("querySelector", ".time"),
+ dateDiv: rootClockElem.Call("querySelector", ".date"),
+ }
+}
diff --git a/cmds/clocks/main.go b/cmds/clocks/main.go
new file mode 100644
index 0000000..cb48fd4
--- /dev/null
+++ b/cmds/clocks/main.go
@@ -0,0 +1,87 @@
+//go:build js
+
+package main
+
+import (
+ "log"
+ "time"
+
+ _ "time/tzdata"
+)
+
+func main() {
+ melTime, err := time.LoadLocation("Australia/Melbourne")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ sg, err := time.LoadLocation("Asia/Singapore")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ nyc, err := time.LoadLocation("America/New_York")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ la, err := time.LoadLocation("America/Los_Angeles")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ utc := time.UTC
+
+ clocks := []clock{
+ {
+ name: "UTC",
+ loc: utc,
+ elements: findClockElements("utc"),
+ },
+ {
+ name: "Australia/Melbourne",
+ loc: melTime,
+ elements: findClockElements("mel"),
+ },
+ {
+ name: "Asia/Singapore",
+ loc: sg,
+ elements: findClockElements("sg"),
+ },
+ {
+ name: "America/New_York",
+ loc: nyc,
+ elements: findClockElements("nyc"),
+ },
+ {
+ name: "America/Los_Angeles",
+ loc: la,
+ elements: findClockElements("la"),
+ },
+ }
+
+ updateClocks(clocks)
+ go startClockUpdater(clocks)
+
+ <-make(chan struct{})
+}
+
+func startClockUpdater(clocks []clock) {
+ for range time.Tick(1 * time.Second) {
+ updateClocks(clocks)
+ }
+}
+
+func updateClocks(clocks []clock) {
+ n := time.Now()
+ for _, c := range clocks {
+ updateClock(n, c)
+ }
+}
+
+func updateClock(t time.Time, clk clock) {
+ lt := t.In(clk.loc)
+ clk.elements.locationDiv.Set("innerText", clk.name)
+ clk.elements.dateDiv.Set("innerText", lt.Format("02 Jan 2006"))
+ clk.elements.timeDiv.Set("innerText", lt.Format("15:04:05"))
+}
diff --git a/cmds/clocks/models.go b/cmds/clocks/models.go
new file mode 100644
index 0000000..91eab7b
--- /dev/null
+++ b/cmds/clocks/models.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+ "syscall/js"
+ "time"
+)
+
+type clock struct {
+ name string
+ loc *time.Location
+ elements htmlElements
+}
+
+type htmlElements struct {
+ locationDiv js.Value
+ timeDiv js.Value
+ dateDiv js.Value
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..71cbe1b
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/lmika/webtools
+
+go 1.24.3
diff --git a/site/clocks/index.html b/site/clocks/index.html
new file mode 100644
index 0000000..9654fd2
--- /dev/null
+++ b/site/clocks/index.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+ Clocks - Tools
+
+
+
+
+
+
+ Clocks
+ Various time zones
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/site/clocks/main.js b/site/clocks/main.js
new file mode 100644
index 0000000..ba74c81
--- /dev/null
+++ b/site/clocks/main.js
@@ -0,0 +1,8 @@
+import "/wasm/wasm_exec.js";
+
+const go = new Go();
+
+WebAssembly.instantiateStreaming(fetch("/wasm/clocks.wasm"), go.importObject)
+ .then((result) => {
+ go.run(result.instance);
+ });
\ No newline at end of file
diff --git a/site/clocks/style.css b/site/clocks/style.css
new file mode 100644
index 0000000..023a284
--- /dev/null
+++ b/site/clocks/style.css
@@ -0,0 +1,13 @@
+div.clock {
+ text-align: center;
+ margin-block-end: 1.8rem;
+}
+
+div.clock div.location {
+ color: rgb(175, 41, 29);
+}
+
+div.clock div.time {
+ font-size: 200%;
+ font-family: var(--pico-font-family-monospace);
+}
\ No newline at end of file
diff --git a/freelens-logo/index.html b/site/freelens-logo/index.html
similarity index 96%
rename from freelens-logo/index.html
rename to site/freelens-logo/index.html
index 30dc187..9c9f52b 100644
--- a/freelens-logo/index.html
+++ b/site/freelens-logo/index.html
@@ -4,8 +4,7 @@
- Freelens Logo
- Tools
+ Freelens Logo Creator - Tools
+
+
+ Clocks
+ Various time zones
+
+
+
+
+
+
+
+
diff --git a/target/clocks/index.html b/target/clocks/index.html
new file mode 100644
index 0000000..9654fd2
--- /dev/null
+++ b/target/clocks/index.html
@@ -0,0 +1,54 @@
+
+
+