@lovek4d/kickflip 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CONTEXT.md +105 -68
  2. package/README.md +1 -1
  3. package/SKILL.md +27 -21
  4. package/bin/kickflip.mjs +27 -18
  5. package/bin/wait-events.mjs +2 -2
  6. package/board/agents.mjs +2 -0
  7. package/board/arch-review.mjs +4 -4
  8. package/board/bin/board.mjs +199 -79
  9. package/board/build-script.mjs +2 -1
  10. package/board/domain.mjs +9 -7
  11. package/board/finish.mjs +4 -4
  12. package/board/headless.mjs +9 -4
  13. package/board/on-host.mjs +1 -1
  14. package/board/onboarding.mjs +11 -0
  15. package/board/prototype-base.mjs +1 -0
  16. package/board/prototype-page.mjs +1 -0
  17. package/board/public/app-arch.js +1 -1
  18. package/board/public/app-asks.js +6 -6
  19. package/board/public/app-banner.js +7 -13
  20. package/board/public/app-cards.js +75 -37
  21. package/board/public/app-core.js +26 -8
  22. package/board/public/app-header.js +8 -8
  23. package/board/public/app-lanes.js +67 -58
  24. package/board/public/app-needs.js +140 -10
  25. package/board/public/app-reader.js +1 -1
  26. package/board/public/app-snapshot.js +46 -72
  27. package/board/public/app-threads.js +78 -75
  28. package/board/public/app.html +9 -4
  29. package/board/public/app.js +114 -38
  30. package/board/public/attach.js +2 -2
  31. package/board/public/dialogs.js +9 -39
  32. package/board/public/hub-kit.js +66 -4
  33. package/board/public/hub.css +2 -0
  34. package/board/public/linear-key.js +46 -35
  35. package/board/public/new-task.js +20 -39
  36. package/board/public/progress-review.html +46 -0
  37. package/board/public/progress-review.js +158 -0
  38. package/board/public/prototype-kit.js +137 -7
  39. package/board/public/prototype-switcher.js +152 -123
  40. package/board/public/prototype.html +57 -3
  41. package/board/public/router.js +7 -6
  42. package/board/public/shell.js +68 -26
  43. package/board/public/sidebar.css +1 -0
  44. package/board/public/sidebar.js +16 -30
  45. package/board/public/style.css +205 -83
  46. package/board/public/ui-cards.js +91 -65
  47. package/board/public/ui-enums.js +1 -1
  48. package/board/public/ui-header.js +27 -18
  49. package/board/public/ui-hub.js +12 -1
  50. package/board/public/ui-links.js +7 -7
  51. package/board/public/ui-order.js +12 -10
  52. package/board/public/ui-page-header.js +19 -7
  53. package/board/public/ui-patch.js +7 -33
  54. package/board/public/ui-progress-bar.js +28 -0
  55. package/board/public/ui-questions.js +47 -52
  56. package/board/public/ui-sidebar.js +34 -26
  57. package/board/public/ui-snapshot.js +43 -52
  58. package/board/public/ui-threads.js +59 -30
  59. package/board/public/ui.js +125 -18
  60. package/board/public/view-hub-settings.js +22 -67
  61. package/board/public/view-hub.js +19 -4
  62. package/board/public/view-notes.js +27 -7
  63. package/board/public/view-questions.js +205 -240
  64. package/board/public/view-settings.js +313 -76
  65. package/board/render-check.mjs +10 -0
  66. package/board/review.mjs +1 -1
  67. package/board/server.mjs +19 -20
  68. package/board/stop.mjs +1 -0
  69. package/board/store.mjs +29 -15
  70. package/board/sync.mjs +2 -0
  71. package/board/tickets.mjs +3 -3
  72. package/board/transcript.mjs +2 -0
  73. package/board/worktree.mjs +25 -20
  74. package/hooks-plugin/hooks/board-data-gate.mjs +50 -0
  75. package/hooks-plugin/hooks/hooks.json +9 -0
  76. package/hooks-plugin/hooks/usage-gate.mjs +20 -3
  77. package/hub/board-repo.mjs +1 -1
  78. package/hub/boards.mjs +4 -4
  79. package/hub/front-door.mjs +5 -5
  80. package/hub/github.mjs +3 -3
  81. package/hub/hub.mjs +11 -11
  82. package/hub/incoming.mjs +2 -2
  83. package/hub/linear.mjs +1 -2
  84. package/hub/live.mjs +2 -2
  85. package/hub/locks.mjs +1 -1
  86. package/hub/owner-checks.mjs +1 -1
  87. package/hub/pr-report.mjs +2 -2
  88. package/hub/presence.mjs +1 -1
  89. package/hub/push.mjs +2 -2
  90. package/hub/readiness.mjs +1 -2
  91. package/hub/review-send.mjs +1 -0
  92. package/hub/secrets.mjs +2 -0
  93. package/hub/self-restart.mjs +2 -0
  94. package/hub/sessions.mjs +2 -2
  95. package/hub/share.mjs +1 -0
  96. package/hub/slack.mjs +2 -2
  97. package/hub/stream.mjs +10 -13
  98. package/hub/try.mjs +7 -7
  99. package/hub/upgrade.mjs +1 -1
  100. package/hub/usage.mjs +3 -3
  101. package/hub/views.mjs +1 -1
  102. package/package.json +2 -1
  103. package/shared/audit.mjs +4 -0
  104. package/shared/card-prs.mjs +1 -1
  105. package/shared/fake-gh.mjs +17 -4
  106. package/shared/machine-gate.mjs +2 -0
  107. package/shared/prototype-frame.js +1 -0
  108. package/shared/service.mjs +4 -4
  109. package/shared/usage-gate.mjs +1 -1
  110. package/board/public/ui-phase-bars.js +0 -17
@@ -82,8 +82,10 @@ button.chip { border: 0; }
82
82
  .status.review { border-color: var(--color-info); background: conic-gradient(var(--color-info) 0 75%, transparent 75% 100%); }
83
83
  .sub.agent { color: var(--color-notice); }
84
84
  .sub.next { color: var(--color-notice); }
85
- .card-hold, .card-blocked, .card-action { color: var(--color-notice); }
85
+ .card-hold { color: var(--color-quiet); }
86
+ .card-blocked, .card-action { color: var(--color-notice); }
86
87
  .card-ready { color: var(--color-success); }
88
+ .card-staging { color: var(--color-notice); }
87
89
  .sub.next.idle { color: var(--muted); }
88
90
  .sub.refused { color: var(--color-danger); }
