aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-17 16:44:11 +0300
committergrm <grm@eyesin.space>2026-09-17 16:44:11 +0300
commitf79f97800f7f9ba1ef2b3e2e56040a952a9668a5 (patch)
treef054c3b030911336e39c57a1498a272bb30d7bc0 /internal/web
parentd0601f06c080a8eb8d781fdd0e3338cd7ed42690 (diff)
downloadblogspace-f79f97800f7f9ba1ef2b3e2e56040a952a9668a5.tar.gz
blogspace-f79f97800f7f9ba1ef2b3e2e56040a952a9668a5.tar.bz2
blogspace-f79f97800f7f9ba1ef2b3e2e56040a952a9668a5.zip
Let the header and footer follow the main column
With keep_columns and empty side columns the posts sit in a centred column while the header and footer still run edge to edge, which reads as two different pages stacked. The new switch insets their content by the column widths (the backgrounds stay full width, phones are untouched) so the whole page lines up as one column — the look a single-column site expects, without bringing back a content max-width. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web')
-rw-r--r--internal/web/templates/dashboard/design.html1
-rw-r--r--internal/web/theme.go9
-rw-r--r--internal/web/web_test.go13
3 files changed, 22 insertions, 1 deletions
diff --git a/internal/web/templates/dashboard/design.html b/internal/web/templates/dashboard/design.html
index 71597e8..c22a35e 100644
--- a/internal/web/templates/dashboard/design.html
+++ b/internal/web/templates/dashboard/design.html
@@ -216,6 +216,7 @@
</div>
<p class="muted small">{{t "Between 15% and 40% each; the main content takes the rest. A column only appears when it has at least one module; without columns the posts take the whole width. On phones the columns stack: main content first, then the left and right columns, then the footer."}}</p>
<label class="check"><input type="checkbox" name="keep_columns"{{if $t.KeepColumns}} checked{{end}}> {{t "Always keep the columns' space, even when a column is empty or hidden"}} <span class="muted">({{t "the main content then keeps its width and stays centred"}})</span></label>
+ <label class="check"><input type="checkbox" name="align_to_main"{{if $t.AlignToMain}} checked{{end}}> {{t "Header and footer follow the main column"}} <span class="muted">({{t "their text lines up with the main content instead of the page edges; the backgrounds stay full width"}})</span></label>
<h3>{{t "Left column"}}</h3>
<label class="check"><input type="checkbox" name="left_on" data-area="left"{{if $t.LeftOn}} checked{{end}}> {{t "Show the left column"}}</label>
{{template "arealist" (dict "area" "left" "root" . "hint" (t "A narrow column beside your posts. Good for the menu, an archive or recent posts."))}}
diff --git a/internal/web/theme.go b/internal/web/theme.go
index 70b218e..1fc78ad 100644
--- a/internal/web/theme.go
+++ b/internal/web/theme.go
@@ -67,6 +67,10 @@ type Theme struct {
// KeepColumns lays the side columns out even when empty or hidden, so
// the main content keeps the same width on every page.
KeepColumns bool `json:"keep_columns"`
+ // AlignToMain insets the header's and footer's content to the main
+ // column's edges (the backgrounds stay full width), so a blog with
+ // KeepColumns and empty columns reads as one centred column.
+ AlignToMain bool `json:"align_to_main"`
}
// Areas of the blog page, in page order. above/below sit inside the main column.
@@ -250,6 +254,7 @@ func ThemeFromForm(cur Theme, f url.Values) Theme {
t.HeaderOn, t.LeftOn, t.RightOn, t.FooterOn = on("header_on"), on("left_on"), on("right_on"), on("footer_on")
t.LeftWidth, t.RightWidth = num("left_width"), num("right_width")
t.KeepColumns = on("keep_columns")
+ t.AlignToMain = on("align_to_main")
t.normalize()
return t
}
@@ -452,6 +457,10 @@ a:hover {{"{"}}{{if .LinkHover}} color:{{.LinkHover}};{{end}}{{if eq .LinkUnderl
{{- with .TitleSizeCSS}}
.site-title { font-size:{{.}}; }
{{- end}}
+{{- if .AlignToMain}}
+ body.has-left .site-header .wrap, body.has-left .site-footer .wrap { padding-left:calc(1em + {{.LeftWidth}}%); }
+ body.has-right .site-header .wrap, body.has-right .site-footer .wrap { padding-right:calc(1em + {{.RightWidth}}%); }
+{{- end}}
}
{{- with .PaddingPhone}}
@media (max-width: 700px) { .content { padding:{{.}}; } }
diff --git a/internal/web/web_test.go b/internal/web/web_test.go
index a13697c..b707339 100644
--- a/internal/web/web_test.go
+++ b/internal/web/web_test.go
@@ -129,7 +129,7 @@ func TestLayoutTheme(t *testing.T) {
// The layout switches ride in the design form: the areas are checkboxes, so an unticked one is off.
th := ThemeFromForm(DefaultTheme(), url.Values{"left_width": {"40"}, "right_width": {"40"}, "keep_columns": {"on"}, "header_on": {"on"}, "left_on": {"on"}, "right_on": {"on"},
"posts_per_page": {"20"}, "title_size": {"large"}, "footer_align": {"left"}})
- if th.LeftWidth != 40 || th.RightWidth != 30 || th.MainWidth() != 30 || !th.KeepColumns {
+ if th.LeftWidth != 40 || th.RightWidth != 30 || th.MainWidth() != 30 || !th.KeepColumns || th.AlignToMain {
t.Errorf("the right column should give way: %+v", th)
}
if th.FooterOn || !th.HeaderOn || th.PostsPerPage != 20 || th.TitleSize != "large" || th.FooterAlign != "left" {
@@ -149,6 +149,17 @@ func TestLayoutTheme(t *testing.T) {
t.Errorf("css missing %q:\n%s", want, css)
}
}
+ if strings.Contains(css, ".site-header .wrap") {
+ t.Error("header insets emitted without align_to_main")
+ }
+ th = ThemeFromForm(th, url.Values{"left_width": {"15"}, "right_width": {"20"}, "align_to_main": {"on"}})
+ css = th.CSS()
+ for _, want := range []string{"body.has-left .site-header .wrap, body.has-left .site-footer .wrap { padding-left:calc(1em + 15%); }",
+ "body.has-right .site-header .wrap, body.has-right .site-footer .wrap { padding-right:calc(1em + 20%); }"} {
+ if !strings.Contains(css, want) {
+ t.Errorf("css missing %q:\n%s", want, css)
+ }
+ }
th.LeftOn = false
if th.AreaOn("left") || !th.AreaOn("above") || !th.AreaOn("bogus") {
t.Errorf("area switches: %+v", th)