Repos / s4g / 1fbb2934bb
commit 1fbb2934bb4ec0010a5b5f0ac8611c36f926312f
Author: Nhân <hi@imnhan.com>
Date:   Sun Aug 6 12:37:08 2023 +0700

    rename to s4g; remove gui

diff --git a/README.md b/README.md
index 03a3bc8..b308cf2 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,13 @@
 
 **Warning: work in progress**
 
-WebMaker2000 is an in-place static site generator, meaning processed files are
-stored right next to their sources. This simplifies composing (source dir
-layout _is_ finished website layout; static assets no longer need to be moved
-around) and publishing (simply `rsync`/`git push` your whole dir). It aims to
-be beginner-friendly while encouraging users to fiddle with html/css. To that
-end, the core feature set is intentionally simple:
+`s4g` (Stupidly Simple Static Site Generator) is an in-place static site
+generator, meaning processed files are stored right next to their sources.
+This simplifies composing (source dir layout _is_ finished website layout;
+static assets no longer need to be moved around) and publishing
+(simply `rsync`/`git push` your whole dir).
+It aims to be beginner-friendly while encouraging users to fiddle with
+html/css. To that end, the core feature set is intentionally simple:
 
 - [x] Finds all `*.dj` files, generates `*.html` in the same place
     + Per-page metadata allows using custom template
@@ -16,15 +17,13 @@
 - [x] Generates RSS/Atom feed
 - [x] Generates redirects from a `redirects.txt` file
 
-Quality-of-life features are not neglected:
+Quality-of-life features:
 
 - [x] Livereload with no browser plugin (works but currently polls which is
   noisy, should probably upgrade to websockets)
 - [x] Shows user error messages on the livereloaded web page
-- [ ] Just enough GUI so user doesn't have to touch a terminal
-- [ ] 1-click deploy to popular static hosting targets (git push, rsync, etc.)
 