89
91
  .toast-stack { position: fixed; left: 50%; bottom: calc(var(--app-bottom, 0px) + 12px + env(safe-area-inset-bottom));
@@ -163,6 +165,14 @@ button.link { background: none; border: 0; color: var(--accent); padding: 8px 12
163
165
  .wb-done { padding: 8px 12px; font-size: 12.5px; color: var(--muted); }
164
166
  .wb-done a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
165
167
  .wb-loosehead { cursor: default; }
168
+ .wb-counts { position: relative; border: 0; background: none; padding: 4px 0; font: inherit; cursor: help; color: var(--muted); font-weight: 400; font-variant-numeric: tabular-nums; white-space: nowrap; }
169
+ .wb-tip { display: none; position: absolute; right: 0; top: 100%; z-index: 6; margin-top: 2px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); box-shadow: 0 6px 18px rgba(0,0,0,.18); color: var(--text); font-weight: 400; text-align: left; }
170
+ .wb-counts:hover .wb-tip, .wb-counts.open .wb-tip { display: block; }
171
+ .wb-counts .wv-cur { font-weight: 700; }
172
+ .wb-counts .wv-review { color: var(--color-info); }
173
+ .wb-counts .wv-progress { color: var(--color-notice); }
174
+ .wb-counts .wv-todo, .wb-counts .wv-done { color: var(--color-quiet); }
175
+ .wb-lane + .wb-lane, .wb-lane + .wb-loose { border-top: 1px solid var(--line); }
166
176
  .wb-loosehead:hover { background: var(--band); }
167
177
  /* A wave card's step number, inside its id chip, and its ⋯ menu. */
168
178
  .wn { color: var(--muted); font-variant-numeric: tabular-nums; padding-right: 5px; margin-right: 4px; border-right: 1px solid var(--line); }
@@ -181,8 +191,9 @@ button.link { background: none; border: 0; color: var(--accent); padding: 8px 12
181
191
  .refresh #refresh { padding: 4px; margin-left: -4px; flex: none; }
182
192
  .refresh #status { font-size: 12px; min-width: 0; }
183
193
  .refresh .late { color: var(--color-notice); font-weight: 600; }
184
- .refresh .down { color: var(--color-danger); font-weight: 600; }
185
- .refresh .asleep { color: var(--muted); font-weight: 600; }
194
+ /* The header's time keeps its text; a stopped or offline board turns it and the refresh arrow red, paused usage orange. */
195
+ .refresh .down, .refresh:has(.down) #refresh { color: var(--color-danger); }
196
+ .refresh .hold, .refresh:has(.hold) #refresh { color: var(--color-notice); }
186
197
  .lane-github { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--muted); text-align: right; }
187
198
  .lane-github .late { color: var(--color-notice); }
188
199
  .msg.other { background: transparent; border: 1px solid var(--line); }
@@ -202,10 +213,12 @@ code { overflow-wrap: anywhere; }
202
213
  /* The ⌘↵ / Ctrl+↵ hint left of Send: only where there is a keyboard to press it on. */
203
214
  .send-hint { display: none; color: var(--muted); font-size: 12px; white-space: nowrap; }
204
215
  @media (hover: hover) and (pointer: fine) { .send-hint { display: inline; } }
205
- /* A composer's text box (BoardUI.dropZone): while files are dragged over the composer, attach.js marks it .dragging and
206
- the box gets a dashed accent outline with Drop to attach over it. */
216
+ /* A composer's text box (BoardUI.dropZone), its attach button inside at the bottom right with the text kept clear of it:
217
+ while files are dragged over the composer, attach.js marks it .dragging and the box gets a dashed accent outline
218
+ with Drop to attach over it. */
207
219
  .dz { position: relative; clear: both; border-radius: 8px; }
208
- .dz > textarea { display: block; }
220
+ .dz > textarea { display: block; padding-bottom: 40px; }
221
+ .dz > .attach { position: absolute; right: 6px; bottom: 6px; width: 32px; height: 32px; z-index: 1; }
209
222
  .dz-hint { display: none; position: absolute; inset: 0; align-items: center; justify-content: center; border-radius: 8px; pointer-events: none;
210
223
  background: color-mix(in srgb, var(--panel) 80%, transparent); color: var(--accent); font-weight: 600; font-size: 15px; }
211
224
  [data-composer].dragging .dz { outline: 2px dashed var(--accent); outline-offset: 3px; }
@@ -232,7 +245,6 @@ code { overflow-wrap: anywhere; }
232
245
  .msg .body:empty { display: none; }
233
246
  .note-tools { display: flex; align-items: flex-start; gap: 6px; }
234
247
  .note-tools .pending-files { flex: 1; margin-top: 4px; }
235
- .note-tools .attach { margin-top: 2px; }
236
248
 
237
249
  /* Compose button and sheet */
238
250
  main { padding-bottom: calc(110px + env(safe-area-inset-bottom)); }
@@ -322,9 +334,24 @@ main { padding-bottom: calc(110px + env(safe-area-inset-bottom)); }
322
334
  }
323
335
  .working-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--color-notice); margin-right: 5px; vertical-align: 1px; }
324
336
  @media (prefers-reduced-motion: no-preference) { .working-dot { animation: pulse 1.4s ease-in-out infinite; } }
325
- form[data-reply] .row .quick { margin-left: auto; margin-right: 6px; }
326
- form[data-reply] .row [data-confirm="finish"] { margin-right: 6px; }
327
- .q .proto { display: inline-block; margin: 0 0 8px; }
337
+ /* A thread box's row: What's left? at the left, the eye, fire and attach at the right, then Send, or a pair below
338
+ (docs/adr/2026-11-05-1918-done-shows-only-in-in-review-beside-reply.md). */
339
+ form[data-reply] .row .quick { margin: 0; }
340
+ form[data-reply] .icons { display: inline-flex; flex: none; margin-left: auto; }
341
+ .dz.bare > textarea { padding-bottom: 12px; }
342
+ /* A composer's send buttons (ui.js sendButton), each with its key as a small gray hint where there is a keyboard and
343
+ room. A pair takes a row of two halves; a Todo card's reply box wraps its pair under the row. */
344
+ .send-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 10px; }
345
+ .send-actions button, [data-sends] button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
346
+ .send-actions button { min-height: 44px; }
347
+ form[data-reply] .row { flex-wrap: wrap; gap: 6px; }
348
+ form[data-reply] .row > .send-actions { flex: 0 0 100%; margin-top: 4px; }
349
+ .key-hint { display: none; font-size: 11px; font-weight: 400; color: var(--muted); white-space: nowrap; }
350
+ button.primary .key-hint { color: color-mix(in srgb, var(--on-accent) 62%, transparent); }
351
+ @media (hover: hover) and (pointer: fine) and (min-width: 600px) { .key-hint { display: inline; } }
352
+ #askStatus { margin: 6px 2px 0; }
353
+ #askStatus:empty { display: none; }
354
+ .q .proto-view { display: inline-block; margin: 0 0 10px; color: var(--accent); }
328
355
  .msg .ask-actions { margin: -4px -6px 0 8px; }
329
356
  button.attach { border: 0; background: transparent; padding: 0; }
330
357
  button.attach:disabled { opacity: .35; }
