Add dedicated edition pages (server, workstation, cloud)

- Add /server, /workstation, /cloud routes with per-page SEO titles
- Extract shared nav, footer, and waitlist form into partials
- Nav links to edition pages with active-state highlighting; swap About/Security order
- Waitlist form pre-checks the current edition and adds a Cloud option
- Add page-hero variant, edition switcher, and active-nav styles
- Responsive fixes: nav wraps at 540px, waitlist checkboxes wrap on narrow screens
This commit is contained in:
Blake Ridgway
2026-08-22 22:02:34 -05:00
parent 8ba41f7e3c
commit b8df82e383
9 changed files with 816 additions and 119 deletions

64
main.go
View File

@@ -14,6 +14,22 @@ import (
var templates = make(map[string]*template.Template)
// pageTitles maps each edition page to its SEO title.
var pageTitles = map[string]string{
"server": "Arcline Server — Hardened SOC / Production Host",
"workstation": "Arcline Workstation — Security Analyst / Blue-Team Desktop",
"cloud": "Arcline Cloud — Security-First Cloud Images",
}
// mustParse parses a template set and fails fast at startup on any error.
func mustParse(name string, files ...string) *template.Template {
t, err := template.ParseFiles(files...)
if err != nil {
log.Fatalf("failed to parse %s template: %v", name, err)
}
return t
}
// contextKey is used to pass per-request log extras from handlers to the logging middleware.
type contextKey struct{}
type logContext struct{ extra string }
@@ -25,11 +41,23 @@ func main() {
}
// Parse templates once at startup.
index, err := template.ParseFiles("templates/base.html", "templates/index.html")
if err != nil {
log.Fatalf("failed to parse index template: %v", err)
templates["index"] = mustParse("index",
"templates/base.html",
"templates/index.html",
"templates/partials/nav.html",
"templates/partials/footer.html",
"templates/partials/waitlist_form.html",
)
for _, name := range []string{"server", "workstation", "cloud"} {
templates[name] = mustParse(name,
"templates/base.html",
"templates/"+name+".html",
"templates/partials/nav.html",
"templates/partials/footer.html",
"templates/partials/waitlist_form.html",
)
}
templates["index"] = index
waitlist, err := template.ParseFiles("templates/partials/waitlist_confirmation.html")
if err != nil {
@@ -45,6 +73,9 @@ func main() {
// Page routes
mux.HandleFunc("/", handleIndex)
mux.HandleFunc("/server", handlePage("server"))
mux.HandleFunc("/workstation", handlePage("workstation"))
mux.HandleFunc("/cloud", handlePage("cloud"))
mux.HandleFunc("/healthz", handleHealthz)
// HTMX partial routes
@@ -163,7 +194,9 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
data := map[string]interface{}{
"Title": "Arcline OS — Hardened Linux for Security Practitioners",
"Title": "Arcline OS — Hardened Linux for Security Practitioners",
"Active": "",
"Edition": "server",
}
if err := templates["index"].ExecuteTemplate(w, "base", data); err != nil {
log.Printf("render error: %v", err)
@@ -171,6 +204,27 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
}
}
// handlePage renders one of the edition pages (server, workstation, cloud).
func handlePage(page string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/"+page {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
data := map[string]interface{}{
"Title": pageTitles[page],
"Active": page,
"Edition": page,
}
if err := templates[page].ExecuteTemplate(w, "base", data); err != nil {
log.Printf("render error: %v", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
}
func handleWaitlist(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)