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
1{{/* Shared fragments. Each takes exactly what it needs as the dot, so a card
2 does not have to know which page it is on. */}}
3
4{{ define "search_form" }}
5<form method="GET" action="/search/" class="position-relative search-form">
6 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="search-icon" viewBox="0 0 16 16">
7 <path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
8 </svg>
9 <input type="text" class="form-control search-input" id="id_search" name="q" value="{{ . }}" placeholder="search posts..." autofocus>
10</form>
11{{ end }}
12
13{{/* One post as a card in a grid. The dot is a *Post. */}}
14{{ define "post_card" }}
15<div class="card rounded border-0 h-100 bg-surface">
16 <div class="mb-3 pt-3 px-3">
17 {{- range .TagLinks }}
18 <a href="{{ .URL }}" class="btn btn-tag btn-sm rounded-pill">{{ .Display }}</a>
19 {{- end }}
20 </div>
21 {{- if .CoverImage }}
22 <a href="{{ .URL }}">
23 <img src="{{ .CoverURL }}" class="card-img-top rounded img-fluid px-3" alt="{{ .Title }}" loading="lazy" decoding="async">
24 </a>
25 {{- end }}
26 <div class="card-body d-flex flex-column">
27 <a href="{{ .URL }}" class="text-decoration-none" style="color: #f0f0f5;">
28 <div class="h5 card-title fw-bold">{{ .Title }}</div>
29 </a>
30 <div class="card-text flex-grow-1" style="color: #a09890;">{{ .Description }}</div>
31 <div class="row g-0 mt-3">
32 <div class="col-12 col-xl-8 d-flex align-items-center">
33 <img class="rounded-circle me-2" src="/content/images/avatar.webp" alt="Isaac Bythewood" width="25" height="25" loading="lazy" decoding="async">
34 <strong class="me-2" style="font-size: 0.9em;">Isaac Bythewood</strong>
35 </div>
36 <div class="col-12 col-xl-4 d-flex justify-content-start justify-content-xl-end text-muted" style="font-size: 0.85em;">
37 {{ .Date }}
38 </div>
39 </div>
40 </div>
41</div>
42{{ end }}
43
44{{/* The wide hero treatment for the newest post. The dot is a *Post. */}}
45{{ define "post_latest" }}
46<div class="container mt-5">
47 <div class="row">
48 <div class="col">
49 <div class="section-label mb-2">Latest post</div>
50 </div>
51 </div>
52 <div class="row">
53 <div class="col-12 col-md-6 col-lg-5 pb-5" style="border-bottom: 1px solid rgba(255,255,255,0.06);">
54 <a href="{{ .URL }}" class="text-decoration-none" style="color: #f0f0f5;">
55 <h2 class="fw-bolder">{{ .Title }}</h2>
56 </a>
57 <div class="mb-3">
58 {{- range .TagLinks }}
59 <a href="{{ .URL }}" class="btn btn-tag btn-sm rounded-pill">{{ .Display }}</a>
60 {{- end }}
61 </div>
62 <p class="h5 text-muted mb-3" style="font-weight: 400;">{{ .Description }}</p>
63 <div class="d-flex align-items-center">
64 <img class="rounded-circle me-3" src="/content/images/avatar.webp" alt="Isaac Bythewood" width="35" height="35">
65 <strong class="me-2">Isaac Bythewood</strong>
66 <div class="d-inline text-muted">{{ .Date }}</div>
67 </div>
68 </div>
69 {{- if .CoverImage }}
70 <div class="col-12 col-md-6 col-lg-7 pb-5" style="border-bottom: 1px solid rgba(255,255,255,0.06);">
71 <a href="{{ .URL }}">
72 <img src="{{ .CoverURL }}" class="img-fluid rounded" alt="{{ .Title }}">
73 </a>
74 </div>
75 {{- end }}
76 </div>
77</div>
78{{ end }}
79
80{{/* A horizontally scrolling strip of cards, used for "random" and "related".
81 The dot is a struct of Label, Blurb and Posts. */}}
82{{ define "post_strip" }}
83<div class="container mt-5 d-print-none">
84 <div class="row">
85 <div class="col">
86 <div class="section-label mb-1">{{ .Label }}</div>
87 <p class="text-muted">{{ .Blurb }}</p>
88 </div>
89 </div>
90 <div class="row d-flex flex-nowrap overflow-auto w-100">
91 {{- range .Posts }}
92 <div class="col-10 col-sm-7 col-md-5 col-lg-4">
93 {{- template "post_card" . }}
94 </div>
95 {{- end }}
96 </div>
97</div>
98{{ end }}
99
100{{/* Bootstrap alert with an icon. The dot is a struct of Kind and Message. */}}
101{{ define "alert" }}
102<div class="alert alert-{{ .Kind }} d-flex align-items-center row g-1">
103 {{- if eq .Kind "warning" }}
104 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="col-2 col-md-1" viewBox="0 0 16 16">
105 <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM8 4a.905.905 0 0 0-.9.995l.35 3.507a.552.552 0 0 0 1.1 0l.35-3.507A.905.905 0 0 0 8 4zm.002 6a1 1 0 1 0 0 2 1 1 0 0 0 0-2z"/>
106 </svg>
107 {{- else }}
108 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="col-2 col-md-1" viewBox="0 0 16 16">
109 <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"/>
110 </svg>
111 {{- end }}
112 <div class="col-10 col-md-11">{{ .Message }}</div>
113</div>
114{{ end }}