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@keyframes index-fadeStart {
2 from {
3 opacity: 0;
4 }
5 to {
6 opacity: 1;
7 }
8}
9
10.index-imageWrapper {
11 position: fixed;
12 top: 0;
13 left: 0;
14 right: 0;
15 bottom: 0;
16 width: 100%;
17 height: 100vh;
18 z-index: 0;
19 pointer-events: none;
20}
21
22.index-imageWrapper img {
23 /* next/image fill mode supplied these inline. */
24 width: 100%;
25 height: 100%;
26 object-fit: cover;
27 object-position: center;
28 display: block;
29}
30
31.index-words {
32 position: fixed;
33 top: 0;
34 bottom: 0;
35 left: 0;
36 right: 0;
37 width: 100%;
38 height: 100vh;
39 display: flex;
40 align-items: center;
41 justify-content: center;
42 z-index: 10;
43 mix-blend-mode: darken;
44 background: rgba(0, 0, 0, 0.8);
45}
46
47.index-word {
48 opacity: 0.2;
49 font-size: 11vw;
50 text-transform: uppercase;
51 margin: 0;
52 position: absolute;
53}
54
55.index-wordAppear,
56.index-wordEnter {
57 opacity: 0;
58 transform: translateY(-200px);
59}
60
61.index-wordAppearActive,
62.index-wordEnterActive {
63 opacity: 0.2;
64 transform: translateY(0);
65 transition-duration: 1000ms;
66 transition-property: opacity, transform;
67}
68
69.index-wordAppearDone,
70.index-wordEnterDone {
71 opacity: 0.2;
72}
73
74.index-wordExit {
75 transform: translateY(0);
76 opacity: 0.2;
77}
78
79.index-wordExitActive {
80 opacity: 0;
81 transform: translateY(200px);
82 transition-duration: 1000ms;
83 transition-property: opacity, transform;
84}
85
86/* Gated on data-js, which the head script sets before first paint. This starts
87 at opacity 0 and is revealed by pages/index.js, so without the gate a throw
88 anywhere in the module leaves the h1, the job title and the latest-post card
89 permanently invisible. A returning visitor gets no curtain to mask it, so the
90 page would render as a dark scrim and nothing else. */
91html[data-js] .index-hero {
92 opacity: 0;
93}
94
95.index-hero {
96 position: relative;
97 z-index: 20;
98 animation-name: index-fadeStart;
99 animation-duration: 1500ms;
100 animation-fill-mode: forwards;
101 animation-play-state: paused;
102}
103
104.index-hero::before {
105 content: "";
106 display: block;
107 width: 60px;
108 height: 4px;
109 margin-bottom: 24px;
110 background-image: linear-gradient(
111 to right,
112 var(--color-blue) 0,
113 var(--color-purple) 100%
114 );
115}
116
117.index-name {
118 margin: 0 0 16px 0;
119 line-height: 0.9;
120 overflow: visible;
121}
122
123.index-firstName {
124 display: block;
125 font-size: clamp(2.8em, 6.5vw, 5em);
126 font-weight: 800;
127 text-transform: uppercase;
128 letter-spacing: -0.02em;
129}
130
131.index-lastName {
132 display: block;
133 font-size: clamp(2.8em, 6.5vw, 5em);
134 font-weight: 800;
135 text-transform: uppercase;
136 letter-spacing: -0.02em;
137 background-image: linear-gradient(
138 to right,
139 var(--color-blue) 0,
140 var(--color-purple) 100%
141 );
142 -webkit-background-clip: text;
143 background-clip: text;
144 -webkit-text-fill-color: transparent;
145 filter: drop-shadow(0 0 20px rgba(0, 0, 0, 0.8));
146}
147
148.index-role {
149 display: flex;
150 align-items: baseline;
151 gap: 10px;
152 font-family: monospace;
153 font-size: 1.5em;
154 letter-spacing: 2px;
155 text-transform: uppercase;
156}
157
158.index-roleTitle {
159 color: white;
160 font-weight: 700;
161}
162
163.index-roleAt {
164 color: rgba(255, 255, 255, 0.4);
165 font-weight: 400;
166}
167
168.index-roleCompany {
169 color: rgba(255, 255, 255, 0.6);
170 font-weight: 400;
171}
172
173.index-notice {
174 display: block;
175 margin-top: 32px;
176 padding: 16px 20px;
177 border-left: 3px solid var(--color-purple);
178 background: rgba(0, 0, 0, 0.5);
179 text-decoration: none;
180 transition: background 250ms ease, border-color 250ms ease;
181 max-width: 420px;
182}
183
184.index-notice:hover {
185 background: rgba(0, 0, 0, 0.65);
186 border-color: var(--color-blue);
187}
188
189.index-noticeLabel {
190 display: inline-block;
191 font-family: monospace;
192 font-size: 0.85em;
193 font-weight: 700;
194 text-transform: uppercase;
195 letter-spacing: 3px;
196 color: rgba(255, 255, 255, 0.6);
197 margin-bottom: 6px;
198}
199
200.index-noticeTitle {
201 display: block;
202 font-size: 1.3em;
203 font-weight: 800;
204 text-transform: uppercase;
205 letter-spacing: 1px;
206 color: white;
207 margin-bottom: 4px;
208}
209
210.index-noticeDesc {
211 display: block;
212 font-size: 0.85em;
213 line-height: 1.5;
214 color: rgba(255, 255, 255, 0.7);
215 font-style: italic;
216}
217
218@media (max-width: 767.98px) {
219 .index-firstName,
220 .index-lastName {
221 font-size: 10vw;
222 }
223
224 .index-role {
225 flex-direction: column;
226 gap: 2px;
227 font-size: 0.85em;
228 }
229
230 .index-notice {
231 margin-top: 20px;
232 padding: 10px 14px;
233 }
234
235 .index-noticeLabel {
236 font-size: 0.6em;
237 letter-spacing: 2px;
238 }
239
240 .index-noticeTitle {
241 font-size: 0.9em;
242 }
243
244 .index-noticeDesc {
245 font-size: 0.75em;
246 }
247}