orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1package main
2
3import (
4 _ "embed"
5 "encoding/json"
6 "fmt"
7 "html/template"
8 "log/slog"
9 "os"
10 "strconv"
11 "strings"
12)
13
14// images.json is read here and by frontend/scripts/images.js, which generates
15// the files, so the page cannot reference a width that was never built.
16//
17//go:embed images.json
18var imagesJSON []byte
19
20type imageSpec struct {
21 Format string `json:"format"`
22 Hero string `json:"hero"`
23 CardWidths []int `json:"cardWidths"`
24 LightboxWidth int `json:"lightboxWidth"`
25 HeroWidth int `json:"heroWidth"`
26 Quality map[string]int `json:"quality"`
27 Avatar struct {
28 Width int `json:"width"`
29 Quality int `json:"quality"`
30 } `json:"avatar"`
31}
32
33var images = loadImageSpec()
34
35func loadImageSpec() imageSpec {
36 var s imageSpec
37 if err := json.Unmarshal(imagesJSON, &s); err != nil {
38 slog.Error(fmt.Sprintf("parse images.json: %v", err))
39 os.Exit(1)
40 }
41 if s.Format == "" || len(s.CardWidths) == 0 {
42 slog.Error("images.json is missing format or cardWidths")
43 os.Exit(1)
44 }
45 // Every width the templates can ask for needs a quality, since the
46 // generator reads the same map and would skip it silently.
47 for _, w := range append(append([]int{}, s.CardWidths...), s.LightboxWidth, s.HeroWidth) {
48 if _, ok := s.Quality[strconv.Itoa(w)]; !ok {
49 slog.Error(fmt.Sprintf("images.json: no quality for width %d", w))
50 os.Exit(1)
51 }
52 }
53 return s
54}
55
56func pourURL(number string, width int) string {
57 return fmt.Sprintf("/static/images/art/acrylic-pours/%s-%d.%s", number, width, images.Format)
58}
59
60// pourSrcset builds the candidate list. template.Srcset keeps html/template
61// from mangling the comma-separated descriptors.
62func pourSrcset(number string, widths ...int) template.Srcset {
63 parts := make([]string, 0, len(widths))
64 for _, w := range widths {
65 parts = append(parts, fmt.Sprintf("%s %dw", pourURL(number, w), w))
66 }
67 return template.Srcset(strings.Join(parts, ", "))
68}
69
70func avatarURL() string {
71 return fmt.Sprintf("/static/images/avatar.%s", images.Format)
72}