@@ -337,21 +364,31 @@ button.attach:disabled { opacity: .35; }
337
364
  pixel off its siblings (docs/adr/2026-11-03-1519-a-building-cards-phase-bars-end-its-time-row.md). */
338
365
  .card-time .phase-bars { display: inline-flex; gap: 2px; vertical-align: middle; position: relative; top: -1px; }
339
366
  .phase-bars i { width: 9px; height: 4px; border-radius: 2px; background: var(--line); }
340
- /* A strip dims, and its running bar stops blinking, while the card waits on something other than a builder
341
- (docs/adr/2026-11-03-2024-every-in-progress-card-shows-its-phase-bars.md). app-cards.js sweeps bars in --bar-fill. */
367
+ /* A strip dims while the card waits on something other than a builder; ui-progress-bar.js then writes its running bar
368
+ as done, so it stops blinking (docs/adr/2026-11-03-2024-every-in-progress-card-shows-its-phase-bars.md). app-cards.js
369
+ sweeps bars in --bar-fill. */
342
370
  .phase-bars { --bar-fill: var(--color-notice); }
343
371
  .phase-bars.dim { --bar-fill: color-mix(in srgb, var(--muted) 55%, transparent); }
344
372
  .phase-bars i.done, .phase-bars i.now { background: var(--bar-fill); }
345
373
  /* A bar part way, to --p: a starting board's clone (ui-sidebar.js). */
346
374
  .phase-bars i.fill { background: linear-gradient(to right, var(--bar-fill) var(--p), var(--line) var(--p)); }
347
- @keyframes pulse-fill { 50% { background-color: color-mix(in srgb, var(--color-notice) 35%, transparent); } }
348
- @media (prefers-reduced-motion: no-preference) { .phase-bars:not(.dim) i.now { animation: pulse-fill 1.4s ease-in-out infinite; } }
375
+ /* The pulse dims the bar's own fill, so it pulses in its lane's colour. */
376
+ @keyframes pulse-fill { 50% { background-color: color-mix(in srgb, var(--bar-fill) 35%, transparent); } }
377
+ /* Every pulse beats on one clock (ui-progress-bar.js onePulse), so a card's running bar and its status dot blink together. */
378
+ @media (prefers-reduced-motion: no-preference) { .phase-bars i.now { animation: pulse-fill 1.4s ease-in-out infinite; } }
349
379
  /* A stale build's status line, dot included, is the needs-you red, and a tap on it nudges Claude (ui-cards.js builderStatus). */
350
380
  /* A status line that is a button is drawn as the div it replaces: this one reset, then only a colour per state. The font
351
- shorthand stays out so .sub's 12px wins. */
352
- button.sub { display: block; width: 100%; margin-inline: 0; margin-bottom: 0; padding: 0; min-height: 0; border: 0; border-radius: 0; background: none;
381
+ shorthand stays out so .sub's 12px wins. It sets no width: a button is its own block formatting context, so a full-width one
382
+ drops below the bump icons' float instead of sitting beside it as the div's line box does (board/layout.test.mjs). */
383
+ button.sub { display: block; margin-inline: 0; margin-bottom: 0; padding: 0; min-height: 0; border: 0; border-radius: 0; background: none;
353
384
  font-family: inherit; font-weight: inherit; letter-spacing: inherit; line-height: inherit; text-align: left; appearance: none; -webkit-appearance: none; }
354
385
  button.sub.agent.stalled { color: var(--color-danger); }
386
+ /* A tap on it calms the line to the in-progress orange, its dot sending a ripple, until the card's next status line (app-cards.js nudge). */
387
+ button.sub.agent.stalled.nudging { color: var(--color-notice); cursor: default; }
388
+ .nudging .working-dot { position: relative; }
389
+ .nudging .working-dot::after { content: ''; position: absolute; inset: -1px; border-radius: 50%; border: 1.5px solid currentColor; opacity: 0; }
390
+ @keyframes nudge-ping { from { transform: scale(1); opacity: .9; } to { transform: scale(2.6); opacity: 0; } }
391
+ @media (prefers-reduced-motion: no-preference) { .nudging .working-dot::after { animation: nudge-ping 1.1s ease-out infinite; } }
355
392
  .stalled .working-dot { background: currentColor; }
356
393
  .brand { display: grid; grid-template-columns: auto auto minmax(0, 1fr); column-gap: 10px; row-gap: 2px; }
357
394
  .brand .hub { grid-row: 1 / span 2; align-self: center; }
@@ -365,23 +402,33 @@ button.sub.agent.stalled { color: var(--color-danger); }
365
402
  .msg.agent { padding: 0 10px; margin: 4px 0; font-size: 12px; font-weight: 600; overflow-wrap: anywhere; }
366
403
  .msg.agent.started { color: var(--color-notice); } .msg.agent.finished { color: var(--color-success); }
367
404
  .msg.agent .muted { font-weight: 400; }
368
- /* A planner's line, dot included, is the owner's blue, on the card and in the thread; its finish stays green
369
- (docs/adr/2026-11-03-1551-planners-and-implementers-are-two-kinds-of-agent.md). */
370
- .sub.agent.planner, .msg.builder-status.planner, .msg.agent.started.planner { color: var(--accent); }
405
+ /* A planner's line in the thread, dot included, is the owner's blue; its finish stays green
406
+ (docs/adr/2026-11-03-1551-planners-and-implementers-are-two-kinds-of-agent.md). On the card it takes the lane's colour below. */
407
+ .msg.builder-status.planner, .msg.agent.started.planner { color: var(--accent); }
371
408
  .planner .working-dot { background: currentColor; }
372
- /* Every status line on an In Progress card is the phase-bar orange, a planner's too, and every build or plan line on a
373
- Todo card the owner's blue, dot included; a Todo card's planner dashes are that blue, the running one pulsing
374
- (docs/adr/2026-11-04-2300-each-lane-is-a-queue-for-the-orchestrator.md). */
409
+ /* A run's status line, dot included, takes its card's lane colour, a planner's too: grey in Todo, the phase-bar orange in
410
+ In Progress, blue in In Review; a Todo card's planner dashes are its grey, the running one pulsing
411
+ (docs/adr/2026-11-06-0145-every-run-takes-its-lanes-colour.md). */
412
+ .sub.agent.in-todo { color: var(--muted); }
375
413
  .sub.agent.in-progress { color: var(--color-notice); }
376
- .sub.agent.in-todo { color: var(--accent); }
377
- .sub.agent.in-progress .working-dot, .sub.agent.in-todo .working-dot { background: currentColor; }
378
- .phase-bars.plan { --bar-fill: var(--accent); }
379
- @keyframes pulse-fill-plan { 50% { background-color: color-mix(in srgb, var(--accent) 35%, transparent); } }
380
- @media (prefers-reduced-motion: no-preference) { .phase-bars.plan:not(.dim) i.now { animation-name: pulse-fill-plan; } }
414
+ .sub.agent.in-review { color: var(--color-info); }
415
+ .sub.agent.in-todo .working-dot, .sub.agent.in-progress .working-dot, .sub.agent.in-review .working-dot { background: currentColor; }
416
+ /* docs/adr/2026-11-05-0410-a-card-status-word-comes-from-a-signal-the-board-checks.md: a quiet builder and a request no planner
417
+ has picked up are grey, after the lane colours so they win. */
418
+ .sub.agent.quiet, .sub.asked { color: var(--muted); }
419
+ .sub.agent.waiting-gray { color: var(--muted); }
420
+ /* The one class every paused status takes, gray: the card and thread line, the header, the hub banner. */
421
+ .paused { color: var(--muted); font-weight: 600; }
422
+ .phase-bars.in-todo { --bar-fill: var(--muted); }
381
423
  .msg.calls { padding: 0 10px; margin: 4px 0; font-size: 12px; overflow-wrap: anywhere; }
382
424
  .msg.calls summary { cursor: pointer; color: var(--muted); list-style: none; }
383
425
  .msg.calls summary::-webkit-details-marker { display: none; }
384
426
  .msg.calls[open] summary .mark-icon { transform: rotate(90deg); }
427
+ .msg.calls.status-fold summary { color: var(--color-notice); overflow-wrap: anywhere; }
428
+ .msg.calls.status-fold.planner summary { color: var(--accent); }
429
+ /* Only a run's finished line is green: the fold's summary mirrors its last line, green once the run has finished, else the
430
+ started and status lines' orange or a planner's blue (docs/adr/2026-11-07-1600-only-a-runs-finished-line-is-green.md). */
431
+ .msg.calls.status-fold summary.finished { color: var(--color-success); }
385
432
  .msg.calls ol { margin: 4px 0; padding-left: 23px; }
386
433
  .agent-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: currentColor; margin-right: 5px; vertical-align: 1px; }
