Add Contact Page and Update Navigation Links

- Updated navigation links across multiple blog pages to replace "Index" with "Home" and added "Contact" link.
- Created a new contact page with a form for user inquiries.
- Added a new contact template to render the contact page content.
- Updated blog index page to enhance structure and styling.
- Added a contact markdown file for content management.
This commit is contained in:
Keith Solomon
2025-04-21 07:15:03 -05:00
parent 9957a5c116
commit 1a9b58d7d7
21 changed files with 888 additions and 583 deletions

View File

@@ -2,6 +2,7 @@ root = "."
[build] [build]
cmd = "go run main.go" cmd = "go run main.go"
bin = ""
delay = 1000 delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "bak", "node_modules", "public"] exclude_dir = ["assets", "tmp", "vendor", "bak", "node_modules", "public"]
include_ext = ["go", "md", "html"] include_ext = ["go", "md", "html"]

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
node_modules/ node_modules/
bak/ bak/
tmp/
*.log

View File

@@ -1,5 +1,6 @@
--- ---
title: Go SSG - About title: Go SSG - About
navTitle: About
description: More About My Go SSG Site. description: More About My Go SSG Site.
date: 2025-04-19 date: 2025-04-19
--- ---

View File

@@ -9,7 +9,7 @@ categories:
## *Tap Tap Tap* Is this thing on? ## *Tap Tap Tap* Is this thing on?
This is my first Go SSG blog post. <p class="text-primary">This is my first Go SSG blog post.</p>
This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently. This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.

4
content/contact.md Normal file
View File

@@ -0,0 +1,4 @@
---
title: Contact
navTitle: Contact
---

View File

@@ -1,5 +1,6 @@
--- ---
title: Go SSG - Home title: Go SSG - Home
navTitle: Home
description: My Go SSG Site. description: My Go SSG Site.
date: 2025-04-19 date: 2025-04-19
--- ---

View File

@@ -17,14 +17,16 @@ header#site_head {
@apply py-4 px-60; @apply py-4 px-60;
nav ul { nav ul {
@apply flex space-x-4 justify-end-safe; @apply flex space-x-4 justify-end-safe prose-a:text-white;
} }
} }
main { main {
@apply prose container mx-auto px-4 py-8 max-w-5xl; article, div.main {
@apply prose container mx-auto px-4 py-8 max-w-5xl;
}
} }
footer#site_foot { footer#site_foot {
@apply bg-secondary text-white text-center; @apply bg-secondary text-white text-center m-0 p-0;
} }

298
main.go
View File

