aboutsummaryrefslogtreecommitdiffstats
path: root/internal
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-17 17:09:01 +0300
committergrm <grm@eyesin.space>2026-09-17 17:09:28 +0300
commitf16bd0f79c624665580615321b5d13b4be27cdad (patch)
tree3f7c47fdd8c8ff2e928b04891dfa65ed3ffe8c75 /internal
parentf79f97800f7f9ba1ef2b3e2e56040a952a9668a5 (diff)
downloadblogspace-f16bd0f79c624665580615321b5d13b4be27cdad.tar.gz
blogspace-f16bd0f79c624665580615321b5d13b4be27cdad.tar.bz2
blogspace-f16bd0f79c624665580615321b5d13b4be27cdad.zip
Add the Gizmo look for announcements: framed boxes that share a row
gizmolab's "next event" and "next assembly" boxes were hand-written HTML in the home page's intro; they are announcements by nature, so now they can be: a fourth style, a box framed in the theme's link colour with an italic heading. Headed Gizmo boxes in the same spot share a row on wide screens (the notices wrapper is a flex row, stacking on old browsers like .cols); one without a heading takes the whole row, which is how a banner line goes above a pair of boxes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal')
-rw-r--r--internal/db/migrations/blog/00009_gizmo_style.sql10
-rw-r--r--internal/i18n/el.go4
-rw-r--r--internal/store/sections.go2
-rw-r--r--internal/web/handlers_sections.go2
-rw-r--r--internal/web/static/blog.css14
-rw-r--r--internal/web/templates/dashboard/section_form.html2
-rw-r--r--internal/web/templates/dashboard/sections.html2
-rw-r--r--internal/web/templates/layouts/blog.html4
-rw-r--r--internal/web/theme.go1
9 files changed, 35 insertions, 6 deletions
diff --git a/internal/db/migrations/blog/00009_gizmo_style.sql b/internal/db/migrations/blog/00009_gizmo_style.sql
new file mode 100644
index 0000000..1af8733
--- /dev/null
+++ b/internal/db/migrations/blog/00009_gizmo_style.sql
@@ -0,0 +1,10 @@
+-- +goose Up
+-- A fourth look for announcements: a box framed in the link colour, with
+-- boxes in the same spot sitting side by side on wide screens.
+ALTER TABLE sections DROP CONSTRAINT sections_style_check;
+ALTER TABLE sections ADD CONSTRAINT sections_style_check CHECK (style IN ('plain', 'note', 'warning', 'gizmo'));
+
+-- +goose Down
+UPDATE sections SET style = 'plain' WHERE style = 'gizmo';
+ALTER TABLE sections DROP CONSTRAINT sections_style_check;
+ALTER TABLE sections ADD CONSTRAINT sections_style_check CHECK (style IN ('plain', 'note', 'warning'));
diff --git a/internal/i18n/el.go b/internal/i18n/el.go
index 88610b4..1fd6ceb 100644
--- a/internal/i18n/el.go
+++ b/internal/i18n/el.go
@@ -142,7 +142,9 @@ var el = map[string]string{
"Highlighted": "Τονισμένη",
"Warning": "Προειδοποίηση",
"Plain": "Απλή",
- "Next meeting": "Επόμενη συνάντηση",
+ "Gizmo": "Gizmo",
+ "Gizmo is a box framed in your link colour. Gizmo boxes with a heading share a row on wide screens (up to three); one without a heading takes the whole row.": "Το Gizmo είναι ένα πλαίσιο στο χρώμα των συνδέσμων σου. Τα πλαίσια Gizmo με επικεφαλίδα μοιράζονται μια σειρά σε φαρδιές οθόνες (μέχρι τρία)· ένα χωρίς επικεφαλίδα πιάνει όλη τη σειρά.",
+ "Next meeting": "Επόμενη συνάντηση",
"“Top” of a side column means above its modules, “bottom” below them. If that column is not shown on your blog (see": "«Κορυφή» μιας πλαϊνής στήλης σημαίνει πάνω από τα στοιχεία της, «τέλος» κάτω από αυτά. Αν η στήλη δεν εμφανίζεται στο ιστολόγιό σας (βλ.",
"the announcement moves to the main content instead.": "η ανακοίνωση μεταφέρεται στο κύριο περιεχόμενο.",
"Shown on the blog": "Εμφανίζεται στο ιστολόγιο",
diff --git a/internal/store/sections.go b/internal/store/sections.go
index 2d72ea4..95c6a00 100644
--- a/internal/store/sections.go
+++ b/internal/store/sections.go
@@ -14,7 +14,7 @@ type Section struct {
BodyHTML string
Format string // FormatMarkdown | FormatHTML: how BodyMD is read
Placement string // <column>-<position>: left|main|right - top|bottom
- Style string // plain | note | warning
+ Style string // plain | note | warning | gizmo
Enabled bool
SortOrder int
CreatedAt time.Time
diff --git a/internal/web/handlers_sections.go b/internal/web/handlers_sections.go
index 8d6c9c9..04ba764 100644
--- a/internal/web/handlers_sections.go
+++ b/internal/web/handlers_sections.go
@@ -53,7 +53,7 @@ func (s *Server) handleSectionForm(w http.ResponseWriter, r *http.Request) {
// normalizeSection clamps the enum fields so templates and CSS class names can trust them.
func normalizeSection(sec *store.Section) {
sec.Placement = pick(sec.Column(), "main", "left", "right") + "-" + pick(sec.Position(), "top", "bottom")
- sec.Style = pick(sec.Style, "note", "plain", "warning")
+ sec.Style = pick(sec.Style, "note", "plain", "warning", "gizmo")
}
func (s *Server) handleSectionSave(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/web/static/blog.css b/internal/web/static/blog.css
index ace5e5a..eabb314 100644
--- a/internal/web/static/blog.css
+++ b/internal/web/static/blog.css
@@ -48,7 +48,19 @@ img { max-width: 100%; height: auto; }
.notice-title { margin: 0 0 0.3em; font-size: 1em; text-transform: uppercase; letter-spacing: 0.05em; }
.notice-body > :first-child { margin-top: 0; }
.notice-body > :last-child { margin-bottom: 0; }
-.content > .notice:last-child { margin: 1.5em 0 0; }
+.content > .notices:last-child { margin-top: 1.5em; }
+.content > .notices:last-child > .notice:last-child { margin-bottom: 0; }
+/* gizmo: a box framed in the link colour (set by the theme CSS). On wide
+ screens the flex wrapper puts up to three headed gizmo boxes in one row
+ (they start at 30% and grow); a gizmo box without a heading, like every
+ plain/note/warning box, takes the whole row */
+.notice-gizmo { padding: 1em 1.2em; border: 1px solid; background: none; }
+.notice-gizmo .notice-title { font-size: 1.15em; font-style: italic; text-transform: none; letter-spacing: 0; margin-bottom: 0.6em; }
+@media (min-width: 701px) {
+ .notices { display: flex; flex-wrap: wrap; gap: 0 1em; align-items: stretch; }
+ .notices .notice { flex: 1 1 100%; }
+ .notices .notice-gizmo.headed { flex: 1 1 30%; }
+}
.pager { margin-top: 2em; padding-top: 1em; border-top: 1px solid rgba(0,0,0,0.1); text-align: center; }
.pager a, .pager span { margin: 0 0.8em; }
.muted { opacity: 0.7; }
diff --git a/internal/web/templates/dashboard/section_form.html b/internal/web/templates/dashboard/section_form.html
index 0171aaa..0a0e4ea 100644
--- a/internal/web/templates/dashboard/section_form.html
+++ b/internal/web/templates/dashboard/section_form.html
@@ -20,8 +20,10 @@
<option value="note"{{if eq $s.Style "note"}} selected{{end}}>{{t "Highlighted"}}</option>
<option value="warning"{{if eq $s.Style "warning"}} selected{{end}}>{{t "Warning"}}</option>
<option value="plain"{{if eq $s.Style "plain"}} selected{{end}}>{{t "Plain"}}</option>
+ <option value="gizmo"{{if eq $s.Style "gizmo"}} selected{{end}}>{{t "Gizmo"}}</option>
</select></label>
</div>
+ <p class="muted small">{{t "Gizmo is a box framed in your link colour. Gizmo boxes with a heading share a row on wide screens (up to three); one without a heading takes the whole row."}}</p>
<p class="muted small">{{t "“Top” of a side column means above its modules, “bottom” below them. If that column is not shown on your blog (see"}} <a href="/b/{{.Blog.Subdomain}}/design#columns">{{t "Design"}}</a>) {{t "the announcement moves to the main content instead."}}</p>
<label for="ed-body">{{t "Text"}}</label>
{{template "editor" (dict "name" "body" "value" $s.BodyMD "format" $s.Format "rows" 10 "tall" false "upload" (print "/b/" .Blog.Subdomain "/files/upload") "preview" (print "/b/" .Blog.Subdomain "/preview") "csrf" .CSRF)}}
diff --git a/internal/web/templates/dashboard/sections.html b/internal/web/templates/dashboard/sections.html
index 4d62f29..9f0e314 100644
--- a/internal/web/templates/dashboard/sections.html
+++ b/internal/web/templates/dashboard/sections.html
@@ -17,7 +17,7 @@
</td>
<td><a href="{{$u}}/{{.ID}}/edit">{{if .Title}}{{.Title}}{{else}}({{t "untitled"}}){{end}}</a>{{if not .Enabled}}<span class="tag off">{{t "hidden"}}</span>{{end}}</td>
<td>{{if eq .Column "main"}}{{t "Main content"}}{{else if eq .Column "left"}}{{t "Left column"}}{{else}}{{t "Right column"}}{{end}}, {{t .Position}}</td>
- <td>{{if eq .Style "note"}}{{t "Highlighted"}}{{else if eq .Style "warning"}}{{t "Warning"}}{{else}}{{t "Plain"}}{{end}}</td>
+ <td>{{if eq .Style "note"}}{{t "Highlighted"}}{{else if eq .Style "warning"}}{{t "Warning"}}{{else if eq .Style "gizmo"}}{{t "Gizmo"}}{{else}}{{t "Plain"}}{{end}}</td>
<td class="nowrap">
<form method="post" action="{{$u}}/{{.ID}}/toggle" class="inline"><input type="hidden" name="_csrf" value="{{$.CSRF}}"><button class="mini">{{if .Enabled}}{{t "Hide"}}{{else}}{{t "Show"}}{{end}}</button></form>
<a class="danger" href="{{$u}}/{{.ID}}/delete">{{t "delete"}}</a>
diff --git a/internal/web/templates/layouts/blog.html b/internal/web/templates/layouts/blog.html
index 6e8b819..76475b9 100644
--- a/internal/web/templates/layouts/blog.html
+++ b/internal/web/templates/layouts/blog.html
@@ -43,10 +43,12 @@
</body>
</html>{{end}}
-{{define "notices"}}{{range .}}<aside class="notice notice-{{.Style}}">
+{{define "notices"}}{{if .}}<div class="notices">
+{{range .}}<aside class="notice notice-{{.Style}}{{if .Title}} headed{{end}}">
{{if .Title}}<h3 class="notice-title">{{.Title}}</h3>{{end}}
<div class="notice-body">{{html .BodyHTML}}</div>
</aside>
+{{end}}</div>
{{end}}{{end}}
{{/* One layout module. .m is the store.Module, .root the page data; the same
diff --git a/internal/web/theme.go b/internal/web/theme.go
index 1fc78ad..df2c613 100644
--- a/internal/web/theme.go
+++ b/internal/web/theme.go
@@ -448,6 +448,7 @@ a:hover {{"{"}}{{if .LinkHover}} color:{{.LinkHover}};{{end}}{{if eq .LinkUnderl
{{- end}}
.content, .mod { background:{{.ContentBg}};{{if eq .ContentStyle "flat"}} border-radius:0; box-shadow:none;{{end}} }
.content {{"{"}}{{with .PaddingDesktop}} padding:{{.}};{{end}} }
+.notice-gizmo { border-color:{{.LinkColor}}; }
.logo { max-height:{{.LogoMaxHeight}}; }
@media (min-width: 701px) {
.col-left { width:{{.LeftWidth}}%; } .col-right { width:{{.RightWidth}}%; }