387
434
  /* Every thread status line's leading mark, icon or dot, sits centred in one 18px box, so the text after it always starts
@@ -398,10 +445,7 @@ button.sub.agent.stalled { color: var(--color-danger); }
398
445
  .msg .reply { display: flex; flex-wrap: wrap; align-items: center; column-gap: 12px; row-gap: 2px; }
399
446
  .msg-foot .foot-status { display: inline-flex; align-items: center; }
400
447
  /* The thread's eye and fire are toggles: pressed reads as chosen, and the hint under the box says what Send posts. */
401
- form[data-reply] .tools { display: inline-flex; flex: none; }
402
448
  button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chip); }
403
- .flag-hint { margin: 4px 2px 0; min-height: 1.2em; color: var(--muted); font-size: 12px; }
404
- .flag-hint.on { color: var(--accent); }
405
449
  /* INT-30: card line order */
406
450
  .line6 { margin-top: 4px; }
407
451
  .card-time time { font-variant-numeric: tabular-nums; }
@@ -418,10 +462,25 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
418
462
  .lane[open] > .list { border-radius: 0 0 10px 10px; }
419
463
  .lane > summary .count { color: var(--muted); font-weight: 400; }
420
464
  /* Each lane's sort icon and its menu, grey (docs/adr/2026-11-01-1817-colour-says-whos-waiting.md) */
