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

33.2 KB · 1087 lines · CSS Raw History
   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); }