repos
/ orchard main

orchard

mirror

Every 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

2.0 KB · 73 lines · Go Raw History
 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}