421
- /* INT-35: offline header */
422
- .view-board.offline #compose { display: none; }
423
- .view-board.offline main > * { opacity: .72; }
424
- .view-board.offline main, .view-board.offline > .top > :not(.nav), .view-board.offline > .top .nav > :not(.refresh) { pointer-events: none; }
465
+ /* A board shared with someone else (docs/adr/2026-11-05-2215-a-board-is-shared-view-only-with-a-tailscale-login.md):
466
+ shell.js marks the page .shared-view, which hides Board settings and the architecture review, and, unless the person
467
+ has Full on this board (.shared-full; docs/adr/2026-11-05-1912-a-shared-person-is-view-or-full.md), the +, every
468
+ composer and a card's buttons, keeping its links (Visit, Notes, Open questions). Merge shows disabled under Full. */
469
+ .shared-view :is(#settingsLink, #archBulb) { display: none !important; }
470
+ .shared-view:not(.shared-full) :is(#compose, [data-composer], .actions button, button[data-confirm]) { display: none !important; }
471
+ .shared-full button[data-confirm="merge"] { opacity: .45; pointer-events: none; }
472
+ .view-only-strip { margin: 8px 0; padding: 8px 12px; border-radius: 10px; background: var(--accent-soft); font-size: 13px; }
473
+ .view-only-strip b { color: var(--accent); }
474
+ /* A board out of reach (docs/adr/2026-11-06-0135-a-board-out-of-reach-keeps-its-copy-or-shows-a-landing.md): it reads as before,
475
+ undimmed, and only the buttons that send, ui.js SENDS, are held, the labelled ones saying why; app-header.js stops their taps. */
476
+ .offline :is(form button[type="submit"], .actions button, button[data-confirm], button[data-deck-pick], button[data-review-answer], button[data-prompt-send], button[data-prompt-choice], button[data-quick], button[data-card-bump], button[data-bump], button[data-withdraw], button[data-band-move], button[data-grill], button[data-proto], button[data-visual], button[data-plan], button[data-main-pull], button[data-arch-run], button[data-try], button[data-reopen]) { opacity: .55; cursor: not-allowed; }
477
+ /* With no copy to show, the landing takes the board's place: the kickflip mark and two lines, as hub/front-door.mjs's page has them. */
478
+ .down-landing { min-height: 60vh; display: grid; place-content: center; justify-items: center; gap: 10px; padding: 32px 24px; text-align: center; }
479
+ .down-landing-mark { display: block; margin-bottom: 8px; width: 70px; height: 60px; object-fit: contain; }
480
+ .down-landing-line { margin: 0; font-size: 20px; font-weight: 600; line-height: 1.3; text-wrap: balance; }
481
+ .down-landing-hint { margin: 0; font-size: 14px; color: var(--muted); }
482
+ .view-board.down main > :not(#downLanding), .view-board.down :is(#needsPane, #compose) { display: none; }
483
+ .view-settings.down .settings-body > :not(.down-landing) { display: none; }
425
484
  /* INT-28: answer banner */
426
485
  .answer-banner { display: flex; align-items: center; gap: 4px; background: var(--accent-soft); border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
427
486
  border-radius: 10px; padding: 2px 4px 2px 0; margin-bottom: 12px; }
@@ -467,11 +526,21 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
467
526
  .top .nav #refresh, .top .nav a.icon { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 4px; border: 0; line-height: 1; }
468
527
  .top .nav #status { line-height: 1.1; white-space: nowrap; }
469
528
  .top .nav #refresh:disabled, .top .nav a.icon[aria-disabled="true"] { opacity: .35; pointer-events: none; }
470
- /* [the bulb runs an architecture review of the board's repo](docs/adr/2026-11-02-1813-the-bulb-runs-an-architecture-review-of-the-boards-repo.md): the bulb sits between refresh and the gear, and glows orange while an architecture review runs */
471
- .top .nav .arch-bulb { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 4px; border: 0; line-height: 1; color: var(--muted); }
472
- .top .nav .arch-bulb:hover { color: var(--text); }
473
- .top .nav .arch-bulb[hidden] { display: none; }
474
- .top .nav .arch-bulb.running { color: var(--color-notice); }
529
+ /* The foot of the board (docs/adr/2026-11-07-1300-board-and-open-questions-are-two-pages.md): the bulb and the gear, centred under the lanes.
530
+ [the bulb runs an architecture review of the board's repo](docs/adr/2026-11-02-1813-the-bulb-runs-an-architecture-review-of-the-boards-repo.md): it glows orange while an architecture review runs */
531
+ .board-foot { display: flex; justify-content: center; gap: 8px; margin: 20px 0 0; }
532
+ .board-foot .arch-bulb, .board-foot a.icon { display: inline-flex; align-items: center; justify-content: center; min-width: 44px; min-height: 44px; margin: 0; padding: 4px; border: 0; line-height: 1; color: var(--muted); }
533
+ .board-foot :is(.arch-bulb, a.icon):hover { color: var(--text); }
534
+ .board-foot .arch-bulb[hidden] { display: none; }
535
+ .board-foot .arch-bulb.running { color: var(--color-notice); }
536
+ .board-foot a.icon[aria-disabled="true"] { opacity: .35; pointer-events: none; }
537
+ /* The pages switch below 900px: the board header's ? with the open-question count on its corner, and Waiting on you's check back to the board. */
538
+ .top .pages-switch { position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none; width: auto; height: auto; margin: 0; padding: 4px; border: 0; border-radius: 0; line-height: 1; color: var(--muted); }
539
+ .top .pages-switch:hover { color: var(--text); }
540
+ .top .pages-switch:active { background: none; }
541
+ .pages-switch .q-badge { position: absolute; top: 0; right: -2px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--color-notice); color: #1c1407;
542
+ font-size: 11px; font-weight: 700; line-height: 16px; text-align: center; box-shadow: 0 0 0 1.5px var(--bg); }
543
+ .pages-switch .q-badge[hidden] { display: none; }
475
544
  .arch-bulb.running svg { filter: drop-shadow(0 0 4px currentColor); }
476
545
  @media (prefers-reduced-motion: no-preference) { .arch-bulb.running svg { animation: arch-glow 1.4s ease-in-out infinite; } }
477
546
  @keyframes arch-glow { 50% { filter: drop-shadow(0 0 1px currentColor); opacity: .55; } }
@@ -481,8 +550,6 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
481
550
  .arch-last .muted { font-size: 12.5px; }
482
551
  .arch-body .arch-waits { color: var(--color-notice); }
483
552
  .arch-body .arch-run { width: 100%; min-height: 44px; font-size: 15px; }
484
- /* INT-64: offline, the header's time and refresh turn red */
485
- .view-board.offline .refresh #refresh, .refresh .link-state.offline { color: var(--color-danger); }
486
553
  .back { display: inline-flex; align-items: center; justify-content: center; flex: none; width: 32px; height: 32px; color: var(--accent); text-decoration: none; }
487
554
  /* The lead slot, logo or back, is one 44px tap box on the page's left edge, so every page's header lines up the same
488
555
  (docs/adr/2026-10-29-1639-a-questions-takes-show-inline.md). The brand keeps that height when the rail hides the logo, so the
@@ -493,15 +560,10 @@ button.attach[aria-pressed="true"] { color: var(--accent); background: var(--chi
493
560
  .top .hub + .back { margin-left: 0; }
494
561
  .top .back:active { background: var(--accent-soft); }
495
562
  .top .brand :is(h1, h2) { font-size: 17px; font-weight: 600; line-height: 22px; }
496
- /* The last visit paints dimmed until the fresh board lands; the controls ui-snapshot.js liveControls blocks rest meanwhile.
497
- A changed line, and a card that changed lanes, gets a blue border that fades once; an updated card, open body included, takes
498
- a blue tint that fades (app-snapshot.js, whose SIGNS holds each animation's length). */
563
+ /* Every change, a line, a moved card or an updated card, open body included, takes the one blue tint that fades
564
+ (app-snapshot.js highlight, whose TINT_MS is its length). */
499
565
  main > * { transition: opacity .25s ease; }
500
- .view-board[data-board="snapshot"]:not(.offline) main > * { opacity: .62; }
501
- .view-board[data-board="snapshot"] :is(.actions button, button[data-plan], button[data-bump], button[data-confirm="bump"], button[data-edit], button[data-withdraw], button[data-check-github], form[data-reply] button[type=submit], form[data-reply] button[data-quick], form[data-reply] button[data-proto], form[data-reply] button[data-confirm], .prompt button) { opacity: .45; pointer-events: none; }
502
- .flash { animation: snapshot-flash 1.6s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
503
- @keyframes snapshot-flash { from { box-shadow: inset 0 0 0 2px var(--color-info); } to { box-shadow: inset 0 0 0 2px transparent; } }
504
- .item.tint { animation: card-tint 2.4s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
566
+ .tint { animation: card-tint 2.4s ease-out var(--sign-delay, 0s) 1; border-radius: 6px; }
505
567
  @keyframes card-tint { 0%, 25% { background: var(--accent-soft); } 100% { background: transparent; } }
506
568
  .msg .quote { margin: 4px 0 6px; padding: 2px 0 2px 8px; border-left: 2px solid var(--line); color: var(--muted); white-space: pre-wrap; overflow-wrap: anywhere; }
507
569
  /* [one board registry for every hub feature](docs/adr/2026-10-29-1639-one-board-registry-for-every-hub-feature.md): the GitHub mark opens the Hub links sheet, with a red dot while a link is broken; a new board's setup checklist */
@@ -530,11 +592,26 @@ main > * { transition: opacity .25s ease; }
530
592
  .view-settings .who.new { border-color: var(--accent); }
531
593
  .view-settings .add { display: flex; gap: 6px; margin-top: 8px; }
532
594
  .view-settings .add input { flex: 1; min-width: 0; }
595
+ .view-settings .add.pick { flex-wrap: wrap; }
596
+ .view-settings .share-now { color: var(--ok); font-weight: 600; }
597
+ .view-settings .share-level { display: flex; gap: 4px; margin-top: 6px; }
598
+ .view-settings .share-level button { min-height: 32px; padding: 4px 12px; font-size: 13px; }
599
+ .view-settings .share-level button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
600
+ .view-settings .share-now::before { content: ""; display: inline-block; width: 7px; height: 7px; margin-right: 5px; border-radius: 50%; background: currentColor; vertical-align: 1px; }
601
+ .view-settings .share-reach { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 4px; }
602
+ .view-settings .share-reach p { flex: 1 1 220px; margin: 0; }
603
+ .view-settings .share-reach button { flex: none; }
604
+ .view-settings .pick-col { flex: 1 1 140px; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
605
+ .view-settings .pick-list { max-height: 160px; overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
606
+ .view-settings .pick-list button { text-align: left; }
607
+ .view-settings .pick-list button[aria-pressed="true"] { border-color: var(--accent); }
608
+ .view-settings .who .sub { display: block; margin-top: 2px; }
533
609
  .view-settings .replies .reply { margin-top: 8px; }
534
610
  .view-settings label.who { gap: 12px; cursor: pointer; }
535
611
  .view-settings label.who .sub { display: block; margin-top: 2px; }
536
612
  .view-settings input.toggle { flex: none; width: 1.4rem; height: 1.4rem; margin: 0; accent-color: var(--accent); }
537
613
  .view-settings input.count { flex: none; width: 4.5em; text-align: center; }
614
+ .view-settings label.who select { flex: none; max-width: 45%; min-height: 36px; }
538
615
  /* A settings switch waiting on the server (ui.js waitOn) keeps its face, disabled, with a spinner beside it
539
616
  (docs/adr/2026-11-03-1921-board-settings-grant-a-boards-session-permissions.md) */
540
617
  .view-settings .toggle-slot { flex: none; display: inline-flex; align-items: center; gap: 8px; }
@@ -555,6 +632,14 @@ main > * { transition: opacity .25s ease; }
555
632
  .view-settings .close-board-warning:has(li) { border: 1px solid var(--color-danger); padding: 0 12px; border-radius: 10px; margin-bottom: 14px; }
556
633
  .view-questions .q { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 12px; margin: 12px 0; }
557
634
  .view-questions .q.urgent { border-color: var(--color-notice); }
635
+ /* docs/adr/2026-11-05-2331-a-sent-answer-is-resolved-in-place-until-the-page-reloads.md
636
+ A requested question takes an accent tint and a sent or resolved one a green tint, each with its border drawn inside
637
+ the card as well, so the card keeps its size. */
638
+ .view-questions .q.pr-busy { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, var(--panel)); box-shadow: inset 0 0 0 1px var(--accent); }
639
+ .view-questions .q.pr-busy .opt { opacity: .55; cursor: default; }
640
+ .view-questions .q.pr-busy .opt:has(input:checked) { opacity: 1; }
641
+ .view-questions .q.resolved { border-color: var(--color-success); background: color-mix(in srgb, var(--color-success) 9%, var(--panel)); box-shadow: inset 0 0 0 1px var(--color-success); }
642
+ .view-questions .q.resolved .sent-line { margin: 6px 0; font-size: 13px; color: var(--color-success); }
558
643
  .view-questions .q h3 { font-size: 15px; margin: 6px 0 4px; line-height: 1.3; }
559
644
  .view-questions .ctx { color: var(--muted); font-size: 13px; margin-bottom: 8px; }
560
645
  .view-questions .opt { display: grid; grid-template-columns: 22px 1fr; gap: 6px; padding: 8px; border: 1px solid var(--line);
@@ -576,18 +661,17 @@ main > * { transition: opacity .25s ease; }
576
661
  .view-questions .topics button { min-width: 0; border: 1px solid var(--line); border-radius: 999px; padding: 4px 11px; font-size: 13px;
577
662
  background: var(--panel); color: var(--text); white-space: normal; text-align: left; overflow-wrap: anywhere; }
578
663
  .view-questions .topics button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); }
664
+ .view-questions .topics button.progress { border-color: var(--line); background: color-mix(in srgb, var(--color-notice) 12%, var(--panel)); }
665
+ .view-questions .topics button.progress[aria-pressed="true"] { border-color: var(--accent); background: color-mix(in srgb, var(--color-notice) 12%, var(--accent-soft)); }
666
+ @keyframes pill-arrive { from { opacity: 0; transform: translateX(-12px); box-shadow: 0 0 0 3px var(--accent); } to { opacity: 1; transform: none; box-shadow: 0 0 0 0 transparent; } }
667
+ @media (prefers-reduced-motion: no-preference) { .view-questions .topics button.arrived { animation: pill-arrive .6s ease-out; } }
579
668
  .view-questions .topics .n { color: var(--muted); margin-left: 4px; }
580
669
  .view-questions .topichead { margin: 2px 0 6px; }
581
670
  .view-questions details.done > summary { cursor: pointer; color: var(--muted); font-size: 13px; margin: 16px 0 4px; }
582
- .view-questions details.done .trow { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
583
- .view-questions details.done button.ghost { font-size: 14px; text-align: left; padding: 6px 2px; }
584
- .view-questions details.done .trow button { min-width: 0; }
585
- .view-questions details.done .trow span { flex: none; }
586
671
  .view-questions details.resolved-group > summary { list-style: none; min-height: 44px; display: flex; align-items: center; gap: 8px; font-size: 14px; margin: 12px 0; }
587
672
  .view-questions details.resolved-group > summary::-webkit-details-marker { display: none; }
588
673
  .view-questions details.resolved-group > summary .caret { transition: transform .15s; }
589
674
  .view-questions details.resolved-group[open] > summary .caret { transform: rotate(90deg); }
590
- .view-questions details.resolved-group > summary .names { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
591
675
  .view-questions .q.resolved button.chip { cursor: pointer; font: inherit; font-size: 12px; min-height: 28px; }
592
676
  .view-questions .q.resolved button.chip:disabled { cursor: default; }
593
677
  .view-questions .q.resolved .resolved-line { margin: 8px 0; }
@@ -600,39 +684,26 @@ main > * { transition: opacity .25s ease; }
600
684
  display: flex; justify-content: space-between; align-items: center; gap: 8px; border-top: 1px solid var(--line); }
601
685
  .view-questions .bar .send-hint { margin-left: auto; }
602
686
  /* The + rides above the sticky Send bar rather than over its Send. */
603
- .view-questions:has(.bar) .fab { bottom: calc(var(--app-bottom, 0px) + var(--fab-edge) + 72px); }
604
687
  .view-questions .bar #qstatus { min-width: 0; }
605
688
  .view-questions .bar #qstatus.sent { color: var(--color-success); font-weight: 600; }
689
+ /* With no open question, the moon and its line sit at the centre of what the page shows between its top and the nav,
690
+ or of what the desktop pane has left under its Needs you lines. */
691
+ .view-questions .q-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; color: var(--muted); text-align: center;
692
+ min-height: calc(var(--vh, 100dvh) - var(--app-bottom, 0px) - max(16px, env(safe-area-inset-top))); }
693
+ .view-questions .q-empty p { margin: 0; font-size: 15px; }
606
694
  .view-questions .solo { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin: 4px 0; }
607
695
  /* docs/adr/2026-11-02-1332-a-prototype-round-ends-with-a-one-take-confirm.md */
608
696
  .view-questions .topichead { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; }
609
697
  .view-questions .topichead .try-spin { width: 9px; height: 9px; margin-right: 5px; vertical-align: -1px; }
610
- .view-questions .q .pr-open { display: inline-block; margin: 0 0 8px; }
611
698
  .view-questions .pr-rule { display: flex; align-items: center; gap: 6px; margin: 0 0 6px; font-size: 12.5px; color: var(--muted); }
612
699
  .view-questions .pr-rule svg { flex: none; }
613
700
 
614
- /* A question's takes sit in as many columns as keep each at least 380px wide, one on a phone; each its option above its
615
- preview, and each row's takes top-aligned, since each frame takes its own page's height. */
701
+ /* A question's drawn takes sit in as many columns as keep each at least 380px wide, one on a phone; each its option above
702
+ its diagram, and each row's takes top-aligned. */
616
703
  .view-questions .takes { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 380px), 1fr)); gap: 10px; align-items: start; margin: 6px 0; }
617
704
  .view-questions .take { min-width: 0; border: 1px solid var(--line); border-radius: 12px; background: var(--bg); padding: 10px; }
618
705
  .view-questions .take:has(input:checked) { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
619
706
  .view-questions .take .opt, .view-questions .take .opt:has(input:checked) { border: 0; padding: 0; margin: 0; background: none; }
620
- /* view-questions.js sets a frame's height to its page's once measured; until then it shows nothing, so a take that turns
621
- its question into the full-width layout never flashes cut off. */
622
- .view-questions .take-shot { position: relative; display: block; margin-top: 8px; height: 360px; border: 1px solid var(--line); border-radius: 10px;
623
- overflow: hidden; transition: height .15s ease; }
624
- .view-questions .take-shot iframe { width: 100%; height: 100%; border: 0; background: var(--bg); }
625
- .view-questions .take-shot.measuring { transition: none; }
626
- .view-questions .take-shot.measuring iframe { visibility: hidden; }
627
- .view-questions .take-shot.measuring::before { content: 'Loading take…'; position: absolute; inset: 0; display: grid; place-items: center;
628
- color: var(--muted); font-size: 12.5px; }
629
- .view-questions:has(.takes-full) main { max-width: none; }
630
- .view-questions .takes-full { grid-template-columns: minmax(0, 1fr); }
631
- .view-questions .take-choices { display: flex; flex-wrap: wrap; gap: 8px; }
632
- .view-questions .take-choices .opt { flex: 1 1 160px; margin: 0; }
633
- .view-questions .take-choices .opt[aria-current="true"] { border-color: var(--accent); }
634
- .view-questions .takes-full .take { padding: 0; border: 0; }
635
- .view-questions .takes-full .take-shot { margin-top: 0; transition: none; }
636
707
 
637
708
  /* A take with nothing to see: boxes and arrows, then what it means in practice. */
638
709
  .view-questions .flow { margin: 10px 0 4px; }
@@ -683,15 +754,15 @@ main > * { transition: opacity .25s ease; }
683
754
  .view-frame .alone { white-space: nowrap; font-size: 13px; }
684
755
 
685
756
  @media (max-width: 899px) {
686
- :root:has(body > .top, body.has-sidebar > :not(.sidebar) > .top):not(.ps-embed) {
757
+ :root:has(body > .top, body.has-sidebar > :not(.sidebar) > .top) {
687
758
  --app-bottom: calc(49px + max(4px, env(safe-area-inset-bottom)));
688
759
  }
689
760
  body > .top, body.has-sidebar > :not(.sidebar) > .top {
690
761
  position: fixed; top: auto; bottom: 0; left: 0; right: 0; z-index: 7;
691
762
  border-bottom: 0; border-top: 1px solid var(--line); padding-top: 4px; padding-bottom: max(4px, env(safe-area-inset-bottom));
692
763
  }
693
- .top .nav #refresh, .top .nav a.icon, .top .nav .arch-bulb { min-width: 44px; min-height: 44px; }
694
- body > main, body.has-sidebar > :not(.sidebar) > main { padding-top: max(16px, env(safe-area-inset-top)); padding-bottom: calc(96px + var(--app-bottom, 0px) + env(safe-area-inset-bottom)); }
764
+ .top .nav #refresh, .top .nav a.icon, .top .pages-switch { min-width: 44px; min-height: 44px; }
765
+ body > main, body.has-sidebar > :not(.sidebar) > main, body.has-sidebar > :not(.sidebar) > .split > main { padding-top: max(16px, env(safe-area-inset-top)); padding-bottom: calc(96px + var(--app-bottom, 0px) + env(safe-area-inset-bottom)); }
695
766
  .view-frame:not(.own-header) { padding-bottom: var(--app-bottom, 0px); }
696
767
  .sheet { bottom: max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px)));
697
768
  max-height: calc(var(--vh, 100dvh) + var(--kb, 0px) - max(var(--app-bottom, 0px), var(--kb, 0px)) - env(safe-area-inset-top) - 12px); }
@@ -699,6 +770,9 @@ main > * { transition: opacity .25s ease; }
699
770
  .sheet.keyboard-sheet { --sheet-limit: max(80px, calc(var(--vh, 100dvh) + var(--kb, 0px) - max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px))) - max(12px, env(safe-area-inset-top)))); }