-There's a sample site up at <https://nhanb.github.io/webmaker2000/about/> which
+There's a sample site up at <https://nhanb.github.io/s4g/about/> which
 also further explains why this project exists.
 
 The markup language of choice is [Djot](https://djot.net/) because it's the
@@ -37,15 +36,13 @@
 
 ```sh
 # Install
-go install go.imnhan.com/webmaker2000@latest
+go install go.imnhan.com/s4g@latest
 
 # Create new site
-webmaker2000 new -f ~/my-blog
+s4g new -f ~/my-blog
 
 # Run program, which:
 # - listens to changes and automatically re-generates
 # - starts a local HTTP server for preview, also livereloads on changes
-webmaker2000 serve -f ~/my-blog
+s4g serve -f ~/my-blog
 ```
-
-GUI Coming Soon (tm).
diff --git a/docs/_theme/includes.tmpl b/docs/_theme/includes.tmpl
index 7f9fdcb..aa3130b 100644
--- a/docs/_theme/includes.tmpl
+++ b/docs/_theme/includes.tmpl
@@ -23,7 +23,7 @@
 {{- if .Site.ShowFooter -}}
 <footer>
 © {{if eq .StartYear .Now.Year}}{{.StartYear}}{{else}}{{.StartYear}}–{{.Now.Year}}{{end}} {{.Site.AuthorName}}<br>
-Made with <a href="https://github.com/nhanb/webmaker2000">WebMaker2000</a>
+Made with <a href="https://github.com/nhanb/s4g">s4g</a>
 </footer>
 {{- end -}}
 {{- end}}
diff --git a/docs/about/index.dj b/docs/about/index.dj
index eed959a..bbe60d5 100644
--- a/docs/about/index.dj
+++ b/docs/about/index.dj
@@ -2,7 +2,7 @@ Title: About
 ShowInFeed: false
 ---
 
-This is a sample website to demonstrate some features of [WebMaker2000][1].
+This is a sample website to demonstrate some features of [s4g][1].
 It also doubles as a lazy end-to-end test suite until the core design
 stablizes. Its source code, as well as output, is in the [/docs/][src] dir.
 
@@ -11,23 +11,10 @@ Don't worry too much about their actual content.
 
 ## Sales pitch
 
-There are 2 main goals:
-
-### 1. Enable more people to Have Opinions On The Internet
-
-It's 2023. Bug-ridden CVE-infested custom-ordered Wordpress sites shouldn't be
-the layman's default option anymore. [Free][gh] [static][gl] [hosting][cf] is
-[available][nc] everywhere, yet it remains a privilege mostly enjoyed by a
-selective group of nerds who are already well versed with the command line. I
-aim to make a user-friendly static site generator that also allows publishing
-to neocities/git/rsync targets with one click.
-
-### 2. Encourage creativity in web design
-
-WebMaker2000 strives to assist, not obscure or constrain. It assumes little
-structure, and encourages personalization. {-The world needs more-} I want more
-creative, [weird][cozy], [neocities-frontpage-worthy][ncf] websites instead of
-yet another cookie-cutter jekyll template.
+s4g aims to assist, not obscure. All it does is reading `.dj` files and
+spitting out `.html`s, leaving unrelated files alone. This means the user is
+free to structure their web directory however they want, and relative links
+work out of the box.
 
 ## Features
 
@@ -40,9 +27,8 @@ So far we have:
 Less obvious features:
 
 - Flexible root dir (the finished site can be served from a subfolder)
-- Livereload on local server
-
-GUI Coming Soon™.
+- Livereload
+- In-browser error messages
 
 ``` =html
 <style>
@@ -50,11 +36,5 @@ h3 { font-size: 1em; }
 </style>
 ```
 
-[1]: https://github.com/nhanb/webmaker2000
-[gh]: https://pages.github.com/
-[gl]: https://docs.gitlab.com/ee/user/project/pages/
-[cf]: https://pages.cloudflare.com/
-[nc]: https://neocities.org/
-[ncf]: https://neocities.org/browse
-[cozy]: https://www.cozynet.org/
-[src]: https://github.com/nhanb/webmaker2000/tree/master/docs
+[1]: https://github.com/nhanb/s4g
+[src]: https://github.com/nhanb/s4g/tree/master/docs
diff --git a/docs/about/index.html b/docs/about/index.html
index d1681b2..4099f11 100644
--- a/docs/about/index.html
+++ b/docs/about/index.html
@@ -5,16 +5,16 @@
   <meta charset="utf-8" />
   <title>About | CoolZone</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/webmaker2000/feed.xml">
-  <link rel="stylesheet" href="/webmaker2000/_theme/base.css">
+  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/s4g/feed.xml">
+  <link rel="stylesheet" href="/s4g/_theme/base.css">
 </head>
 
 <body>
 
-<link rel="stylesheet" href="/webmaker2000/_theme/navbar.css">
+<link rel="stylesheet" href="/s4g/_theme/navbar.css">
 <nav>
-  <a href="/webmaker2000/">Home</a>
-  <a href="/webmaker2000/about/">About</a>
+  <a href="/s4g/">Home</a>
+  <a href="/s4g/about/">About</a>
 
 </nav>
 <hr class="nav-hr">
@@ -22,30 +22,17 @@
 
 <main>
 <h1>About</h1>
-<p>This is a sample website to demonstrate some features of <a href="https://github.com/nhanb/webmaker2000">WebMaker2000</a>.
+<p>This is a sample website to demonstrate some features of <a href="https://github.com/nhanb/s4g">s4g</a>.
 It also doubles as a lazy end-to-end test suite until the core design
-stablizes. Its source code, as well as output, is in the <a href="https://github.com/nhanb/webmaker2000/tree/master/docs">/docs/</a> dir.</p>
+stablizes. Its source code, as well as output, is in the <a href="https://github.com/nhanb/s4g/tree/master/docs">/docs/</a> dir.</p>
 <p>The articles themselves are just, uh, <em>colorful</em> lorem ipsums.
 Don’t worry too much about their actual content.</p>
 <section id="Sales-pitch">
 <h2>Sales pitch</h2>
-<p>There are 2 main goals:</p>
-<section id="1-Enable-more-people-to-Have-Opinions-On-The-Internet">
-<h3>1. Enable more people to Have Opinions On The Internet</h3>
-<p>It’s 2023. Bug-ridden CVE-infested custom-ordered Wordpress sites shouldn’t be
-the layman’s default option anymore. <a href="https://pages.github.com/">Free</a> <a href="https://docs.gitlab.com/ee/user/project/pages/">static</a> <a href="https://pages.cloudflare.com/">hosting</a> is
-<a href="https://neocities.org/">available</a> everywhere, yet it remains a privilege mostly enjoyed by a
-selective group of nerds who are already well versed with the command line. I
-aim to make a user-friendly static site generator that also allows publishing
-to neocities/git/rsync targets with one click.</p>
-</section>
-<section id="2-Encourage-creativity-in-web-design">
-<h3>2. Encourage creativity in web design</h3>
-<p>WebMaker2000 strives to assist, not obscure or constrain. It assumes little
-structure, and encourages personalization. <del>The world needs more</del> I want more
-creative, <a href="https://www.cozynet.org/">weird</a>, <a href="https://neocities.org/browse">neocities-frontpage-worthy</a> websites instead of
-yet another cookie-cutter jekyll template.</p>
-</section>
+<p>s4g aims to assist, not obscure. All it does is reading <code>.dj</code> files and
+spitting out <code>.html</code>s, leaving unrelated files alone. This means the user is
+free to structure their web directory however they want, and relative links
+work out of the box.</p>
 </section>
 <section id="Features">
 <h2>Features</h2>
@@ -67,10 +54,12 @@ <h2>Features</h2>
 Flexible root dir (the finished site can be served from a subfolder)
 </li>
 <li>
-Livereload on local server
+Livereload
+</li>
+<li>
+In-browser error messages
 </li>
 </ul>
-<p>GUI Coming Soon™.</p>
 <style>
 h3 { font-size: 1em; }
 </style>
@@ -80,7 +69,7 @@ <h2>Features</h2>
 
 <footer>
 © 2008–2023 Coolio McCool<br>
-Made with <a href="https://github.com/nhanb/webmaker2000">WebMaker2000</a>
+Made with <a href="https://github.com/nhanb/s4g">s4g</a>
 </footer>
 
 </body>
diff --git a/docs/feed.xml b/docs/feed.xml
index 4947aba..1d62a92 100644
--- a/docs/feed.xml
+++ b/docs/feed.xml
@@ -1,7 +1,7 @@
 <feed xmlns="http://www.w3.org/2005/Atom">
   <title>CoolZone</title>
   <id>https://coolzone.example.com/</id>
-  <link rel="self" href="/webmaker2000/feed.xml"></link>
+  <link rel="self" href="/s4g/feed.xml"></link>
   <updated>2023-04-05T00:00:00+07:00</updated>
   <author>
     <name>Coolio McCool</name>
@@ -10,15 +10,15 @@
   </author>
   <entry>
     <title>This is a motherfucking website.</title>
-    <id>https://coolzone.example.com/webmaker2000/mfws.html</id>
-    <link href="https://coolzone.example.com/webmaker2000/mfws.html"></link>
+    <id>https://coolzone.example.com/s4g/mfws.html</id>
+    <link href="https://coolzone.example.com/s4g/mfws.html"></link>
     <published>2023-04-05T00:00:00+07:00</published>
     <updated>2023-04-05T00:00:00+07:00</updated>
   </entry>
   <entry>
     <title>I&#39;m Going To Scale My Foot Up Your Ass</title>
-    <id>https://coolzone.example.com/webmaker2000/scale/</id>
-    <link href="https://coolzone.example.com/webmaker2000/scale/"></link>
+    <id>https://coolzone.example.com/s4g/scale/</id>
+    <link href="https://coolzone.example.com/s4g/scale/"></link>
     <published>2008-04-24T00:00:00+07:00</published>
     <updated>2008-04-24T00:00:00+07:00</updated>
   </entry>
diff --git a/docs/index.html b/docs/index.html
index 839474d..5387fce 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -5,8 +5,8 @@
   <meta charset="utf-8" />
   <title>Home | CoolZone</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/webmaker2000/feed.xml">
-  <link rel="stylesheet" href="/webmaker2000/_theme/base.css">
+  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/s4g/feed.xml">
+  <link rel="stylesheet" href="/s4g/_theme/base.css">
 </head>
 
 <body>
@@ -19,10 +19,10 @@ <h1 class="site-title">CoolZone</h1>
 <hr>
 
 <div class="pages">
-  <a href="/webmaker2000/">Home</a>
-  <a href="/webmaker2000/about/">About</a>
-  <a class="feed-link" href="/webmaker2000/feed.xml">
-    <img src="/webmaker2000/_theme/feed.svg" alt="Atom Feed" title="Atom Feed">
+  <a href="/s4g/">Home</a>
+  <a href="/s4g/about/">About</a>
+  <a class="feed-link" href="/s4g/feed.xml">
+    <img src="/s4g/_theme/feed.svg" alt="Atom Feed" title="Atom Feed">
   </a>
 </div>
 
@@ -35,12 +35,12 @@ <h1 class="site-title">CoolZone</h1>
 <ul>
   <li>
     <span class="time-prefix">2023-04-05 — </span>
-    <a href="/webmaker2000/mfws.html">This is a motherfucking website.</a>
+    <a href="/s4g/mfws.html">This is a motherfucking website.</a>
     <span class="time-suffix">(2023-04-05)</span>
   </li>
   <li>
     <span class="time-prefix">2008-04-24 — </span>
-    <a href="/webmaker2000/scale/">I&#39;m Going To Scale My Foot Up Your Ass</a>
+    <a href="/s4g/scale/">I&#39;m Going To Scale My Foot Up Your Ass</a>
     <span class="time-suffix">(2008-04-24)</span>
   </li>
 </ul>
@@ -96,7 +96,7 @@ <h1 class="site-title">CoolZone</h1>
 
 <footer>
 © 2008–2023 Coolio McCool<br>
-Made with <a href="https://github.com/nhanb/webmaker2000">WebMaker2000</a>
+Made with <a href="https://github.com/nhanb/s4g">s4g</a>
 </footer>
 
 
diff --git a/docs/mfws.html b/docs/mfws.html
index a45d28d..c3d0595 100644
--- a/docs/mfws.html
+++ b/docs/mfws.html
@@ -5,16 +5,16 @@
   <meta charset="utf-8" />
   <title>This is a motherfucking website. | CoolZone</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/webmaker2000/feed.xml">
-  <link rel="stylesheet" href="/webmaker2000/_theme/base.css">
+  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/s4g/feed.xml">
+  <link rel="stylesheet" href="/s4g/_theme/base.css">
 </head>
 
 <body>
 
-<link rel="stylesheet" href="/webmaker2000/_theme/navbar.css">
+<link rel="stylesheet" href="/s4g/_theme/navbar.css">
 <nav>
-  <a href="/webmaker2000/">Home</a>
-  <a href="/webmaker2000/about/">About</a>
+  <a href="/s4g/">Home</a>
+  <a href="/s4g/about/">About</a>
   <span class="posted-on">
     Posted on
     <time datetime="2023-04-05">
@@ -123,7 +123,7 @@ <h2>Epilogue</h2>
 
 <footer>
 © 2008–2023 Coolio McCool<br>
-Made with <a href="https://github.com/nhanb/webmaker2000">WebMaker2000</a>
+Made with <a href="https://github.com/nhanb/s4g">s4g</a>
 </footer>
 
 </body>
diff --git a/docs/mfws/index.html b/docs/mfws/index.html
index d3ca5e5..23fa9ae 100644
--- a/docs/mfws/index.html
+++ b/docs/mfws/index.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
-    <title>Redirecting to /webmaker2000/mfws.html</title>
-    <meta http-equiv="Refresh" content="0; URL=/webmaker2000/mfws.html" />
+    <title>Redirecting to /s4g/mfws.html</title>
+    <meta http-equiv="Refresh" content="0; URL=/s4g/mfws.html" />
   </head>
   <body>
-    The page you're looking for has been moved to <a href="/webmaker2000/mfws.html">/webmaker2000/mfws.html</a>.
+    The page you're looking for has been moved to <a href="/s4g/mfws.html">/s4g/mfws.html</a>.
   </body>
 </html>
diff --git a/docs/scale.html b/docs/scale.html
index df77cf9..03d8901 100644
--- a/docs/scale.html
+++ b/docs/scale.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
-    <title>Redirecting to /webmaker2000/scale/</title>
-    <meta http-equiv="Refresh" content="0; URL=/webmaker2000/scale/" />
+    <title>Redirecting to /s4g/scale/</title>
+    <meta http-equiv="Refresh" content="0; URL=/s4g/scale/" />
   </head>
   <body>
-    The page you're looking for has been moved to <a href="/webmaker2000/scale/">/webmaker2000/scale/</a>.
+    The page you're looking for has been moved to <a href="/s4g/scale/">/s4g/scale/</a>.
   </body>
 </html>
diff --git a/docs/scale/index.html b/docs/scale/index.html
index c22f440..e931c97 100644
--- a/docs/scale/index.html
+++ b/docs/scale/index.html
@@ -5,16 +5,16 @@
   <meta charset="utf-8" />
   <title>I&#39;m Going To Scale My Foot Up Your Ass | CoolZone</title>
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />
-  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/webmaker2000/feed.xml">
-  <link rel="stylesheet" href="/webmaker2000/_theme/base.css">
+  <link rel="alternate" type="application/atom+xml" title="Atom feed" href="/s4g/feed.xml">
+  <link rel="stylesheet" href="/s4g/_theme/base.css">
 </head>
 
 <body>
 <div class="navbar-container">
-<link rel="stylesheet" href="/webmaker2000/_theme/navbar.css">
+<link rel="stylesheet" href="/s4g/_theme/navbar.css">
 <nav>
-  <a href="/webmaker2000/">Home</a>
-  <a href="/webmaker2000/about/">About</a>
+  <a href="/s4g/">Home</a>
+  <a href="/s4g/about/">About</a>
   <span class="posted-on">
     Posted on
     <time datetime="2008-04-24">
@@ -96,7 +96,7 @@ <h2>tl;dr</h2>
 <div class="footer-container">
 <footer>
 © 2008–2023 Coolio McCool<br>
-Made with <a href="https://github.com/nhanb/webmaker2000">WebMaker2000</a>
+Made with <a href="https://github.com/nhanb/s4g">s4g</a>
 </footer>
 </div>
 
diff --git a/docs/website.wbmkr2k b/docs/website.s4g
similarity index 91%
rename from docs/website.wbmkr2k
rename to docs/website.s4g
index cf91244..d2055b0 100644
--- a/docs/website.wbmkr2k
+++ b/docs/website.s4g
@@ -1,7 +1,7 @@
 Address: https://coolzone.example.com
 Name: CoolZone
 Tagline: Cool people only.
-Root: /webmaker2000/
+Root: /s4g/
 NavbarLinks: index.dj, about/index.dj
 
 AuthorName: Coolio McCool
diff --git a/errs/errs.go b/errs/errs.go
index 2614757..6a674f6 100644
--- a/errs/errs.go
+++ b/errs/errs.go
@@ -5,7 +5,7 @@
 	"html/template"
 )
 
