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/* Cassette futurism, borrowed from dash and held to the same rule: the texture
2 is structure, not costume. Where dash is amber because it is a public page
3 glanced at, this is green, because DESIGN.md reserves green for the tools
4 that get read for minutes at a time and this is one of those.
5
6 Everything here is drawn in CSS. No bitmaps, so the grain scales and costs
7 no request. */
8
9:root {
10 /* Warm near-black, six steps. Never pure black: phosphor sat on warm glass
11 and #000 reads as a hole in the page rather than a screen.
12
13 Every value below is measured against the ground with the CRT overlays
14 applied, since a multiply blend over the whole page costs more contrast
15 than any colour choice does. Do not darken one to make something look
16 softer without re-measuring what sits on it. */
17 --void: #0a0908;
18 --bg: #0e0d0a;
19 --panel: #1e1b16;
20 --panel-2: #29251e;
21 --bar: #201d16;
22 --line: #414e41;
23 --line-hot: #5c725c;
24
25 --text: #ece5d8;
26 --muted: #c6b9a3;
27 --dim: #ab9c86;
28 --faint: #9c8e7a;
29
30 /* P1 green, the phosphor this is pretending to be. */
31 --green: #6b9e78;
32 --green-bright: #7db88c;
33 --green-hot: #95cca2;
34 --green-glow: rgba(107, 158, 120, 0.16);
35
36 /* Amber is the instrument, not the data. Labels, brackets and warnings. */
37 --amber: #d4b45a;
38 --amber-hot: #ffb000;
39 --terracotta: #c47055;
40
41 --mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
42 --track: 0.14em;
43 --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
44
45 /* One number for the reading column, so the bar and the content line up
46 rather than the bar running edge to edge over a centred page. */
47 --col: 56rem;
48
49 /* The ask box sticks directly under the bar, so both read this rather than
50 each carrying its own number and drifting a few pixels apart. */
51 --barh: 2.9rem;
52}
53
54* { box-sizing: border-box; }
55
56html {
57 color-scheme: dark;
58 scrollbar-color: var(--line-hot) var(--void);
59 scroll-behavior: smooth;
60}
61
62body {
63 margin: 0;
64 min-height: 100vh;
65 background: var(--bg);
66 color: var(--text);
67 font-family: var(--mono);
68 font-size: 13.5px;
69 line-height: 1.6;
70 font-variant-numeric: tabular-nums;
71 -webkit-font-smoothing: antialiased;
72 padding-bottom: 7rem;
73
74 /* The structural grid. 32px so it lines up with the panel padding rhythm
75 rather than cutting across it. */
76 background-image:
77 linear-gradient(rgba(107, 158, 120, 0.028) 1px, transparent 1px),
78 linear-gradient(90deg, rgba(107, 158, 120, 0.028) 1px, transparent 1px);
79 background-size: 32px 32px;
80 background-position: center top;
81}
82
83/* --- CRT overlays ------------------------------------------------------- */
84
85/* Both of these are multiply blends over the whole page, so they cost contrast
86 everywhere rather than only where you can see them. Body text measured 15.5
87 to 1 nominally and 3.9 to 1 under the pair of them at a page corner, which is
88 below the floor for text a person actually reads. The values here are what is
89 left after measuring rather than what looked good. */
90
91/* Scanlines at 3px so they read as structure at any zoom. */
92.crt {
93 position: fixed;
94 inset: 0;
95 z-index: 9000;
96 pointer-events: none;
97 background: repeating-linear-gradient(
98 180deg,
99 rgba(0, 0, 0, 0.08) 0px,
100 rgba(0, 0, 0, 0.08) 1px,
101 transparent 1px,
102 transparent 3px
103 );
104 mix-blend-mode: multiply;
105}
106
107/* Under the header rather than over it, because the corner is where this bites
108 hardest and the corner is where the bar keeps its smallest text. */
109.vignette {
110 position: fixed;
111 inset: 0;
112 z-index: 5;
113 pointer-events: none;
114 background: radial-gradient(ellipse at center, transparent 62%, rgba(0, 0, 0, 0.22) 100%);
115}
116
117/* --- header ------------------------------------------------------------- */
118
119/* Chrome is a plane above the page, so it is a step lighter than the ground
120 rather than a step darker. A bar painted below the body colour reads as a
121 hole in the page and gives everything sat on it nowhere to go. */
122.bar {
123 height: var(--barh);
124 background: var(--bar);
125 border-bottom: 1px solid var(--line-hot);
126 position: sticky;
127 top: 0;
128 z-index: 20;
129}
130
131/* The bar runs the full width and its contents do not, so the brand lines up
132 with the first character of the page under it. */
133.barin {
134 display: flex;
135 align-items: center;
136 gap: 1.1rem;
137 height: 100%;
138 max-width: var(--col);
139 margin: 0 auto;
140 padding: 0 1.5rem;
141}
142.landingbar .barin { max-width: 60rem; }
143
144/* The mark is a lit key rather than a word with a character in front of it: a
145 bordered tile carrying the caret, then the name. It is the only place on the
146 site that gets to be a logo. */
147.brand {
148 display: inline-flex;
149 align-items: center;
150 gap: 0.55rem;
151 color: var(--text);
152 text-decoration: none;
153 font-weight: 700;
154 font-size: 0.95rem;
155 letter-spacing: 0.02em;
156}
157.brand .key {
158 display: inline-grid;
159 place-items: center;
160 width: 1.5rem;
161 height: 1.5rem;
162 border: 1px solid var(--green);
163 border-radius: 2px;
164 background: var(--green-glow);
165 box-shadow: inset 0 0 10px -4px var(--green);
166 transition: background 0.2s var(--ease), border-color 0.2s var(--ease);
167}
168/* The cursor is drawn rather than typed. A block glyph renders at a different
169 width in every monospace face and came out as a hairline in some, which is an
170 empty box where the mark should be. */
171.brand .cursor {
172 display: block;
173 width: 0.42rem;
174 height: 0.85rem;
175 background: var(--green-hot);
176 box-shadow: 0 0 6px -1px var(--green-hot);
177}
178.brand:hover .key { background: var(--green); border-color: var(--green-hot); }
179.brand:hover .cursor { background: var(--void); box-shadow: none; }
180
181/* What page you are on, beside the mark and behind a rule, so the bar says
182 something rather than only carrying controls. */
183.where {
184 display: flex;
185 align-items: center;
186 gap: 0.7rem;
187 font-size: 0.68rem;
188 text-transform: uppercase;
189 letter-spacing: var(--track);
190 color: var(--amber);
191}
192.where::before {
193 content: "";
194 width: 1px;
195 height: 0.95rem;
196 background: var(--line-hot);
197}
198
199.spacer { flex: 1; }
200
201.label {
202 text-transform: uppercase;
203 letter-spacing: var(--track);
204 font-weight: 600;
205 font-size: 0.68rem;
206 color: var(--amber);
207}
208.faint { color: var(--faint); font-size: 0.82rem; }
209.dim { color: var(--dim); }
210
211/* The archive counts and the gauge label sit in the top corner, which is the
212 hardest place on the page to read, so neither gets the faint step. */
213.counts { font-size: 0.68rem; letter-spacing: 0.08em; color: var(--dim); text-transform: uppercase; }
214.counts b { color: var(--text); font-weight: 700; }
215
216.ghost {
217 background: transparent;
218 border: 1px solid var(--line-hot);
219 color: var(--muted);
220 font: inherit;
221 font-size: 0.68rem;
222 text-transform: uppercase;
223 letter-spacing: var(--track);
224 padding: 0.2rem 0.7rem;
225 border-radius: 2px;
226 cursor: pointer;
227 transition: all 0.2s var(--ease);
228}
229.ghost:hover { border-color: var(--green); color: var(--green-bright); background: var(--green-glow); }
230
231/* Seven pixels of colour is not a state, so the ring carries it too: hollow
232 when the model is down and filled when it is up. */
233.dot {
234 width: 9px; height: 9px;
235 border-radius: 50%;
236 border: 1px solid var(--terracotta);
237 background: transparent;
238 color: var(--terracotta);
239}
240.dot.up { background: var(--green); border-color: var(--green); color: var(--green); box-shadow: 0 0 7px var(--green); }
241
242/* --- capacity meter, segmented like a level gauge ----------------------- */
243
244.gauge { display: flex; align-items: center; gap: 0.5rem; }
245.segs { display: flex; gap: 2px; }
246.seg {
247 width: 4px; height: 10px;
248 background: var(--line-hot);
249 border-radius: 1px;
250 transition: background 0.35s var(--ease);
251}
252.seg.on { background: var(--green); }
253.gauge.low .seg.on { background: var(--amber-hot); }
254.gauge.out .seg.on { background: var(--terracotta); }
255.gtext {
256 font-size: 0.66rem;
257 letter-spacing: 0.08em;
258 color: var(--dim);
259 text-transform: uppercase;
260 min-width: 5.5rem;
261}
262.gauge.low .gtext { color: var(--amber-hot); }
263.gauge.out .gtext { color: var(--terracotta); }
264
265/* --- layout ------------------------------------------------------------- */
266
267main { max-width: var(--col); margin: 0 auto; padding: 1.5rem 1.5rem 0; }
268
269.intro { margin-bottom: 2.5rem; animation: rise 0.5s var(--ease) both; }
270.introline {
271 color: var(--muted);
272 border-left: 2px solid var(--line-hot);
273 padding-left: 1rem;
274 margin: 0 0 1rem;
275}
276.ambient {
277 font-size: 0.78rem;
278 color: var(--faint);
279 border-left: 2px solid var(--line);
280 padding-left: 1rem;
281 margin: 0;
282}
283
284/* --- the ask box, button inside the field ------------------------------- */
285
286/* The field is the point of the page, so it sits above the helper text rather
287 than under it, and follows down the page as answers scroll past. Sticking to
288 the top rather than the bottom means it is in the same place whether the page
289 is empty or holds an hour of conversation. */
290.askwrap {
291 position: sticky;
292 top: var(--barh);
293 z-index: 15;
294 margin-bottom: 2rem;
295 padding: 0.9rem 0;
296 background: var(--bg);
297 box-shadow: 0 12px 16px -12px var(--bg), 0 1px 0 0 var(--bg);
298}
299
300.ask {
301 position: relative;
302 display: block;
303}
304
305/* The bracket corners are the whole cassette-futurism trick: a frame that
306 reads as an instrument bezel rather than a form control. */
307.ask::before,
308.ask::after {
309 content: "";
310 position: absolute;
311 width: 10px; height: 10px;
312 border: 1px solid var(--green);
313 opacity: 0.6;
314 transition: opacity 0.25s var(--ease);
315 pointer-events: none;
316}
317.ask::before { top: -3px; left: -3px; border-right: 0; border-bottom: 0; }
318.ask::after { bottom: -3px; right: -3px; border-left: 0; border-top: 0; }
319.ask:focus-within::before,
320.ask:focus-within::after { opacity: 1; }
321
322.ask input {
323 width: 100%;
324 /* The right padding clears the button plus its inset, so text never runs
325 under it. */
326 padding: 1rem 8rem 1rem 1.1rem;
327 background: var(--panel);
328 border: 1px solid var(--line-hot);
329 color: var(--text);
330 font: inherit;
331 font-size: 0.95rem;
332 border-radius: 2px;
333 transition: border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
334}
335.ask input:focus {
336 outline: none;
337 border-color: var(--green);
338 box-shadow: 0 0 0 1px var(--green-glow), 0 0 24px -8px var(--green);
339}
340.ask input::placeholder { color: var(--faint); }
341.ask input:disabled { opacity: 0.55; }
342
343/* Pinned to three edges rather than centred on one, so the gap around it is the
344 same 6px on top, right and bottom. Centring vertically and insetting
345 horizontally gives two different gaps, which is what made it look crooked. */
346.ask button {
347 position: absolute;
348 top: 6px;
349 right: 6px;
350 bottom: 6px;
351 display: inline-flex;
352 align-items: center;
353 justify-content: center;
354 padding: 0 1.3rem;
355 background: var(--green);
356 border: none;
357 color: var(--void);
358 font: inherit;
359 font-size: 0.72rem;
360 font-weight: 700;
361 line-height: 1;
362 text-transform: uppercase;
363 letter-spacing: var(--track);
364 text-indent: var(--track); /* balances the trailing letter-space */
365 cursor: pointer;
366 border-radius: 2px;
367 transition: background 0.2s var(--ease), opacity 0.2s var(--ease);
368}
369.ask button:hover:not(:disabled) { background: var(--green-hot); }
370.ask button:disabled { opacity: 0.35; cursor: default; }
371
372/* The choice at the end of an answer. Following on is the common next move, so
373 it is the filled button, and starting over sits beside it rather than in a
374 corner. */
375.turnfoot {
376 display: flex;
377 gap: 0.6rem;
378 align-items: center;
379 margin-top: 1.6rem;
380 padding-top: 1.1rem;
381 border-top: 1px solid var(--line);
382}
383.act {
384 font: inherit;
385 font-size: 0.7rem;
386 text-transform: uppercase;
387 letter-spacing: var(--track);
388 padding: 0.5rem 1.1rem;
389 border-radius: 2px;
390 cursor: pointer;
391 background: transparent;
392 border: 1px solid var(--line-hot);
393 color: var(--dim);
394 transition: all 0.2s var(--ease);
395}
396.act:hover { border-color: var(--green); color: var(--green-bright); background: var(--green-glow); }
397.act.primary {
398 background: var(--green);
399 border-color: var(--green);
400 color: var(--void);
401 font-weight: 700;
402}
403.act.primary:hover { background: var(--green-hot); border-color: var(--green-hot); color: var(--void); }
404
405/* In follow-up mode the field is marked, so it is never a surprise that the
406 next question is measured against the last answer. */
407.ask.followon input { border-color: var(--green); }
408.ask.followon::before, .ask.followon::after { opacity: 1; }
409
410.hint { margin: 0.5rem 0 0; font-size: 0.76rem; }
411.capnote { color: var(--amber-hot); }
412
413/* --- a turn ------------------------------------------------------------- */
414
415.turn { margin-bottom: 3rem; animation: rise 0.4s var(--ease) both; }
416
417@keyframes rise {
418 from { opacity: 0; transform: translateY(10px); }
419 to { opacity: 1; transform: none; }
420}
421
422.question {
423 font-size: 1.08rem;
424 color: var(--green-bright);
425 border-left: 2px solid var(--green);
426 padding-left: 1rem;
427 margin-bottom: 1.2rem;
428}
429.question::before {
430 content: "ASK";
431 display: block;
432 font-size: 0.62rem;
433 letter-spacing: var(--track);
434 color: var(--amber);
435 margin-bottom: 0.15rem;
436}
437
438/* --- live status -------------------------------------------------------- */
439
440.status {
441 background: var(--panel);
442 border: 1px solid var(--line);
443 border-radius: 2px;
444 padding: 1rem 1.2rem;
445 position: relative;
446 overflow: hidden;
447}
448
449/* A sweep along the top edge, so the panel reads as working rather than as a
450 static box with a spinner glued to it. */
451.status::after {
452 content: "";
453 position: absolute;
454 top: 0; left: 0;
455 height: 1px; width: 35%;
456 background: linear-gradient(90deg, transparent, var(--green-hot), transparent);
457 animation: sweep 1.8s linear infinite;
458}
459@keyframes sweep {
460 from { transform: translateX(-100%); }
461 to { transform: translateX(390%); }
462}
463
464.stephead { display: flex; align-items: center; gap: 0.6rem; }
465.spinner {
466 width: 7px; height: 7px;
467 border-radius: 50%;
468 background: var(--green);
469 box-shadow: 0 0 8px var(--green);
470 animation: pulse 1.2s ease-in-out infinite;
471 flex: none;
472}
473@keyframes pulse { 0%, 100% { opacity: 0.25; transform: scale(0.85); } 50% { opacity: 1; transform: scale(1); } }
474
475.steptext { color: var(--green-bright); flex: 1; }
476.clock { color: var(--faint); font-size: 0.76rem; font-variant-numeric: tabular-nums; }
477
478.why {
479 color: var(--faint);
480 font-size: 0.76rem;
481 margin: 0.35rem 0 0 1.3rem;
482 min-height: 1.2em;
483 transition: opacity 0.3s var(--ease);
484}
485
486.trail { list-style: none; margin: 0.5rem 0 0; padding: 0 0 0 1.3rem; }
487.trail li {
488 color: var(--faint);
489 font-size: 0.78rem;
490 line-height: 1.6;
491 animation: slidein 0.3s var(--ease) both;
492}
493.trail li::before { content: "› "; color: var(--green); }
494@keyframes slidein {
495 from { opacity: 0; transform: translateX(-6px); }
496 to { opacity: 1; transform: none; }
497}
498
499/* --- answer ------------------------------------------------------------- */
500
501.answerwrap { animation: rise 0.45s var(--ease) both; }
502
503.meta { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.8rem; }
504.chip {
505 font-size: 0.68rem;
506 color: var(--dim);
507 background: var(--panel-2);
508 border: 1px solid var(--line);
509 padding: 0.1rem 0.5rem;
510 border-radius: 2px;
511}
512.flag {
513 font-size: 0.62rem;
514 text-transform: uppercase;
515 letter-spacing: var(--track);
516 color: var(--amber-hot);
517 border: 1px solid var(--amber-hot);
518 padding: 0 0.4rem;
519 border-radius: 2px;
520}
521
522.answer {
523 background: var(--panel);
524 border: 1px solid var(--line-hot);
525 border-radius: 2px;
526 padding: 0.5rem 1.5rem 1.3rem;
527 position: relative;
528}
529/* A hairline of phosphor down the left edge, the way a lit panel would sit. */
530.answer::before {
531 content: "";
532 position: absolute;
533 left: -1px; top: 0; bottom: 0;
534 width: 2px;
535 background: linear-gradient(180deg, var(--green), transparent);
536}
537
538.prose > *:first-child { margin-top: 1rem; }
539.prose h1, .prose h2, .prose h3 {
540 font-size: 0.7rem;
541 text-transform: uppercase;
542 letter-spacing: var(--track);
543 color: var(--amber);
544 margin: 1.6rem 0 0.6rem;
545 padding-bottom: 0.3rem;
546 border-bottom: 1px solid var(--line);
547}
548.prose p { margin: 0.85rem 0; }
549.prose ul, .prose ol { margin: 0.85rem 0; padding-left: 1.4rem; }
550.prose li { margin: 0.3rem 0; }
551.prose li::marker { color: var(--green); }
552.prose strong { color: var(--green-hot); font-weight: 700; }
553.prose code {
554 background: var(--panel-2);
555 padding: 0.1rem 0.35rem;
556 border-radius: 2px;
557 color: var(--green-bright);
558 font-size: 0.92em;
559}
560.prose pre {
561 background: var(--void);
562 border: 1px solid var(--line);
563 padding: 0.9rem;
564 overflow-x: auto;
565 border-radius: 2px;
566}
567.prose table { border-collapse: collapse; width: 100%; display: block; overflow-x: auto; }
568.prose th, .prose td { border: 1px solid var(--line); padding: 0.35rem 0.7rem; text-align: left; }
569.prose th { color: var(--amber); font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.08em; }
570.prose a { color: var(--green-bright); }
571.prose blockquote {
572 border-left: 2px solid var(--line-hot);
573 margin: 0.85rem 0;
574 padding-left: 1rem;
575 color: var(--muted);
576}
577
578a.cite {
579 color: var(--green-hot);
580 text-decoration: none;
581 font-size: 0.8em;
582 vertical-align: super;
583 padding: 0 0.08em;
584 transition: color 0.15s var(--ease);
585}
586a.cite:hover { color: var(--amber-hot); }
587
588/* --- validation --------------------------------------------------------- */
589
590section { margin-bottom: 1.8rem; }
591.validation { margin-top: 1.8rem; }
592.vhead { display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; }
593
594/* A verification bar, filled by the share that held up. */
595.vbar {
596 flex: 1;
597 min-width: 6rem;
598 height: 4px;
599 background: var(--line);
600 border-radius: 2px;
601 overflow: hidden;
602}
603.vfill {
604 display: block;
605 height: 100%;
606 background: var(--green);
607 width: 0;
608 transition: width 0.8s var(--ease);
609}
610.vbar.bad .vfill { background: var(--amber-hot); }
611.score { font-size: 0.76rem; color: var(--green-bright); }
612.score.bad { color: var(--amber-hot); }
613
614.explain { margin: 0.6rem 0 0.9rem; max-width: 48rem; line-height: 1.65; }
615
616.checks { list-style: none; padding: 0; margin: 0.7rem 0 0; }
617.checks li {
618 display: flex;
619 gap: 0.8rem;
620 align-items: baseline;
621 padding: 0.55rem 0.8rem;
622 border-left: 2px solid var(--green);
623 background: var(--panel);
624 margin-bottom: 3px;
625 transition: background 0.2s var(--ease);
626}
627.checks li:hover { background: var(--panel-2); }
628.checks li.bad { border-left-color: var(--terracotta); }
629.verdict {
630 font-size: 0.62rem;
631 text-transform: uppercase;
632 letter-spacing: 0.1em;
633 color: var(--green-bright);
634 flex: 0 0 5.5rem;
635}
636.checks li.bad .verdict { color: var(--terracotta); }
637.sentence { color: var(--muted); font-size: 0.84rem; }
638.note { display: block; color: var(--faint); font-size: 0.74rem; font-style: italic; }
639
640/* --- sources ------------------------------------------------------------ */
641
642.sources { list-style: none; padding: 0; margin: 0.7rem 0 0; counter-reset: src; }
643.sources li {
644 counter-increment: src;
645 margin-bottom: 0.8rem;
646 padding-left: 2.2rem;
647 position: relative;
648}
649.sources li::before {
650 content: "[" counter(src) "]";
651 position: absolute;
652 left: 0;
653 color: var(--green);
654 font-size: 0.78rem;
655}
656.sources a { color: var(--green-bright); text-decoration: none; }
657.sources a:hover { color: var(--green-hot); text-decoration: underline; }
658.cached { color: var(--amber); }
659
660details.passages { margin-top: 1.2rem; border-top: 1px solid var(--line); padding-top: 0.9rem; }
661summary { cursor: pointer; list-style: none; }
662summary::-webkit-details-marker { display: none; }
663summary::before { content: "â–¸ "; color: var(--green); }
664details[open] > summary::before { content: "â–¾ "; }
665.passage {
666 border-left: 2px solid var(--line-hot);
667 padding: 0.5rem 0 0.5rem 0.9rem;
668 margin: 0.9rem 0;
669 scroll-margin-top: 5rem;
670 transition: border-color 0.3s var(--ease), background 0.3s var(--ease);
671}
672.passage:target { border-left-color: var(--green-hot); background: var(--panel-2); }
673.phead { display: flex; gap: 0.6rem; align-items: baseline; }
674.passage p { color: var(--muted); font-size: 0.82rem; margin: 0.35rem 0 0; white-space: pre-wrap; }
675
676.error { color: var(--terracotta); }
677.warn { color: var(--amber-hot); font-size: 0.8rem; margin: 0.4rem 0 0; }
678
679@media (prefers-reduced-motion: reduce) {
680 *, *::before, *::after { animation: none !important; transition: none !important; }
681 html { scroll-behavior: auto; }
682}
683
684/* --- the public landing ------------------------------------------------- */
685
686.landing { max-width: 60rem; padding-bottom: 4rem; }
687
688.hero { margin: 4rem 0 5rem; }
689.eyebrow {
690 display: block;
691 font-size: 0.66rem;
692 text-transform: uppercase;
693 letter-spacing: var(--track);
694 color: var(--amber);
695 margin-bottom: 1.2rem;
696}
697.hero h1 {
698 font-size: clamp(2rem, 5vw, 3.2rem);
699 font-weight: 700;
700 letter-spacing: -0.01em;
701 line-height: 1.15;
702 margin: 0 0 1.4rem;
703}
704.hero h1 .accent { color: var(--green-bright); }
705.lede { color: var(--muted); max-width: 44rem; margin: 0 0 2rem; font-size: 0.95rem; }
706.ctas { display: flex; gap: 0.7rem; flex-wrap: wrap; }
707.ctas .act { text-decoration: none; display: inline-flex; align-items: center; }
708
709/* Four cards, so two columns. auto-fit gave three at this width and left the
710 fourth alone beside an empty half row. */
711.grid {
712 display: grid;
713 grid-template-columns: repeat(2, 1fr);
714 gap: 1px;
715 background: var(--line);
716 border: 1px solid var(--line);
717 margin-bottom: 3rem;
718}
719.card { background: var(--panel); padding: 1.4rem 1.5rem; }
720.card p { color: var(--muted); font-size: 0.84rem; margin: 0.5rem 0 0; }
721
722.strip {
723 display: flex;
724 flex-wrap: wrap;
725 gap: 2.5rem;
726 padding: 1.5rem 0;
727 border-top: 1px solid var(--line);
728 border-bottom: 1px solid var(--line);
729 margin-bottom: 2.5rem;
730}
731.stat { display: flex; flex-direction: column; }
732.statv { font-size: 1.5rem; color: var(--green-bright); font-weight: 700; }
733.statl {
734 font-size: 0.64rem;
735 text-transform: uppercase;
736 letter-spacing: var(--track);
737 color: var(--dim);
738}
739
740.foot p { margin: 0; }
741.foot a { color: var(--green-bright); }
742a.ghost { text-decoration: none; display: inline-flex; align-items: center; }
743
744/* --- narrow screens ------------------------------------------------------
745 This gets read on a phone as much as a desktop. Three things drive the
746 layout here: the header has five things in it and only two of them matter
747 small, the validation rows are a two column table that has to become one,
748 and the sticky ask box must not eat the viewport when a keyboard opens. */
749
750@media (max-width: 46rem) {
751 body { font-size: 13px; padding-bottom: 5rem; }
752
753 .barin { gap: 0.7rem; padding: 0 1rem; }
754 /* The archive count is context, not something to act on, so it goes first. */
755 #archive { display: none; }
756 .where { display: none; }
757 .gtext { min-width: 0; }
758
759 main { padding: 1.5rem 1rem 0; }
760
761 .question { font-size: 1rem; }
762
763 .ask input { padding: 0.9rem 6.5rem 0.9rem 0.9rem; font-size: 16px; }
764 .ask button { padding: 0 0.9rem; font-size: 0.66rem; letter-spacing: 0.1em; }
765
766 .answer { padding: 0.4rem 1rem 1.1rem; }
767 .prose ul, .prose ol { padding-left: 1.2rem; }
768
769 /* One column: a 5.5rem verdict beside wrapping text leaves about eight
770 characters a line on a phone. */
771 .checks li { flex-direction: column; gap: 0.2rem; padding: 0.6rem 0.7rem; }
772 .verdict { flex: none; }
773 .sentence { font-size: 0.86rem; }
774
775 .turnfoot { flex-direction: column; align-items: stretch; }
776 .act { text-align: center; justify-content: center; }
777
778 .meta { gap: 0.4rem; }
779 .chip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
780
781 .sources li { padding-left: 1.9rem; }
782
783 .grid { grid-template-columns: 1fr; }
784 .hero { margin: 2.5rem 0 3rem; }
785 .strip { gap: 1.5rem; }
786 .passage { padding-left: 0.7rem; }
787}
788
789/* The header loses its label text before it wraps. */
790@media (max-width: 30rem) {
791 .gtext { display: none; }
792 .barin { gap: 0.55rem; }
793 .brand { font-size: 0.88rem; }
794 .ask input { padding-right: 3.4rem; }
795 /* An icon-sized button, since "search" does not fit beside a question. */
796 .ask button { padding: 0 0.7rem; font-size: 0; }
797 .ask button::after { content: "\2192"; font-size: 1.1rem; line-height: 1; }
798 .statv { font-size: 1.25rem; }
799}
800
801/* A phone keyboard shrinks the viewport rather than scrolling the page, so a
802 sticky element measured against vh ends up above the fold. */
803@supports (height: 100dvh) {
804 body { min-height: 100dvh; }
805}
806
807/* A calculated answer has no sources and no validation, so it is the number
808 and the working and nothing else. */
809.answer.calc { text-align: center; padding: 1.6rem 1.4rem; }
810.answer.calc p:first-child strong {
811 font-size: clamp(2rem, 7vw, 3rem);
812 color: var(--green-hot);
813 line-height: 1.1;
814 display: block;
815 margin-bottom: 0.6rem;
816}
817.answer.calc code { background: transparent; color: var(--faint); font-size: 0.86rem; }
818
819/* --- verified links to what the answer named ---------------------------- */
820
821.entlinks { list-style: none; padding: 0; margin: 0.7rem 0 0; }
822.entlinks li {
823 padding: 0.6rem 0.8rem;
824 background: var(--panel);
825 border-left: 2px solid var(--green-hot);
826 margin-bottom: 3px;
827}
828.entname {
829 color: var(--green-hot);
830 text-decoration: none;
831 font-weight: 700;
832}
833.entname:hover { text-decoration: underline; }
834.entname::after { content: " \2197"; font-weight: 400; font-size: 0.85em; }
835
836
837/* --- the try panel, an instrument list rather than a wrapping grid ------- */
838
839.skills { margin: 1.8rem 0; }
840.skhead { display: flex; align-items: center; gap: 0.8rem; margin-bottom: 0.5rem; }
841.skrule { flex: 1; height: 1px; background: var(--line); }
842
843.skillrow { list-style: none; margin: 0; padding: 0; }
844.skillrow li + li { border-top: 1px solid var(--line); }
845
846.skill {
847 display: grid;
848 grid-template-columns: 7rem 1fr auto;
849 align-items: baseline;
850 gap: 1rem;
851 width: 100%;
852 text-align: left;
853 background: transparent;
854 border: 0;
855 border-left: 2px solid transparent;
856 padding: 0.5rem 0.7rem;
857 font: inherit;
858 cursor: pointer;
859 transition: border-color 0.18s var(--ease), background 0.18s var(--ease);
860}
861.skill:hover, .skill:focus-visible {
862 background: var(--panel);
863 border-left-color: var(--green);
864 outline: none;
865}
866
867.skname {
868 font-size: 0.64rem;
869 text-transform: uppercase;
870 letter-spacing: var(--track);
871 color: var(--amber);
872}
873.skex { color: var(--dim); font-size: 0.84rem; }
874.skill:hover .skex { color: var(--green-bright); }
875
876.sktag {
877 font-size: 0.58rem;
878 text-transform: uppercase;
879 letter-spacing: 0.1em;
880 color: var(--faint);
881 border: 1px solid var(--line-hot);
882 padding: 0 0.35rem;
883 border-radius: 2px;
884}
885.skill:hover .sktag { color: var(--green); border-color: var(--green); }
886
887@media (max-width: 46rem) {
888 .skill { grid-template-columns: 1fr auto; gap: 0.2rem 0.6rem; padding: 0.6rem 0.7rem; }
889 .skname { grid-column: 1 / -1; }
890}
891
892/* --- waiting for the people ahead --------------------------------------- */
893
894.status.waiting::after { animation-duration: 3.2s; opacity: 0.55; }
895.status.waiting .spinner { background: var(--amber-hot); box-shadow: 0 0 8px var(--amber-hot); }
896.status.waiting .steptext { color: var(--amber-hot); }
897
898/* --- code --------------------------------------------------------------- */
899
900/* A code answer is copied, not read, so the block is a thing with a lid rather
901 than a paragraph in a box: what it is on the left, the button on the right. */
902.codeblock {
903 margin: 0.85rem 0;
904 border: 1px solid var(--line);
905 border-radius: 2px;
906 overflow: hidden;
907 background: var(--void);
908}
909.codehead {
910 display: flex;
911 align-items: center;
912 gap: 0.6rem;
913 padding: 0.35rem 0.6rem;
914 background: var(--panel);
915 border-bottom: 1px solid var(--line);
916}
917.codename {
918 font-size: 0.72rem;
919 letter-spacing: 0.04em;
920 color: var(--green-bright);
921}
922.codelang {
923 font-size: 0.62rem;
924 text-transform: uppercase;
925 letter-spacing: var(--track);
926 color: var(--faint);
927}
928.codehead .spacer { flex: 1; }
929
930.copy {
931 font: inherit;
932 font-size: 0.62rem;
933 text-transform: uppercase;
934 letter-spacing: var(--track);
935 color: var(--amber);
936 background: transparent;
937 border: 1px solid var(--line-hot);
938 border-radius: 2px;
939 padding: 0.15rem 0.55rem;
940 cursor: pointer;
941 transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
942}
943.copy:hover { color: var(--amber-hot); border-color: var(--amber); }
944.copy.done { color: var(--green-hot); border-color: var(--green); }
945
946.codeblock pre {
947 margin: 0;
948 border: 0;
949 border-radius: 0;
950 background: transparent;
951}
952.codeblock pre code { background: transparent; padding: 0; color: var(--text); font-size: 0.82rem; }
953/* Long lines scroll inside the block. Wrapping a command over three lines is
954 how a pasted command ends up missing an argument. */
955.codeblock pre { white-space: pre; }
956
957.deps { list-style: none; padding: 0; margin: 0.7rem 0 0; }
958.deps li {
959 display: flex;
960 align-items: baseline;
961 gap: 0.6rem;
962 padding: 0.35rem 0.7rem;
963 border-left: 2px solid var(--green);
964 background: var(--panel);
965 margin-bottom: 2px;
966}
967.deps li.bad { border-left-color: var(--terracotta); }
968.deps li.unknown { border-left-color: var(--line-hot); }
969.depname { color: var(--text); font-size: 0.82rem; }
970.depeco {
971 font-size: 0.62rem;
972 text-transform: uppercase;
973 letter-spacing: var(--track);
974 color: var(--faint);
975}
976.depstate { margin-left: auto; font-size: 0.72rem; color: var(--green); }
977.deps li.bad .depstate { color: var(--terracotta); }
978.deps li.unknown .depstate { color: var(--faint); }
979
980@media (max-width: 620px) {
981 .deps li { flex-wrap: wrap; gap: 0.3rem 0.6rem; }
982 .depstate { margin-left: 0; width: 100%; }
983}
984
985/* Incognito. Amber because that is what amber is for on this site, the
986 instrument rather than the data, and because a mode that is easy to forget
987 you left on has to be impossible to miss. It lives in the bar rather than in
988 the ask row, since it is a mode rather than a per question checkbox, and
989 because every button in the ask row is absolutely positioned on top of the
990 same corner.
991
992 Four things carry it: the toggle fills, the bar takes an amber rule, the ask
993 box goes amber with a note under it, and every answer asked in it is tagged
994 and dashed. */
995.incogbtn[aria-pressed="true"] {
996 border-color: var(--amber-hot);
997 color: var(--void);
998 background: var(--amber-hot);
999}
1000.incogbtn[aria-pressed="true"]:hover {
1001 border-color: var(--amber-hot);
1002 color: var(--void);
1003 background: var(--amber);
1004}
1005
1006body.incognito .bar { border-bottom: 1px solid var(--amber-hot); }
1007body.incognito .askwrap {
1008 border-left: 2px dashed var(--amber-hot);
1009 padding-left: 0.7rem;
1010}
1011body.incognito .ask input { border-color: var(--amber); }
1012body.incognito .ask input:focus {
1013 border-color: var(--amber-hot);
1014 box-shadow: 0 0 0 1px rgba(255, 176, 0, 0.16), 0 0 24px -8px var(--amber-hot);
1015}
1016body.incognito .ask::before,
1017body.incognito .ask::after { border-color: var(--amber-hot); }
1018.incognote {
1019 font-size: 0.78rem;
1020 color: var(--dim);
1021 margin: 0.45rem 0 0;
1022 line-height: 1.5;
1023}
1024.incognote .label { margin-right: 0.5rem; color: var(--amber-hot); }
1025
1026.turn.incog { border-left: 2px dashed var(--amber-hot); padding-left: 0.7rem; }
1027
1028/* The thumb. It sits under the answer rather than in the meta row, because it
1029 is asked after reading rather than before. */
1030.rating {
1031 display: flex;
1032 flex-wrap: wrap;
1033 align-items: center;
1034 gap: 0.5rem;
1035 margin-top: 0.9rem;
1036 padding-top: 0.7rem;
1037 border-top: 1px solid var(--line);
1038}
1039.thumb {
1040 background: transparent;
1041 border: 1px solid var(--line-hot);
1042 color: var(--muted);
1043 font: inherit;
1044 font-size: 0.7rem;
1045 padding: 0.15rem 0.6rem;
1046 border-radius: 2px;
1047 cursor: pointer;
1048}
1049.thumb:hover { border-color: var(--green); color: var(--green-bright); background: var(--green-glow); }
1050.thumb:disabled { opacity: 0.4; cursor: default; }
1051.thumb.down:hover { border-color: var(--amber-hot); color: var(--amber-hot); background: rgba(255, 176, 0, 0.08); }
1052.why-not { display: flex; flex-wrap: wrap; gap: 0.4rem; }
1053/* A display rule beats the hidden attribute, so the reasons would sit there
1054 before anyone said the answer was wrong. */
1055.why-not[hidden] { display: none; }
1056
1057/* The history page. One row per question, opened to see the answer it gave. */
1058.hist { list-style: none; padding: 0; margin: 1rem 0 0; }
1059.hrow { border-bottom: 1px solid var(--line); padding: 0.7rem 0; }
1060.hrow summary { cursor: pointer; display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; }
1061.hrow summary::-webkit-details-marker { display: none; }
1062.hq { color: var(--text); flex: 1 1 20rem; }
1063.hwhen { color: var(--faint); font-size: 0.75rem; }
1064.verdict { white-space: nowrap; font-size: 0.62rem; text-transform: uppercase; letter-spacing: var(--track); padding: 0 0.4rem; border-radius: 2px; }
1065.verdict.up { color: var(--green-hot); border: 1px solid var(--green); }
1066.verdict.down { color: var(--amber-hot); border: 1px solid var(--amber-hot); }
1067.hbody { padding: 0.8rem 0 0.2rem; }
1068.hbody .answer { border-left: 1px solid var(--line); padding-left: 0.8rem; }
1069.hmeta { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.6rem; align-items: center; }
1070.stamp { font-size: 0.68rem; color: var(--faint); font-family: var(--mono); }
1071.danger {
1072 background: transparent;
1073 border: 1px solid var(--terracotta);
1074 color: var(--terracotta);
1075 font: inherit;
1076 font-size: 0.68rem;
1077 text-transform: uppercase;
1078 letter-spacing: var(--track);
1079 padding: 0.2rem 0.7rem;
1080 border-radius: 2px;
1081 cursor: pointer;
1082}
1083.danger:hover { background: rgba(196, 112, 85, 0.12); }
1084.filters { display: flex; gap: 0.5rem; margin-top: 0.8rem; flex-wrap: wrap; align-items: center; }
1085.filters a { color: var(--dim); text-decoration: none; font-size: 0.7rem; text-transform: uppercase; letter-spacing: var(--track); border: 1px solid var(--line); padding: 0.15rem 0.6rem; border-radius: 2px; }
1086.filters a.on { color: var(--green-bright); border-color: var(--green); }