700
771
  }
701
772
 
773
+ /* Typing, a text field focused (shell.js sets :root.typing): the floating + is disabled and the page stays as it is. */
774
+ :root.typing .fab { opacity: .35; pointer-events: none; }
775
+
702
776
  .settings-modal { position: fixed; inset: max(12px, env(safe-area-inset-top)) 0 calc(12px + max(var(--kb, 0px), var(--app-bottom, 0px))); margin: auto; width: min(680px, calc(100% - 24px));
703
777
  height: fit-content; max-width: none; max-height: max(120px, calc(var(--vh, 100dvh) - max(0px, calc(var(--app-bottom, 0px) - var(--kb, 0px))) - 24px - env(safe-area-inset-top)));
704
778
  padding: 0; border: 1px solid var(--line); border-radius: 18px; background: var(--panel); color: var(--text);
@@ -716,21 +790,18 @@ main > * { transition: opacity .25s ease; }
716
790
  .lk-over, .lk-over > main { display: contents; }
717
791
 
718
792
  .tk-section { margin: 10px 0; }
719
- /* The Tickets source row's live state, and Hub's key field opened under it (dialogs.js ticketsControl keyState). */
793
+ /* The Tickets source row's live state (dialogs.js ticketsControl keyState). */
720
794
  .tk-status { display: flex; align-items: center; gap: 8px; margin: 8px 0 0; font-size: 13px; color: var(--muted); }
721
795
  .tk-status b { font-weight: 600; color: var(--text); }
722
796
  .tk-status .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--line); flex: none; }