-// Represents a user input error, which in webmaker2000's case is almost
+// Represents a user input error, which in s4g's case is almost
 // always some malformed file.
 type UserErr struct {
 	File string
diff --git a/go.mod b/go.mod
index 622f03d..af3c911 100644
--- a/go.mod
+++ b/go.mod
@@ -1,4 +1,4 @@
-module go.imnhan.com/webmaker2000
+module go.imnhan.com/s4g
 
 go 1.20
 
diff --git a/gui/gui.go b/gui/gui.go
deleted file mode 100644
index cb3ec50..0000000
--- a/gui/gui.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package gui
-
-import (
-	"bufio"
-	_ "embed"
-	"fmt"
-	"io"
-	"os/exec"
-	"path/filepath"
-	"strings"
-
-	"go.imnhan.com/webmaker2000/gui/ipc"
-)
-
-//go:embed tcl/main.tcl
-var tclMain []byte
-
-//go:embed tcl/choose-task.tcl
-var tclChooseTask []byte
-
-func Main(tclPath string) {
-	interp := newInterp(tclPath)
-
-	go func() {
-		errscanner := bufio.NewScanner(interp.stderr)
-		for errscanner.Scan() {
-			errtext := errscanner.Text()
-			fmt.Printf("XXX %s\n", errtext)
-		}
-	}()
-
-	_, err := interp.stdin.Write(tclMain)
-	if err != nil {
-		panic(err)
-	}
-	println("Loaded main tcl script.")
-
-	fmt.Fprintln(interp.stdin, "initialize")
-
-	respond := func(values ...string) {
-		ipc.Respond(interp.stdin, values)
-	}
-
-	for req := range ipc.Requests(interp.stdout) {
-		switch req.Method {
-
-		case "forcefocus":
-			//err := forceFocus(req.Args[0])
-			//if err != nil {
-			//fmt.Printf("forcefocus: %s\n", err)
-			//}
-			respond("ok")
-		}
-
-	}
-
-	println("Tcl process terminated.")
-}
-
-type Task string
-
-const (
-	TaskOpen   Task = "open"
-	TaskCreate Task = "create"
-)
-
-func ChooseTask(tclPath string) (task Task, path string, ok bool) {
-	interp := newInterp(tclPath)
-	interp.stdin.Write(tclChooseTask)
-	interp.stdin.Close()
-	resp, err := io.ReadAll(interp.stdout)
-	if err != nil {
-		panic(err)
-	}
-
-	action, path, found := strings.Cut(strings.TrimSpace(string(resp)), " ")
-	if !found {
-		fmt.Println("No task chosen")
-		return "", "", false
-	}
-
-	switch action {
-	case string(TaskOpen):
-		return TaskOpen, filepath.Dir(path), true
-	case string(TaskCreate):
-		return TaskCreate, path, true
-	default:
-		fmt.Printf("Unexpected tclChooseTask output: %s\n", string(resp))
-		return "", "", false
-	}
-}
-
-type tclInterp struct {
-	cmd    *exec.Cmd
-	stdin  io.WriteCloser
-	stdout io.ReadCloser
-	stderr io.ReadCloser
-}
-
-func newInterp(tclPath string) *tclInterp {
-	cmd := exec.Command(tclPath, "-encoding", "utf-8")
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		panic(err)
-	}
-
-	stdin, err := cmd.StdinPipe()
-	if err != nil {
-		panic(err)
-	}
-
-	stderr, err := cmd.StderrPipe()
-	if err != nil {
-		panic(err)
-	}
-
-	err = cmd.Start()
-	if err != nil {
-		panic(err)
-	}
-
-	return &tclInterp{
-		cmd:    cmd,
-		stdin:  stdin,
-		stdout: stdout,
-		stderr: stderr,
-	}
-}
diff --git a/gui/gui_linux.go b/gui/gui_linux.go
deleted file mode 100644
index 877416f..0000000
--- a/gui/gui_linux.go
+++ /dev/null
@@ -1,7 +0,0 @@
-//go:build linux
-
-package gui
-
-const DefaultTclPath = "tclsh"
-
-func forceFocus(handle string) error { return nil }
diff --git a/gui/gui_windows.go b/gui/gui_windows.go
deleted file mode 100644
index f4cf208..0000000
--- a/gui/gui_windows.go
+++ /dev/null
@@ -1,25 +0,0 @@
-//go:build windows
-
-package gui
-
-import (
-	"fmt"
-	"strconv"
-
-	"github.com/lxn/win"
-)
-
-const DefaultTclPath = "./IronTcl/bin/wish86t.exe"
-
-func forceFocus(handle string) error {
-	hex := handle[2:] // trim the "0x" prefix
-	uintHandle, err := strconv.ParseUint(hex, 16, 64)
-	if err != nil {
-		return fmt.Errorf("failed to parse handle: %w", err)
-	}
-
-	h := win.HWND(uintptr(uintHandle))
-	win.SetForegroundWindow(h)
-	win.SetFocus(h)
-	return nil
-}
diff --git a/gui/ipc/ipc.go b/gui/ipc/ipc.go
deleted file mode 100644
index 2a8f426..0000000
--- a/gui/ipc/ipc.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package ipc
-
-import (
-	"bufio"
-	"fmt"
-	"io"
-	"log"
-	"strconv"
-	"strings"
-)
-
-type Request struct {
-	Method string
-	Args   []string
-}
-
-func debug(prefix string, msg string) {
-	out := prefix + " " + msg
-	if len(out) > 35 {
-		out = out[:35] + "[...]"
-	}
-	fmt.Println(out)
-}
-
-func Requests(r io.Reader) chan Request {
-	scanner := bufio.NewScanner(r)
-	ch := make(chan Request)
-	next := func() string {
-		scanner.Scan()
-		v := scanner.Text()
-		debug("-->", v)
-		return v
-	}
-
-	go func() {
-		for scanner.Scan() {
-			line := scanner.Text()
-			debug("-->", line)
-			request := strings.SplitN(line, " ", 2)
-			method := request[0]
-			numArgs, err := strconv.Atoi(request[1])
-			if err != nil {
-				panic(err)
-			}
-			args := make([]string, numArgs)
-			for i := 0; i < numArgs; i++ {
-				args[i] = next()
-			}
-
-			ch <- Request{Method: method, Args: args}
-		}
-		if err := scanner.Err(); err != nil {
-			log.Fatal(err)
-		}
-
-		close(ch)
-	}()
-
-	return ch
-}
-
-func Respond(w io.Writer, values []string) {
-	numValues := strconv.Itoa(len(values))
-	debug("<--", numValues)
-	fmt.Fprintln(w, numValues)
-	for i, val := range values {
-		// Only print debug message for the first 10 items
-		if i <= 10 {
-			var msg string
-			if i < 10 {
-				msg = val
-			} else if i == 10 {
-				msg = "[...]"
-			}
-			debug("<--", msg)
-		}
-
-		fmt.Fprintln(w, val)
-	}
-}
diff --git a/gui/tcl/choose-task.tcl b/gui/tcl/choose-task.tcl
deleted file mode 100644
index 74f92e2..0000000
--- a/gui/tcl/choose-task.tcl
+++ /dev/null
@@ -1,41 +0,0 @@
-package require Tk
-
-wm title . "Create or Open?"
-
-set OS [lindex $tcl_platform(os) 0]
-if {$OS == "Windows"} {
-    ttk::style theme use vista
-} elseif {$OS == "Darwin"} {
-    ttk::style theme use aqua
-} else {
-    ttk::style theme use clam
-}
-
-set types {
-    {{WebMaker2000 Files} {.wbmkr2k}}
-}
-
-ttk::frame .c -padding "10"
-ttk::label .c.label -text {Would you like to create a new blog, or open an existing one?}
-ttk::button .c.createBtn -text "Create..." -padding 5 -command {
-    #set filename [tk_getSaveFile -title "Create" -filetypes $types]
-    set filename [tk_chooseDirectory]
-    if {$filename != ""} {
-        puts "create ${filename}"
-        exit
-    }
-}
-ttk::button .c.openBtn -text "Open..." -padding 5 -command {
-    set filename [tk_getOpenFile -filetypes $types]
-    if {$filename != ""} {
-        puts "open ${filename}"
-        exit
-    }
-}
-
-grid .c -column 0 -row 0
-grid .c.label -column 0 -row 0 -columnspan 2 -pady "0 10"
-grid .c.createBtn -column 0 -row 1 -padx 10
-grid .c.openBtn -column 1 -row 1 -padx 10
-
-tk::PlaceWindow . center
diff --git a/gui/tcl/main.tcl b/gui/tcl/main.tcl
deleted file mode 100644
index 7dbf5ea..0000000
--- a/gui/tcl/main.tcl
+++ /dev/null
@@ -1,225 +0,0 @@
-# Tcl on Windows has unfortunate defaults:
-#   - cp1252 encoding, which will mangle utf-8 source code
-#   - crlf linebreaks instead of unix-style lf
-# Let's be consistent cross-platform to avoid surprises:
-encoding system "utf-8"
-foreach p {stdin stdout stderr} {
-    fconfigure $p -encoding "utf-8"
-    fconfigure $p -translation lf
-}
-
-package require Tk
-
-wm title . "WebMaker2000"
-tk appname webmaker2000
-
-set OS [lindex $tcl_platform(os) 0]
-if {$OS == "Windows"} {
-    ttk::style theme use vista
-} elseif {$OS == "Darwin"} {
-    ttk::style theme use aqua
-} else {
-    ttk::style theme use clam
-}
-
-wm protocol . WM_DELETE_WINDOW {
-    exit 0
-}
-
-proc initialize {} {
-    # By default this window is not focused and not even brought to
-    # foreground on Windows. I suspect it's because tcl is exec'ed from Go.
-    # The old "iconify, deiconify" trick no longer seems to work, so this time
-    # I'm passing it to Go to call the winapi's SetForegroundWindow directly.
-    if {$::tcl_platform(platform) == "windows"} {
-        windows_forcefocus
-    }
-}
-
-# Very simple line-based IPC system where Tcl client talks to Go server
-# via stdin/stdout
-proc ipc_write {method args} {
-    puts "$method [llength $args]"
-    foreach a $args {
-        puts "$a"
-    }
-}
-proc ipc_read {} {
-    set results {}
-    set numlines [gets stdin]
-    for {set i 0} {$i < $numlines} {incr i} {
-        lappend results [gets stdin]
-    }
-    return $results
-}
-proc ipc {method args} {
-    ipc_write $method {*}$args
-    return [ipc_read]
-}
-
-proc windows_forcefocus {} {
-    # First call winapi's SetForegroundWindow()
-    set handle [winfo id .]
-    ipc "forcefocus" $handle
-    # Then call force focus on tcl side
-    focus -force .
-    # We must do both in order to properly focus on main tk window.
-    # Don't ask me why - that's just how it works.
-    #
-    # Alternatively we can try making Tcl our entrypoint instead of exec-ing
-    # Tcl from Go. Maybe some other time.
-}
-
-proc loadicon {} {
-    set iconblob [image create photo -file gorts.png]
-    wm iconphoto . -default $iconblob
-}
-
-proc loadstartgg {} {
-    set resp [ipc "getstartgg"]
-    set ::startgg(token) [lindex $resp 0]
-    set ::startgg(slug) [lindex $resp 1]
-}
-
-proc loadwebmsg {} {
-    set resp [ipc "getwebport"]
-    set webport [lindex $resp 0]
-    set ::mainstatus "Point your OBS browser source to http://localhost:${webport}"
-}
-
-proc loadcountrycodes {} {
-    set codes [ipc "getcountrycodes"]
-    .n.m.players.p1country configure -values $codes
-    .n.m.players.p2country configure -values $codes
-}
-
-proc loadscoreboard {} {
-    set sb [ipc "getscoreboard"]
-    set ::scoreboard(description) [lindex $sb 0]
-    set ::scoreboard(subtitle) [lindex $sb 1]
-    set ::scoreboard(p1name) [lindex $sb 2]
-    set ::scoreboard(p1country) [lindex $sb 3]
-    set ::scoreboard(p1score) [lindex $sb 4]
-    set ::scoreboard(p1team) [lindex $sb 5]
-    set ::scoreboard(p2name) [lindex $sb 6]
-    set ::scoreboard(p2country) [lindex $sb 7]
-    set ::scoreboard(p2score) [lindex $sb 8]
-    set ::scoreboard(p2team) [lindex $sb 9]
-    update_applied_scoreboard
-}
-
-proc applyscoreboard {} {
-    set sb [ \
-        ipc "applyscoreboard" \
-        $::scoreboard(description) \
-        $::scoreboard(subtitle) \
-        $::scoreboard(p1name) \
-        $::scoreboard(p1country) \
-        $::scoreboard(p1score) \
-        $::scoreboard(p1team) \
-        $::scoreboard(p2name) \
-        $::scoreboard(p2country) \
-        $::scoreboard(p2score) \
-        $::scoreboard(p2team) \
-    ]
-    update_applied_scoreboard
-}
-
-proc loadplayernames {} {
-    set playernames [ipc "searchplayers" ""]
-    .n.m.players.p1name configure -values $playernames
-    .n.m.players.p2name configure -values $playernames
-}
-
-proc setupplayersuggestion {} {
-    proc update_suggestions {_ key _} {
-        if {!($key == "p1name" || $key == "p2name")} {
-            return
-        }
-        set newvalue $::scoreboard($key)
-        set widget .n.m.players.$key
-        set matches [ipc "searchplayers" $newvalue]
-        $widget configure -values $matches
-
-        if {[llength matches] == 1 && [lindex $matches 0] == $newvalue} {
-            set countryvar "p[string index $key 1]country"
-            set country [lindex [ipc "getplayercountry" $newvalue] 0]
-            set ::scoreboard($countryvar) $country
-        }
-    }
-    trace add variable ::scoreboard write update_suggestions
-}
-
-proc fetchplayers {} {
-    if {$::startgg(token) == "" || $::startgg(slug) == ""} {
-        set ::startgg(msg) "Please enter token & slug first."
-        return
-    }
-    .n.s.buttons.fetch configure -state disabled
-    .n.s.buttons.clear configure -state disabled
-    .n.s.token configure -state disabled
-    .n.s.tournamentslug configure -state disabled
-    .n state disabled
-    set ::startgg(msg) "Fetching..."
-    ipc_write "fetchplayers" $::startgg(token) $::startgg(slug)
-}
-
-proc fetchplayers__resp {} {
-    set resp [ipc_read]
-    set status [lindex $resp 0]
-    set msg [lindex $resp 1]
-
-    set ::startgg(msg) $msg
-
-    if {$status == "ok"} {
-        loadplayernames
-    }
-
-    .n.s.buttons.fetch configure -state normal
-    .n.s.buttons.clear configure -state normal
-    .n.s.token configure -state normal
-    .n.s.tournamentslug configure -state normal
-    .n state !disabled
-}
-
-proc clearstartgg {} {
-    set ::startgg(token) ""
-    set ::startgg(slug) ""
-    set ::startgg(msg) ""
-    ipc_write "clearstartgg"
-}
-
-proc discardscoreboard {} {
-    foreach key [array names ::scoreboard] {
-        set ::scoreboard($key) $::applied_scoreboard($key)
-    }
-    # Country is updated whenever player name is updated,
-    # so make sure we set countries last.
-    set ::scoreboard(p1country) $::applied_scoreboard(p1country)
-    set ::scoreboard(p2country) $::applied_scoreboard(p2country)
-}
-
-proc update_applied_scoreboard {} {
-    foreach key [array names ::scoreboard] {
-        set ::applied_scoreboard($key) $::scoreboard($key)
-    }
-}
-
-proc setupdiffcheck {} {
-    # Define styling for "dirty"
-    foreach x {TEntry TCombobox TSpinbox} {
-        ttk::style configure "Dirty.$x" -fieldbackground #dffcde
-    }
-
-    trace add variable ::scoreboard write ::checkdiff
-    trace add variable ::applied_scoreboard write ::checkdiff
-}
-
-proc checkdiff {_ key _} {
-    set widget $::var_to_widget($key)
-    if {$::scoreboard($key) == $::applied_scoreboard($key)} {
-        $widget configure -style [winfo class $widget]
-    } else {
-        $widget configure -style "Dirty.[winfo class $widget]"
-    }
-}
diff --git a/livereload/error.go b/livereload/error.go
index 5750618..30b3d42 100644
--- a/livereload/error.go
+++ b/livereload/error.go
@@ -7,7 +7,7 @@
 	"html/template"
 	"net/http"
 