@@ -11,6 +11,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"time" "time"
"sort"
"io/fs"
"github.com/russross/blackfriday/v2" "github.com/russross/blackfriday/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
@@ -23,6 +25,7 @@ type NavItem struct {
type PageMeta struct { type PageMeta struct {
Title string `yaml:"title"` Title string `yaml:"title"`
NavTitle string `yaml:"navTitle"`
Description string `yaml:"description"` Description string `yaml:"description"`
Date string `yaml:"date"` Date string `yaml:"date"`
Categories []string `yaml:"categories"` Categories []string `yaml:"categories"`
@@ -35,8 +38,7 @@ func main() {
contentDir := "./content" contentDir := "./content"
outputDir := "./public" outputDir := "./public"
// Load all templates in the templates folder tpl, err := loadTemplates()
tpl, err := template.New("").Funcs(templateFuncs()).ParseGlob("templates/*.html")
if err != nil { if err != nil {
fmt.Printf("Template parsing error: %v\n", err) fmt.Printf("Template parsing error: %v\n", err)
return return
@@ -48,69 +50,159 @@ func main() {
return return
} }
entries, _ := os.ReadDir(contentDir)
nav := buildNav(entries, contentDir)
renderStaticPages(files, contentDir, outputDir, tpl, nav)
blogPosts, categoryMap := processBlogPosts(contentDir, outputDir, tpl, nav)
buildBlogIndex(blogPosts, tpl, outputDir, nav)
buildCategoryPages(categoryMap, tpl, outputDir, nav)
fmt.Println("✅ Site generation complete.")
}
func loadTemplates() (*template.Template, error) {
return template.New("").Funcs(templateFuncs()).ParseGlob("templates/*.html")
}
func buildNav(entries []fs.DirEntry, contentDir string) []NavItem {
var nav []NavItem var nav []NavItem
for _, file := range files {
if filepath.Ext(file.Name()) == ".md" { for _, entry := range entries {
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) if entry.Type().IsDir() || !strings.HasSuffix(entry.Name(), ".md") {
url := "/" continue
if name != "index" {
url = "/" + name + "/"
}
nav = append(nav, NavItem{Title: strings.Title(name), URL: url})
} }
name := strings.TrimSuffix(entry.Name(), filepath.Ext(entry.Name()))
path := filepath.Join(contentDir, entry.Name())
rawContent, err := os.ReadFile(path)
if err != nil {
fmt.Printf("Failed to read %s: %v\n", entry.Name(), err)
continue
}
meta, _ := parseFrontMatter(rawContent)
title := meta.NavTitle
if title == "" {
title = meta.Title
}
if title == "" && name == "index" {
title = "Home"
} else if title == "" {
title = strings.Title(name)
}
url := "/"
if name != "index" {
url = "/" + name + "/"
}
nav = append(nav, NavItem{Title: title, URL: url})
} }
// Add blog to main nav // Add Blog manually
nav = append(nav, NavItem{Title: "Blog", URL: "/blog/"}) nav = append(nav, NavItem{Title: "Blog", URL: "/blog/"})
// Static page rendering // Optional: order the nav explicitly
for _, file := range files { preferredOrder := map[string]int{
if filepath.Ext(file.Name()) == ".md" { "Home": 0,
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) "Blog": 1,
title := strings.Title(name) "About": 2,
mdPath := filepath.Join(contentDir, file.Name()) "Contact": 3,
rawContent, err := ioutil.ReadFile(mdPath)
if err != nil {
fmt.Printf("Failed to read %s: %v\n", file.Name(), err)
continue
}
meta, content := parseFrontMatter(rawContent)
htmlContent := blackfriday.Run(content)
if meta.Title == "" {
meta.Title = title
}
var outPath string
if name == "index" {
outPath = filepath.Join(outputDir, "index.html")
} else {
subDir := filepath.Join(outputDir, name)
os.MkdirAll(subDir, os.ModePerm)
outPath = filepath.Join(subDir, "index.html")
}
outFile, err := os.Create(outPath)
if err != nil {
fmt.Printf("Failed to create %s: %v\n", outPath, err)
continue
}
tpl.ExecuteTemplate(outFile, "static_page", map[string]interface{}{
"Title": meta.Title,
"Description": meta.Description,
"Date": meta.Date,
"Categories": meta.Categories,
"Content": template.HTML(htmlContent),
"Nav": nav,
"Year": time.Now().Year(),
"PageTemplate": "static",
})
fmt.Printf("Generated: %s\n", outPath)
}
} }
sort.SliceStable(nav, func(i, j int) bool {
return preferredOrder[nav[i].Title] < preferredOrder[nav[j].Title]
})
return nav
}
func renderStaticPages(files []os.FileInfo, contentDir, outputDir string, tpl *template.Template, nav []NavItem) {
for _, file := range files {
if filepath.Ext(file.Name()) == ".md" {
renderStaticPage(file, contentDir, outputDir, tpl, nav)
}
}
}
func renderStaticPage(file os.FileInfo, contentDir, outputDir string, tpl *template.Template, nav []NavItem) {
name := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
title := strings.Title(name)
mdPath := filepath.Join(contentDir, file.Name())
rawContent, err := ioutil.ReadFile(mdPath)
if err != nil {
fmt.Printf("Failed to read %s: %v\n", file.Name(), err)
return
}
meta, content := parseFrontMatter(rawContent)
htmlContent := blackfriday.Run(content)
if meta.Title == "" {
meta.Title = title
}
var outPath string
if name == "index" {
outPath = filepath.Join(outputDir, "index.html")
} else {
subDir := filepath.Join(outputDir, name)
os.MkdirAll(subDir, os.ModePerm)
outPath = filepath.Join(subDir, "index.html")
}
outFile, err := os.Create(outPath)
if err != nil {
fmt.Printf("Failed to create %s: %v\n", outPath, err)
return
}
tpl.ExecuteTemplate(outFile, "static_page", map[string]interface{}{
"Title": meta.Title,
"Description": meta.Description,
"Date": meta.Date,
"Categories": meta.Categories,
"Content": template.HTML(htmlContent),
"Nav": nav,
"Year": time.Now().Year(),
"PageTemplate": "static",
})
fmt.Printf("Generated: %s\n", outPath)
}
func renderContactPage(contentDir, outputDir string, tpl *template.Template, nav []NavItem) {
contactPath := filepath.Join(contentDir, "contact.md")
rawContent, err := os.ReadFile(contactPath)
if err != nil {
fmt.Printf("Contact page not found: %v\n", err)
return
}
meta, _ := parseFrontMatter(rawContent)
outPath := filepath.Join(outputDir, "contact", "index.html")
os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
outFile, err := os.Create(outPath)
if err != nil {
fmt.Printf("Failed to create contact page: %v\n", err)
return
}
tpl.ExecuteTemplate(outFile, "contact_page", map[string]interface{}{
"Title": meta.Title,
"Description": meta.Description,
"Nav": nav,
"Year": time.Now().Year(),
"PageTemplate": "contact_page",
})
fmt.Println("Generated: /contact/")
}
func processBlogPosts(contentDir, outputDir string, tpl *template.Template, nav []NavItem) ([]PageMeta, map[string][]PageMeta) {
var blogPosts []PageMeta var blogPosts []PageMeta
categoryMap := make(map[string][]PageMeta) categoryMap := make(map[string][]PageMeta)
@@ -122,51 +214,57 @@ func main() {
continue continue
} }
slug := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name())) processBlogPost(file, blogDir, outputDir, tpl, nav, &blogPosts, categoryMap)
blogPath := filepath.Join(blogDir, file.Name())
rawContent, err := ioutil.ReadFile(blogPath)
if err != nil {
fmt.Printf("Failed to read blog post %s: %v\n", file.Name(), err)
continue
}
meta, content := parseFrontMatter(rawContent)
html := blackfriday.Run(content)
meta.Slug = slug
if meta.Title == "" {
meta.Title = strings.Title(slug)
}
for _, cat := range meta.Categories {
slug := slugify(cat)
categoryMap[slug] = append(categoryMap[slug], meta)
}
outPath := filepath.Join(outputDir, "blog", slug, "index.html")
os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
outFile, err := os.Create(outPath)
if err != nil {
fmt.Printf("Failed to create blog file %s: %v\n", outPath, err)
continue
}
tpl.ExecuteTemplate(outFile, "blog_post_page", map[string]interface{}{
"Title": meta.Title,
"Description": meta.Description,
"Date": meta.Date,
"Categories": meta.Categories,
"Content": template.HTML(html),
"Nav": nav,
"Year": time.Now().Year(),
"PageTemplate": "blog_post",
})
blogPosts = append(blogPosts, meta)
} }
buildBlogIndex(blogPosts, tpl, outputDir, nav) return blogPosts, categoryMap
}
func processBlogPost(file os.FileInfo, blogDir, outputDir string, tpl *template.Template, nav []NavItem, blogPosts *[]PageMeta, categoryMap map[string][]PageMeta) {
slug := strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
blogPath := filepath.Join(blogDir, file.Name())
rawContent, err := ioutil.ReadFile(blogPath)
if err != nil {
fmt.Printf("Failed to read blog post %s: %v\n", file.Name(), err)
return
}
meta, content := parseFrontMatter(rawContent)
html := blackfriday.Run(content)
meta.Slug = slug
if meta.Title == "" {
meta.Title = strings.Title(slug)
}
for _, cat := range meta.Categories {
slug := slugify(cat)
categoryMap[slug] = append(categoryMap[slug], meta)
}
outPath := filepath.Join(outputDir, "blog", slug, "index.html")
os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
outFile, err := os.Create(outPath)
if err != nil {
fmt.Printf("Failed to create blog file %s: %v\n", outPath, err)
return
}
tpl.ExecuteTemplate(outFile, "blog_post_page", map[string]interface{}{
"Title": meta.Title,
"Description": meta.Description,
"Date": meta.Date,
"Categories": meta.Categories,
"Content": template.HTML(html),
"Nav": nav,
"Year": time.Now().Year(),
"PageTemplate": "blog_post",
})
*blogPosts = append(*blogPosts, meta)
}
func buildCategoryPages(categoryMap map[string][]PageMeta, tpl *template.Template, outputDir string, nav []NavItem) {
for catSlug, posts := range categoryMap { for catSlug, posts := range categoryMap {
outDir := filepath.Join(outputDir, "blog", "category", catSlug) outDir := filepath.Join(outputDir, "blog", "category", catSlug)
os.MkdirAll(outDir, os.ModePerm) os.MkdirAll(outDir, os.ModePerm)
@@ -188,8 +286,6 @@ func main() {
fmt.Printf("Generated category: /blog/category/%s/\n", catSlug) fmt.Printf("Generated category: /blog/category/%s/\n", catSlug)
} }
fmt.Println("✅ Site generation complete.")
} }
func parseFrontMatter(raw []byte) (PageMeta, []byte) { func parseFrontMatter(raw []byte) (PageMeta, []byte) {

View File

@@ -17,12 +17,14 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

File diff suppressed because it is too large Load Diff

View File

@@ -17,12 +17,14 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@@ -17,12 +17,14 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@@ -17,12 +17,14 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@@ -17,12 +17,14 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@@ -17,26 +17,31 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>
<main> <main>
<h1>Blog Index</h1> <div class="main">
<ul> <h1>Go SSG - Blog Index</h1>
<li><a href="/blog/another-blog-post/">Go SSG - Another Blog Post</a><br>A longer summary of the post. Lorem ipsum dolor set amit.</li> <ul>
<li><a href="/blog/my-first-blog-post/">Go SSG - My First Blog Post</a><br>A short summary of the post.</li> <li><a href="/blog/another-blog-post/">Go SSG - Another Blog Post</a><br>A longer summary of the post. Lorem ipsum dolor set amit.</li>
</ul> <li><a href="/blog/my-first-blog-post/">Go SSG - My First Blog Post</a><br>A short summary of the post.</li>
</ul>
</div>
</main> </main>

View File

@@ -17,12 +17,14 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>
@@ -48,7 +50,7 @@
<h2><em>Tap Tap Tap</em> Is this thing on?</h2> <h2><em>Tap Tap Tap</em> Is this thing on?</h2>
<p>This is my first Go SSG blog post.</p> <p class="text-primary">This is my first Go SSG blog post.</p>
<p>This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.</p> <p>This project is a simple static site generator written in Go. It is designed to be easy to use and extend, allowing you to create a static website quickly and efficiently.</p>

47
public/contact/index.html Normal file
View File

@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Contact</title>
<link rel="stylesheet" href="/assets/style.css">
</head>
<body>
<header id="site_head">
<h1 class="text-white text-40px hover:text-primary-200"><a href="/">Go SSG</a></h1>
<nav>
<ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul>
</nav>
</header>
<main>
<article>
<h1>Contact</h1>
</article>
</main>
<footer id="site_foot">
<p class="p-0 py-4 m-0 leading-none">&copy; 2025 Keith Solomon - Go SSG</p>
</footer>
</body>
</html>

View File

@@ -17,12 +17,14 @@
<nav> <nav>
<ul class="list-none"> <ul class="list-none">
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/">Home</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/">Index</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li> <li class="list-none"><a class="text-white hover:text-primary-200" href="/blog/">Blog</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/about/">About</a></li>
<li class="list-none"><a class="text-white hover:text-primary-200" href="/contact/">Contact</a></li>
</ul> </ul>
</nav> </nav>
</header> </header>

View File

@@ -20,6 +20,8 @@
{{ template "blog_post_content" . }} {{ template "blog_post_content" . }}
{{- else if eq .PageTemplate "category_page" -}} {{- else if eq .PageTemplate "category_page" -}}
{{ template "category_content" . }} {{ template "category_content" . }}
{{- else if eq .PageTemplate "contact_page" -}}
{{ template "contact_content" . }}
{{- else -}} {{- else -}}
<p>Unknown PageTemplate: {{ .PageTemplate }}</p> <p>Unknown PageTemplate: {{ .PageTemplate }}</p>
{{- end -}} {{- end -}}

View File

@@ -3,12 +3,15 @@
{{ end }} {{ end }}
{{ define "blog_index_content" }} {{ define "blog_index_content" }}
<h1>Blog Index</h1> <div class="main">
<ul> <h1>Go SSG - Blog Index</h1>
{{ range .Posts }}
<li><a href="/blog/{{ .Slug }}/">{{ .Title }}</a><br>{{ .Description }}</li> <ul>
{{ else }} {{ range .Posts }}
<li>No posts found.</li> <li><a href="/blog/{{ .Slug }}/">{{ .Title }}</a><br>{{ .Description }}</li>
{{ end }} {{ else }}
</ul> <li>No posts found.</li>
{{ end }}
</ul>
</div>
{{ end }} {{ end }}

View File

@@ -0,0 +1,37 @@
{{ define "contact_page" }}
{{ template "base" . }}
{{ end }}
{{ define "contact_content" }}
<h1 class="text-2xl font-bold mb-4">Contact Me</h1>
<form action="https://api.staticforms.xyz/submit" method="POST" class="space-y-4 max-w-xl">
<!-- StaticForms API Key -->
<input type="hidden" name="accessKey" value="sf_5m86ek16hmele1jlkl62ghml" />
<!-- Optional redirect after success -->
<!-- <input type="hidden" name="redirectTo" value="https://yourdomain.com/thanks/" /> -->
<label class="block">
<span class="block text-sm font-medium">Your Name</span>
<input type="text" name="name" required class="w-full border p-2 rounded" />
</label>
<label class="block">
<span class="block text-sm font-medium">Email Address</span>
<input type="email" name="email" required class="w-full border p-2 rounded" />
</label>
<label class="block">
<span class="block text-sm font-medium">Message</span>
<textarea name="message" rows="5" required class="w-full border p-2 rounded"></textarea>
</label>
<!-- reCAPTCHA if configured in dashboard -->
<!-- <div class="g-recaptcha" data-sitekey="your-recaptcha-site-key"></div> -->
<button type="submit" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700">
Send Message
</button>
</form>
{{ end }}