723
797
  .tk-status.on .dot { background: var(--accent); }
724
798
  .tk-status.on b { color: var(--accent); }
725
- .tk-inline { margin: 6px 0 10px; border: 1px solid var(--accent); border-radius: 10px; padding: 8px 10px; background: var(--accent-soft); }
726
- .tk-inline .tk-ask { margin: 2px 0 6px; font-size: 13px; }
727
- .tk-inline .hub-view main { padding: 0; }
728
799
  .tk-seg { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px; padding: 4px; margin: 8px 0; border: 1px solid var(--line); border-radius: 9px; background: var(--chip); }
729
800
  .tk-seg button { min-width: 0; min-height: 44px; padding: 6px 4px; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--muted); font: inherit; font-size: 13px; cursor: pointer; }
730
801
  .tk-seg button[aria-pressed="true"] { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
731
- .ask-seg { grid-template-columns: repeat(2, minmax(0, 1fr)); margin: 10px 0 0; }
732
- .ask-choice-help { margin: 4px 2px 0; }
733
802
  .tk-seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }
803
+ .landing-seg button { display: flex; flex-direction: column; align-items: center; gap: 4px; padding: 8px 6px; text-align: center; }
804
+ .landing-seg .sub { font-weight: 400; opacity: .8; line-height: 1.25; }
734
805
  .tk-grid { display: grid; gap: 8px; grid-template-columns: minmax(0, 1fr); }