-	"go.imnhan.com/webmaker2000/errs"
+	"go.imnhan.com/s4g/errs"
 )
 
 //go:embed error.html
diff --git a/livereload/livereload.go b/livereload/livereload.go
index 4eb7615..e73eaeb 100644
--- a/livereload/livereload.go
+++ b/livereload/livereload.go
@@ -8,7 +8,7 @@
 	"strings"
 	"sync"
 
-	"go.imnhan.com/webmaker2000/writablefs"
+	"go.imnhan.com/s4g/writablefs"
 )
 
 const endpoint = "/_livereload"
diff --git a/livereload/livereload.html b/livereload/livereload.html
index 0333797..7f97f80 100644
--- a/livereload/livereload.html
+++ b/livereload/livereload.html
@@ -1,5 +1,5 @@
 <!-- Start LiveReload script
-This <script> tag is injected by WebMaker2000's local server to force the
+This <script> tag is injected by s4g's local server to force the
 browser tab to refresh whenever you make a change to your website folder.
 It will not appear in your published website.
 -->
diff --git a/main.go b/main.go
index b429160..82713a2 100644
--- a/main.go
+++ b/main.go
@@ -17,22 +17,21 @@
 	"sync"
 	"time"
 
-	"go.imnhan.com/webmaker2000/djot"
-	"go.imnhan.com/webmaker2000/errs"
-	"go.imnhan.com/webmaker2000/gui"
-	"go.imnhan.com/webmaker2000/livereload"
-	"go.imnhan.com/webmaker2000/writablefs"
+	"go.imnhan.com/s4g/djot"
+	"go.imnhan.com/s4g/errs"
+	"go.imnhan.com/s4g/livereload"
+	"go.imnhan.com/s4g/writablefs"
 )
 
 const DjotExt = ".dj"
