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

43.3 KB · 1165 lines · CSS Raw History
   1/* Cassette futurism, held to the same rule as dash and search: the texture is
   2   structure, not costume. Green because DESIGN.md reserves green for the tools
   3   read for minutes at a time, and a conversation is the longest read here.
   4
   5   The layout is deliberately a familiar chat: history on the left, thread in
   6   the middle, composer pinned at the bottom. The costume is in the palette and
   7   the chrome, not in where things are, because a chat you have to learn is
   8   worse than one that looks like every other chat.
   9
  10   Every value is measured against the ground with the overlays applied. A
  11   multiply blend over the page costs more contrast than any colour choice, so
  12   do not darken one of these to soften something without re-measuring. */
  13
  14:root {
  15  --void: #0a0908;
  16  --bg: #0e0d0a;
  17  --panel: #1e1b16;
  18  --panel-2: #29251e;
  19  --bar: #201d16;
  20  --line: #414e41;
  21  --line-hot: #5c725c;
  22
  23  --text: #ece5d8;
  24  --muted: #c6b9a3;
  25  --dim: #ab9c86;
  26  --faint: #9c8e7a;
  27
  28  --green: #6b9e78;
  29  --green-bright: #7db88c;
  30  --green-hot: #95cca2;
  31  --green-glow: rgba(107, 158, 120, 0.16);
  32
  33  --amber: #d4b45a;
  34  --terracotta: #c47055;
  35
  36  --mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  37  --track: 0.14em;
  38  --ease: cubic-bezier(0.2, 0.8, 0.2, 1);
  39
  40  --col: 46rem;      /* the reading column for a message */
  41  --side: 16.5rem;
  42  --barh: 3.1rem;
  43}
  44
  45* { box-sizing: border-box; }
  46
  47html {
  48  color-scheme: dark;
  49  scrollbar-color: var(--line-hot) var(--void);
  50}
  51
  52body {
  53  margin: 0;
  54  background: var(--bg);
  55  color: var(--text);
  56  font: 400 0.92rem/1.7 var(--mono);
  57  /* The 32px grid every site here draws, in two gradients and nothing else. */
  58  background-image:
  59    linear-gradient(rgba(107, 158, 120, 0.028) 1px, transparent 1px),
  60    linear-gradient(90deg, rgba(107, 158, 120, 0.028) 1px, transparent 1px);
  61  background-size: 32px 32px;
  62  background-position: center top;
  63  overflow: hidden;
  64}
  65
  66/* The two overlays. Both are well below where search shipped them, because
  67   measured with a multiply blend applied the body copy there came out at 3.9
  68   to 1 against a nominal 15.5. */
  69.scan, .vignette { position: fixed; inset: 0; pointer-events: none; }
  70.scan {
  71  z-index: 4;
  72  background: repeating-linear-gradient(
  73    to bottom, rgba(0,0,0,0.08) 0 1px, transparent 1px 3px);
  74  mix-blend-mode: multiply;
  75}
  76.vignette {
  77  z-index: 5;
  78  background: radial-gradient(ellipse at center, transparent 62%, rgba(0,0,0,0.22) 100%);
  79}
  80
  81/* ------------------------------------------------------------------ shell */
  82
  83/* --app-h is the visual viewport in pixels once the browser reports one, since
  84   an open keyboard shrinks that and leaves every viewport unit unchanged. */
  85.shell { display: flex; height: 100vh; height: var(--app-h, 100dvh); position: relative; z-index: 10; }
  86
  87/* ------------------------------------------------------------------ side */
  88
  89.side {
  90  width: var(--side);
  91  flex: 0 0 var(--side);
  92  background: var(--void);
  93  border-right: 1px solid var(--line);
  94  display: flex;
  95  flex-direction: column;
  96  min-height: 0;
  97  transition: margin-left 0.2s var(--ease);
  98}
  99.side[hidden], .side.closed { margin-left: calc(var(--side) * -1); }
 100
 101.side-head {
 102  height: var(--barh);
 103  flex: 0 0 var(--barh);
 104  display: flex; align-items: center; gap: 0.5rem;
 105  padding: 0 0.55rem 0 0.7rem;
 106  border-bottom: 1px solid var(--line);
 107}
 108.brand { display: flex; align-items: center; gap: 0.5rem; text-decoration: none; margin-right: auto; }
 109.key {
 110  display: inline-flex; align-items: center; justify-content: center;
 111  width: 1.35rem; height: 1.35rem;
 112  border: 1px solid var(--line-hot);
 113  color: var(--green-bright);
 114  font-size: 0.72rem; font-weight: 700;
 115  position: relative;
 116}
 117/* The cursor is drawn rather than typed, since a block glyph renders as a
 118   hairline in some faces. */
 119.cur {
 120  position: absolute; right: 2px; bottom: 3px;
 121  width: 3px; height: 7px; background: var(--green-hot);
 122}
 123@keyframes blink { 50% { opacity: 0; } }
 124.brand-name { color: var(--text); font-weight: 700; letter-spacing: 0.02em; }
 125
 126.icon-btn {
 127  background: none; border: 1px solid transparent; color: var(--dim);
 128  font: inherit; font-size: 0.8rem; cursor: pointer; padding: 0.2rem 0.4rem;
 129  border-radius: 2px; position: relative;
 130}
 131.icon-btn:hover { color: var(--green-hot); border-color: var(--line); background: var(--panel); }
 132
 133.new-chat {
 134  margin: 0.65rem 0.7rem 0.2rem;
 135  padding: 0.5rem 0.7rem;
 136  background: var(--panel);
 137  border: 1px solid rgba(92, 114, 92, 0.5);
 138  color: var(--text);
 139  font: inherit; text-align: left; cursor: pointer; border-radius: 2px;
 140  display: flex; align-items: center; gap: 0.5rem;
 141}
 142.new-chat:hover { background: var(--panel-2); border-color: var(--green); color: var(--green-hot); }
 143.plus { color: var(--green-bright); font-weight: 700; }
 144
 145.side-label {
 146  padding: 0.9rem 0.85rem 0.35rem;
 147  color: var(--amber);
 148  font-size: 0.66rem; font-weight: 600; letter-spacing: var(--track); text-transform: uppercase;
 149}
 150
 151.convs { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
 152  padding: 0 0.45rem 0.5rem; min-height: 0; }
 153.conv {
 154  display: grid;
 155  grid-template-columns: 1fr auto auto;
 156  align-items: center; gap: 0.4rem;
 157  padding: 0.4rem 0.5rem;
 158  color: var(--muted); text-decoration: none;
 159  border: 1px solid transparent; border-radius: 2px;
 160  font-size: 0.8rem;
 161}
 162.conv:hover { background: var(--panel); border-color: rgba(65, 78, 65, 0.42); color: var(--text); }
 163.conv.active { background: var(--panel); border-color: var(--line-hot); color: var(--green-hot); }
 164
 165/* A turn that finished while the reader was somewhere else. A dot rather than a
 166   count, because the number of answers waiting is not a thing anybody acts on,
 167   and the row already says which conversation it was. */
 168.conv.done { position: relative; }
 169.conv.done .conv-title::after {
 170  content: "";
 171  display: inline-block; width: 0.4rem; height: 0.4rem;
 172  margin-left: 0.4rem; vertical-align: middle;
 173  border-radius: 50%; background: var(--green-bright);
 174}
 175/* The same signal on the button that opens the drawer, since on a phone the
 176   list it marks is off screen. */
 177.icon-btn.dot-on::after {
 178  content: "";
 179  position: absolute; top: 0.3rem; right: 0.3rem;
 180  width: 0.35rem; height: 0.35rem;
 181  border-radius: 50%; background: var(--green-bright);
 182}
 183.conv-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
 184.conv-when { color: var(--faint); font-size: 0.68rem; }
 185.conv-del {
 186  background: none; border: 0; color: var(--faint); cursor: pointer;
 187  font-size: 0.66rem; padding: 0 0.15rem; opacity: 0; line-height: 1;
 188}
 189.conv:hover .conv-del { opacity: 1; }
 190@media (hover: none) { .conv-del { opacity: 1; } }
 191.conv-del:hover { color: var(--terracotta); }
 192.empty { color: var(--faint); font-size: 0.78rem; padding: 0.3rem 0.5rem; margin: 0; }
 193
 194.side-foot { border-top: 1px solid var(--line); padding: 0 0.7rem 0.75rem; flex: 0 0 auto; }
 195
 196.incog { display: flex; gap: 0.55rem; align-items: flex-start; cursor: pointer; padding: 0.15rem 0; }
 197.incog input { position: absolute; opacity: 0; width: 0; height: 0; }
 198.incog-box {
 199  width: 0.95rem; height: 0.95rem; margin-top: 0.28rem; flex: 0 0 auto;
 200  border: 1px solid var(--line-hot); border-radius: 2px; position: relative;
 201}
 202.incog input:checked + .incog-box { border-color: var(--amber); }
 203.incog input:checked + .incog-box::after {
 204  content: ""; position: absolute; inset: 2px; background: var(--amber);
 205}
 206.incog input:focus-visible + .incog-box { outline: 2px solid var(--green-hot); outline-offset: 2px; }
 207.incog-text { font-size: 0.76rem; line-height: 1.45; }
 208.incog-text b { color: var(--text); font-weight: 600; }
 209.incog-text small { display: block; color: var(--faint); font-size: 0.68rem; }
 210
 211.side-stats {
 212  display: flex; gap: 0.8rem; padding: 0.6rem 0 0.5rem;
 213  color: var(--faint); font-size: 0.68rem;
 214}
 215.side-stats b { color: var(--green-bright); font-weight: 600; }
 216
 217.wipe {
 218  width: 100%; background: none; border: 1px solid rgba(196, 112, 85, 0.4);
 219  color: var(--terracotta); font: inherit; font-size: 0.72rem;
 220  padding: 0.3rem; cursor: pointer; border-radius: 2px;
 221}
 222.wipe:hover { background: rgba(196, 112, 85, 0.12); border-color: var(--terracotta); }
 223
 224/* ------------------------------------------------------------------ main */
 225
 226.main { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; min-height: 0; }
 227
 228/* Chrome sits a step above the ground, never below it. A bar darker than the
 229   page reads as a hole and leaves the small text on it nowhere to go. */
 230.bar {
 231  height: var(--barh); flex: 0 0 var(--barh);
 232  display: flex; align-items: center; gap: 0.6rem;
 233  padding: 0 0.9rem;
 234  background: var(--bar);
 235  border-bottom: 1px solid var(--line-hot);
 236  position: relative; z-index: 20;
 237}
 238.bar-title {
 239  color: var(--muted); font-size: 0.82rem;
 240  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
 241  margin-right: auto; min-width: 0;
 242}
 243.bar-right { display: flex; align-items: center; gap: 0.7rem; flex-shrink: 0; }
 244.incog-flag {
 245  color: var(--amber); font-size: 0.64rem; font-weight: 600;
 246  letter-spacing: var(--track); border: 1px solid rgba(212, 180, 90, 0.5);
 247  padding: 0.1rem 0.4rem; border-radius: 2px;
 248}
 249/* Amber is the warning here, per DESIGN.md, and this is the one tool failure
 250   the bar reports: a turn without search answers from memory and otherwise
 251   reads exactly like an answer that looked something up. */
 252.warn-flag {
 253  color: var(--terracotta); font-size: 0.64rem; font-weight: 600;
 254  letter-spacing: var(--track); border: 1px solid rgba(196, 112, 85, 0.55);
 255  padding: 0.1rem 0.4rem; border-radius: 2px; white-space: nowrap;
 256}
 257.warn-flag[hidden] { display: none; }
 258
 259.model { color: var(--dim); font-size: 0.72rem; display: flex; align-items: center; gap: 0.4rem; white-space: nowrap; }
 260
 261/* The context meter. A number beside a bar rather than a percentage, because
 262   how many tokens are left is the thing being decided and a percentage of a
 263   window whose size varies by model is not. */
 264.meter { display: flex; align-items: center; gap: 0.4rem; font-size: 0.68rem; color: var(--faint); white-space: nowrap; }
 265/* A display rule beats the hidden attribute, so anything here that sets
 266   display has to say so itself or it shows through hidden. */
 267.meter[hidden], .tps[hidden], .incog-flag[hidden] { display: none; }
 268.meter-num { color: var(--dim); font-variant-numeric: tabular-nums; }
 269.meter-cap { font-variant-numeric: tabular-nums; }
 270.meter-track {
 271  width: 4.5rem; height: 0.4rem;
 272  border: 1px solid var(--line); border-radius: 1px;
 273  background: var(--void); overflow: hidden;
 274}
 275.meter-fill {
 276  display: block; height: 100%; width: 0;
 277  background: var(--green); transition: width 0.3s var(--ease);
 278}
 279/* Over half the window is where compaction starts, so the bar says so before
 280   it happens rather than after. */
 281.meter.warm .meter-fill { background: var(--amber); }
 282.meter.warm .meter-num { color: var(--amber); }
 283.meter.hot .meter-fill { background: var(--terracotta); }
 284.meter.hot .meter-num { color: var(--terracotta); }
 285
 286.tps { color: var(--faint); font-size: 0.68rem; font-variant-numeric: tabular-nums; white-space: nowrap; }
 287.tps b { color: var(--green-bright); font-weight: 600; }
 288
 289@media (max-width: 60em) {
 290  .meter-track, .meter-cap { display: none; }
 291}
 292
 293/* State is a ring that fills, not colour alone. */
 294.dot {
 295  width: 0.5rem; height: 0.5rem; border-radius: 50%;
 296  border: 1px solid var(--line-hot); background: transparent;
 297  display: inline-block; flex: 0 0 auto;
 298}
 299.dot.on { background: var(--green-bright); border-color: var(--green-bright); box-shadow: 0 0 6px var(--green-glow); }
 300.dot.busy { background: var(--amber); border-color: var(--amber); animation: pulse 1s ease-in-out infinite; }
 301@keyframes pulse { 50% { opacity: 0.35; } }
 302
 303.thread { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
 304  padding: 1.6rem 1rem 1rem; min-height: 0; }
 305
 306/* ------------------------------------------------------------------ welcome */
 307
 308.welcome { max-width: var(--col); margin: 6vh auto 0; }
 309.eyebrow {
 310  display: inline-flex; align-items: center; gap: 0.45rem;
 311  border: 1px solid var(--line); border-radius: 999px;
 312  padding: 0.18rem 0.65rem; margin-bottom: 1.1rem;
 313  color: var(--dim); font-size: 0.68rem; letter-spacing: 0.04em;
 314}
 315.eyebrow .sep { color: var(--faint); }
 316.welcome h1 {
 317  font-size: clamp(1.5rem, 3.4vw, 2.1rem); line-height: 1.3;
 318  font-weight: 700; letter-spacing: -0.01em; margin: 0 0 0.8rem;
 319}
 320.welcome h1 .hl { color: var(--green-bright); }
 321.welcome p { color: var(--muted); margin: 0 0 1.4rem; max-width: 42rem; }
 322
 323.welcome-hint { color: var(--faint); font-size: 0.72rem; margin: -0.6rem 0 1.1rem; }
 324.welcome-hint kbd { margin-right: 0.15rem; }
 325
 326.chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }
 327.chip {
 328  background: var(--panel); border: 1px solid rgba(65, 78, 65, 0.42);
 329  color: var(--muted); font: inherit; font-size: 0.78rem;
 330  padding: 0.4rem 0.7rem; border-radius: 2px; cursor: pointer; text-align: left;
 331}
 332.chip:hover { background: var(--panel-2); border-color: var(--green); color: var(--green-hot); }
 333
 334/* ------------------------------------------------------------------ messages */
 335
 336.msg { max-width: var(--col); margin: 0 auto 1.6rem; }
 337.who {
 338  color: var(--amber); font-size: 0.64rem; font-weight: 600;
 339  letter-spacing: var(--track); text-transform: uppercase; margin-bottom: 0.4rem;
 340}
 341.msg.user .who { color: var(--faint); }
 342.msg.user .body {
 343  background: var(--panel);
 344  border: 1px solid rgba(65, 78, 65, 0.42);
 345  border-left: 2px solid var(--line-hot);
 346  padding: 0.7rem 0.9rem; border-radius: 2px;
 347  color: var(--text); white-space: pre-wrap; overflow-wrap: anywhere;
 348}
 349.msg.bot .body { color: var(--text); }
 350.body > *:first-child { margin-top: 0; }
 351.body > *:last-child { margin-bottom: 0; }
 352.body p { margin: 0 0 0.85rem; }
 353.body h1, .body h2, .body h3, .body h4 {
 354  font-size: 1rem; font-weight: 700; letter-spacing: -0.01em;
 355  margin: 1.4rem 0 0.5rem; color: var(--text);
 356}
 357.body h1 { font-size: 1.15rem; }
 358.body strong { color: var(--green-bright); font-weight: 600; }
 359.body a { color: var(--green-bright); text-decoration: underline; text-underline-offset: 2px; }
 360.body a:hover { color: var(--green-hot); }
 361.body ul, .body ol { margin: 0 0 0.9rem; padding-left: 1.2rem; }
 362.body li { margin-bottom: 0.3rem; }
 363.body li::marker { color: var(--green); }
 364.body blockquote {
 365  margin: 0 0 0.9rem; padding: 0.1rem 0 0.1rem 0.8rem;
 366  border-left: 2px solid var(--line-hot); color: var(--muted);
 367}
 368.body code {
 369  background: var(--panel); border: 1px solid rgba(65, 78, 65, 0.42);
 370  padding: 0.05rem 0.3rem; border-radius: 2px; font-size: 0.86em; color: var(--green-hot);
 371}
 372.body pre {
 373  background: var(--void); border: 1px solid var(--line);
 374  border-left: 2px solid var(--green); border-radius: 2px;
 375  padding: 0.8rem 0.9rem; overflow-x: auto; margin: 0 0 0.9rem;
 376}
 377.body pre code { background: none; border: 0; padding: 0; color: var(--text); font-size: 0.84rem; }
 378.body table { border-collapse: collapse; width: 100%; margin: 0 0 0.9rem; font-size: 0.84rem; display: block; overflow-x: auto; }
 379.body th, .body td { border: 1px solid rgba(65, 78, 65, 0.42); padding: 0.35rem 0.55rem; text-align: left; }
 380.body th { color: var(--amber); font-weight: 600; letter-spacing: 0.03em; background: var(--panel); }
 381.body hr { border: 0; border-top: 1px solid var(--line); margin: 1.2rem 0; }
 382
 383/* The unfinished paragraph. It is styled exactly like a rendered one so that
 384   the moment it becomes a block nothing visibly moves. */
 385.body .tail { margin: 0 0 0.85rem; white-space: pre-wrap; }
 386.body .tail:empty { display: none; }
 387
 388/* The cursor while text is streaming in. */
 389.body.typing .tail::after {
 390  content: ""; display: inline-block; width: 0.45rem; height: 0.9rem;
 391  background: var(--green-hot); margin-left: 2px; vertical-align: -0.12rem;
 392  animation: blink 1.1s steps(1) infinite;
 393}
 394.body.typing .tail:empty { display: inline-block; min-height: 0.9rem; }
 395
 396/* ------------------------------------------------------------------ tools */
 397
 398.tools { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.7rem; }
 399.tools[hidden] { display: none; }
 400.tool {
 401  display: inline-flex; align-items: center; gap: 0.35rem;
 402  border: 1px solid rgba(65, 78, 65, 0.42); background: var(--panel);
 403  color: var(--dim); font-size: 0.68rem; padding: 0.15rem 0.45rem; border-radius: 2px;
 404}
 405.tool b { color: var(--green-bright); font-weight: 600; }
 406.tool .ms { color: var(--faint); }
 407/* Amber per DESIGN.md, where it is the warning on a green site. The age of a
 408   snapshot is the one thing on a chip a reader has to weigh. */
 409.tool .age { color: var(--amber); }
 410.tool.bad { border-color: rgba(196, 112, 85, 0.5); }
 411.tool.bad b { color: var(--terracotta); }
 412.tool.run b { color: var(--amber); }
 413
 414/* The number after a sentence, linking to the page it came from. Raised rather
 415   than superscripted, since a real <sup> at this size loses a pixel of the
 416   digit and the tap target with it. */
 417.cite {
 418  display: inline-block; min-width: 1.05rem; text-align: center;
 419  margin-left: 0.18rem; padding: 0 0.18rem;
 420  border: 1px solid rgba(65, 78, 65, 0.55); border-radius: 2px;
 421  background: var(--panel); color: var(--green-bright);
 422  font-size: 0.62rem; line-height: 1.25; vertical-align: 0.32em;
 423  text-decoration: none; font-variant-numeric: tabular-nums;
 424}
 425.cite:hover, .cite:focus-visible { border-color: var(--green-bright); background: var(--green-glow); color: var(--green-hot); }
 426
 427.sources { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.7rem; }
 428.sources[hidden] { display: none; }
 429.src {
 430  display: inline-flex; align-items: center; gap: 0.35rem;
 431  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
 432  border: 1px solid rgba(65, 78, 65, 0.42); background: var(--panel);
 433  color: var(--dim); font-size: 0.68rem; padding: 0.15rem 0.45rem; border-radius: 2px;
 434  text-decoration: none;
 435}
 436.src b { color: var(--green-bright); font-weight: 600; }
 437.src:hover, .src:focus-visible { border-color: var(--green-bright); color: var(--text); }
 438
 439/* Room under the newest exchange so its question can sit at the top of the
 440   viewport. It shrinks to nothing once the answer is long enough to fill the
 441   screen by itself. */
 442.spacer { height: 0; flex: 0 0 auto; }
 443
 444.status {
 445  max-width: var(--col); margin: 0 auto 1.2rem;
 446  color: var(--dim); font-size: 0.76rem;
 447  display: flex; align-items: center; gap: 0.5rem;
 448}
 449.status .dots::after {
 450  content: ""; animation: dots 1.4s steps(4, end) infinite;
 451}
 452@keyframes dots { 0% { content: ""; } 25% { content: "."; } 50% { content: ".."; } 75% { content: "..."; } }
 453
 454.err {
 455  max-width: var(--col); margin: 0 auto 1.4rem;
 456  border: 1px solid rgba(196, 112, 85, 0.5); border-left: 2px solid var(--terracotta);
 457  background: rgba(196, 112, 85, 0.08); color: var(--muted);
 458  padding: 0.6rem 0.8rem; border-radius: 2px; font-size: 0.82rem;
 459}
 460
 461/* ------------------------------------------------------------------ composer */
 462
 463.composer {
 464  flex: 0 0 auto; padding: 0.5rem 1rem 0.7rem;
 465  border-top: 1px solid var(--line);
 466  background: linear-gradient(to top, var(--bg) 70%, transparent);
 467}
 468.ask {
 469  max-width: var(--col); margin: 0 auto;
 470  display: flex; align-items: flex-end; gap: 0.5rem;
 471  background: var(--panel);
 472  border: 1px solid rgba(92, 114, 92, 0.5);
 473  border-radius: 3px; padding: 0.4rem 0.4rem 0.4rem 0.7rem;
 474}
 475.ask:focus-within { border-color: var(--green); box-shadow: 0 0 0 3px var(--green-glow); }
 476.ask textarea {
 477  flex: 1 1 auto; background: none; border: 0; resize: none;
 478  color: var(--text); font: inherit; line-height: 1.6;
 479  max-height: 11rem; padding: 0.25rem 0; outline: none;
 480}
 481.ask textarea::placeholder { color: var(--faint); }
 482.ask button {
 483  flex: 0 0 auto; width: 2rem; height: 2rem;
 484  background: var(--green); border: 0; border-radius: 2px;
 485  color: var(--void); font-size: 0.72rem; cursor: pointer;
 486}
 487.ask button:hover { background: var(--green-hot); }
 488.ask button:disabled { background: var(--panel-2); color: var(--faint); cursor: not-allowed; }
 489#stop { background: var(--terracotta); color: var(--void); }
 490
 491/* ------------------------------------------------------------- attachments */
 492
 493/* The attach button is the one control in the composer that is not the send
 494   action, so it reads as chrome rather than taking the green. */
 495.ask .attach {
 496  background: none; border: 1px solid rgba(92, 114, 92, 0.5);
 497  color: var(--dim); font-size: 1rem; line-height: 1;
 498}
 499.ask .attach:hover { background: var(--panel-2); color: var(--green-bright); border-color: var(--line-hot); }
 500
 501.tray {
 502  max-width: var(--col); margin: 0 auto 0.4rem;
 503  display: flex; flex-wrap: wrap; gap: 0.35rem;
 504}
 505.tray[hidden] { display: none; }
 506.chip {
 507  display: inline-flex; align-items: center; gap: 0.4rem;
 508  border: 1px solid rgba(92, 114, 92, 0.5); background: var(--panel);
 509  color: var(--muted); font-size: 0.7rem; padding: 0.2rem 0.25rem 0.2rem 0.5rem;
 510  border-radius: 2px; max-width: 18rem;
 511}
 512.chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
 513.chip .sz { color: var(--faint); }
 514.chip.bad { border-color: rgba(196, 112, 85, 0.5); color: var(--terracotta); }
 515.chip button {
 516  background: none; border: 0; color: var(--faint); cursor: pointer;
 517  font-size: 0.7rem; line-height: 1; padding: 0.15rem 0.2rem;
 518}
 519.chip button:hover { color: var(--terracotta); }
 520
 521/* Attachment chips on a sent message. Same shape as the tool chips above them
 522   and not interactive, since the file itself was never kept. */
 523.files { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-bottom: 0.7rem; }
 524.files[hidden] { display: none; }
 525
 526/* The drop target is the whole window, because a file dragged out of a file
 527   manager is released wherever the pointer is rather than over the composer. */
 528.drop {
 529  position: fixed; inset: 0; z-index: 40;
 530  display: flex; align-items: center; justify-content: center;
 531  background: rgba(10, 9, 8, 0.82); backdrop-filter: blur(2px);
 532  border: 2px dashed var(--green); pointer-events: none;
 533}
 534.drop[hidden] { display: none; }
 535.drop span {
 536  color: var(--green-hot); font-size: 0.8rem; font-weight: 700;
 537  letter-spacing: var(--track); text-transform: uppercase;
 538}
 539
 540.foot-note {
 541  max-width: var(--col); margin: 0.4rem auto 0;
 542  display: flex; justify-content: space-between; gap: 1rem;
 543  color: var(--faint); font-size: 0.68rem;
 544}
 545.foot-note a { color: var(--faint); text-decoration: none; }
 546.foot-note a:hover { color: var(--green-bright); text-decoration: underline; }
 547
 548/* ------------------------------------------------------------------ keys */
 549
 550.filter {
 551  margin: 0 0.45rem 0.4rem; width: calc(100% - 0.9rem);
 552  background: var(--panel); border: 1px solid rgba(92, 114, 92, 0.5);
 553  color: var(--text); font: inherit; font-size: 0.78rem;
 554  padding: 0.25rem 0.45rem; border-radius: 2px;
 555}
 556.filter[hidden] { display: none; }
 557.filter:focus { outline: none; border-color: var(--green); }
 558
 559.sheet {
 560  position: fixed; inset: 0; z-index: 60;
 561  background: rgba(10, 9, 8, 0.72);
 562  display: flex; align-items: center; justify-content: center; padding: 1rem;
 563}
 564.sheet[hidden] { display: none; }
 565.sheet-card {
 566  background: var(--panel); border: 1px solid var(--line-hot); border-radius: 3px;
 567  padding: 1rem 1.1rem; width: min(30rem, 100%);
 568  box-shadow: 0 20px 60px rgba(0,0,0,0.6);
 569}
 570.sheet-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem; }
 571
 572.keys { display: grid; grid-template-columns: auto 1fr; gap: 0.4rem 0.9rem; margin: 0; align-items: center; }
 573.keys dt { display: flex; gap: 0.2rem; }
 574.keys dd { margin: 0; color: var(--muted); font-size: 0.8rem; }
 575kbd {
 576  background: var(--void); border: 1px solid var(--line-hot);
 577  border-bottom-width: 2px; border-radius: 2px;
 578  padding: 0.05rem 0.32rem; font: inherit; font-size: 0.7rem; color: var(--green-bright);
 579}
 580.keys-note { color: var(--faint); font-size: 0.7rem; margin: 0.9rem 0 0; line-height: 1.6; }
 581.keys-note kbd { font-size: 0.66rem; }
 582
 583.foot-right { display: flex; gap: 0.8rem; align-items: center; }
 584.linkish {
 585  background: none; border: 0; color: var(--faint); font: inherit; font-size: 0.68rem;
 586  cursor: pointer; padding: 0; text-decoration: none;
 587}
 588.linkish:hover { color: var(--green-bright); text-decoration: underline; }
 589.keys-hint { display: inline-flex; align-items: center; gap: 0.2rem; }
 590.keys-hint kbd { font-size: 0.62rem; padding: 0 0.25rem; border-bottom-width: 1px; }
 591.keys-hint:hover { text-decoration: none; }
 592.keys-hint:hover kbd { border-color: var(--green); color: var(--green-hot); }
 593
 594
 595
 596@media (prefers-reduced-motion: reduce) {
 597  * { animation: none !important; transition: none !important; }
 598}
 599
 600/* ---------------------------------------------------------------- landing */
 601
 602/* The one page a signed out visitor sees. It reuses the palette and the brand
 603   mark and nothing else, since the app's layout is a three pane shell and this
 604   is a single column of prose. */
 605
 606.landing-body { display: block; overflow-y: auto; }
 607
 608.landing {
 609  max-width: 42rem;
 610  margin: 0 auto;
 611  padding: 4rem 1.4rem 5rem;
 612}
 613.landing .brand { display: inline-flex; align-items: center; gap: 0.5rem; text-decoration: none; }
 614.landing .brand:hover { text-decoration: none; }
 615
 616.landing .eyebrow {
 617  color: var(--amber); font-size: 0.64rem; font-weight: 600;
 618  letter-spacing: var(--track); text-transform: uppercase;
 619  margin: 1.6rem 0 0.6rem;
 620}
 621.landing h1 {
 622  font-size: 1.7rem; line-height: 1.3; font-weight: 700;
 623  letter-spacing: -0.01em; margin: 0 0 1rem; color: var(--text);
 624}
 625.landing h1 .accent { color: var(--green-bright); }
 626.landing .lede { color: var(--muted); font-size: 0.92rem; margin: 0 0 1.6rem; }
 627
 628.landing .points { list-style: none; padding: 0; margin: 0 0 2rem; }
 629.landing .points li {
 630  color: var(--dim); font-size: 0.82rem;
 631  padding: 0.5rem 0 0.5rem 1rem;
 632  border-left: 1px solid var(--line);
 633  margin-bottom: 0.3rem;
 634}
 635.landing .points b { color: var(--green-bright); font-weight: 700; }
 636
 637.landing .ctas { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-bottom: 2.4rem; }
 638.landing .btn {
 639  display: inline-block; background: var(--green); color: var(--void);
 640  border: 1px solid var(--green); border-radius: 2px;
 641  font-size: 0.78rem; font-weight: 700; padding: 0.45rem 0.9rem;
 642  text-decoration: none;
 643}
 644.landing .btn:hover { background: var(--green-hot); border-color: var(--green-hot); text-decoration: none; }
 645.landing .btn.ghost { background: none; color: var(--dim); border-color: var(--line); }
 646.landing .btn.ghost:hover { color: var(--green-bright); border-color: var(--line-hot); }
 647
 648
 649/* ------------------------------------------------------- memory overlay */
 650
 651/* Taller than the shortcuts sheet because the list grows, and scrolling
 652   inside the card rather than the page so the conversation behind it does
 653   not move while you read. */
 654.mem-card { width: min(38rem, 100%); max-height: min(80vh, 42rem); display: flex; flex-direction: column; }
 655.mem-lede { color: var(--dim); font-size: 0.74rem; margin: 0 0 0.7rem; }
 656
 657.mem-msg {
 658  border-left: 2px solid var(--green); background: var(--void);
 659  padding: 0.4rem 0.6rem; margin-bottom: 0.7rem;
 660  font-size: 0.76rem; color: var(--muted);
 661}
 662.mem-msg[hidden] { display: none; }
 663.mem-msg.bad { border-left-color: var(--terracotta); color: var(--terracotta); }
 664
 665.teach { display: flex; gap: 0.5rem; align-items: flex-end; margin-bottom: 0.8rem; }
 666.teach textarea {
 667  flex: 1 1 auto; background: var(--void); color: var(--text);
 668  border: 1px solid rgba(92, 114, 92, 0.5); border-radius: 3px;
 669  font: inherit; font-size: 0.82rem; line-height: 1.5;
 670  padding: 0.4rem 0.55rem; resize: vertical; max-height: 8rem;
 671}
 672.teach textarea:focus { outline: none; border-color: var(--green); box-shadow: 0 0 0 3px var(--green-glow); }
 673.teach textarea::placeholder { color: var(--faint); }
 674.teach button {
 675  flex: 0 0 auto; background: var(--green); color: var(--void); border: 0;
 676  border-radius: 2px; font: inherit; font-size: 0.74rem; font-weight: 700;
 677  padding: 0.45rem 0.75rem; cursor: pointer;
 678}
 679.teach button:hover { background: var(--green-hot); }
 680.teach button:disabled { background: var(--panel-2); color: var(--faint); cursor: not-allowed; }
 681
 682.mem-list { flex: 1 1 auto; overflow-y: auto; min-height: 3rem; }
 683.fact {
 684  display: flex; align-items: baseline; gap: 0.6rem;
 685  padding: 0.45rem 0; border-bottom: 1px solid rgba(65, 78, 65, 0.45);
 686}
 687.fact:last-child { border-bottom: 0; }
 688.fact .ft { flex: 1 1 auto; color: var(--text); font-size: 0.82rem; }
 689.fact .fu { flex: 0 0 auto; color: var(--faint); font-size: 0.64rem; white-space: nowrap; }
 690.fact .fx {
 691  flex: 0 0 auto; background: none; border: 0; color: var(--faint);
 692  cursor: pointer; font-size: 0.68rem; padding: 0.1rem 0.2rem;
 693}
 694.fact .fx:hover { color: var(--terracotta); }
 695.fact.going { opacity: 0.35; }
 696
 697.mem-empty { color: var(--faint); font-size: 0.78rem; padding: 0.6rem 0; }
 698
 699.mem-foot {
 700  display: flex; align-items: center; justify-content: space-between;
 701  gap: 1rem; margin-top: 0.8rem; padding-top: 0.6rem;
 702  border-top: 1px solid var(--line);
 703  color: var(--faint); font-size: 0.68rem;
 704}
 705.mem-foot .danger {
 706  background: none; border: 1px solid rgba(196, 112, 85, 0.5);
 707  color: var(--terracotta); border-radius: 2px;
 708  font: inherit; font-size: 0.68rem; padding: 0.25rem 0.5rem; cursor: pointer;
 709}
 710.mem-foot .danger:hover { background: rgba(196, 112, 85, 0.12); }
 711
 712/* ------------------------------------------------------------------ narrow */
 713
 714@media (max-width: 52em) {
 715  /* Wider than the desktop rail, because a conversation title has to be
 716     readable and 264px of a phone truncates most of them. It stays short of
 717     the full width so the dimmed strip says there is a page behind it. */
 718  .side {
 719    --side: min(85vw, 20rem);
 720    position: fixed; inset: 0 auto 0 0; z-index: 40;
 721    width: var(--side); flex-basis: var(--side);
 722    box-shadow: 0 0 40px rgba(0,0,0,0.6);
 723    margin-left: 0;
 724    transition: transform 0.2s var(--ease);
 725  }
 726  /* Off screen by transform rather than by margin, since a margin animation
 727     relays out the page every frame and scores as a layout shift. */
 728  .side.closed { margin-left: 0; transform: translateX(-100%); }
 729
 730  /* Two rows, because a title and a timestamp side by side on a phone leaves
 731     the title about twelve characters. It also makes the row a thumb high. */
 732  .conv {
 733    grid-template-columns: 1fr auto;
 734    grid-template-areas: "title del" "when del";
 735    row-gap: 0.05rem;
 736    padding: 0.5rem;
 737  }
 738  .conv-title { grid-area: title; }
 739  .conv-when { grid-area: when; }
 740  .conv-del { grid-area: del; align-self: center; }
 741  .thread { padding: 1.2rem 0.8rem 0.8rem; }
 742  .welcome { margin-top: 3vh; }
 743
 744  /* Everything about a keyboard, on a device without one. */
 745  .welcome-hint, #foot-state, .keys-hint { display: none; }
 746
 747  /* What is left is two links, and they fit on one line once the keyboard
 748     advice above is gone. */
 749  .foot-note { justify-content: flex-end; font-size: 0.72rem; }
 750  .foot-right { gap: 0.9rem; }
 751  .foot-note a, .foot-note .linkish { white-space: nowrap; }
 752
 753  /* iOS zooms the whole page when a field it focuses is under 16px, and then
 754     leaves it zoomed, which is the single worst thing that happens on a phone
 755     here. The value is in px because it is about the browser's rule and not
 756     about this page's type scale. */
 757  .ask textarea, .teach textarea, .filter { font-size: 16px; }
 758
 759  /* Touch targets. 32px is comfortable with a mouse and small with a thumb. */
 760  .ask button { width: 2.75rem; height: 2.75rem; font-size: 0.9rem; }
 761  .icon-btn { min-width: 2.5rem; min-height: 2.5rem; padding: 0.4rem 0.5rem; }
 762  .chip { padding: 0.5rem 0.7rem; }
 763  .fact .fx { padding: 0.4rem 0.5rem; }
 764  .conv-del { padding: 0.4rem 0.5rem; }
 765  .tray .chip button { padding: 0.35rem 0.45rem; }
 766  .incog { padding: 0.4rem 0; }
 767
 768  /* The bar is the tightest row on the page, so it gives up its padding first
 769     and the model name stops wrapping to a second line. */
 770  .bar { gap: 0.45rem; padding: 0 0.55rem; }
 771  .model { font-size: 0.66rem; white-space: nowrap; }
 772  .bar-title { font-size: 0.76rem; }
 773
 774  /* Dimming what is behind it is what says the sidebar is a layer rather than
 775     part of the page, and tapping it is how a phone expects to close one. It
 776     has to be inside .shell, which carries a z-index of its own and so makes a
 777     stacking context the sidebar cannot climb out of. */
 778  .scrim {
 779    position: fixed; inset: 0; z-index: 35;
 780    background: rgba(10, 9, 8, 0.6);
 781  }
 782  .scrim[hidden] { display: none; }
 783
 784  /* A tap is a click here and never a double tap to zoom, so the browser has
 785     no reason to hold the gesture for 300ms first. */
 786  button, .conv, .chip, a { touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
 787
 788  .sheet { padding: 0.5rem; align-items: flex-start; padding-top: 3vh; }
 789  .sheet-card { padding: 0.8rem 0.85rem; }
 790  .mem-card { max-height: calc(var(--app-h, 100dvh) - 2.5rem); }
 791  .teach { flex-direction: column; align-items: stretch; }
 792  .teach button { width: 100%; padding: 0.6rem; }
 793}
 794
 795/* The sidebar is a layer at every width where it overlays, and the scrim only
 796   ever exists there. */
 797.scrim { display: none; }
 798@media (max-width: 52em) {
 799  .scrim:not([hidden]) { display: block; }
 800}
 801
 802/* Widgets ------------------------------------------------------------------
 803
 804   The panel a ticker or a forecast draws above the answer. The language is
 805   dash's, since that is where these readings already live here and a second
 806   visual grammar for the same numbers would be worse than a familiar one, but
 807   the palette is this site's green rather than dash's amber. Direction is the
 808   one place a colour means something, so up and down are defined here and used
 809   nowhere else. */
 810
 811:root {
 812  --up: #7fc98f;
 813  --up-soft: rgba(127, 201, 143, 0.09);
 814  --down: #d98063;
 815  --down-soft: rgba(217, 128, 99, 0.09);
 816  --flat: #9c8e7a;
 817}
 818
 819/* The hidden attribute is a UA rule of one specificity, so any display set
 820   below beats it, and on an svg child it is not honoured at all. Both the
 821   readout and the cursor lines are toggled with it, so it is restated here. */
 822.wdg [hidden] { display: none !important; }
 823
 824.widgets {
 825  display: flex;
 826  flex-direction: column;
 827  gap: 0.7rem;
 828  margin: 0 0 0.9rem;
 829}
 830
 831.wdg {
 832  border: 1px solid var(--line);
 833  background: var(--panel);
 834  font-size: 0.82rem;
 835}
 836
 837.wdg-head {
 838  display: flex;
 839  align-items: flex-start;
 840  justify-content: space-between;
 841  gap: 1rem;
 842  padding: 0.6rem 0.75rem 0.5rem;
 843}
 844
 845.wdg-id { min-width: 0; }
 846
 847.wdg-sym {
 848  display: block;
 849  font-size: 0.9rem;
 850  letter-spacing: 0.06em;
 851  color: var(--text);
 852}
 853
 854.wdg-name {
 855  display: block;
 856  font-size: 0.72rem;
 857  color: var(--dim);
 858  white-space: nowrap;
 859  overflow: hidden;
 860  text-overflow: ellipsis;
 861  max-width: 22rem;
 862}
 863
 864.wdg-quote { text-align: right; white-space: nowrap; }
 865
 866.wdg-price {
 867  display: block;
 868  font-size: 1.25rem;
 869  letter-spacing: -0.02em;
 870  color: var(--text);
 871}
 872
 873.wdg-move { display: block; font-size: 0.75rem; }
 874.wdg-chg { color: var(--dim); }
 875.wdg-pct { margin-left: 0.45rem; color: var(--dim); }
 876
 877.wdg[data-dir="up"] .wdg-pct, .wdg[data-dir="up"] .wdg-chg { color: var(--up); }
 878.wdg[data-dir="down"] .wdg-pct, .wdg[data-dir="down"] .wdg-chg { color: var(--down); }
 879.wdg[data-dir="flat"] .wdg-pct, .wdg[data-dir="flat"] .wdg-chg { color: var(--flat); }
 880
 881.wdg-ranges {
 882  display: flex;
 883  gap: 1px;
 884  padding: 0 0.75rem 0.55rem;
 885}
 886
 887.wdg-ranges button {
 888  font: inherit;
 889  font-size: 0.7rem;
 890  letter-spacing: var(--track);
 891  padding: 0.2rem 0.55rem;
 892  color: var(--faint);
 893  background: transparent;
 894  border: 1px solid transparent;
 895  cursor: pointer;
 896}
 897
 898.wdg-ranges button:hover { color: var(--text); }
 899
 900.wdg-ranges button.on {
 901  color: var(--green-hot);
 902  border-color: var(--line-hot);
 903  background: var(--panel-2);
 904}
 905
 906/* The plot is the positioning context for the cursor dot and the readout,
 907   which are HTML over the top of the svg. The box is stretched to the column
 908   width, so a circle drawn inside it would come out an ellipse. */
 909.wdg-plot { position: relative; }
 910
 911.wdg-plot svg {
 912  display: block;
 913  width: 100%;
 914  height: 8.5rem;
 915  touch-action: pan-y;
 916  cursor: crosshair;
 917}
 918
 919.wdg-line {
 920  fill: none;
 921  stroke: var(--flat);
 922  stroke-width: 1.4;
 923  stroke-linejoin: round;
 924  stroke-linecap: round;
 925  vector-effect: non-scaling-stroke;
 926}
 927
 928.wdg-area { stroke: none; fill: rgba(156, 142, 122, 0.07); }
 929
 930.wdg[data-dir="up"] .wdg-line { stroke: var(--up); }
 931.wdg[data-dir="up"] .wdg-area { fill: var(--up-soft); }
 932.wdg[data-dir="down"] .wdg-line { stroke: var(--down); }
 933.wdg[data-dir="down"] .wdg-area { fill: var(--down-soft); }
 934
 935/* Yesterday's close, which is what the intraday chart is measured against and
 936   what makes its shape mean anything on its own. */
 937.wdg-base {
 938  stroke: var(--faint);
 939  stroke-width: 1;
 940  stroke-dasharray: 1 4;
 941  vector-effect: non-scaling-stroke;
 942}
 943
 944.wdg-cursor, .wdg-anchor {
 945  stroke: var(--green-hot);
 946  stroke-width: 1;
 947  vector-effect: non-scaling-stroke;
 948}
 949
 950.wdg-anchor { stroke-dasharray: 2 3; opacity: 0.7; }
 951.wdg-band { fill: var(--green-glow); }
 952
 953.wdg-dot {
 954  position: absolute;
 955  width: 7px;
 956  height: 7px;
 957  margin: -3.5px 0 0 -3.5px;
 958  border-radius: 50%;
 959  background: var(--text);
 960  pointer-events: none;
 961}
 962
 963.wdg-dot-a { background: var(--faint); }
 964
 965.wdg-read {
 966  position: absolute;
 967  top: 0.3rem;
 968  display: flex;
 969  gap: 0.5rem;
 970  align-items: baseline;
 971  padding: 0.25rem 0.5rem;
 972  background: var(--bar);
 973  border: 1px solid var(--line-hot);
 974  font-size: 0.72rem;
 975  white-space: nowrap;
 976  pointer-events: none;
 977}
 978
 979.wdg-read[data-side="right"] { transform: translateX(0.5rem); }
 980.wdg-read[data-side="left"] { transform: translateX(calc(-100% - 0.5rem)); }
 981.wdg-read-pct[data-dir="up"] { color: var(--up); }
 982.wdg-read-pct[data-dir="down"] { color: var(--down); }
 983.wdg-read-pct[data-dir="flat"] { color: var(--flat); }
 984.wdg-read-at { color: var(--faint); }
 985
 986.wdg-msg {
 987  margin: 0;
 988  padding: 1.6rem 0.75rem;
 989  text-align: center;
 990  color: var(--faint);
 991  font-size: 0.75rem;
 992}
 993
 994.wdg-plot .wdg-msg:empty { display: none; }
 995.wdg.loading { opacity: 0.65; }
 996
 997.wdg-foot {
 998  display: flex;
 999  justify-content: space-between;
1000  gap: 1rem;
1001  padding: 0.4rem 0.75rem 0.5rem;
1002  border-top: 1px solid var(--line);
1003  font-size: 0.68rem;
1004  letter-spacing: 0.04em;
1005  color: var(--faint);
1006}
1007
1008/* Weather ------------------------------------------------------------------ */
1009
1010.wdg-tiles {
1011  display: grid;
1012  grid-template-columns: repeat(auto-fit, minmax(5.5rem, 1fr));
1013  gap: 1px;
1014  background: var(--line);
1015  border-top: 1px solid var(--line);
1016  border-bottom: 1px solid var(--line);
1017}
1018
1019.wdg-tile {
1020  background: var(--panel-2);
1021  padding: 0.45rem 0.6rem 0.5rem;
1022}
1023
1024.wdg-tile-k {
1025  display: block;
1026  font-size: 0.62rem;
1027  letter-spacing: var(--track);
1028  text-transform: uppercase;
1029  color: var(--faint);
1030}
1031
1032.wdg-tile-v { display: block; font-size: 1rem; color: var(--text); }
1033.wdg-tile-n { display: block; font-size: 0.66rem; color: var(--dim); }
1034.wdg-tile[data-dir="up"] .wdg-tile-v { color: var(--up); }
1035.wdg-tile[data-dir="down"] .wdg-tile-v { color: var(--down); }
1036
1037.wdg-days { list-style: none; margin: 0; padding: 0; }
1038
1039.wdg-day {
1040  display: grid;
1041  grid-template-columns: 4.2rem 3.2rem 6rem 1fr;
1042  align-items: baseline;
1043  gap: 0.5rem;
1044  padding: 0.32rem 0.75rem;
1045  border-top: 1px solid var(--line);
1046}
1047
1048.wdg-day:first-child { border-top: none; }
1049.wdg-day-k { color: var(--muted); font-size: 0.75rem; }
1050.wdg-day-rain { font-size: 0.72rem; color: var(--faint); text-align: right; }
1051.wdg-day-rain[data-wet="yes"] { color: var(--green-hot); }
1052.wdg-day-t b { color: var(--text); }
1053.wdg-day-t i { color: var(--faint); font-style: normal; margin-left: 0.5rem; }
1054
1055.wdg-day-feels {
1056  font-size: 0.72rem;
1057  color: var(--faint);
1058}
1059
1060@media (max-width: 34rem) {
1061  .wdg-name { max-width: 11rem; }
1062  .wdg-day { grid-template-columns: 3.4rem 2.8rem 1fr; }
1063  .wdg-day-feels { display: none; }
1064}
1065
1066/* The record of what a turn did. Open while it runs and collapsed after, since
1067   the answer is what the reader came for and the work is what they check. The
1068   caps label and the amber are DESIGN.md's, and the kind of a step is the only
1069   thing here that carries colour, so the list reads as one object. */
1070.work { margin: 0.15rem 0 0.7rem; }
1071.work[hidden] { display: none; }
1072.work-head {
1073  display: flex; align-items: center; gap: 0.5rem; width: 100%;
1074  background: none; border: 0; padding: 0.2rem 0; cursor: pointer;
1075  color: var(--dim); font: inherit; text-align: left;
1076}
1077.work-k {
1078  color: var(--amber); font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase;
1079}
1080.work-sum { color: var(--faint); font-size: 0.75rem; }
1081.work-head .chev {
1082  width: 0; height: 0; flex: none;
1083  border-left: 4px solid var(--faint);
1084  border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
1085  transition: transform 120ms ease;
1086}
1087.work-head[aria-expanded="true"] .chev { transform: rotate(90deg); }
1088.work-head:hover .work-sum, .work-head:hover .chev { color: var(--green-bright); }
1089
1090.work-steps {
1091  list-style: none; margin: 0.3rem 0 0; padding: 0 0 0 0.42rem;
1092  border-left: 1px solid rgba(65, 78, 65, 0.42);
1093}
1094.work-steps[hidden] { display: none; }
1095
1096.step { position: relative; }
1097.step-head {
1098  display: flex; align-items: baseline; gap: 0.45rem; width: 100%;
1099  background: none; border: 0; padding: 0.24rem 0; cursor: pointer;
1100  color: var(--dim); font: inherit; font-size: 0.8rem; text-align: left;
1101}
1102.step-head[disabled] { cursor: default; }
1103/* The dot sits on the rule the list hangs from, so the steps read as one run
1104   down the page rather than as separate rows. */
1105.step .dot {
1106  flex: none; width: 5px; height: 5px; border-radius: 50%;
1107  margin-left: -0.42rem; transform: translateX(-50%);
1108  background: var(--green-bright);
1109}
1110.step[data-kind="wikipedia"] .dot { background: var(--amber); }
1111.step[data-kind="gate"] .dot { background: var(--amber); }
1112.step.bad .dot { background: var(--terracotta); }
1113.step .k {
1114  flex: none; color: var(--green-bright);
1115  letter-spacing: 0.12em; text-transform: uppercase; font-size: 0.7rem;
1116}
1117.step[data-kind="wikipedia"] .k, .step[data-kind="gate"] .k { color: var(--amber); }
1118.step.bad .k { color: var(--terracotta); }
1119/* Both the label and the detail give way on a narrow screen rather than
1120   pushing the time off the edge, and the detail goes first because the label
1121   is what names the step. */
1122.step .l {
1123  flex: 0 1 auto; min-width: 0; color: var(--dim);
1124  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1125}
1126/* The glance at what a step actually did. It takes whatever width is left and
1127   is the only part of the row that truncates, so the kind, the label and the
1128   time never move as one arrives. */
1129.step .d {
1130  /* Capped so a long url can never squeeze the tool name beside it, which is
1131     the one word on the row worth reading. */
1132  flex: 1 1 auto; min-width: 0; max-width: 52%;
1133  color: var(--faint); opacity: 0.72; margin-left: 0.15rem;
1134  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1135}
1136.step-head:hover .d { color: var(--dim); opacity: 1; }
1137
1138/* A phone has room for the kind, the label and the time and not for the
1139   argument as well, and a row truncated to "web_sea…" has lost the one word
1140   that made it worth reading. The glance is a wide screen affordance, and the
1141   step still opens on a tap. */
1142@media (max-width: 34rem) {
1143  .step .d { display: none; }
1144}
1145/* Pushed right by the margin rather than by whatever is beside it, so the
1146   times line up as a column whether or not a row carries a detail. */
1147.step .ms { flex: none; margin-left: auto; padding-left: 0.5rem; color: var(--faint); }
1148.step-head:hover .l { color: var(--text); }
1149
1150.step-body { margin: 0.1rem 0 0.45rem 0.55rem; }
1151.step-body[hidden] { display: none; }
1152.step-meta { color: var(--faint); font-size: 0.72rem; margin-bottom: 0.3rem; }
1153.io { display: flex; gap: 0.45rem; margin-bottom: 0.3rem; }
1154.io-k {
1155  flex: none; width: 2rem; color: var(--amber);
1156  font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; padding-top: 0.15rem;
1157}
1158.io pre {
1159  flex: 1 1 auto; margin: 0; max-height: 13rem; overflow: auto;
1160  border: 1px solid rgba(65, 78, 65, 0.42); background: var(--panel);
1161  padding: 0.35rem 0.45rem; border-radius: 2px;
1162  color: var(--dim); font-size: 0.75rem; line-height: 1.6;
1163  white-space: pre-wrap; overflow-wrap: anywhere;
1164}