735
806
  @media (min-width: 560px) { .tk-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); } }
736
807
  .tk-field { display: block; min-width: 0; }
@@ -844,3 +915,54 @@ button[aria-busy="true"]:is([data-card-bump], [data-bump]) svg { fill: currentCo
844
915
  .rv-links a { color: var(--accent); text-decoration: none; }
845
916
  .rv-acts { display: flex; flex-wrap: wrap; gap: 8px; }
846
917
  .view-questions .bar #qstatus .try-spin { color: var(--accent); margin-right: 6px; }
918
+
919
+ /* Needs you (app-needs.js): a deck at the top of the board on a phone; from 900px a pane beside the lanes, its lines over
920
+ Waiting on you (docs/adr/2026-11-05-0408-needs-you-is-a-deck-on-a-phone-and-a-pane-on-a-desktop.md). */
921
+ .needs-pane { display: none; }
922
+ .needs-deck { margin: 0 0 12px; border: 1px solid color-mix(in srgb, var(--color-notice) 45%, var(--line)); border-radius: 14px;
923
+ background: color-mix(in srgb, var(--color-notice) 7%, var(--panel)); overflow: hidden; }
924
+ .deck-head { display: flex; align-items: center; gap: 8px; padding: 10px 14px 4px; }
925
+ .deck-head h2 { margin: 0; flex: 1; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--color-notice); }
926
+ .deck-n { min-width: 20px; padding: 1px 7px; border-radius: 999px; background: var(--color-notice); color: #1c1407; font-size: 12px; font-weight: 700; text-align: center; }
927
+ .deck-card { padding: 2px 14px 12px; min-width: 0; overflow-wrap: anywhere; }
928
+ .deck-what { margin: 0 0 2px; font-size: 12px; color: var(--muted); }
929
+ .deck-card h3 { margin: 0 0 4px; font-size: 15px; line-height: 1.3; }
930
+ .deck-card h3 a { color: inherit; text-decoration: none; }
931
+ .deck-card .ctx { margin: 0 0 10px; font-size: 13px; color: var(--muted); }
932
+ .deck-proto { margin: 0 0 10px; color: var(--accent); }
933
+ .deck-opts { display: flex; flex-direction: column; gap: 6px; }
934
+ .deck-opts button { text-align: left; border-radius: 10px; padding: 10px 12px; font-size: 14px; }
935
+ .deck-opts button.rec { border-color: var(--accent); }
936
+ .deck-opts .rec-word { color: var(--accent); font-weight: 500; font-size: 12px; margin-left: 4px; }
937
+ .deck-opts small { display: block; color: var(--muted); font-size: 12.5px; }
938
+ .deck-prompt { margin: 0; padding: 0; list-style: none; }
939
+ .deck-prompt li.prompt { padding: 8px 10px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; }
940
+ .deck-acts { display: flex; flex-wrap: wrap; gap: 8px; }
941
+ .deck-bar { display: flex; align-items: center; gap: 10px; padding: 0 14px 10px; }
942
+ .deck-dots { flex: 1; display: flex; flex-wrap: wrap; gap: 5px; }
943
+ .deck-dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--line); }
944
+ .deck-dots i.on { background: var(--color-notice); }
945
+ .deck-bar button { border-radius: 999px; padding: 5px 12px; font-size: 13px; }
946
+ .deck-more { display: flex; align-items: center; justify-content: space-between; min-height: 46px; padding: 10px 14px; border-top: 1px solid color-mix(in srgb, var(--color-notice) 28%, var(--line));
947
+ background: color-mix(in srgb, var(--color-notice) 14%, var(--panel)); color: var(--text); font-weight: 600; font-size: 14px; text-decoration: none; }
948
+ .deck-more::after { content: '›'; color: var(--accent); font-size: 18px; }
949
+ .deck-more:hover { text-decoration: none; }
950
+ @media (min-width: 900px) {
951
+ .split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: start; }
952
+ .split > main { max-width: none; margin: 0; }
953
+ .needs-deck { display: none; }
954
+ /* Waiting on you is the board's pane here, so neither page shows the switch to the other. */
955
+ .top .pages-switch { display: none; }
956
+ /* The board's + sits at the bottom right of the left pane: the pane ends at the middle of the space right of the sidebar. */
957
+ .view-board .fab { right: calc(50% - var(--sidebar-w, 0px) / 2 + var(--fab-edge)); }
958
+ body.has-sidebar { --sidebar-w: 272px; }
959
+ html.sb-collapsed body.has-sidebar { --sidebar-w: 0px; }
960
+ .needs-pane { display: block; position: sticky; top: 56px; height: calc(100vh - 56px); overflow-y: auto; overscroll-behavior: contain; padding: 12px 16px 0; border-left: 1px solid var(--line); }
961
+ .needs-pane .needs-questions main { padding: 0; max-width: none; }
962
+ .needs-pane:has(.q-empty), .needs-pane .needs-questions:has(.q-empty), .needs-pane .needs-questions:has(.q-empty) main, .needs-pane #page:has(.q-empty) { display: flex; flex-direction: column; }
963
+ .needs-pane .needs-questions:has(.q-empty), .needs-pane .needs-questions:has(.q-empty) main, .needs-pane #page:has(.q-empty), .needs-pane .q-empty { flex: 1; min-height: 0; }
964
+ .needs-pane .waiting { margin: 0 0 12px; }
965
+ }
966
+ /* The Send bar is the last thing on its page: only the phone nav under it is left clear, so its background runs to the edge. */
967
+ body .view-questions main { padding-bottom: var(--app-bottom, 0px); }
968
+ .msg.calls.status-fold summary .fold-log { margin-left: 6px; color: var(--accent); text-decoration: underline; }