-const SiteExt = ".wbmkr2k"
+const SiteExt = ".s4g"
 const SiteFileName = "website" + SiteExt
 const FeedPath = "feed.xml"
 const RedirectsPath = "redirects.txt"
 
 func main() {
 	invalidCommand := func() {
-		fmt.Println("Usage: webfolder2000 new|serve|gui [...]")
+		fmt.Println("Usage: s4g new|serve [...]")
 		os.Exit(1)
 	}
 
@@ -40,7 +39,7 @@ func main() {
 	var cmd string
 	var args []string
 	if len(os.Args) < 2 {
-		cmd = "gui"
+		cmd = "serve"
 		args = os.Args[1:]
 	} else {
 		cmd = os.Args[1]
@@ -63,8 +62,6 @@ func main() {
 	case "serve":
 		serveCmd.Parse(args)
 		handleServeCmd(serveFolder, servePort)
-	case "gui":
-		handleGuiCmd()
 	default:
 		invalidCommand()
 	}
@@ -78,22 +75,6 @@ func handleNewCmd(folder string) {
 	}
 }
 
-func handleGuiCmd() {
-	task, path, ok := gui.ChooseTask(gui.DefaultTclPath)
-	if !ok {
-		return
-	}
-
-	switch task {
-	case gui.TaskOpen:
-		fmt.Println(task, path)
-	case gui.TaskCreate:
-		fmt.Println(task, path)
-	default:
-		panic(task)
-	}
-}
-
 func handleServeCmd(folder, port string) {
 	djot.StartService()
 	fmt.Println("Started djot.js service")
diff --git a/manifest.go b/manifest.go
index 42c23f7..e8d2cab 100644
--- a/manifest.go
+++ b/manifest.go
@@ -6,12 +6,12 @@
 	"sort"
 	"strings"
 
-	"go.imnhan.com/webmaker2000/writablefs"
+	"go.imnhan.com/s4g/writablefs"
 )
 
 const ManifestPath = "manifest.txt"
 
-// Write list of files generated by webmaker2000
+// Write list of files generated by s4g
 func WriteManifest(fsys writablefs.FS, files map[string]bool) {
 	lines := make([]string, 0, len(files))
 	for path := range files {
diff --git a/metadata.go b/metadata.go
index 0b5a3f4..ea68c76 100644
--- a/metadata.go
+++ b/metadata.go
@@ -11,8 +11,8 @@
 	"strings"
 	"time"
 
-	"go.imnhan.com/webmaker2000/errs"
-	"go.imnhan.com/webmaker2000/writablefs"
+	"go.imnhan.com/s4g/errs"
+	"go.imnhan.com/s4g/writablefs"
 )
 
 type SiteMetadata struct {
diff --git a/redirects.go b/redirects.go
index a02e42c..603a3bb 100644
--- a/redirects.go
+++ b/redirects.go
@@ -9,8 +9,8 @@
 	"path/filepath"
 	"strings"
 
-	"go.imnhan.com/webmaker2000/errs"
-	"go.imnhan.com/webmaker2000/writablefs"
+	"go.imnhan.com/s4g/errs"
+	"go.imnhan.com/s4g/writablefs"
 )
 
 // Returns list of generated files
diff --git a/theme/includes.tmpl b/theme/includes.tmpl
index 7f9fdcb..aa3130b 100644
--- a/theme/includes.tmpl
+++ b/theme/includes.tmpl
@@ -23,7 +23,7 @@
 {{- if .Site.ShowFooter -}}
 <footer>
 © {{if eq .StartYear .Now.Year}}{{.StartYear}}{{else}}{{.StartYear}}–{{.Now.Year}}{{end}} {{.Site.AuthorName}}<br>
-Made with <a href="https://github.com/nhanb/webmaker2000">WebMaker2000</a>
+Made with <a href="https://github.com/nhanb/s4g">s4g</a>
 </footer>
 {{- end -}}
 {{- end}}
diff --git a/watcher.go b/watcher.go
index e9e1e47..b7c3e2a 100644
--- a/watcher.go
+++ b/watcher.go
@@ -9,7 +9,7 @@
 	"time"
 
 	"github.com/fsnotify/fsnotify"
-	"go.imnhan.com/webmaker2000/writablefs"
+	"go.imnhan.com/s4g/writablefs"
 )
 
 var WatchedExts = []string{DjotExt, SiteExt, ".tmpl"}